##// END OF EJS Templates
compare: using f_path shouldn't generate full range of commits....
marcink -
r1262:fa7f32fd default
parent child Browse files
Show More
@@ -1,270 +1,277 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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 Compare controller for showing differences between two commits/refs/tags etc.
22 Compare controller for showing differences between two commits/refs/tags etc.
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from webob.exc import HTTPBadRequest
27 from webob.exc import HTTPBadRequest
28 from pylons import request, tmpl_context as c, url
28 from pylons import request, tmpl_context as c, url
29 from pylons.controllers.util import redirect
29 from pylons.controllers.util import redirect
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
32 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import diffs, codeblocks
34 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.utils import safe_str
37 from rhodecode.lib.utils import safe_str
38 from rhodecode.lib.utils2 import safe_unicode, str2bool
38 from rhodecode.lib.utils2 import safe_unicode, str2bool
39 from rhodecode.lib.vcs.exceptions import (
39 from rhodecode.lib.vcs.exceptions import (
40 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
40 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
41 NodeDoesNotExistError)
41 NodeDoesNotExistError)
42 from rhodecode.model.db import Repository, ChangesetStatus
42 from rhodecode.model.db import Repository, ChangesetStatus
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class CompareController(BaseRepoController):
47 class CompareController(BaseRepoController):
48
48
49 def __before__(self):
49 def __before__(self):
50 super(CompareController, self).__before__()
50 super(CompareController, self).__before__()
51
51
52 def _get_commit_or_redirect(
52 def _get_commit_or_redirect(
53 self, ref, ref_type, repo, redirect_after=True, partial=False):
53 self, ref, ref_type, repo, redirect_after=True, partial=False):
54 """
54 """
55 This is a safe way to get a commit. If an error occurs it
55 This is a safe way to get a commit. If an error occurs it
56 redirects to a commit with a proper message. If partial is set
56 redirects to a commit with a proper message. If partial is set
57 then it does not do redirect raise and throws an exception instead.
57 then it does not do redirect raise and throws an exception instead.
58 """
58 """
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 redirect(url('summary_home', repo_name=repo.repo_name))
66 redirect(url('summary_home', repo_name=repo.repo_name))
67
67
68 except RepositoryError as e:
68 except RepositoryError as e:
69 msg = safe_str(e)
69 msg = safe_str(e)
70 log.exception(msg)
70 log.exception(msg)
71 h.flash(msg, category='warning')
71 h.flash(msg, category='warning')
72 if not partial:
72 if not partial:
73 redirect(h.url('summary_home', repo_name=repo.repo_name))
73 redirect(h.url('summary_home', repo_name=repo.repo_name))
74 raise HTTPBadRequest()
74 raise HTTPBadRequest()
75
75
76 @LoginRequired()
76 @LoginRequired()
77 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
77 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
78 'repository.admin')
78 'repository.admin')
79 def index(self, repo_name):
79 def index(self, repo_name):
80 c.compare_home = True
80 c.compare_home = True
81 c.commit_ranges = []
81 c.commit_ranges = []
82 c.diffset = None
82 c.diffset = None
83 c.limited_diff = False
83 c.limited_diff = False
84 source_repo = c.rhodecode_db_repo.repo_name
84 source_repo = c.rhodecode_db_repo.repo_name
85 target_repo = request.GET.get('target_repo', source_repo)
85 target_repo = request.GET.get('target_repo', source_repo)
86 c.source_repo = Repository.get_by_repo_name(source_repo)
86 c.source_repo = Repository.get_by_repo_name(source_repo)
87 c.target_repo = Repository.get_by_repo_name(target_repo)
87 c.target_repo = Repository.get_by_repo_name(target_repo)
88 c.source_ref = c.target_ref = _('Select commit')
88 c.source_ref = c.target_ref = _('Select commit')
89 c.source_ref_type = ""
89 c.source_ref_type = ""
90 c.target_ref_type = ""
90 c.target_ref_type = ""
91 c.commit_statuses = ChangesetStatus.STATUSES
91 c.commit_statuses = ChangesetStatus.STATUSES
92 c.preview_mode = False
92 c.preview_mode = False
93 c.file_path = None
93 c.file_path = None
94 return render('compare/compare_diff.html')
94 return render('compare/compare_diff.html')
95
95
96 @LoginRequired()
96 @LoginRequired()
97 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
97 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
98 'repository.admin')
98 'repository.admin')
99 def compare(self, repo_name, source_ref_type, source_ref,
99 def compare(self, repo_name, source_ref_type, source_ref,
100 target_ref_type, target_ref):
100 target_ref_type, target_ref):
101 # source_ref will be evaluated in source_repo
101 # source_ref will be evaluated in source_repo
102 source_repo_name = c.rhodecode_db_repo.repo_name
102 source_repo_name = c.rhodecode_db_repo.repo_name
103 source_path, source_id = parse_path_ref(source_ref)
103 source_path, source_id = parse_path_ref(source_ref)
104
104
105 # target_ref will be evaluated in target_repo
105 # target_ref will be evaluated in target_repo
106 target_repo_name = request.GET.get('target_repo', source_repo_name)
106 target_repo_name = request.GET.get('target_repo', source_repo_name)
107 target_path, target_id = parse_path_ref(
107 target_path, target_id = parse_path_ref(
108 target_ref, default_path=request.GET.get('f_path', ''))
108 target_ref, default_path=request.GET.get('f_path', ''))
109
109
110 c.file_path = target_path
110 c.file_path = target_path
111 c.commit_statuses = ChangesetStatus.STATUSES
111 c.commit_statuses = ChangesetStatus.STATUSES
112
112
113 # if merge is True
113 # if merge is True
114 # Show what changes since the shared ancestor commit of target/source
114 # Show what changes since the shared ancestor commit of target/source
115 # the source would get if it was merged with target. Only commits
115 # the source would get if it was merged with target. Only commits
116 # which are in target but not in source will be shown.
116 # which are in target but not in source will be shown.
117 merge = str2bool(request.GET.get('merge'))
117 merge = str2bool(request.GET.get('merge'))
118 # if merge is False
118 # if merge is False
119 # Show a raw diff of source/target refs even if no ancestor exists
119 # Show a raw diff of source/target refs even if no ancestor exists
120
120
121 # c.fulldiff disables cut_off_limit
121 # c.fulldiff disables cut_off_limit
122 c.fulldiff = str2bool(request.GET.get('fulldiff'))
122 c.fulldiff = str2bool(request.GET.get('fulldiff'))
123
123
124 # if partial, returns just compare_commits.html (commits log)
124 # if partial, returns just compare_commits.html (commits log)
125 partial = request.is_xhr
125 partial = request.is_xhr
126
126
127 # swap url for compare_diff page
127 # swap url for compare_diff page
128 c.swap_url = h.url(
128 c.swap_url = h.url(
129 'compare_url',
129 'compare_url',
130 repo_name=target_repo_name,
130 repo_name=target_repo_name,
131 source_ref_type=target_ref_type,
131 source_ref_type=target_ref_type,
132 source_ref=target_ref,
132 source_ref=target_ref,
133 target_repo=source_repo_name,
133 target_repo=source_repo_name,
134 target_ref_type=source_ref_type,
134 target_ref_type=source_ref_type,
135 target_ref=source_ref,
135 target_ref=source_ref,
136 merge=merge and '1' or '',
136 merge=merge and '1' or '',
137 f_path=target_path)
137 f_path=target_path)
138
138
139 source_repo = Repository.get_by_repo_name(source_repo_name)
139 source_repo = Repository.get_by_repo_name(source_repo_name)
140 target_repo = Repository.get_by_repo_name(target_repo_name)
140 target_repo = Repository.get_by_repo_name(target_repo_name)
141
141
142 if source_repo is None:
142 if source_repo is None:
143 msg = _('Could not find the original repo: %(repo)s') % {
143 msg = _('Could not find the original repo: %(repo)s') % {
144 'repo': source_repo}
144 'repo': source_repo}
145
145
146 log.error(msg)
146 log.error(msg)
147 h.flash(msg, category='error')
147 h.flash(msg, category='error')
148 return redirect(url('compare_home', repo_name=c.repo_name))
148 return redirect(url('compare_home', repo_name=c.repo_name))
149
149
150 if target_repo is None:
150 if target_repo is None:
151 msg = _('Could not find the other repo: %(repo)s') % {
151 msg = _('Could not find the other repo: %(repo)s') % {
152 'repo': target_repo_name}
152 'repo': target_repo_name}
153 log.error(msg)
153 log.error(msg)
154 h.flash(msg, category='error')
154 h.flash(msg, category='error')
155 return redirect(url('compare_home', repo_name=c.repo_name))
155 return redirect(url('compare_home', repo_name=c.repo_name))
156
156
157 source_scm = source_repo.scm_instance()
157 source_scm = source_repo.scm_instance()
158 target_scm = target_repo.scm_instance()
158 target_scm = target_repo.scm_instance()
159
159
160 source_alias = source_scm.alias
160 source_alias = source_scm.alias
161 target_alias = target_scm.alias
161 target_alias = target_scm.alias
162 if source_alias != target_alias:
162 if source_alias != target_alias:
163 msg = _('The comparison of two different kinds of remote repos '
163 msg = _('The comparison of two different kinds of remote repos '
164 'is not available')
164 'is not available')
165 log.error(msg)
165 log.error(msg)
166 h.flash(msg, category='error')
166 h.flash(msg, category='error')
167 return redirect(url('compare_home', repo_name=c.repo_name))
167 return redirect(url('compare_home', repo_name=c.repo_name))
168
168
169 source_commit = self._get_commit_or_redirect(
169 source_commit = self._get_commit_or_redirect(
170 ref=source_id, ref_type=source_ref_type, repo=source_repo,
170 ref=source_id, ref_type=source_ref_type, repo=source_repo,
171 partial=partial)
171 partial=partial)
172 target_commit = self._get_commit_or_redirect(
172 target_commit = self._get_commit_or_redirect(
173 ref=target_id, ref_type=target_ref_type, repo=target_repo,
173 ref=target_id, ref_type=target_ref_type, repo=target_repo,
174 partial=partial)
174 partial=partial)
175
175
176 c.compare_home = False
176 c.compare_home = False
177 c.source_repo = source_repo
177 c.source_repo = source_repo
178 c.target_repo = target_repo
178 c.target_repo = target_repo
179 c.source_ref = source_ref
179 c.source_ref = source_ref
180 c.target_ref = target_ref
180 c.target_ref = target_ref
181 c.source_ref_type = source_ref_type
181 c.source_ref_type = source_ref_type
182 c.target_ref_type = target_ref_type
182 c.target_ref_type = target_ref_type
183
183
184 pre_load = ["author", "branch", "date", "message"]
184 pre_load = ["author", "branch", "date", "message"]
185 c.ancestor = None
185 c.ancestor = None
186 try:
186
187 c.commit_ranges = source_scm.compare(
187 if c.file_path:
188 source_commit.raw_id, target_commit.raw_id,
188 if source_commit == target_commit:
189 target_scm, merge, pre_load=pre_load)
189 c.commit_ranges = []
190 if merge:
190 else:
191 c.ancestor = source_scm.get_common_ancestor(
191 c.commit_ranges = [target_commit]
192 source_commit.raw_id, target_commit.raw_id, target_scm)
192 else:
193 except RepositoryRequirementError:
193 try:
194 msg = _('Could not compare repos with different '
194 c.commit_ranges = source_scm.compare(
195 'large file settings')
195 source_commit.raw_id, target_commit.raw_id,
196 log.error(msg)
196 target_scm, merge, pre_load=pre_load)
197 if partial:
197 if merge:
198 return msg
198 c.ancestor = source_scm.get_common_ancestor(
199 h.flash(msg, category='error')
199 source_commit.raw_id, target_commit.raw_id, target_scm)
200 return redirect(url('compare_home', repo_name=c.repo_name))
200 except RepositoryRequirementError:
201 msg = _('Could not compare repos with different '
202 'large file settings')
203 log.error(msg)
204 if partial:
205 return msg
206 h.flash(msg, category='error')
207 return redirect(url('compare_home', repo_name=c.repo_name))
201
208
202 c.statuses = c.rhodecode_db_repo.statuses(
209 c.statuses = c.rhodecode_db_repo.statuses(
203 [x.raw_id for x in c.commit_ranges])
210 [x.raw_id for x in c.commit_ranges])
204
211
205 if partial: # for PR ajax commits loader
212 if partial: # for PR ajax commits loader
206 if not c.ancestor:
213 if not c.ancestor:
207 return '' # cannot merge if there is no ancestor
214 return '' # cannot merge if there is no ancestor
208 return render('compare/compare_commits.html')
215 return render('compare/compare_commits.html')
209
216
210 if c.ancestor:
217 if c.ancestor:
211 # case we want a simple diff without incoming commits,
218 # case we want a simple diff without incoming commits,
212 # previewing what will be merged.
219 # previewing what will be merged.
213 # Make the diff on target repo (which is known to have target_ref)
220 # Make the diff on target repo (which is known to have target_ref)
214 log.debug('Using ancestor %s as source_ref instead of %s'
221 log.debug('Using ancestor %s as source_ref instead of %s'
215 % (c.ancestor, source_ref))
222 % (c.ancestor, source_ref))
216 source_repo = target_repo
223 source_repo = target_repo
217 source_commit = target_repo.get_commit(commit_id=c.ancestor)
224 source_commit = target_repo.get_commit(commit_id=c.ancestor)
218
225
219 # diff_limit will cut off the whole diff if the limit is applied
226 # diff_limit will cut off the whole diff if the limit is applied
220 # otherwise it will just hide the big files from the front-end
227 # otherwise it will just hide the big files from the front-end
221 diff_limit = self.cut_off_limit_diff
228 diff_limit = self.cut_off_limit_diff
222 file_limit = self.cut_off_limit_file
229 file_limit = self.cut_off_limit_file
223
230
224 log.debug('calculating diff between '
231 log.debug('calculating diff between '
225 'source_ref:%s and target_ref:%s for repo `%s`',
232 'source_ref:%s and target_ref:%s for repo `%s`',
226 source_commit, target_commit,
233 source_commit, target_commit,
227 safe_unicode(source_repo.scm_instance().path))
234 safe_unicode(source_repo.scm_instance().path))
228
235
229 if source_commit.repository != target_commit.repository:
236 if source_commit.repository != target_commit.repository:
230 msg = _(
237 msg = _(
231 "Repositories unrelated. "
238 "Repositories unrelated. "
232 "Cannot compare commit %(commit1)s from repository %(repo1)s "
239 "Cannot compare commit %(commit1)s from repository %(repo1)s "
233 "with commit %(commit2)s from repository %(repo2)s.") % {
240 "with commit %(commit2)s from repository %(repo2)s.") % {
234 'commit1': h.show_id(source_commit),
241 'commit1': h.show_id(source_commit),
235 'repo1': source_repo.repo_name,
242 'repo1': source_repo.repo_name,
236 'commit2': h.show_id(target_commit),
243 'commit2': h.show_id(target_commit),
237 'repo2': target_repo.repo_name,
244 'repo2': target_repo.repo_name,
238 }
245 }
239 h.flash(msg, category='error')
246 h.flash(msg, category='error')
240 raise HTTPBadRequest()
247 raise HTTPBadRequest()
241
248
242 txtdiff = source_repo.scm_instance().get_diff(
249 txtdiff = source_repo.scm_instance().get_diff(
243 commit1=source_commit, commit2=target_commit,
250 commit1=source_commit, commit2=target_commit,
244 path=target_path, path1=source_path)
251 path=target_path, path1=source_path)
245
252
246 diff_processor = diffs.DiffProcessor(
253 diff_processor = diffs.DiffProcessor(
247 txtdiff, format='newdiff', diff_limit=diff_limit,
254 txtdiff, format='newdiff', diff_limit=diff_limit,
248 file_limit=file_limit, show_full_diff=c.fulldiff)
255 file_limit=file_limit, show_full_diff=c.fulldiff)
249 _parsed = diff_processor.prepare()
256 _parsed = diff_processor.prepare()
250
257
251 def _node_getter(commit):
258 def _node_getter(commit):
252 """ Returns a function that returns a node for a commit or None """
259 """ Returns a function that returns a node for a commit or None """
253 def get_node(fname):
260 def get_node(fname):
254 try:
261 try:
255 return commit.get_node(fname)
262 return commit.get_node(fname)
256 except NodeDoesNotExistError:
263 except NodeDoesNotExistError:
257 return None
264 return None
258 return get_node
265 return get_node
259
266
260 c.diffset = codeblocks.DiffSet(
267 c.diffset = codeblocks.DiffSet(
261 repo_name=source_repo.repo_name,
268 repo_name=source_repo.repo_name,
262 source_node_getter=_node_getter(source_commit),
269 source_node_getter=_node_getter(source_commit),
263 target_node_getter=_node_getter(target_commit),
270 target_node_getter=_node_getter(target_commit),
264 ).render_patchset(_parsed, source_ref, target_ref)
271 ).render_patchset(_parsed, source_ref, target_ref)
265
272
266 c.preview_mode = merge
273 c.preview_mode = merge
267 c.source_commit = source_commit
274 c.source_commit = source_commit
268 c.target_commit = target_commit
275 c.target_commit = target_commit
269
276
270 return render('compare/compare_diff.html')
277 return render('compare/compare_diff.html')
General Comments 0
You need to be logged in to leave comments. Login now