##// END OF EJS Templates
compare: single file mode will now show start and end commit range for this particular files instead of the last commit
marcink -
r3711:6a5b530d new-ui
parent child Browse files
Show More
@@ -1,317 +1,317 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2019 RhodeCode GmbH
3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30
30
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib import diffs, codeblocks
32 from rhodecode.lib import diffs, codeblocks
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.utils import safe_str
34 from rhodecode.lib.utils import safe_str
35 from rhodecode.lib.utils2 import safe_unicode, str2bool
35 from rhodecode.lib.utils2 import safe_unicode, str2bool
36 from rhodecode.lib.view_utils import parse_path_ref, get_commit_from_ref_name
36 from rhodecode.lib.view_utils import parse_path_ref, get_commit_from_ref_name
37 from rhodecode.lib.vcs.exceptions import (
37 from rhodecode.lib.vcs.exceptions import (
38 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
38 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
39 NodeDoesNotExistError)
39 NodeDoesNotExistError)
40 from rhodecode.model.db import Repository, ChangesetStatus
40 from rhodecode.model.db import Repository, ChangesetStatus
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class RepoCompareView(RepoAppView):
45 class RepoCompareView(RepoAppView):
46 def load_default_context(self):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context(include_app_defaults=True)
47 c = self._get_local_tmpl_context(include_app_defaults=True)
48 c.rhodecode_repo = self.rhodecode_vcs_repo
48 c.rhodecode_repo = self.rhodecode_vcs_repo
49 return c
49 return c
50
50
51 def _get_commit_or_redirect(
51 def _get_commit_or_redirect(
52 self, ref, ref_type, repo, redirect_after=True, partial=False):
52 self, ref, ref_type, repo, redirect_after=True, partial=False):
53 """
53 """
54 This is a safe way to get a commit. If an error occurs it
54 This is a safe way to get a commit. If an error occurs it
55 redirects to a commit with a proper message. If partial is set
55 redirects to a commit with a proper message. If partial is set
56 then it does not do redirect raise and throws an exception instead.
56 then it does not do redirect raise and throws an exception instead.
57 """
57 """
58 _ = self.request.translate
58 _ = self.request.translate
59 try:
59 try:
60 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
60 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
61 except EmptyRepositoryError:
61 except EmptyRepositoryError:
62 if not redirect_after:
62 if not redirect_after:
63 return repo.scm_instance().EMPTY_COMMIT
63 return repo.scm_instance().EMPTY_COMMIT
64 h.flash(h.literal(_('There are no commits yet')),
64 h.flash(h.literal(_('There are no commits yet')),
65 category='warning')
65 category='warning')
66 if not partial:
66 if not partial:
67 raise HTTPFound(
67 raise HTTPFound(
68 h.route_path('repo_summary', repo_name=repo.repo_name))
68 h.route_path('repo_summary', repo_name=repo.repo_name))
69 raise HTTPBadRequest()
69 raise HTTPBadRequest()
70
70
71 except RepositoryError as e:
71 except RepositoryError as e:
72 log.exception(safe_str(e))
72 log.exception(safe_str(e))
73 h.flash(safe_str(h.escape(e)), category='warning')
73 h.flash(safe_str(h.escape(e)), category='warning')
74 if not partial:
74 if not partial:
75 raise HTTPFound(
75 raise HTTPFound(
76 h.route_path('repo_summary', repo_name=repo.repo_name))
76 h.route_path('repo_summary', repo_name=repo.repo_name))
77 raise HTTPBadRequest()
77 raise HTTPBadRequest()
78
78
79 @LoginRequired()
79 @LoginRequired()
80 @HasRepoPermissionAnyDecorator(
80 @HasRepoPermissionAnyDecorator(
81 'repository.read', 'repository.write', 'repository.admin')
81 'repository.read', 'repository.write', 'repository.admin')
82 @view_config(
82 @view_config(
83 route_name='repo_compare_select', request_method='GET',
83 route_name='repo_compare_select', request_method='GET',
84 renderer='rhodecode:templates/compare/compare_diff.mako')
84 renderer='rhodecode:templates/compare/compare_diff.mako')
85 def compare_select(self):
85 def compare_select(self):
86 _ = self.request.translate
86 _ = self.request.translate
87 c = self.load_default_context()
87 c = self.load_default_context()
88
88
89 source_repo = self.db_repo_name
89 source_repo = self.db_repo_name
90 target_repo = self.request.GET.get('target_repo', source_repo)
90 target_repo = self.request.GET.get('target_repo', source_repo)
91 c.source_repo = Repository.get_by_repo_name(source_repo)
91 c.source_repo = Repository.get_by_repo_name(source_repo)
92 c.target_repo = Repository.get_by_repo_name(target_repo)
92 c.target_repo = Repository.get_by_repo_name(target_repo)
93
93
94 if c.source_repo is None or c.target_repo is None:
94 if c.source_repo is None or c.target_repo is None:
95 raise HTTPNotFound()
95 raise HTTPNotFound()
96
96
97 c.compare_home = True
97 c.compare_home = True
98 c.commit_ranges = []
98 c.commit_ranges = []
99 c.collapse_all_commits = False
99 c.collapse_all_commits = False
100 c.diffset = None
100 c.diffset = None
101 c.limited_diff = False
101 c.limited_diff = False
102 c.source_ref = c.target_ref = _('Select commit')
102 c.source_ref = c.target_ref = _('Select commit')
103 c.source_ref_type = ""
103 c.source_ref_type = ""
104 c.target_ref_type = ""
104 c.target_ref_type = ""
105 c.commit_statuses = ChangesetStatus.STATUSES
105 c.commit_statuses = ChangesetStatus.STATUSES
106 c.preview_mode = False
106 c.preview_mode = False
107 c.file_path = None
107 c.file_path = None
108
108
109 return self._get_template_context(c)
109 return self._get_template_context(c)
110
110
111 @LoginRequired()
111 @LoginRequired()
112 @HasRepoPermissionAnyDecorator(
112 @HasRepoPermissionAnyDecorator(
113 'repository.read', 'repository.write', 'repository.admin')
113 'repository.read', 'repository.write', 'repository.admin')
114 @view_config(
114 @view_config(
115 route_name='repo_compare', request_method='GET',
115 route_name='repo_compare', request_method='GET',
116 renderer=None)
116 renderer=None)
117 def compare(self):
117 def compare(self):
118 _ = self.request.translate
118 _ = self.request.translate
119 c = self.load_default_context()
119 c = self.load_default_context()
120
120
121 source_ref_type = self.request.matchdict['source_ref_type']
121 source_ref_type = self.request.matchdict['source_ref_type']
122 source_ref = self.request.matchdict['source_ref']
122 source_ref = self.request.matchdict['source_ref']
123 target_ref_type = self.request.matchdict['target_ref_type']
123 target_ref_type = self.request.matchdict['target_ref_type']
124 target_ref = self.request.matchdict['target_ref']
124 target_ref = self.request.matchdict['target_ref']
125
125
126 # source_ref will be evaluated in source_repo
126 # source_ref will be evaluated in source_repo
127 source_repo_name = self.db_repo_name
127 source_repo_name = self.db_repo_name
128 source_path, source_id = parse_path_ref(source_ref)
128 source_path, source_id = parse_path_ref(source_ref)
129
129
130 # target_ref will be evaluated in target_repo
130 # target_ref will be evaluated in target_repo
131 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
131 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
132 target_path, target_id = parse_path_ref(
132 target_path, target_id = parse_path_ref(
133 target_ref, default_path=self.request.GET.get('f_path', ''))
133 target_ref, default_path=self.request.GET.get('f_path', ''))
134
134
135 # if merge is True
135 # if merge is True
136 # Show what changes since the shared ancestor commit of target/source
136 # Show what changes since the shared ancestor commit of target/source
137 # the source would get if it was merged with target. Only commits
137 # the source would get if it was merged with target. Only commits
138 # which are in target but not in source will be shown.
138 # which are in target but not in source will be shown.
139 merge = str2bool(self.request.GET.get('merge'))
139 merge = str2bool(self.request.GET.get('merge'))
140 # if merge is False
140 # if merge is False
141 # Show a raw diff of source/target refs even if no ancestor exists
141 # Show a raw diff of source/target refs even if no ancestor exists
142
142
143 # c.fulldiff disables cut_off_limit
143 # c.fulldiff disables cut_off_limit
144 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
144 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
145
145
146 # fetch global flags of ignore ws or context lines
146 # fetch global flags of ignore ws or context lines
147 diff_context = diffs.get_diff_context(self.request)
147 diff_context = diffs.get_diff_context(self.request)
148 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
148 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
149
149
150 c.file_path = target_path
150 c.file_path = target_path
151 c.commit_statuses = ChangesetStatus.STATUSES
151 c.commit_statuses = ChangesetStatus.STATUSES
152
152
153 # if partial, returns just compare_commits.html (commits log)
153 # if partial, returns just compare_commits.html (commits log)
154 partial = self.request.is_xhr
154 partial = self.request.is_xhr
155
155
156 # swap url for compare_diff page
156 # swap url for compare_diff page
157 c.swap_url = h.route_path(
157 c.swap_url = h.route_path(
158 'repo_compare',
158 'repo_compare',
159 repo_name=target_repo_name,
159 repo_name=target_repo_name,
160 source_ref_type=target_ref_type,
160 source_ref_type=target_ref_type,
161 source_ref=target_ref,
161 source_ref=target_ref,
162 target_repo=source_repo_name,
162 target_repo=source_repo_name,
163 target_ref_type=source_ref_type,
163 target_ref_type=source_ref_type,
164 target_ref=source_ref,
164 target_ref=source_ref,
165 _query=dict(merge=merge and '1' or '', f_path=target_path))
165 _query=dict(merge=merge and '1' or '', f_path=target_path))
166
166
167 source_repo = Repository.get_by_repo_name(source_repo_name)
167 source_repo = Repository.get_by_repo_name(source_repo_name)
168 target_repo = Repository.get_by_repo_name(target_repo_name)
168 target_repo = Repository.get_by_repo_name(target_repo_name)
169
169
170 if source_repo is None:
170 if source_repo is None:
171 log.error('Could not find the source repo: {}'
171 log.error('Could not find the source repo: {}'
172 .format(source_repo_name))
172 .format(source_repo_name))
173 h.flash(_('Could not find the source repo: `{}`')
173 h.flash(_('Could not find the source repo: `{}`')
174 .format(h.escape(source_repo_name)), category='error')
174 .format(h.escape(source_repo_name)), category='error')
175 raise HTTPFound(
175 raise HTTPFound(
176 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
176 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
177
177
178 if target_repo is None:
178 if target_repo is None:
179 log.error('Could not find the target repo: {}'
179 log.error('Could not find the target repo: {}'
180 .format(source_repo_name))
180 .format(source_repo_name))
181 h.flash(_('Could not find the target repo: `{}`')
181 h.flash(_('Could not find the target repo: `{}`')
182 .format(h.escape(target_repo_name)), category='error')
182 .format(h.escape(target_repo_name)), category='error')
183 raise HTTPFound(
183 raise HTTPFound(
184 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
184 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
185
185
186 source_scm = source_repo.scm_instance()
186 source_scm = source_repo.scm_instance()
187 target_scm = target_repo.scm_instance()
187 target_scm = target_repo.scm_instance()
188
188
189 source_alias = source_scm.alias
189 source_alias = source_scm.alias
190 target_alias = target_scm.alias
190 target_alias = target_scm.alias
191 if source_alias != target_alias:
191 if source_alias != target_alias:
192 msg = _('The comparison of two different kinds of remote repos '
192 msg = _('The comparison of two different kinds of remote repos '
193 'is not available')
193 'is not available')
194 log.error(msg)
194 log.error(msg)
195 h.flash(msg, category='error')
195 h.flash(msg, category='error')
196 raise HTTPFound(
196 raise HTTPFound(
197 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
197 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
198
198
199 source_commit = self._get_commit_or_redirect(
199 source_commit = self._get_commit_or_redirect(
200 ref=source_id, ref_type=source_ref_type, repo=source_repo,
200 ref=source_id, ref_type=source_ref_type, repo=source_repo,
201 partial=partial)
201 partial=partial)
202 target_commit = self._get_commit_or_redirect(
202 target_commit = self._get_commit_or_redirect(
203 ref=target_id, ref_type=target_ref_type, repo=target_repo,
203 ref=target_id, ref_type=target_ref_type, repo=target_repo,
204 partial=partial)
204 partial=partial)
205
205
206 c.compare_home = False
206 c.compare_home = False
207 c.source_repo = source_repo
207 c.source_repo = source_repo
208 c.target_repo = target_repo
208 c.target_repo = target_repo
209 c.source_ref = source_ref
209 c.source_ref = source_ref
210 c.target_ref = target_ref
210 c.target_ref = target_ref
211 c.source_ref_type = source_ref_type
211 c.source_ref_type = source_ref_type
212 c.target_ref_type = target_ref_type
212 c.target_ref_type = target_ref_type
213
213
214 pre_load = ["author", "branch", "date", "message"]
214 pre_load = ["author", "branch", "date", "message"]
215 c.ancestor = None
215 c.ancestor = None
216
216
217 if c.file_path:
217 if c.file_path:
218 if source_commit == target_commit:
218 if source_commit == target_commit:
219 c.commit_ranges = []
219 c.commit_ranges = []
220 else:
220 else:
221 c.commit_ranges = [target_commit]
221 c.commit_ranges = [source_commit, target_commit]
222 else:
222 else:
223 try:
223 try:
224 c.commit_ranges = source_scm.compare(
224 c.commit_ranges = source_scm.compare(
225 source_commit.raw_id, target_commit.raw_id,
225 source_commit.raw_id, target_commit.raw_id,
226 target_scm, merge, pre_load=pre_load)
226 target_scm, merge, pre_load=pre_load)
227 if merge:
227 if merge:
228 c.ancestor = source_scm.get_common_ancestor(
228 c.ancestor = source_scm.get_common_ancestor(
229 source_commit.raw_id, target_commit.raw_id, target_scm)
229 source_commit.raw_id, target_commit.raw_id, target_scm)
230 except RepositoryRequirementError:
230 except RepositoryRequirementError:
231 msg = _('Could not compare repos with different '
231 msg = _('Could not compare repos with different '
232 'large file settings')
232 'large file settings')
233 log.error(msg)
233 log.error(msg)
234 if partial:
234 if partial:
235 return Response(msg)
235 return Response(msg)
236 h.flash(msg, category='error')
236 h.flash(msg, category='error')
237 raise HTTPFound(
237 raise HTTPFound(
238 h.route_path('repo_compare_select',
238 h.route_path('repo_compare_select',
239 repo_name=self.db_repo_name))
239 repo_name=self.db_repo_name))
240
240
241 c.statuses = self.db_repo.statuses(
241 c.statuses = self.db_repo.statuses(
242 [x.raw_id for x in c.commit_ranges])
242 [x.raw_id for x in c.commit_ranges])
243
243
244 # auto collapse if we have more than limit
244 # auto collapse if we have more than limit
245 collapse_limit = diffs.DiffProcessor._collapse_commits_over
245 collapse_limit = diffs.DiffProcessor._collapse_commits_over
246 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
246 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
247
247
248 if partial: # for PR ajax commits loader
248 if partial: # for PR ajax commits loader
249 if not c.ancestor:
249 if not c.ancestor:
250 return Response('') # cannot merge if there is no ancestor
250 return Response('') # cannot merge if there is no ancestor
251
251
252 html = render(
252 html = render(
253 'rhodecode:templates/compare/compare_commits.mako',
253 'rhodecode:templates/compare/compare_commits.mako',
254 self._get_template_context(c), self.request)
254 self._get_template_context(c), self.request)
255 return Response(html)
255 return Response(html)
256
256
257 if c.ancestor:
257 if c.ancestor:
258 # case we want a simple diff without incoming commits,
258 # case we want a simple diff without incoming commits,
259 # previewing what will be merged.
259 # previewing what will be merged.
260 # Make the diff on target repo (which is known to have target_ref)
260 # Make the diff on target repo (which is known to have target_ref)
261 log.debug('Using ancestor %s as source_ref instead of %s',
261 log.debug('Using ancestor %s as source_ref instead of %s',
262 c.ancestor, source_ref)
262 c.ancestor, source_ref)
263 source_repo = target_repo
263 source_repo = target_repo
264 source_commit = target_repo.get_commit(commit_id=c.ancestor)
264 source_commit = target_repo.get_commit(commit_id=c.ancestor)
265
265
266 # diff_limit will cut off the whole diff if the limit is applied
266 # diff_limit will cut off the whole diff if the limit is applied
267 # otherwise it will just hide the big files from the front-end
267 # otherwise it will just hide the big files from the front-end
268 diff_limit = c.visual.cut_off_limit_diff
268 diff_limit = c.visual.cut_off_limit_diff
269 file_limit = c.visual.cut_off_limit_file
269 file_limit = c.visual.cut_off_limit_file
270
270
271 log.debug('calculating diff between '
271 log.debug('calculating diff between '
272 'source_ref:%s and target_ref:%s for repo `%s`',
272 'source_ref:%s and target_ref:%s for repo `%s`',
273 source_commit, target_commit,
273 source_commit, target_commit,
274 safe_unicode(source_repo.scm_instance().path))
274 safe_unicode(source_repo.scm_instance().path))
275
275
276 if source_commit.repository != target_commit.repository:
276 if source_commit.repository != target_commit.repository:
277 msg = _(
277 msg = _(
278 "Repositories unrelated. "
278 "Repositories unrelated. "
279 "Cannot compare commit %(commit1)s from repository %(repo1)s "
279 "Cannot compare commit %(commit1)s from repository %(repo1)s "
280 "with commit %(commit2)s from repository %(repo2)s.") % {
280 "with commit %(commit2)s from repository %(repo2)s.") % {
281 'commit1': h.show_id(source_commit),
281 'commit1': h.show_id(source_commit),
282 'repo1': source_repo.repo_name,
282 'repo1': source_repo.repo_name,
283 'commit2': h.show_id(target_commit),
283 'commit2': h.show_id(target_commit),
284 'repo2': target_repo.repo_name,
284 'repo2': target_repo.repo_name,
285 }
285 }
286 h.flash(msg, category='error')
286 h.flash(msg, category='error')
287 raise HTTPFound(
287 raise HTTPFound(
288 h.route_path('repo_compare_select',
288 h.route_path('repo_compare_select',
289 repo_name=self.db_repo_name))
289 repo_name=self.db_repo_name))
290
290
291 txt_diff = source_repo.scm_instance().get_diff(
291 txt_diff = source_repo.scm_instance().get_diff(
292 commit1=source_commit, commit2=target_commit,
292 commit1=source_commit, commit2=target_commit,
293 path=target_path, path1=source_path,
293 path=target_path, path1=source_path,
294 ignore_whitespace=hide_whitespace_changes, context=diff_context)
294 ignore_whitespace=hide_whitespace_changes, context=diff_context)
295
295
296 diff_processor = diffs.DiffProcessor(
296 diff_processor = diffs.DiffProcessor(
297 txt_diff, format='newdiff', diff_limit=diff_limit,
297 txt_diff, format='newdiff', diff_limit=diff_limit,
298 file_limit=file_limit, show_full_diff=c.fulldiff)
298 file_limit=file_limit, show_full_diff=c.fulldiff)
299 _parsed = diff_processor.prepare()
299 _parsed = diff_processor.prepare()
300
300
301 diffset = codeblocks.DiffSet(
301 diffset = codeblocks.DiffSet(
302 repo_name=source_repo.repo_name,
302 repo_name=source_repo.repo_name,
303 source_node_getter=codeblocks.diffset_node_getter(source_commit),
303 source_node_getter=codeblocks.diffset_node_getter(source_commit),
304 target_repo_name=self.db_repo_name,
304 target_repo_name=self.db_repo_name,
305 target_node_getter=codeblocks.diffset_node_getter(target_commit),
305 target_node_getter=codeblocks.diffset_node_getter(target_commit),
306 )
306 )
307 c.diffset = self.path_filter.render_patchset_filtered(
307 c.diffset = self.path_filter.render_patchset_filtered(
308 diffset, _parsed, source_ref, target_ref)
308 diffset, _parsed, source_ref, target_ref)
309
309
310 c.preview_mode = merge
310 c.preview_mode = merge
311 c.source_commit = source_commit
311 c.source_commit = source_commit
312 c.target_commit = target_commit
312 c.target_commit = target_commit
313
313
314 html = render(
314 html = render(
315 'rhodecode:templates/compare/compare_diff.mako',
315 'rhodecode:templates/compare/compare_diff.mako',
316 self._get_template_context(c), self.request)
316 self._get_template_context(c), self.request)
317 return Response(html) No newline at end of file
317 return Response(html)
General Comments 0
You need to be logged in to leave comments. Login now