##// END OF EJS Templates
changeset: add new diffs to changeset controller
dan -
r1139:35f09407 default
parent child Browse files
Show More
@@ -1,465 +1,469 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 commit controller for RhodeCode showing changes between commits
23 23 """
24 24
25 25 import logging
26 26
27 27 from collections import defaultdict
28 28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
29 29
30 30 from pylons import tmpl_context as c, request, response
31 31 from pylons.i18n.translation import _
32 32 from pylons.controllers.util import redirect
33 33
34 34 from rhodecode.lib import auth
35 from rhodecode.lib import diffs
35 from rhodecode.lib import diffs, codeblocks
36 36 from rhodecode.lib.auth import (
37 37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
38 38 from rhodecode.lib.base import BaseRepoController, render
39 39 from rhodecode.lib.compat import OrderedDict
40 40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 41 import rhodecode.lib.helpers as h
42 42 from rhodecode.lib.utils import action_logger, jsonify
43 43 from rhodecode.lib.utils2 import safe_unicode
44 44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 45 from rhodecode.lib.vcs.exceptions import (
46 46 RepositoryError, CommitDoesNotExistError)
47 47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
48 48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 49 from rhodecode.model.comment import ChangesetCommentsModel
50 50 from rhodecode.model.meta import Session
51 51 from rhodecode.model.repo import RepoModel
52 52
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 def _update_with_GET(params, GET):
58 58 for k in ['diff1', 'diff2', 'diff']:
59 59 params[k] += GET.getall(k)
60 60
61 61
62 62 def get_ignore_ws(fid, GET):
63 63 ig_ws_global = GET.get('ignorews')
64 64 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
65 65 if ig_ws:
66 66 try:
67 67 return int(ig_ws[0].split(':')[-1])
68 68 except Exception:
69 69 pass
70 70 return ig_ws_global
71 71
72 72
73 73 def _ignorews_url(GET, fileid=None):
74 74 fileid = str(fileid) if fileid else None
75 75 params = defaultdict(list)
76 76 _update_with_GET(params, GET)
77 77 label = _('Show whitespace')
78 78 tooltiplbl = _('Show whitespace for all diffs')
79 79 ig_ws = get_ignore_ws(fileid, GET)
80 80 ln_ctx = get_line_ctx(fileid, GET)
81 81
82 82 if ig_ws is None:
83 83 params['ignorews'] += [1]
84 84 label = _('Ignore whitespace')
85 85 tooltiplbl = _('Ignore whitespace for all diffs')
86 86 ctx_key = 'context'
87 87 ctx_val = ln_ctx
88 88
89 89 # if we have passed in ln_ctx pass it along to our params
90 90 if ln_ctx:
91 91 params[ctx_key] += [ctx_val]
92 92
93 93 if fileid:
94 94 params['anchor'] = 'a_' + fileid
95 95 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
96 96
97 97
98 98 def get_line_ctx(fid, GET):
99 99 ln_ctx_global = GET.get('context')
100 100 if fid:
101 101 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102 102 else:
103 103 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
104 104 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
105 105 if ln_ctx:
106 106 ln_ctx = [ln_ctx]
107 107
108 108 if ln_ctx:
109 109 retval = ln_ctx[0].split(':')[-1]
110 110 else:
111 111 retval = ln_ctx_global
112 112
113 113 try:
114 114 return int(retval)
115 115 except Exception:
116 116 return 3
117 117
118 118
119 119 def _context_url(GET, fileid=None):
120 120 """
121 121 Generates a url for context lines.
122 122
123 123 :param fileid:
124 124 """
125 125
126 126 fileid = str(fileid) if fileid else None
127 127 ig_ws = get_ignore_ws(fileid, GET)
128 128 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
129 129
130 130 params = defaultdict(list)
131 131 _update_with_GET(params, GET)
132 132
133 133 if ln_ctx > 0:
134 134 params['context'] += [ln_ctx]
135 135
136 136 if ig_ws:
137 137 ig_ws_key = 'ignorews'
138 138 ig_ws_val = 1
139 139 params[ig_ws_key] += [ig_ws_val]
140 140
141 141 lbl = _('Increase context')
142 142 tooltiplbl = _('Increase context for all diffs')
143 143
144 144 if fileid:
145 145 params['anchor'] = 'a_' + fileid
146 146 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
147 147
148 148
149 149 class ChangesetController(BaseRepoController):
150 150
151 151 def __before__(self):
152 152 super(ChangesetController, self).__before__()
153 153 c.affected_files_cut_off = 60
154 154
155 155 def _index(self, commit_id_range, method):
156 156 c.ignorews_url = _ignorews_url
157 157 c.context_url = _context_url
158 158 c.fulldiff = fulldiff = request.GET.get('fulldiff')
159 159 # get ranges of commit ids if preset
160 160 commit_range = commit_id_range.split('...')[:2]
161 161 enable_comments = True
162 162 try:
163 163 pre_load = ['affected_files', 'author', 'branch', 'date',
164 164 'message', 'parents']
165 165
166 166 if len(commit_range) == 2:
167 167 enable_comments = False
168 168 commits = c.rhodecode_repo.get_commits(
169 169 start_id=commit_range[0], end_id=commit_range[1],
170 170 pre_load=pre_load)
171 171 commits = list(commits)
172 172 else:
173 173 commits = [c.rhodecode_repo.get_commit(
174 174 commit_id=commit_id_range, pre_load=pre_load)]
175 175
176 176 c.commit_ranges = commits
177 177 if not c.commit_ranges:
178 178 raise RepositoryError(
179 179 'The commit range returned an empty result')
180 180 except CommitDoesNotExistError:
181 181 msg = _('No such commit exists for this repository')
182 182 h.flash(msg, category='error')
183 183 raise HTTPNotFound()
184 184 except Exception:
185 185 log.exception("General failure")
186 186 raise HTTPNotFound()
187 187
188 188 c.changes = OrderedDict()
189 189 c.lines_added = 0
190 190 c.lines_deleted = 0
191 191
192 192 c.commit_statuses = ChangesetStatus.STATUSES
193 193 c.comments = []
194 194 c.statuses = []
195 195 c.inline_comments = []
196 196 c.inline_cnt = 0
197 197 c.files = []
198 198
199 199 # Iterate over ranges (default commit view is always one commit)
200 200 for commit in c.commit_ranges:
201 201 if method == 'show':
202 202 c.statuses.extend([ChangesetStatusModel().get_status(
203 203 c.rhodecode_db_repo.repo_id, commit.raw_id)])
204 204
205 205 c.comments.extend(ChangesetCommentsModel().get_comments(
206 206 c.rhodecode_db_repo.repo_id,
207 207 revision=commit.raw_id))
208 208
209 209 # comments from PR
210 210 st = ChangesetStatusModel().get_statuses(
211 211 c.rhodecode_db_repo.repo_id, commit.raw_id,
212 212 with_revisions=True)
213 213
214 214 # from associated statuses, check the pull requests, and
215 215 # show comments from them
216 216
217 217 prs = set(x.pull_request for x in
218 218 filter(lambda x: x.pull_request is not None, st))
219 219 for pr in prs:
220 220 c.comments.extend(pr.comments)
221 221
222 222 inlines = ChangesetCommentsModel().get_inline_comments(
223 223 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
224 224 c.inline_comments.extend(inlines.iteritems())
225 225
226 226 c.changes[commit.raw_id] = []
227 227
228 228 commit2 = commit
229 229 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
230 230
231 231 # fetch global flags of ignore ws or context lines
232 232 context_lcl = get_line_ctx('', request.GET)
233 233 ign_whitespace_lcl = get_ignore_ws('', request.GET)
234 234
235 235 _diff = c.rhodecode_repo.get_diff(
236 236 commit1, commit2,
237 237 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
238 238
239 239 # diff_limit will cut off the whole diff if the limit is applied
240 240 # otherwise it will just hide the big files from the front-end
241 241 diff_limit = self.cut_off_limit_diff
242 242 file_limit = self.cut_off_limit_file
243 243
244 244 diff_processor = diffs.DiffProcessor(
245 _diff, format='gitdiff', diff_limit=diff_limit,
245 _diff, format='newdiff', diff_limit=diff_limit,
246 246 file_limit=file_limit, show_full_diff=fulldiff)
247 247 commit_changes = OrderedDict()
248 248 if method == 'show':
249 249 _parsed = diff_processor.prepare()
250 250 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
251 for f in _parsed:
252 c.files.append(f)
253 st = f['stats']
254 c.lines_added += st['added']
255 c.lines_deleted += st['deleted']
256 fid = h.FID(commit.raw_id, f['filename'])
257 diff = diff_processor.as_html(enable_comments=enable_comments,
258 parsed_lines=[f])
259 commit_changes[fid] = [
260 commit1.raw_id, commit2.raw_id,
261 f['operation'], f['filename'], diff, st, f]
251
252 _parsed = diff_processor.prepare()
253
254 def _node_getter(commit):
255 def get_node(fname):
256 try:
257 return commit.get_node(fname)
258 except NodeDoesNotExistError:
259 return None
260 return get_node
261
262 diffset = codeblocks.DiffSet(
263 source_node_getter=_node_getter(commit1),
264 target_node_getter=_node_getter(commit2),
265 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
266 c.changes[commit.raw_id] = diffset
262 267 else:
263 268 # downloads/raw we only need RAW diff nothing else
264 269 diff = diff_processor.as_raw()
265 commit_changes[''] = [None, None, None, None, diff, None, None]
266 c.changes[commit.raw_id] = commit_changes
270 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
267 271
268 272 # sort comments by how they were generated
269 273 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
270 274
271 275 # count inline comments
272 276 for __, lines in c.inline_comments:
273 277 for comments in lines.values():
274 278 c.inline_cnt += len(comments)
275 279
276 280 if len(c.commit_ranges) == 1:
277 281 c.commit = c.commit_ranges[0]
278 282 c.parent_tmpl = ''.join(
279 283 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
280 284 if method == 'download':
281 285 response.content_type = 'text/plain'
282 286 response.content_disposition = (
283 287 'attachment; filename=%s.diff' % commit_id_range[:12])
284 288 return diff
285 289 elif method == 'patch':
286 290 response.content_type = 'text/plain'
287 291 c.diff = safe_unicode(diff)
288 292 return render('changeset/patch_changeset.html')
289 293 elif method == 'raw':
290 294 response.content_type = 'text/plain'
291 295 return diff
292 296 elif method == 'show':
293 297 if len(c.commit_ranges) == 1:
294 298 return render('changeset/changeset.html')
295 299 else:
296 300 c.ancestor = None
297 301 c.target_repo = c.rhodecode_db_repo
298 302 return render('changeset/changeset_range.html')
299 303
300 304 @LoginRequired()
301 305 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
302 306 'repository.admin')
303 307 def index(self, revision, method='show'):
304 308 return self._index(revision, method=method)
305 309
306 310 @LoginRequired()
307 311 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
308 312 'repository.admin')
309 313 def changeset_raw(self, revision):
310 314 return self._index(revision, method='raw')
311 315
312 316 @LoginRequired()
313 317 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
314 318 'repository.admin')
315 319 def changeset_patch(self, revision):
316 320 return self._index(revision, method='patch')
317 321
318 322 @LoginRequired()
319 323 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
320 324 'repository.admin')
321 325 def changeset_download(self, revision):
322 326 return self._index(revision, method='download')
323 327
324 328 @LoginRequired()
325 329 @NotAnonymous()
326 330 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 331 'repository.admin')
328 332 @auth.CSRFRequired()
329 333 @jsonify
330 334 def comment(self, repo_name, revision):
331 335 commit_id = revision
332 336 status = request.POST.get('changeset_status', None)
333 337 text = request.POST.get('text')
334 338 if status:
335 339 text = text or (_('Status change %(transition_icon)s %(status)s')
336 340 % {'transition_icon': '>',
337 341 'status': ChangesetStatus.get_status_lbl(status)})
338 342
339 343 multi_commit_ids = filter(
340 344 lambda s: s not in ['', None],
341 345 request.POST.get('commit_ids', '').split(','),)
342 346
343 347 commit_ids = multi_commit_ids or [commit_id]
344 348 comment = None
345 349 for current_id in filter(None, commit_ids):
346 350 c.co = comment = ChangesetCommentsModel().create(
347 351 text=text,
348 352 repo=c.rhodecode_db_repo.repo_id,
349 353 user=c.rhodecode_user.user_id,
350 354 revision=current_id,
351 355 f_path=request.POST.get('f_path'),
352 356 line_no=request.POST.get('line'),
353 357 status_change=(ChangesetStatus.get_status_lbl(status)
354 358 if status else None),
355 359 status_change_type=status
356 360 )
357 361 # get status if set !
358 362 if status:
359 363 # if latest status was from pull request and it's closed
360 364 # disallow changing status !
361 365 # dont_allow_on_closed_pull_request = True !
362 366
363 367 try:
364 368 ChangesetStatusModel().set_status(
365 369 c.rhodecode_db_repo.repo_id,
366 370 status,
367 371 c.rhodecode_user.user_id,
368 372 comment,
369 373 revision=current_id,
370 374 dont_allow_on_closed_pull_request=True
371 375 )
372 376 except StatusChangeOnClosedPullRequestError:
373 377 msg = _('Changing the status of a commit associated with '
374 378 'a closed pull request is not allowed')
375 379 log.exception(msg)
376 380 h.flash(msg, category='warning')
377 381 return redirect(h.url(
378 382 'changeset_home', repo_name=repo_name,
379 383 revision=current_id))
380 384
381 385 # finalize, commit and redirect
382 386 Session().commit()
383 387
384 388 data = {
385 389 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
386 390 }
387 391 if comment:
388 392 data.update(comment.get_dict())
389 393 data.update({'rendered_text':
390 394 render('changeset/changeset_comment_block.html')})
391 395
392 396 return data
393 397
394 398 @LoginRequired()
395 399 @NotAnonymous()
396 400 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
397 401 'repository.admin')
398 402 @auth.CSRFRequired()
399 403 def preview_comment(self):
400 404 # Technically a CSRF token is not needed as no state changes with this
401 405 # call. However, as this is a POST is better to have it, so automated
402 406 # tools don't flag it as potential CSRF.
403 407 # Post is required because the payload could be bigger than the maximum
404 408 # allowed by GET.
405 409 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
406 410 raise HTTPBadRequest()
407 411 text = request.POST.get('text')
408 412 renderer = request.POST.get('renderer') or 'rst'
409 413 if text:
410 414 return h.render(text, renderer=renderer, mentions=True)
411 415 return ''
412 416
413 417 @LoginRequired()
414 418 @NotAnonymous()
415 419 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
416 420 'repository.admin')
417 421 @auth.CSRFRequired()
418 422 @jsonify
419 423 def delete_comment(self, repo_name, comment_id):
420 424 comment = ChangesetComment.get(comment_id)
421 425 owner = (comment.author.user_id == c.rhodecode_user.user_id)
422 426 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
423 427 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
424 428 ChangesetCommentsModel().delete(comment=comment)
425 429 Session().commit()
426 430 return True
427 431 else:
428 432 raise HTTPForbidden()
429 433
430 434 @LoginRequired()
431 435 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
432 436 'repository.admin')
433 437 @jsonify
434 438 def changeset_info(self, repo_name, revision):
435 439 if request.is_xhr:
436 440 try:
437 441 return c.rhodecode_repo.get_commit(commit_id=revision)
438 442 except CommitDoesNotExistError as e:
439 443 return EmptyCommit(message=str(e))
440 444 else:
441 445 raise HTTPBadRequest()
442 446
443 447 @LoginRequired()
444 448 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
445 449 'repository.admin')
446 450 @jsonify
447 451 def changeset_children(self, repo_name, revision):
448 452 if request.is_xhr:
449 453 commit = c.rhodecode_repo.get_commit(commit_id=revision)
450 454 result = {"results": commit.children}
451 455 return result
452 456 else:
453 457 raise HTTPBadRequest()
454 458
455 459 @LoginRequired()
456 460 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
457 461 'repository.admin')
458 462 @jsonify
459 463 def changeset_parents(self, repo_name, revision):
460 464 if request.is_xhr:
461 465 commit = c.rhodecode_repo.get_commit(commit_id=revision)
462 466 result = {"results": commit.parents}
463 467 return result
464 468 else:
465 469 raise HTTPBadRequest()
@@ -1,395 +1,317 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
5 5
6 6 <%def name="title()">
7 7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
8 8 %if c.rhodecode_name:
9 9 &middot; ${h.branding(c.rhodecode_name)}
10 10 %endif
11 11 </%def>
12 12
13 13 <%def name="menu_bar_nav()">
14 14 ${self.menu_items(active='repositories')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.repo_menu(active='changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <script>
23 23 // TODO: marcink switch this to pyroutes
24 24 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
25 25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
26 26 </script>
27 27 <div class="box">
28 28 <div class="title">
29 29 ${self.repo_page_title(c.rhodecode_db_repo)}
30 30 </div>
31 31
32 32 <div id="changeset_compare_view_content" class="summary changeset">
33 33 <div class="summary-detail">
34 34 <div class="summary-detail-header">
35 35 <span class="breadcrumbs files_location">
36 36 <h4>${_('Commit')}
37 37 <code>
38 38 ${h.show_id(c.commit)}
39 39 </code>
40 40 </h4>
41 41 </span>
42 42 <span id="parent_link">
43 43 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
44 44 </span>
45 45 |
46 46 <span id="child_link">
47 47 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
48 48 </span>
49 49 </div>
50 50
51 51 <div class="fieldset">
52 52 <div class="left-label">
53 53 ${_('Description')}:
54 54 </div>
55 55 <div class="right-content">
56 56 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
57 57 <div id="message_expand" style="display:none;">
58 58 ${_('Expand')}
59 59 </div>
60 60 </div>
61 61 </div>
62 62
63 63 %if c.statuses:
64 64 <div class="fieldset">
65 65 <div class="left-label">
66 66 ${_('Commit status')}:
67 67 </div>
68 68 <div class="right-content">
69 69 <div class="changeset-status-ico">
70 70 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
71 71 </div>
72 72 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
73 73 </div>
74 74 </div>
75 75 %endif
76 76
77 77 <div class="fieldset">
78 78 <div class="left-label">
79 79 ${_('References')}:
80 80 </div>
81 81 <div class="right-content">
82 82 <div class="tags">
83 83
84 84 %if c.commit.merge:
85 85 <span class="mergetag tag">
86 86 <i class="icon-merge"></i>${_('merge')}
87 87 </span>
88 88 %endif
89 89
90 90 %if h.is_hg(c.rhodecode_repo):
91 91 %for book in c.commit.bookmarks:
92 92 <span class="booktag tag" title="${_('Bookmark %s') % book}">
93 93 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
94 94 </span>
95 95 %endfor
96 96 %endif
97 97
98 98 %for tag in c.commit.tags:
99 99 <span class="tagtag tag" title="${_('Tag %s') % tag}">
100 100 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
101 101 </span>
102 102 %endfor
103 103
104 104 %if c.commit.branch:
105 105 <span class="branchtag tag" title="${_('Branch %s') % c.commit.branch}">
106 106 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
107 107 </span>
108 108 %endif
109 109 </div>
110 110 </div>
111 111 </div>
112 112
113 113 <div class="fieldset">
114 114 <div class="left-label">
115 115 ${_('Diffs')}:
116 116 </div>
117 117 <div class="right-content">
118 118 <div class="diff-actions">
119 119 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
120 120 ${_('Raw Diff')}
121 121 </a>
122 122 |
123 123 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
124 124 ${_('Patch Diff')}
125 125 </a>
126 126 |
127 127 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
128 128 ${_('Download Diff')}
129 129 </a>
130 130 |
131 131 ${c.ignorews_url(request.GET)}
132 132 |
133 133 ${c.context_url(request.GET)}
134 134 </div>
135 135 </div>
136 136 </div>
137 137
138 138 <div class="fieldset">
139 139 <div class="left-label">
140 140 ${_('Comments')}:
141 141 </div>
142 142 <div class="right-content">
143 143 <div class="comments-number">
144 144 %if c.comments:
145 145 <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
146 146 %else:
147 147 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
148 148 %endif
149 149 %if c.inline_cnt:
150 150 ## this is replaced with a proper link to first comment via JS linkifyComments() func
151 151 <a href="#inline-comments" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
152 152 %else:
153 153 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
154 154 %endif
155 155 </div>
156 156 </div>
157 157 </div>
158 158
159 159 </div> <!-- end summary-detail -->
160 160
161 161 <div id="commit-stats" class="sidebar-right">
162 162 <div class="summary-detail-header">
163 163 <h4 class="item">
164 164 ${_('Author')}
165 165 </h4>
166 166 </div>
167 167 <div class="sidebar-right-content">
168 168 ${self.gravatar_with_user(c.commit.author)}
169 169 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
170 170 </div>
171 171 </div><!-- end sidebar -->
172 172 </div> <!-- end summary -->
173 <div class="cs_files_title">
174 <span class="cs_files_expand">
175 <span id="files_link"><a href="#" title="${_('Browse files at current commit')}">${_('Browse files')}</a></span> |
173 <div class="cs_files">
174 ${cbdiffs.render_diffset_menu()}
176 175
177 <span id="expand_all_files">${_('Expand All')}</span> | <span id="collapse_all_files">${_('Collapse All')}</span>
178 </span>
179 <h2>
180 ${diff_block.diff_summary_text(len(c.files), c.lines_added, c.lines_deleted, c.limited_diff)}
181 </h2>
176 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
177 ${cbdiffs.render_diffset(c.changes[c.commit.raw_id], commit=c.commit)}
182 178 </div>
183 179 </div>
184 180
185 <div class="cs_files">
186
187 %if not c.files:
188 <p class="empty_data">${_('No files')}</p>
189 %endif
190
191 <table class="compare_view_files commit_diff">
192 %for FID, (cs1, cs2, change, path, diff, stats, file) in c.changes[c.commit.raw_id].iteritems():
193 <tr class="cs_${change} collapse_file" fid="${FID}">
194 <td class="cs_icon_td">
195 <span class="collapse_file_icon" fid="${FID}"></span>
196 </td>
197 <td class="cs_icon_td">
198 <div class="flag_status not_reviewed hidden"></div>
199 </td>
200 <td class="cs_${change}" id="a_${FID}">
201 <div class="node">
202 <a href="#a_${FID}">
203 <i class="icon-file-${change.lower()}"></i>
204 ${h.safe_unicode(path)}
205 </a>
206 </div>
207 </td>
208 <td>
209 <div class="changes pull-right">${h.fancy_file_stats(stats)}</div>
210 <div class="comment-bubble pull-right" data-path="${path}">
211 <i class="icon-comment"></i>
212 </div>
213 </td>
214 </tr>
215 <tr fid="${FID}" id="diff_${FID}" class="diff_links">
216 <td></td>
217 <td></td>
218 <td class="cs_${change}">
219 ${diff_block.diff_menu(c.repo_name, h.safe_unicode(path), cs1, cs2, change, file)}
220 </td>
221 <td class="td-actions rc-form">
222 ${c.ignorews_url(request.GET, h.FID(cs2,path))} |
223 ${c.context_url(request.GET, h.FID(cs2,path))} |
224 <div data-comment-id="${h.FID(cs2,path)}" class="btn-link show-inline-comments comments-visible">
225 <span class="comments-show">${_('Show comments')}</span>
226 <span class="comments-hide">${_('Hide comments')}</span>
227 </div>
228 </td>
229 </tr>
230 <tr id="tr_${FID}">
231 <td></td>
232 <td></td>
233 <td class="injected_diff" colspan="2">
234 <div class="diff-container" id="${'diff-container-%s' % (id(change))}">
235 <div id="${FID}" class="diffblock margined comm">
236 <div class="code-body">
237 <div class="full_f_path" path="${h.safe_unicode(path)}"></div>
238 ${diff|n}
239 % if file and file["is_limited_diff"]:
240 % if file["exceeds_limit"]:
241 ${diff_block.file_message()}
242 % else:
243 <h5>${_('Diff was truncated. File content available only in full diff.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
244 % endif
245 % endif
246 </div>
247 </div>
248 </div>
249 </td>
250 </tr>
251 %endfor
252 </table>
253 </div>
254
255 % if c.limited_diff:
256 ${diff_block.changeset_message()}
257 % endif
258
259 181 ## template for inline comment form
260 182 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
261 183 ${comment.comment_inline_form()}
262 184
263 185 ## render comments and inlines
264 186 ${comment.generate_comments()}
265 187
266 188 ## main comment form and it status
267 189 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
268 190 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
269 191
270 192 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
271 193 <script type="text/javascript">
272 194
273 195 $(document).ready(function() {
274 196
275 197 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
276 198 if($('#trimmed_message_box').height() === boxmax){
277 199 $('#message_expand').show();
278 200 }
279 201
280 202 $('#message_expand').on('click', function(e){
281 203 $('#trimmed_message_box').css('max-height', 'none');
282 204 $(this).hide();
283 205 });
284 206
285 207 $('.show-inline-comments').on('click', function(e){
286 208 var boxid = $(this).attr('data-comment-id');
287 209 var button = $(this);
288 210
289 211 if(button.hasClass("comments-visible")) {
290 212 $('#{0} .inline-comments'.format(boxid)).each(function(index){
291 213 $(this).hide();
292 214 })
293 215 button.removeClass("comments-visible");
294 216 } else {
295 217 $('#{0} .inline-comments'.format(boxid)).each(function(index){
296 218 $(this).show();
297 219 })
298 220 button.addClass("comments-visible");
299 221 }
300 222 });
301 223
302 224
303 225 // next links
304 226 $('#child_link').on('click', function(e){
305 227 // fetch via ajax what is going to be the next link, if we have
306 228 // >1 links show them to user to choose
307 229 if(!$('#child_link').hasClass('disabled')){
308 230 $.ajax({
309 231 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
310 232 success: function(data) {
311 233 if(data.results.length === 0){
312 234 $('#child_link').html('${_('No Child Commits')}').addClass('disabled');
313 235 }
314 236 if(data.results.length === 1){
315 237 var commit = data.results[0];
316 238 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
317 239 }
318 240 else if(data.results.length === 2){
319 241 $('#child_link').addClass('disabled');
320 242 $('#child_link').addClass('double');
321 243 var _html = '';
322 244 _html +='<a title="__title__" href="__url__">__rev__</a> '
323 245 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
324 246 .replace('__title__', data.results[0].message)
325 247 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
326 248 _html +=' | '
327 249 _html +='<a title="__title__" href="__url__">__rev__</a> '
328 250 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
329 251 .replace('__title__', data.results[1].message)
330 252 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
331 253 $('#child_link').html(_html);
332 254 }
333 255 }
334 256 });
335 257 e.preventDefault();
336 258 }
337 259 });
338 260
339 261 // prev links
340 262 $('#parent_link').on('click', function(e){
341 263 // fetch via ajax what is going to be the next link, if we have
342 264 // >1 links show them to user to choose
343 265 if(!$('#parent_link').hasClass('disabled')){
344 266 $.ajax({
345 267 url: '${h.url('changeset_parents',repo_name=c.repo_name, revision=c.commit.raw_id)}',
346 268 success: function(data) {
347 269 if(data.results.length === 0){
348 270 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
349 271 }
350 272 if(data.results.length === 1){
351 273 var commit = data.results[0];
352 274 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
353 275 }
354 276 else if(data.results.length === 2){
355 277 $('#parent_link').addClass('disabled');
356 278 $('#parent_link').addClass('double');
357 279 var _html = '';
358 280 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
359 281 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
360 282 .replace('__title__', data.results[0].message)
361 283 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
362 284 _html +=' | '
363 285 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
364 286 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
365 287 .replace('__title__', data.results[1].message)
366 288 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
367 289 $('#parent_link').html(_html);
368 290 }
369 291 }
370 292 });
371 293 e.preventDefault();
372 294 }
373 295 });
374 296
375 297 if (location.hash) {
376 298 var result = splitDelimitedHash(location.hash);
377 299 var line = $('html').find(result.loc);
378 300 if (line.length > 0){
379 301 offsetScroll(line, 70);
380 302 }
381 303 }
382 304
383 305 // browse tree @ revision
384 306 $('#files_link').on('click', function(e){
385 307 window.location = '${h.url('files_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path='')}';
386 308 e.preventDefault();
387 309 });
388 310
389 311 // inject comments into their proper positions
390 312 var file_comments = $('.inline-comment-placeholder');
391 313 renderInlineComments(file_comments, true);
392 314 })
393 315 </script>
394 316
395 317 </%def>
@@ -1,126 +1,71 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s Commits') % c.repo_name} -
6 6 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
7 7 ...
8 8 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
9 9 ${ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 ${_('Commits')} -
17 17 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
18 18 ...
19 19 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
20 20 ${ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
21 21 </%def>
22 22
23 23 <%def name="menu_bar_nav()">
24 24 ${self.menu_items(active='repositories')}
25 25 </%def>
26 26
27 27 <%def name="menu_bar_subnav()">
28 28 ${self.repo_menu(active='changelog')}
29 29 </%def>
30 30
31 31 <%def name="main()">
32 32 <div class="summary-header">
33 33 <div class="title">
34 34 <div class="title-content">
35 35 ${self.repo_page_title(c.rhodecode_db_repo)}
36 36 </div>
37 37 </div>
38 38 <div class="header-buttons">
39 39 <a href="${h.url('compare_url', repo_name=c.repo_name, source_ref_type='rev', source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'), target_ref_type='rev', target_ref=c.commit_ranges[-1].raw_id)}"
40 40 class="btn btn-default">
41 41 ${_('Show combined compare')}
42 42 </a>
43 43 </div>
44 44 </div>
45 45
46 46 <div class="summary-detail">
47 47 <div class="title">
48 48 <h2>
49 49 ${self.breadcrumbs_links()}
50 50 </h2>
51 51 </div>
52
52 </div>
53 53 <div id="changeset_compare_view_content">
54 54 ##CS
55 55 <%include file="../compare/compare_commits.html"/>
56 ## FILES
57 <div class="cs_files_title">
58 <span class="cs_files_expand">
59 <span id="expand_all_files">${_('Expand All')}</span> | <span id="collapse_all_files">${_('Collapse All')}</span>
60 </span>
61 <h2>
62 ${diff_block.diff_summary_text(len(c.files), c.lines_added, c.lines_deleted, c.limited_diff)}
63 </h2>
64 </div>
65 </div>
66 </div>
67
68 56 <div class="cs_files">
69 <table class="compare_view_files">
57 ${cbdiffs.render_diffset_menu()}
58 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
70 59 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
71 60 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
72 %for cs in c.commit_ranges:
73 <tr class="rctable">
74 <td colspan="4">
75 <a class="tooltip revision" title="${h.tooltip(cs.message)}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">${'r%s:%s' % (cs.revision,h.short_id(cs.raw_id))}</a> |
76 ${h.age_component(cs.date)}
77 </td>
78 </tr>
79 %for FID, (cs1, cs2, change, path, diff, stats, file) in c.changes[cs.raw_id].iteritems():
80 <tr class="cs_${change} collapse_file" fid="${FID}">
81 <td class="cs_icon_td">
82 <span class="collapse_file_icon" fid="${FID}"></span>
83 </td>
84 <td class="cs_icon_td">
85 <div class="flag_status not_reviewed hidden"></div>
86 </td>
87 <td class="cs_${change}" id="a_${FID}">
88 <div class="node">
89 <a href="#a_${FID}">
90 <i class="icon-file-${change.lower()}"></i>
91 ${h.safe_unicode(path)}
92 </a>
93 </div>
94 </td>
95 <td>
96 <div class="changes">${h.fancy_file_stats(stats)}</div>
97 </td>
98 </tr>
99 <tr fid="${FID}" id="diff_${FID}" class="diff_links">
100 <td></td>
101 <td></td>
102 <td class="cs_${change}">
103 ${diff_block.diff_menu(c.repo_name, h.safe_unicode(path), cs1, cs2, change, file)}
104 </td>
105 <td class="td-actions rc-form"></td>
106 </tr>
107 <tr id="tr_${FID}">
108 <td></td>
109 <td></td>
110 <td class="injected_diff" colspan="2">
111 <div id="diff-container-${FID}" class="diff-container">
112 <div id="${FID}" class="diffblock margined comm">
113 <div class="code-body">
114 ${diff|n}
115 </div>
116 </div>
117 </div>
118 </td>
119 </tr>
120 %endfor
61 %for commit in c.commit_ranges:
62 ${cbdifss.render_diffset(
63 diffset=c.changes[commit.raw_id],
64 collapse_when_files_over=5,
65 commit=commit,
66 )}
121 67 %endfor
122 68 </table>
123 69 </div>
124 ## end summary detail
125
126 </%def> No newline at end of file
70 </div>
71 </%def>
@@ -1,420 +1,420 b''
1 1 <%def name="diff_line_anchor(filename, line, type)"><%
2 2 return '%s_%s_%i' % (h.safeid(filename), type, line)
3 3 %></%def>
4 4
5 5 <%def name="action_class(action)"><%
6 6 return {
7 7 '-': 'cb-deletion',
8 8 '+': 'cb-addition',
9 9 ' ': 'cb-context',
10 10 }.get(action, 'cb-empty')
11 11 %></%def>
12 12
13 13 <%def name="op_class(op_id)"><%
14 14 return {
15 15 DEL_FILENODE: 'deletion', # file deleted
16 16 BIN_FILENODE: 'warning' # binary diff hidden
17 17 }.get(op_id, 'addition')
18 18 %></%def>
19 19
20 20 <%def name="link_for(**kw)"><%
21 21 new_args = request.GET.mixed()
22 22 new_args.update(kw)
23 23 return h.url('', **new_args)
24 24 %></%def>
25 25
26 26 <%def name="render_diffset(diffset, commit=None,
27 27
28 28 # collapse all file diff entries when there are more than this amount of files in the diff
29 29 collapse_when_files_over=20,
30 30
31 31 # collapse lines in the diff when more than this amount of lines changed in the file diff
32 32 lines_changed_limit=500,
33 33
34 34 # add a ruler at to the output
35 35 ruler_at_chars=0,
36 36
37 37 )">
38 38 <%
39 39 collapse_all = len(diffset.files) > collapse_when_files_over
40 40 %>
41 41
42 42 %if c.diffmode == 'sideside':
43 43 <style>
44 44 .wrapper {
45 45 max-width: 1600px !important;
46 46 }
47 47 </style>
48 48 %endif
49 49 %if ruler_at_chars:
50 50 <style>
51 51 .diff table.cb .cb-content:after {
52 52 content: "";
53 53 border-left: 1px solid blue;
54 54 position: absolute;
55 55 top: 0;
56 56 height: 18px;
57 57 opacity: .2;
58 58 z-index: 10;
59 59 ## +5 to account for diff action (+/-)
60 60 left: ${ruler_at_chars + 5}ch;
61 61 </style>
62 62 %endif
63 63
64 64 <div class="diffset">
65 65 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
66 66 %if commit:
67 67 <div class="pull-right">
68 68 <a class="btn tooltip" title="${_('Browse Files at revision {}').format(commit.raw_id)}" href="${h.url('files_home',repo_name=c.repo_name, revision=commit.raw_id, f_path='')}">
69 69 ${_('Browse Files')}
70 70 </a>
71 71 </div>
72 72 %endif
73 73 <h2 class="clearinner">
74 74 %if commit:
75 75 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))}</a> -
76 76 ${h.age_component(commit.date)} -
77 77 %endif
78 78 %if diffset.limited_diff:
79 79 ${_('The requested commit is too big and content was truncated.')}
80 80
81 81 ${ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}}
82 82 <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
83 83 %else:
84 84 ${ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
85 85 '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}}
86 86 %endif
87 87 </h2>
88 88 </div>
89 89
90 90 %if not diffset.files:
91 91 <p class="empty_data">${_('No files')}</p>
92 92 %endif
93 93
94 94 <div class="filediffs">
95 95 %for i, filediff in enumerate(diffset.files):
96 96 <%
97 97 lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted']
98 98 over_lines_changed_limit = lines_changed > lines_changed_limit
99 99 %>
100 100 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
101 101 <div
102 102 class="filediff"
103 103 data-f-path="${filediff['patch']['filename']}"
104 104 id="a_${h.FID(commit and commit.raw_id or '', filediff['patch']['filename'])}">
105 105 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
106 106 <div class="filediff-collapse-indicator"></div>
107 107 ${diff_ops(filediff)}
108 108 </label>
109 109 ${diff_menu(filediff)}
110 110 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
111 111 %if not filediff.hunks:
112 112 %for op_id, op_text in filediff['patch']['stats']['ops'].items():
113 113 <tr>
114 114 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=3' or 'colspan=4'}>
115 115 %if op_id == DEL_FILENODE:
116 116 ${_('File was deleted')}
117 117 %elif op_id == BIN_FILENODE:
118 118 ${_('Binary file hidden')}
119 119 %else:
120 120 ${op_text}
121 121 %endif
122 122 </td>
123 123 </tr>
124 124 %endfor
125 125 %endif
126 126 %if over_lines_changed_limit:
127 127 <tr class="cb-warning cb-collapser">
128 128 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=3' or 'colspan=4'}>
129 129 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
130 130 <a href="#" class="cb-expand"
131 131 onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')}
132 132 </a>
133 133 <a href="#" class="cb-collapse"
134 134 onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')}
135 135 </a>
136 136 </td>
137 137 </tr>
138 138 %endif
139 139 %if filediff.patch['is_limited_diff']:
140 140 <tr class="cb-warning cb-collapser">
141 141 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=3' or 'colspan=4'}>
142 142 ${_('The requested commit is too big and content was truncated.')} <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
143 143 </td>
144 144 </tr>
145 145 %endif
146 146 %for hunk in filediff.hunks:
147 147 <tr class="cb-hunk">
148 148 <td ${c.diffmode == 'unified' and 'colspan=2' or ''}>
149 149 ## TODO: dan: add ajax loading of more context here
150 150 ## <a href="#">
151 151 <i class="icon-more"></i>
152 152 ## </a>
153 153 </td>
154 154 <td ${c.diffmode == 'sideside' and 'colspan=3' or ''}>
155 155 @@
156 156 -${hunk.source_start},${hunk.source_length}
157 157 +${hunk.target_start},${hunk.target_length}
158 158 ${hunk.section_header}
159 159 </td>
160 160 </tr>
161 161 %if c.diffmode == 'unified':
162 162 ${render_hunk_lines_unified(hunk)}
163 163 %elif c.diffmode == 'sideside':
164 164 ${render_hunk_lines_sideside(hunk)}
165 165 %else:
166 166 <tr class="cb-line">
167 167 <td>unknown diff mode</td>
168 168 </tr>
169 169 %endif
170 170 %endfor
171 171 </table>
172 172 </div>
173 173 %endfor
174 174 </div>
175 175 </div>
176 176 </%def>
177 177
178 178 <%def name="diff_ops(filediff)">
179 179 <%
180 180 stats = filediff['patch']['stats']
181 181 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
182 182 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
183 183 %>
184 184 <span class="pill">
185 185 %if filediff.source_file_path and filediff.target_file_path:
186 186 %if filediff.source_file_path != filediff.target_file_path: # file was renamed
187 187 <strong>${filediff.target_file_path}</strong> β¬… <del>${filediff.source_file_path}</del>
188 188 %else:
189 189 ## file was modified
190 190 <strong>${filediff.source_file_path}</strong>
191 191 %endif
192 192 %else:
193 193 %if filediff.source_file_path:
194 194 ## file was deleted
195 195 <strong>${filediff.source_file_path}</strong>
196 196 %else:
197 197 ## file was added
198 198 <strong>${filediff.target_file_path}</strong>
199 199 %endif
200 200 %endif
201 201 </span>
202 202 <span class="pill-group" style="float: left">
203 203 %if filediff.patch['is_limited_diff']:
204 204 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
205 205 %endif
206 206 %if RENAMED_FILENODE in stats['ops']:
207 207 <span class="pill" op="renamed">renamed</span>
208 208 %endif
209 209
210 210 %if NEW_FILENODE in stats['ops']:
211 211 <span class="pill" op="created">created</span>
212 212 %if filediff['target_mode'].startswith('120'):
213 213 <span class="pill" op="symlink">symlink</span>
214 214 %else:
215 215 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
216 216 %endif
217 217 %endif
218 218
219 219 %if DEL_FILENODE in stats['ops']:
220 220 <span class="pill" op="removed">removed</span>
221 221 %endif
222 222
223 223 %if CHMOD_FILENODE in stats['ops']:
224 224 <span class="pill" op="mode">
225 225 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
226 226 </span>
227 227 %endif
228 228 </span>
229 229
230 230 <a class="pill filediff-anchor" href="#a_${h.FID(commit and commit.raw_id or '', filediff.patch['filename'])}">ΒΆ</a>
231 231
232 232 <span class="pill-group" style="float: right">
233 233 %if BIN_FILENODE in stats['ops']:
234 234 <span class="pill" op="binary">binary</span>
235 235 %if MOD_FILENODE in stats['ops']:
236 236 <span class="pill" op="modified">modified</span>
237 237 %endif
238 238 %endif
239 239 %if stats['added']:
240 240 <span class="pill" op="added">+${stats['added']}</span>
241 241 %endif
242 242 %if stats['deleted']:
243 243 <span class="pill" op="deleted">-${stats['deleted']}</span>
244 244 %endif
245 245 </span>
246 246
247 247 </%def>
248 248
249 249 <%def name="nice_mode(filemode)">
250 250 ${filemode.startswith('100') and filemode[3:] or filemode}
251 251 </%def>
252 252
253 253 <%def name="diff_menu(filediff)">
254 254 <div class="filediff-menu">
255 255 %if filediff.diffset.source_ref:
256 256 %if filediff.patch['operation'] in ['D', 'M']:
257 257 <a
258 258 class="tooltip"
259 259 href="${h.url('files_home',repo_name=c.repo_name,f_path=filediff.source_file_path,revision=filediff.diffset.source_ref)}"
260 260 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
261 261 >
262 262 ${_('Show file before')}
263 263 </a>
264 264 %else:
265 265 <span
266 266 class="tooltip"
267 267 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
268 268 >
269 269 ${_('Show file before')}
270 270 </span>
271 271 %endif
272 272 %if filediff.patch['operation'] in ['A', 'M']:
273 273 <a
274 274 class="tooltip"
275 275 href="${h.url('files_home',repo_name=c.repo_name,f_path=filediff.target_file_path,revision=filediff.diffset.target_ref)}"
276 276 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
277 277 >
278 278 ${_('Show file after')}
279 279 </a>
280 280 %else:
281 281 <span
282 282 class="tooltip"
283 283 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
284 284 >
285 285 ${_('Show file after')}
286 286 </span>
287 287 %endif
288 288 <a
289 289 class="tooltip"
290 290 title="${h.tooltip(_('Raw diff'))}"
291 291 href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw')}"
292 292 >
293 293 ${_('Raw diff')}
294 294 </a>
295 295 <a
296 296 class="tooltip"
297 297 title="${h.tooltip(_('Download diff'))}"
298 298 href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download')}"
299 299 >
300 300 ${_('Download diff')}
301 301 </a>
302 302 %endif
303 303 </div>
304 304 </%def>
305 305
306 306
307 307 <%def name="render_hunk_lines_sideside(hunk)">
308 308 %for i, line in enumerate(hunk.sideside):
309 309 <%
310 310 old_line_anchor, new_line_anchor = None, None
311 311 if line.original.lineno:
312 312 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, line.original.lineno, 'o')
313 313 if line.modified.lineno:
314 314 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, line.modified.lineno, 'n')
315 315 %>
316 316 <tr class="cb-line">
317 317 <td class="cb-lineno ${action_class(line.original.action)}"
318 318 data-line-number="${line.original.lineno}"
319 319 %if old_line_anchor:
320 320 id="${old_line_anchor}"
321 321 %endif
322 322 >
323 323 %if line.original.lineno:
324 324 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
325 325 %endif
326 326 </td>
327 327 <td class="cb-content ${action_class(line.original.action)}"
328 328 data-line-number="o${line.original.lineno}"
329 329 ><span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
330 330 </td>
331 331 <td class="cb-lineno ${action_class(line.modified.action)}"
332 332 data-line-number="${line.modified.lineno}"
333 333 %if new_line_anchor:
334 334 id="${new_line_anchor}"
335 335 %endif
336 336 >
337 337 %if line.modified.lineno:
338 338 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
339 339 %endif
340 340 </td>
341 341 <td class="cb-content ${action_class(line.modified.action)}"
342 342 data-line-number="n${line.modified.lineno}"
343 343 >
344 344 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
345 345 </td>
346 346 </tr>
347 347 %endfor
348 348 </%def>
349 349
350 350
351 351 <%def name="render_hunk_lines_unified(hunk)">
352 352 %for old_line_no, new_line_no, action, content in hunk.unified:
353 353 <%
354 354 old_line_anchor, new_line_anchor = None, None
355 355 if old_line_no:
356 356 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, old_line_no, 'o')
357 357 if new_line_no:
358 358 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, new_line_no, 'n')
359 359 %>
360 360 <tr class="cb-line">
361 361 <td class="cb-lineno ${action_class(action)}"
362 362 data-line-number="${old_line_no}"
363 363 %if old_line_anchor:
364 364 id="${old_line_anchor}"
365 365 %endif
366 366 >
367 367 %if old_line_anchor:
368 368 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
369 369 %endif
370 370 </td>
371 371 <td class="cb-lineno ${action_class(action)}"
372 372 data-line-number="${new_line_no}"
373 373 %if new_line_anchor:
374 374 id="${new_line_anchor}"
375 375 %endif
376 376 >
377 377 %if new_line_anchor:
378 378 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
379 379 %endif
380 380 </td>
381 381 <td class="cb-content ${action_class(action)}"
382 382 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
383 383 ><span class="cb-code">${action} ${content or '' | n}</span>
384 384 </td>
385 385 </tr>
386 386 %endfor
387 387 </%def>
388 388
389 389
390 390 <%def name="render_diffset_menu()">
391 391 <div class="diffset-menu clearinner">
392 392 <div class="pull-right">
393 393 <div class="btn-group">
394 394 <a
395 395 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
396 396 title="${_('View side by side')}"
397 397 href="${h.url_replace(diffmode='sideside')}">
398 398 <span>${_('Side by Side')}</span>
399 399 </a>
400 400 <a
401 401 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
402 402 title="${_('View unified')}" href="${h.url_replace(diffmode='unified')}">
403 403 <span>${_('Unified')}</span>
404 404 </a>
405 405 </div>
406 406 </div>
407 407 <div class="pull-left">
408 408 <div class="btn-group">
409 409 <a
410 410 class="btn"
411 411 href="#"
412 412 onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All')}</a>
413 413 <a
414 414 class="btn"
415 415 href="#"
416 416 onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All')}</a>
417 417 </div>
418 418 </div>
419 419 </div>
420 </%def> No newline at end of file
420 </%def>
General Comments 0
You need to be logged in to leave comments. Login now