##// END OF EJS Templates
pullrequest: disable pr on commits that dont share ancestor
dan -
r64:0b85876a default
parent child Browse files
Show More
@@ -1,265 +1,267 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
34 from rhodecode.lib import diffs
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 from rhodecode.model.db import Repository, ChangesetStatus
41 from rhodecode.model.db import Repository, ChangesetStatus
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class CompareController(BaseRepoController):
46 class CompareController(BaseRepoController):
47
47
48 def __before__(self):
48 def __before__(self):
49 super(CompareController, self).__before__()
49 super(CompareController, self).__before__()
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 try:
58 try:
59 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
59 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
60 except EmptyRepositoryError:
60 except EmptyRepositoryError:
61 if not redirect_after:
61 if not redirect_after:
62 return repo.scm_instance().EMPTY_COMMIT
62 return repo.scm_instance().EMPTY_COMMIT
63 h.flash(h.literal(_('There are no commits yet')),
63 h.flash(h.literal(_('There are no commits yet')),
64 category='warning')
64 category='warning')
65 redirect(url('summary_home', repo_name=repo.repo_name))
65 redirect(url('summary_home', repo_name=repo.repo_name))
66
66
67 except RepositoryError as e:
67 except RepositoryError as e:
68 msg = safe_str(e)
68 msg = safe_str(e)
69 log.exception(msg)
69 log.exception(msg)
70 h.flash(msg, category='warning')
70 h.flash(msg, category='warning')
71 if not partial:
71 if not partial:
72 redirect(h.url('summary_home', repo_name=repo.repo_name))
72 redirect(h.url('summary_home', repo_name=repo.repo_name))
73 raise HTTPBadRequest()
73 raise HTTPBadRequest()
74
74
75 @LoginRequired()
75 @LoginRequired()
76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
77 'repository.admin')
77 'repository.admin')
78 def index(self, repo_name):
78 def index(self, repo_name):
79 c.compare_home = True
79 c.compare_home = True
80 c.commit_ranges = []
80 c.commit_ranges = []
81 c.files = []
81 c.files = []
82 c.limited_diff = False
82 c.limited_diff = False
83 source_repo = c.rhodecode_db_repo.repo_name
83 source_repo = c.rhodecode_db_repo.repo_name
84 target_repo = request.GET.get('target_repo', source_repo)
84 target_repo = request.GET.get('target_repo', source_repo)
85 c.source_repo = Repository.get_by_repo_name(source_repo)
85 c.source_repo = Repository.get_by_repo_name(source_repo)
86 c.target_repo = Repository.get_by_repo_name(target_repo)
86 c.target_repo = Repository.get_by_repo_name(target_repo)
87 c.source_ref = c.target_ref = _('Select commit')
87 c.source_ref = c.target_ref = _('Select commit')
88 c.source_ref_type = ""
88 c.source_ref_type = ""
89 c.target_ref_type = ""
89 c.target_ref_type = ""
90 c.commit_statuses = ChangesetStatus.STATUSES
90 c.commit_statuses = ChangesetStatus.STATUSES
91 c.preview_mode = False
91 c.preview_mode = False
92 return render('compare/compare_diff.html')
92 return render('compare/compare_diff.html')
93
93
94 @LoginRequired()
94 @LoginRequired()
95 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
95 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
96 'repository.admin')
96 'repository.admin')
97 def compare(self, repo_name, source_ref_type, source_ref,
97 def compare(self, repo_name, source_ref_type, source_ref,
98 target_ref_type, target_ref):
98 target_ref_type, target_ref):
99 # source_ref will be evaluated in source_repo
99 # source_ref will be evaluated in source_repo
100 source_repo_name = c.rhodecode_db_repo.repo_name
100 source_repo_name = c.rhodecode_db_repo.repo_name
101 source_path, source_id = parse_path_ref(source_ref)
101 source_path, source_id = parse_path_ref(source_ref)
102
102
103 # target_ref will be evaluated in target_repo
103 # target_ref will be evaluated in target_repo
104 target_repo_name = request.GET.get('target_repo', source_repo_name)
104 target_repo_name = request.GET.get('target_repo', source_repo_name)
105 target_path, target_id = parse_path_ref(target_ref)
105 target_path, target_id = parse_path_ref(target_ref)
106
106
107 c.commit_statuses = ChangesetStatus.STATUSES
107 c.commit_statuses = ChangesetStatus.STATUSES
108
108
109 # if merge is True
109 # if merge is True
110 # Show what changes since the shared ancestor commit of target/source
110 # Show what changes since the shared ancestor commit of target/source
111 # the source would get if it was merged with target. Only commits
111 # the source would get if it was merged with target. Only commits
112 # which are in target but not in source will be shown.
112 # which are in target but not in source will be shown.
113 merge = str2bool(request.GET.get('merge'))
113 merge = str2bool(request.GET.get('merge'))
114 # if merge is False
114 # if merge is False
115 # Show a raw diff of source/target refs even if no ancestor exists
115 # Show a raw diff of source/target refs even if no ancestor exists
116
116
117
117
118 # c.fulldiff disables cut_off_limit
118 # c.fulldiff disables cut_off_limit
119 c.fulldiff = str2bool(request.GET.get('fulldiff'))
119 c.fulldiff = str2bool(request.GET.get('fulldiff'))
120
120
121 # if partial, returns just compare_commits.html (commits log)
121 # if partial, returns just compare_commits.html (commits log)
122 partial = request.is_xhr
122 partial = request.is_xhr
123
123
124 # swap url for compare_diff page
124 # swap url for compare_diff page
125 c.swap_url = h.url(
125 c.swap_url = h.url(
126 'compare_url',
126 'compare_url',
127 repo_name=target_repo_name,
127 repo_name=target_repo_name,
128 source_ref_type=target_ref_type,
128 source_ref_type=target_ref_type,
129 source_ref=target_ref,
129 source_ref=target_ref,
130 target_repo=source_repo_name,
130 target_repo=source_repo_name,
131 target_ref_type=source_ref_type,
131 target_ref_type=source_ref_type,
132 target_ref=source_ref,
132 target_ref=source_ref,
133 merge=merge and '1' or '')
133 merge=merge and '1' or '')
134
134
135 source_repo = Repository.get_by_repo_name(source_repo_name)
135 source_repo = Repository.get_by_repo_name(source_repo_name)
136 target_repo = Repository.get_by_repo_name(target_repo_name)
136 target_repo = Repository.get_by_repo_name(target_repo_name)
137
137
138 if source_repo is None:
138 if source_repo is None:
139 msg = _('Could not find the original repo: %(repo)s') % {
139 msg = _('Could not find the original repo: %(repo)s') % {
140 'repo': source_repo}
140 'repo': source_repo}
141
141
142 log.error(msg)
142 log.error(msg)
143 h.flash(msg, category='error')
143 h.flash(msg, category='error')
144 return redirect(url('compare_home', repo_name=c.repo_name))
144 return redirect(url('compare_home', repo_name=c.repo_name))
145
145
146 if target_repo is None:
146 if target_repo is None:
147 msg = _('Could not find the other repo: %(repo)s') % {
147 msg = _('Could not find the other repo: %(repo)s') % {
148 'repo': target_repo_name}
148 'repo': target_repo_name}
149 log.error(msg)
149 log.error(msg)
150 h.flash(msg, category='error')
150 h.flash(msg, category='error')
151 return redirect(url('compare_home', repo_name=c.repo_name))
151 return redirect(url('compare_home', repo_name=c.repo_name))
152
152
153 source_alias = source_repo.scm_instance().alias
153 source_alias = source_repo.scm_instance().alias
154 target_alias = target_repo.scm_instance().alias
154 target_alias = target_repo.scm_instance().alias
155 if source_alias != target_alias:
155 if source_alias != target_alias:
156 msg = _('The comparison of two different kinds of remote repos '
156 msg = _('The comparison of two different kinds of remote repos '
157 'is not available')
157 'is not available')
158 log.error(msg)
158 log.error(msg)
159 h.flash(msg, category='error')
159 h.flash(msg, category='error')
160 return redirect(url('compare_home', repo_name=c.repo_name))
160 return redirect(url('compare_home', repo_name=c.repo_name))
161
161
162 source_commit = self._get_commit_or_redirect(
162 source_commit = self._get_commit_or_redirect(
163 ref=source_id, ref_type=source_ref_type, repo=source_repo,
163 ref=source_id, ref_type=source_ref_type, repo=source_repo,
164 partial=partial)
164 partial=partial)
165 target_commit = self._get_commit_or_redirect(
165 target_commit = self._get_commit_or_redirect(
166 ref=target_id, ref_type=target_ref_type, repo=target_repo,
166 ref=target_id, ref_type=target_ref_type, repo=target_repo,
167 partial=partial)
167 partial=partial)
168
168
169 c.compare_home = False
169 c.compare_home = False
170 c.source_repo = source_repo
170 c.source_repo = source_repo
171 c.target_repo = target_repo
171 c.target_repo = target_repo
172 c.source_ref = source_ref
172 c.source_ref = source_ref
173 c.target_ref = target_ref
173 c.target_ref = target_ref
174 c.source_ref_type = source_ref_type
174 c.source_ref_type = source_ref_type
175 c.target_ref_type = target_ref_type
175 c.target_ref_type = target_ref_type
176
176
177 source_scm = source_repo.scm_instance()
177 source_scm = source_repo.scm_instance()
178 target_scm = target_repo.scm_instance()
178 target_scm = target_repo.scm_instance()
179
179
180 pre_load = ["author", "branch", "date", "message"]
180 pre_load = ["author", "branch", "date", "message"]
181 c.ancestor = None
181 c.ancestor = None
182 try:
182 try:
183 c.commit_ranges = source_scm.compare(
183 c.commit_ranges = source_scm.compare(
184 source_commit.raw_id, target_commit.raw_id,
184 source_commit.raw_id, target_commit.raw_id,
185 target_scm, merge, pre_load=pre_load)
185 target_scm, merge, pre_load=pre_load)
186 if merge:
186 if merge:
187 c.ancestor = source_scm.get_common_ancestor(
187 c.ancestor = source_scm.get_common_ancestor(
188 source_commit.raw_id, target_commit.raw_id, target_scm)
188 source_commit.raw_id, target_commit.raw_id, target_scm)
189 except RepositoryRequirementError:
189 except RepositoryRequirementError:
190 msg = _('Could not compare repos with different '
190 msg = _('Could not compare repos with different '
191 'large file settings')
191 'large file settings')
192 log.error(msg)
192 log.error(msg)
193 if partial:
193 if partial:
194 return msg
194 return msg
195 h.flash(msg, category='error')
195 h.flash(msg, category='error')
196 return redirect(url('compare_home', repo_name=c.repo_name))
196 return redirect(url('compare_home', repo_name=c.repo_name))
197
197
198 c.statuses = c.rhodecode_db_repo.statuses(
198 c.statuses = c.rhodecode_db_repo.statuses(
199 [x.raw_id for x in c.commit_ranges])
199 [x.raw_id for x in c.commit_ranges])
200
200
201 if partial:
201 if partial: # for PR ajax commits loader
202 if not c.ancestor:
203 return '' # cannot merge if there is no ancestor
202 return render('compare/compare_commits.html')
204 return render('compare/compare_commits.html')
203
205
204 if c.ancestor:
206 if c.ancestor:
205 # case we want a simple diff without incoming commits,
207 # case we want a simple diff without incoming commits,
206 # previewing what will be merged.
208 # previewing what will be merged.
207 # Make the diff on target repo (which is known to have target_ref)
209 # Make the diff on target repo (which is known to have target_ref)
208 log.debug('Using ancestor %s as source_ref instead of %s'
210 log.debug('Using ancestor %s as source_ref instead of %s'
209 % (c.ancestor, source_ref))
211 % (c.ancestor, source_ref))
210 source_repo = target_repo
212 source_repo = target_repo
211 source_commit = target_repo.get_commit(commit_id=c.ancestor)
213 source_commit = target_repo.get_commit(commit_id=c.ancestor)
212
214
213 # diff_limit will cut off the whole diff if the limit is applied
215 # diff_limit will cut off the whole diff if the limit is applied
214 # otherwise it will just hide the big files from the front-end
216 # otherwise it will just hide the big files from the front-end
215 diff_limit = self.cut_off_limit_diff
217 diff_limit = self.cut_off_limit_diff
216 file_limit = self.cut_off_limit_file
218 file_limit = self.cut_off_limit_file
217
219
218 log.debug('calculating diff between '
220 log.debug('calculating diff between '
219 'source_ref:%s and target_ref:%s for repo `%s`',
221 'source_ref:%s and target_ref:%s for repo `%s`',
220 source_commit, target_commit,
222 source_commit, target_commit,
221 safe_unicode(source_repo.scm_instance().path))
223 safe_unicode(source_repo.scm_instance().path))
222
224
223 if source_commit.repository != target_commit.repository:
225 if source_commit.repository != target_commit.repository:
224 msg = _(
226 msg = _(
225 "Repositories unrelated. "
227 "Repositories unrelated. "
226 "Cannot compare commit %(commit1)s from repository %(repo1)s "
228 "Cannot compare commit %(commit1)s from repository %(repo1)s "
227 "with commit %(commit2)s from repository %(repo2)s.") % {
229 "with commit %(commit2)s from repository %(repo2)s.") % {
228 'commit1': h.show_id(source_commit),
230 'commit1': h.show_id(source_commit),
229 'repo1': source_repo.repo_name,
231 'repo1': source_repo.repo_name,
230 'commit2': h.show_id(target_commit),
232 'commit2': h.show_id(target_commit),
231 'repo2': target_repo.repo_name,
233 'repo2': target_repo.repo_name,
232 }
234 }
233 h.flash(msg, category='error')
235 h.flash(msg, category='error')
234 raise HTTPBadRequest()
236 raise HTTPBadRequest()
235
237
236 txtdiff = source_repo.scm_instance().get_diff(
238 txtdiff = source_repo.scm_instance().get_diff(
237 commit1=source_commit, commit2=target_commit,
239 commit1=source_commit, commit2=target_commit,
238 path1=source_path, path=target_path)
240 path1=source_path, path=target_path)
239 diff_processor = diffs.DiffProcessor(
241 diff_processor = diffs.DiffProcessor(
240 txtdiff, format='gitdiff', diff_limit=diff_limit,
242 txtdiff, format='gitdiff', diff_limit=diff_limit,
241 file_limit=file_limit, show_full_diff=c.fulldiff)
243 file_limit=file_limit, show_full_diff=c.fulldiff)
242 _parsed = diff_processor.prepare()
244 _parsed = diff_processor.prepare()
243
245
244 c.limited_diff = False
246 c.limited_diff = False
245 if isinstance(_parsed, diffs.LimitedDiffContainer):
247 if isinstance(_parsed, diffs.LimitedDiffContainer):
246 c.limited_diff = True
248 c.limited_diff = True
247
249
248 c.files = []
250 c.files = []
249 c.changes = {}
251 c.changes = {}
250 c.lines_added = 0
252 c.lines_added = 0
251 c.lines_deleted = 0
253 c.lines_deleted = 0
252 for f in _parsed:
254 for f in _parsed:
253 st = f['stats']
255 st = f['stats']
254 if not st['binary']:
256 if not st['binary']:
255 c.lines_added += st['added']
257 c.lines_added += st['added']
256 c.lines_deleted += st['deleted']
258 c.lines_deleted += st['deleted']
257 fid = h.FID('', f['filename'])
259 fid = h.FID('', f['filename'])
258 c.files.append([fid, f['operation'], f['filename'], f['stats'], f])
260 c.files.append([fid, f['operation'], f['filename'], f['stats'], f])
259 htmldiff = diff_processor.as_html(
261 htmldiff = diff_processor.as_html(
260 enable_comments=False, parsed_lines=[f])
262 enable_comments=False, parsed_lines=[f])
261 c.changes[fid] = [f['operation'], f['filename'], htmldiff, f]
263 c.changes[fid] = [f['operation'], f['filename'], htmldiff, f]
262
264
263 c.preview_mode = merge
265 c.preview_mode = merge
264
266
265 return render('compare/compare_diff.html')
267 return render('compare/compare_diff.html')
General Comments 0
You need to be logged in to leave comments. Login now