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