##// END OF EJS Templates
pull-requests: updated versioning support....
marcink -
r1268:7eb711c7 default
parent child Browse files
Show More
@@ -1,464 +1,468 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, codeblocks
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, NodeDoesNotExistError)
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
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
159
160 # fetch global flags of ignore ws or context lines
160 # fetch global flags of ignore ws or context lines
161 context_lcl = get_line_ctx('', request.GET)
161 context_lcl = get_line_ctx('', request.GET)
162 ign_whitespace_lcl = get_ignore_ws('', request.GET)
162 ign_whitespace_lcl = get_ignore_ws('', request.GET)
163
163
164 # diff_limit will cut off the whole diff if the limit is applied
164 # diff_limit will cut off the whole diff if the limit is applied
165 # otherwise it will just hide the big files from the front-end
165 # otherwise it will just hide the big files from the front-end
166 diff_limit = self.cut_off_limit_diff
166 diff_limit = self.cut_off_limit_diff
167 file_limit = self.cut_off_limit_file
167 file_limit = self.cut_off_limit_file
168
168
169 # get ranges of commit ids if preset
169 # get ranges of commit ids if preset
170 commit_range = commit_id_range.split('...')[:2]
170 commit_range = commit_id_range.split('...')[:2]
171
171
172 try:
172 try:
173 pre_load = ['affected_files', 'author', 'branch', 'date',
173 pre_load = ['affected_files', 'author', 'branch', 'date',
174 'message', 'parents']
174 'message', 'parents']
175
175
176 if len(commit_range) == 2:
176 if len(commit_range) == 2:
177 commits = c.rhodecode_repo.get_commits(
177 commits = c.rhodecode_repo.get_commits(
178 start_id=commit_range[0], end_id=commit_range[1],
178 start_id=commit_range[0], end_id=commit_range[1],
179 pre_load=pre_load)
179 pre_load=pre_load)
180 commits = list(commits)
180 commits = list(commits)
181 else:
181 else:
182 commits = [c.rhodecode_repo.get_commit(
182 commits = [c.rhodecode_repo.get_commit(
183 commit_id=commit_id_range, pre_load=pre_load)]
183 commit_id=commit_id_range, pre_load=pre_load)]
184
184
185 c.commit_ranges = commits
185 c.commit_ranges = commits
186 if not c.commit_ranges:
186 if not c.commit_ranges:
187 raise RepositoryError(
187 raise RepositoryError(
188 'The commit range returned an empty result')
188 'The commit range returned an empty result')
189 except CommitDoesNotExistError:
189 except CommitDoesNotExistError:
190 msg = _('No such commit exists for this repository')
190 msg = _('No such commit exists for this repository')
191 h.flash(msg, category='error')
191 h.flash(msg, category='error')
192 raise HTTPNotFound()
192 raise HTTPNotFound()
193 except Exception:
193 except Exception:
194 log.exception("General failure")
194 log.exception("General failure")
195 raise HTTPNotFound()
195 raise HTTPNotFound()
196
196
197 c.changes = OrderedDict()
197 c.changes = OrderedDict()
198 c.lines_added = 0
198 c.lines_added = 0
199 c.lines_deleted = 0
199 c.lines_deleted = 0
200
200
201 # auto collapse if we have more than limit
202 collapse_limit = diffs.DiffProcessor._collapse_commits_over
203 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
204
201 c.commit_statuses = ChangesetStatus.STATUSES
205 c.commit_statuses = ChangesetStatus.STATUSES
202 c.inline_comments = []
206 c.inline_comments = []
203 c.files = []
207 c.files = []
204
208
205 c.statuses = []
209 c.statuses = []
206 c.comments = []
210 c.comments = []
207 if len(c.commit_ranges) == 1:
211 if len(c.commit_ranges) == 1:
208 commit = c.commit_ranges[0]
212 commit = c.commit_ranges[0]
209 c.comments = ChangesetCommentsModel().get_comments(
213 c.comments = ChangesetCommentsModel().get_comments(
210 c.rhodecode_db_repo.repo_id,
214 c.rhodecode_db_repo.repo_id,
211 revision=commit.raw_id)
215 revision=commit.raw_id)
212 c.statuses.append(ChangesetStatusModel().get_status(
216 c.statuses.append(ChangesetStatusModel().get_status(
213 c.rhodecode_db_repo.repo_id, commit.raw_id))
217 c.rhodecode_db_repo.repo_id, commit.raw_id))
214 # comments from PR
218 # comments from PR
215 statuses = ChangesetStatusModel().get_statuses(
219 statuses = ChangesetStatusModel().get_statuses(
216 c.rhodecode_db_repo.repo_id, commit.raw_id,
220 c.rhodecode_db_repo.repo_id, commit.raw_id,
217 with_revisions=True)
221 with_revisions=True)
218 prs = set(st.pull_request for st in statuses
222 prs = set(st.pull_request for st in statuses
219 if st.pull_request is not None)
223 if st.pull_request is not None)
220 # from associated statuses, check the pull requests, and
224 # from associated statuses, check the pull requests, and
221 # show comments from them
225 # show comments from them
222 for pr in prs:
226 for pr in prs:
223 c.comments.extend(pr.comments)
227 c.comments.extend(pr.comments)
224
228
225 # Iterate over ranges (default commit view is always one commit)
229 # Iterate over ranges (default commit view is always one commit)
226 for commit in c.commit_ranges:
230 for commit in c.commit_ranges:
227 c.changes[commit.raw_id] = []
231 c.changes[commit.raw_id] = []
228
232
229 commit2 = commit
233 commit2 = commit
230 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
234 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
231
235
232 _diff = c.rhodecode_repo.get_diff(
236 _diff = c.rhodecode_repo.get_diff(
233 commit1, commit2,
237 commit1, commit2,
234 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
238 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
235 diff_processor = diffs.DiffProcessor(
239 diff_processor = diffs.DiffProcessor(
236 _diff, format='newdiff', diff_limit=diff_limit,
240 _diff, format='newdiff', diff_limit=diff_limit,
237 file_limit=file_limit, show_full_diff=fulldiff)
241 file_limit=file_limit, show_full_diff=fulldiff)
238
242
239 commit_changes = OrderedDict()
243 commit_changes = OrderedDict()
240 if method == 'show':
244 if method == 'show':
241 _parsed = diff_processor.prepare()
245 _parsed = diff_processor.prepare()
242 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
246 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
243
247
244 _parsed = diff_processor.prepare()
248 _parsed = diff_processor.prepare()
245
249
246 def _node_getter(commit):
250 def _node_getter(commit):
247 def get_node(fname):
251 def get_node(fname):
248 try:
252 try:
249 return commit.get_node(fname)
253 return commit.get_node(fname)
250 except NodeDoesNotExistError:
254 except NodeDoesNotExistError:
251 return None
255 return None
252 return get_node
256 return get_node
253
257
254 inline_comments = ChangesetCommentsModel().get_inline_comments(
258 inline_comments = ChangesetCommentsModel().get_inline_comments(
255 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
259 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
256 c.inline_cnt = ChangesetCommentsModel().get_inline_comments_count(
260 c.inline_cnt = ChangesetCommentsModel().get_inline_comments_count(
257 inline_comments)
261 inline_comments)
258
262
259 diffset = codeblocks.DiffSet(
263 diffset = codeblocks.DiffSet(
260 repo_name=c.repo_name,
264 repo_name=c.repo_name,
261 source_node_getter=_node_getter(commit1),
265 source_node_getter=_node_getter(commit1),
262 target_node_getter=_node_getter(commit2),
266 target_node_getter=_node_getter(commit2),
263 comments=inline_comments
267 comments=inline_comments
264 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
268 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
265 c.changes[commit.raw_id] = diffset
269 c.changes[commit.raw_id] = diffset
266 else:
270 else:
267 # downloads/raw we only need RAW diff nothing else
271 # downloads/raw we only need RAW diff nothing else
268 diff = diff_processor.as_raw()
272 diff = diff_processor.as_raw()
269 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
273 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
270
274
271 # sort comments by how they were generated
275 # sort comments by how they were generated
272 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
276 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
273
277
274
278
275 if len(c.commit_ranges) == 1:
279 if len(c.commit_ranges) == 1:
276 c.commit = c.commit_ranges[0]
280 c.commit = c.commit_ranges[0]
277 c.parent_tmpl = ''.join(
281 c.parent_tmpl = ''.join(
278 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
282 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
279 if method == 'download':
283 if method == 'download':
280 response.content_type = 'text/plain'
284 response.content_type = 'text/plain'
281 response.content_disposition = (
285 response.content_disposition = (
282 'attachment; filename=%s.diff' % commit_id_range[:12])
286 'attachment; filename=%s.diff' % commit_id_range[:12])
283 return diff
287 return diff
284 elif method == 'patch':
288 elif method == 'patch':
285 response.content_type = 'text/plain'
289 response.content_type = 'text/plain'
286 c.diff = safe_unicode(diff)
290 c.diff = safe_unicode(diff)
287 return render('changeset/patch_changeset.html')
291 return render('changeset/patch_changeset.html')
288 elif method == 'raw':
292 elif method == 'raw':
289 response.content_type = 'text/plain'
293 response.content_type = 'text/plain'
290 return diff
294 return diff
291 elif method == 'show':
295 elif method == 'show':
292 if len(c.commit_ranges) == 1:
296 if len(c.commit_ranges) == 1:
293 return render('changeset/changeset.html')
297 return render('changeset/changeset.html')
294 else:
298 else:
295 c.ancestor = None
299 c.ancestor = None
296 c.target_repo = c.rhodecode_db_repo
300 c.target_repo = c.rhodecode_db_repo
297 return render('changeset/changeset_range.html')
301 return render('changeset/changeset_range.html')
298
302
299 @LoginRequired()
303 @LoginRequired()
300 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
304 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
301 'repository.admin')
305 'repository.admin')
302 def index(self, revision, method='show'):
306 def index(self, revision, method='show'):
303 return self._index(revision, method=method)
307 return self._index(revision, method=method)
304
308
305 @LoginRequired()
309 @LoginRequired()
306 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
310 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
307 'repository.admin')
311 'repository.admin')
308 def changeset_raw(self, revision):
312 def changeset_raw(self, revision):
309 return self._index(revision, method='raw')
313 return self._index(revision, method='raw')
310
314
311 @LoginRequired()
315 @LoginRequired()
312 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
313 'repository.admin')
317 'repository.admin')
314 def changeset_patch(self, revision):
318 def changeset_patch(self, revision):
315 return self._index(revision, method='patch')
319 return self._index(revision, method='patch')
316
320
317 @LoginRequired()
321 @LoginRequired()
318 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
319 'repository.admin')
323 'repository.admin')
320 def changeset_download(self, revision):
324 def changeset_download(self, revision):
321 return self._index(revision, method='download')
325 return self._index(revision, method='download')
322
326
323 @LoginRequired()
327 @LoginRequired()
324 @NotAnonymous()
328 @NotAnonymous()
325 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
329 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
326 'repository.admin')
330 'repository.admin')
327 @auth.CSRFRequired()
331 @auth.CSRFRequired()
328 @jsonify
332 @jsonify
329 def comment(self, repo_name, revision):
333 def comment(self, repo_name, revision):
330 commit_id = revision
334 commit_id = revision
331 status = request.POST.get('changeset_status', None)
335 status = request.POST.get('changeset_status', None)
332 text = request.POST.get('text')
336 text = request.POST.get('text')
333 if status:
337 if status:
334 text = text or (_('Status change %(transition_icon)s %(status)s')
338 text = text or (_('Status change %(transition_icon)s %(status)s')
335 % {'transition_icon': '>',
339 % {'transition_icon': '>',
336 'status': ChangesetStatus.get_status_lbl(status)})
340 'status': ChangesetStatus.get_status_lbl(status)})
337
341
338 multi_commit_ids = filter(
342 multi_commit_ids = filter(
339 lambda s: s not in ['', None],
343 lambda s: s not in ['', None],
340 request.POST.get('commit_ids', '').split(','),)
344 request.POST.get('commit_ids', '').split(','),)
341
345
342 commit_ids = multi_commit_ids or [commit_id]
346 commit_ids = multi_commit_ids or [commit_id]
343 comment = None
347 comment = None
344 for current_id in filter(None, commit_ids):
348 for current_id in filter(None, commit_ids):
345 c.co = comment = ChangesetCommentsModel().create(
349 c.co = comment = ChangesetCommentsModel().create(
346 text=text,
350 text=text,
347 repo=c.rhodecode_db_repo.repo_id,
351 repo=c.rhodecode_db_repo.repo_id,
348 user=c.rhodecode_user.user_id,
352 user=c.rhodecode_user.user_id,
349 revision=current_id,
353 revision=current_id,
350 f_path=request.POST.get('f_path'),
354 f_path=request.POST.get('f_path'),
351 line_no=request.POST.get('line'),
355 line_no=request.POST.get('line'),
352 status_change=(ChangesetStatus.get_status_lbl(status)
356 status_change=(ChangesetStatus.get_status_lbl(status)
353 if status else None),
357 if status else None),
354 status_change_type=status
358 status_change_type=status
355 )
359 )
356 # get status if set !
360 # get status if set !
357 if status:
361 if status:
358 # if latest status was from pull request and it's closed
362 # if latest status was from pull request and it's closed
359 # disallow changing status !
363 # disallow changing status !
360 # dont_allow_on_closed_pull_request = True !
364 # dont_allow_on_closed_pull_request = True !
361
365
362 try:
366 try:
363 ChangesetStatusModel().set_status(
367 ChangesetStatusModel().set_status(
364 c.rhodecode_db_repo.repo_id,
368 c.rhodecode_db_repo.repo_id,
365 status,
369 status,
366 c.rhodecode_user.user_id,
370 c.rhodecode_user.user_id,
367 comment,
371 comment,
368 revision=current_id,
372 revision=current_id,
369 dont_allow_on_closed_pull_request=True
373 dont_allow_on_closed_pull_request=True
370 )
374 )
371 except StatusChangeOnClosedPullRequestError:
375 except StatusChangeOnClosedPullRequestError:
372 msg = _('Changing the status of a commit associated with '
376 msg = _('Changing the status of a commit associated with '
373 'a closed pull request is not allowed')
377 'a closed pull request is not allowed')
374 log.exception(msg)
378 log.exception(msg)
375 h.flash(msg, category='warning')
379 h.flash(msg, category='warning')
376 return redirect(h.url(
380 return redirect(h.url(
377 'changeset_home', repo_name=repo_name,
381 'changeset_home', repo_name=repo_name,
378 revision=current_id))
382 revision=current_id))
379
383
380 # finalize, commit and redirect
384 # finalize, commit and redirect
381 Session().commit()
385 Session().commit()
382
386
383 data = {
387 data = {
384 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
388 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
385 }
389 }
386 if comment:
390 if comment:
387 data.update(comment.get_dict())
391 data.update(comment.get_dict())
388 data.update({'rendered_text':
392 data.update({'rendered_text':
389 render('changeset/changeset_comment_block.html')})
393 render('changeset/changeset_comment_block.html')})
390
394
391 return data
395 return data
392
396
393 @LoginRequired()
397 @LoginRequired()
394 @NotAnonymous()
398 @NotAnonymous()
395 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
399 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
396 'repository.admin')
400 'repository.admin')
397 @auth.CSRFRequired()
401 @auth.CSRFRequired()
398 def preview_comment(self):
402 def preview_comment(self):
399 # Technically a CSRF token is not needed as no state changes with this
403 # Technically a CSRF token is not needed as no state changes with this
400 # call. However, as this is a POST is better to have it, so automated
404 # call. However, as this is a POST is better to have it, so automated
401 # tools don't flag it as potential CSRF.
405 # tools don't flag it as potential CSRF.
402 # Post is required because the payload could be bigger than the maximum
406 # Post is required because the payload could be bigger than the maximum
403 # allowed by GET.
407 # allowed by GET.
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
408 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
405 raise HTTPBadRequest()
409 raise HTTPBadRequest()
406 text = request.POST.get('text')
410 text = request.POST.get('text')
407 renderer = request.POST.get('renderer') or 'rst'
411 renderer = request.POST.get('renderer') or 'rst'
408 if text:
412 if text:
409 return h.render(text, renderer=renderer, mentions=True)
413 return h.render(text, renderer=renderer, mentions=True)
410 return ''
414 return ''
411
415
412 @LoginRequired()
416 @LoginRequired()
413 @NotAnonymous()
417 @NotAnonymous()
414 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
418 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
415 'repository.admin')
419 'repository.admin')
416 @auth.CSRFRequired()
420 @auth.CSRFRequired()
417 @jsonify
421 @jsonify
418 def delete_comment(self, repo_name, comment_id):
422 def delete_comment(self, repo_name, comment_id):
419 comment = ChangesetComment.get(comment_id)
423 comment = ChangesetComment.get(comment_id)
420 owner = (comment.author.user_id == c.rhodecode_user.user_id)
424 owner = (comment.author.user_id == c.rhodecode_user.user_id)
421 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
425 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
422 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
426 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
423 ChangesetCommentsModel().delete(comment=comment)
427 ChangesetCommentsModel().delete(comment=comment)
424 Session().commit()
428 Session().commit()
425 return True
429 return True
426 else:
430 else:
427 raise HTTPForbidden()
431 raise HTTPForbidden()
428
432
429 @LoginRequired()
433 @LoginRequired()
430 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
434 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
431 'repository.admin')
435 'repository.admin')
432 @jsonify
436 @jsonify
433 def changeset_info(self, repo_name, revision):
437 def changeset_info(self, repo_name, revision):
434 if request.is_xhr:
438 if request.is_xhr:
435 try:
439 try:
436 return c.rhodecode_repo.get_commit(commit_id=revision)
440 return c.rhodecode_repo.get_commit(commit_id=revision)
437 except CommitDoesNotExistError as e:
441 except CommitDoesNotExistError as e:
438 return EmptyCommit(message=str(e))
442 return EmptyCommit(message=str(e))
439 else:
443 else:
440 raise HTTPBadRequest()
444 raise HTTPBadRequest()
441
445
442 @LoginRequired()
446 @LoginRequired()
443 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
447 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
444 'repository.admin')
448 'repository.admin')
445 @jsonify
449 @jsonify
446 def changeset_children(self, repo_name, revision):
450 def changeset_children(self, repo_name, revision):
447 if request.is_xhr:
451 if request.is_xhr:
448 commit = c.rhodecode_repo.get_commit(commit_id=revision)
452 commit = c.rhodecode_repo.get_commit(commit_id=revision)
449 result = {"results": commit.children}
453 result = {"results": commit.children}
450 return result
454 return result
451 else:
455 else:
452 raise HTTPBadRequest()
456 raise HTTPBadRequest()
453
457
454 @LoginRequired()
458 @LoginRequired()
455 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
459 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
456 'repository.admin')
460 'repository.admin')
457 @jsonify
461 @jsonify
458 def changeset_parents(self, repo_name, revision):
462 def changeset_parents(self, repo_name, revision):
459 if request.is_xhr:
463 if request.is_xhr:
460 commit = c.rhodecode_repo.get_commit(commit_id=revision)
464 commit = c.rhodecode_repo.get_commit(commit_id=revision)
461 result = {"results": commit.parents}
465 result = {"results": commit.parents}
462 return result
466 return result
463 else:
467 else:
464 raise HTTPBadRequest()
468 raise HTTPBadRequest()
@@ -1,277 +1,282 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Compare controller for showing differences between two commits/refs/tags etc.
22 Compare controller for showing differences between two commits/refs/tags etc.
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from webob.exc import HTTPBadRequest
27 from webob.exc import HTTPBadRequest
28 from pylons import request, tmpl_context as c, url
28 from pylons import request, tmpl_context as c, url
29 from pylons.controllers.util import redirect
29 from pylons.controllers.util import redirect
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
32 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import diffs, codeblocks
34 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.utils import safe_str
37 from rhodecode.lib.utils import safe_str
38 from rhodecode.lib.utils2 import safe_unicode, str2bool
38 from rhodecode.lib.utils2 import safe_unicode, str2bool
39 from rhodecode.lib.vcs.exceptions import (
39 from rhodecode.lib.vcs.exceptions import (
40 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
40 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
41 NodeDoesNotExistError)
41 NodeDoesNotExistError)
42 from rhodecode.model.db import Repository, ChangesetStatus
42 from rhodecode.model.db import Repository, ChangesetStatus
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class CompareController(BaseRepoController):
47 class CompareController(BaseRepoController):
48
48
49 def __before__(self):
49 def __before__(self):
50 super(CompareController, self).__before__()
50 super(CompareController, self).__before__()
51
51
52 def _get_commit_or_redirect(
52 def _get_commit_or_redirect(
53 self, ref, ref_type, repo, redirect_after=True, partial=False):
53 self, ref, ref_type, repo, redirect_after=True, partial=False):
54 """
54 """
55 This is a safe way to get a commit. If an error occurs it
55 This is a safe way to get a commit. If an error occurs it
56 redirects to a commit with a proper message. If partial is set
56 redirects to a commit with a proper message. If partial is set
57 then it does not do redirect raise and throws an exception instead.
57 then it does not do redirect raise and throws an exception instead.
58 """
58 """
59 try:
59 try:
60 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
60 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
61 except EmptyRepositoryError:
61 except EmptyRepositoryError:
62 if not redirect_after:
62 if not redirect_after:
63 return repo.scm_instance().EMPTY_COMMIT
63 return repo.scm_instance().EMPTY_COMMIT
64 h.flash(h.literal(_('There are no commits yet')),
64 h.flash(h.literal(_('There are no commits yet')),
65 category='warning')
65 category='warning')
66 redirect(url('summary_home', repo_name=repo.repo_name))
66 redirect(url('summary_home', repo_name=repo.repo_name))
67
67
68 except RepositoryError as e:
68 except RepositoryError as e:
69 msg = safe_str(e)
69 msg = safe_str(e)
70 log.exception(msg)
70 log.exception(msg)
71 h.flash(msg, category='warning')
71 h.flash(msg, category='warning')
72 if not partial:
72 if not partial:
73 redirect(h.url('summary_home', repo_name=repo.repo_name))
73 redirect(h.url('summary_home', repo_name=repo.repo_name))
74 raise HTTPBadRequest()
74 raise HTTPBadRequest()
75
75
76 @LoginRequired()
76 @LoginRequired()
77 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
77 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
78 'repository.admin')
78 'repository.admin')
79 def index(self, repo_name):
79 def index(self, repo_name):
80 c.compare_home = True
80 c.compare_home = True
81 c.commit_ranges = []
81 c.commit_ranges = []
82 c.collapse_all_commits = False
82 c.diffset = None
83 c.diffset = None
83 c.limited_diff = False
84 c.limited_diff = False
84 source_repo = c.rhodecode_db_repo.repo_name
85 source_repo = c.rhodecode_db_repo.repo_name
85 target_repo = request.GET.get('target_repo', source_repo)
86 target_repo = request.GET.get('target_repo', source_repo)
86 c.source_repo = Repository.get_by_repo_name(source_repo)
87 c.source_repo = Repository.get_by_repo_name(source_repo)
87 c.target_repo = Repository.get_by_repo_name(target_repo)
88 c.target_repo = Repository.get_by_repo_name(target_repo)
88 c.source_ref = c.target_ref = _('Select commit')
89 c.source_ref = c.target_ref = _('Select commit')
89 c.source_ref_type = ""
90 c.source_ref_type = ""
90 c.target_ref_type = ""
91 c.target_ref_type = ""
91 c.commit_statuses = ChangesetStatus.STATUSES
92 c.commit_statuses = ChangesetStatus.STATUSES
92 c.preview_mode = False
93 c.preview_mode = False
93 c.file_path = None
94 c.file_path = None
94 return render('compare/compare_diff.html')
95 return render('compare/compare_diff.html')
95
96
96 @LoginRequired()
97 @LoginRequired()
97 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
98 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
98 'repository.admin')
99 'repository.admin')
99 def compare(self, repo_name, source_ref_type, source_ref,
100 def compare(self, repo_name, source_ref_type, source_ref,
100 target_ref_type, target_ref):
101 target_ref_type, target_ref):
101 # source_ref will be evaluated in source_repo
102 # source_ref will be evaluated in source_repo
102 source_repo_name = c.rhodecode_db_repo.repo_name
103 source_repo_name = c.rhodecode_db_repo.repo_name
103 source_path, source_id = parse_path_ref(source_ref)
104 source_path, source_id = parse_path_ref(source_ref)
104
105
105 # target_ref will be evaluated in target_repo
106 # target_ref will be evaluated in target_repo
106 target_repo_name = request.GET.get('target_repo', source_repo_name)
107 target_repo_name = request.GET.get('target_repo', source_repo_name)
107 target_path, target_id = parse_path_ref(
108 target_path, target_id = parse_path_ref(
108 target_ref, default_path=request.GET.get('f_path', ''))
109 target_ref, default_path=request.GET.get('f_path', ''))
109
110
110 c.file_path = target_path
111 c.file_path = target_path
111 c.commit_statuses = ChangesetStatus.STATUSES
112 c.commit_statuses = ChangesetStatus.STATUSES
112
113
113 # if merge is True
114 # if merge is True
114 # Show what changes since the shared ancestor commit of target/source
115 # Show what changes since the shared ancestor commit of target/source
115 # the source would get if it was merged with target. Only commits
116 # the source would get if it was merged with target. Only commits
116 # which are in target but not in source will be shown.
117 # which are in target but not in source will be shown.
117 merge = str2bool(request.GET.get('merge'))
118 merge = str2bool(request.GET.get('merge'))
118 # if merge is False
119 # if merge is False
119 # Show a raw diff of source/target refs even if no ancestor exists
120 # Show a raw diff of source/target refs even if no ancestor exists
120
121
121 # c.fulldiff disables cut_off_limit
122 # c.fulldiff disables cut_off_limit
122 c.fulldiff = str2bool(request.GET.get('fulldiff'))
123 c.fulldiff = str2bool(request.GET.get('fulldiff'))
123
124
124 # if partial, returns just compare_commits.html (commits log)
125 # if partial, returns just compare_commits.html (commits log)
125 partial = request.is_xhr
126 partial = request.is_xhr
126
127
127 # swap url for compare_diff page
128 # swap url for compare_diff page
128 c.swap_url = h.url(
129 c.swap_url = h.url(
129 'compare_url',
130 'compare_url',
130 repo_name=target_repo_name,
131 repo_name=target_repo_name,
131 source_ref_type=target_ref_type,
132 source_ref_type=target_ref_type,
132 source_ref=target_ref,
133 source_ref=target_ref,
133 target_repo=source_repo_name,
134 target_repo=source_repo_name,
134 target_ref_type=source_ref_type,
135 target_ref_type=source_ref_type,
135 target_ref=source_ref,
136 target_ref=source_ref,
136 merge=merge and '1' or '',
137 merge=merge and '1' or '',
137 f_path=target_path)
138 f_path=target_path)
138
139
139 source_repo = Repository.get_by_repo_name(source_repo_name)
140 source_repo = Repository.get_by_repo_name(source_repo_name)
140 target_repo = Repository.get_by_repo_name(target_repo_name)
141 target_repo = Repository.get_by_repo_name(target_repo_name)
141
142
142 if source_repo is None:
143 if source_repo is None:
143 msg = _('Could not find the original repo: %(repo)s') % {
144 msg = _('Could not find the original repo: %(repo)s') % {
144 'repo': source_repo}
145 'repo': source_repo}
145
146
146 log.error(msg)
147 log.error(msg)
147 h.flash(msg, category='error')
148 h.flash(msg, category='error')
148 return redirect(url('compare_home', repo_name=c.repo_name))
149 return redirect(url('compare_home', repo_name=c.repo_name))
149
150
150 if target_repo is None:
151 if target_repo is None:
151 msg = _('Could not find the other repo: %(repo)s') % {
152 msg = _('Could not find the other repo: %(repo)s') % {
152 'repo': target_repo_name}
153 'repo': target_repo_name}
153 log.error(msg)
154 log.error(msg)
154 h.flash(msg, category='error')
155 h.flash(msg, category='error')
155 return redirect(url('compare_home', repo_name=c.repo_name))
156 return redirect(url('compare_home', repo_name=c.repo_name))
156
157
157 source_scm = source_repo.scm_instance()
158 source_scm = source_repo.scm_instance()
158 target_scm = target_repo.scm_instance()
159 target_scm = target_repo.scm_instance()
159
160
160 source_alias = source_scm.alias
161 source_alias = source_scm.alias
161 target_alias = target_scm.alias
162 target_alias = target_scm.alias
162 if source_alias != target_alias:
163 if source_alias != target_alias:
163 msg = _('The comparison of two different kinds of remote repos '
164 msg = _('The comparison of two different kinds of remote repos '
164 'is not available')
165 'is not available')
165 log.error(msg)
166 log.error(msg)
166 h.flash(msg, category='error')
167 h.flash(msg, category='error')
167 return redirect(url('compare_home', repo_name=c.repo_name))
168 return redirect(url('compare_home', repo_name=c.repo_name))
168
169
169 source_commit = self._get_commit_or_redirect(
170 source_commit = self._get_commit_or_redirect(
170 ref=source_id, ref_type=source_ref_type, repo=source_repo,
171 ref=source_id, ref_type=source_ref_type, repo=source_repo,
171 partial=partial)
172 partial=partial)
172 target_commit = self._get_commit_or_redirect(
173 target_commit = self._get_commit_or_redirect(
173 ref=target_id, ref_type=target_ref_type, repo=target_repo,
174 ref=target_id, ref_type=target_ref_type, repo=target_repo,
174 partial=partial)
175 partial=partial)
175
176
176 c.compare_home = False
177 c.compare_home = False
177 c.source_repo = source_repo
178 c.source_repo = source_repo
178 c.target_repo = target_repo
179 c.target_repo = target_repo
179 c.source_ref = source_ref
180 c.source_ref = source_ref
180 c.target_ref = target_ref
181 c.target_ref = target_ref
181 c.source_ref_type = source_ref_type
182 c.source_ref_type = source_ref_type
182 c.target_ref_type = target_ref_type
183 c.target_ref_type = target_ref_type
183
184
184 pre_load = ["author", "branch", "date", "message"]
185 pre_load = ["author", "branch", "date", "message"]
185 c.ancestor = None
186 c.ancestor = None
186
187
187 if c.file_path:
188 if c.file_path:
188 if source_commit == target_commit:
189 if source_commit == target_commit:
189 c.commit_ranges = []
190 c.commit_ranges = []
190 else:
191 else:
191 c.commit_ranges = [target_commit]
192 c.commit_ranges = [target_commit]
192 else:
193 else:
193 try:
194 try:
194 c.commit_ranges = source_scm.compare(
195 c.commit_ranges = source_scm.compare(
195 source_commit.raw_id, target_commit.raw_id,
196 source_commit.raw_id, target_commit.raw_id,
196 target_scm, merge, pre_load=pre_load)
197 target_scm, merge, pre_load=pre_load)
197 if merge:
198 if merge:
198 c.ancestor = source_scm.get_common_ancestor(
199 c.ancestor = source_scm.get_common_ancestor(
199 source_commit.raw_id, target_commit.raw_id, target_scm)
200 source_commit.raw_id, target_commit.raw_id, target_scm)
200 except RepositoryRequirementError:
201 except RepositoryRequirementError:
201 msg = _('Could not compare repos with different '
202 msg = _('Could not compare repos with different '
202 'large file settings')
203 'large file settings')
203 log.error(msg)
204 log.error(msg)
204 if partial:
205 if partial:
205 return msg
206 return msg
206 h.flash(msg, category='error')
207 h.flash(msg, category='error')
207 return redirect(url('compare_home', repo_name=c.repo_name))
208 return redirect(url('compare_home', repo_name=c.repo_name))
208
209
209 c.statuses = c.rhodecode_db_repo.statuses(
210 c.statuses = c.rhodecode_db_repo.statuses(
210 [x.raw_id for x in c.commit_ranges])
211 [x.raw_id for x in c.commit_ranges])
211
212
213 # auto collapse if we have more than limit
214 collapse_limit = diffs.DiffProcessor._collapse_commits_over
215 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
216
212 if partial: # for PR ajax commits loader
217 if partial: # for PR ajax commits loader
213 if not c.ancestor:
218 if not c.ancestor:
214 return '' # cannot merge if there is no ancestor
219 return '' # cannot merge if there is no ancestor
215 return render('compare/compare_commits.html')
220 return render('compare/compare_commits.html')
216
221
217 if c.ancestor:
222 if c.ancestor:
218 # case we want a simple diff without incoming commits,
223 # case we want a simple diff without incoming commits,
219 # previewing what will be merged.
224 # previewing what will be merged.
220 # Make the diff on target repo (which is known to have target_ref)
225 # Make the diff on target repo (which is known to have target_ref)
221 log.debug('Using ancestor %s as source_ref instead of %s'
226 log.debug('Using ancestor %s as source_ref instead of %s'
222 % (c.ancestor, source_ref))
227 % (c.ancestor, source_ref))
223 source_repo = target_repo
228 source_repo = target_repo
224 source_commit = target_repo.get_commit(commit_id=c.ancestor)
229 source_commit = target_repo.get_commit(commit_id=c.ancestor)
225
230
226 # diff_limit will cut off the whole diff if the limit is applied
231 # diff_limit will cut off the whole diff if the limit is applied
227 # otherwise it will just hide the big files from the front-end
232 # otherwise it will just hide the big files from the front-end
228 diff_limit = self.cut_off_limit_diff
233 diff_limit = self.cut_off_limit_diff
229 file_limit = self.cut_off_limit_file
234 file_limit = self.cut_off_limit_file
230
235
231 log.debug('calculating diff between '
236 log.debug('calculating diff between '
232 'source_ref:%s and target_ref:%s for repo `%s`',
237 'source_ref:%s and target_ref:%s for repo `%s`',
233 source_commit, target_commit,
238 source_commit, target_commit,
234 safe_unicode(source_repo.scm_instance().path))
239 safe_unicode(source_repo.scm_instance().path))
235
240
236 if source_commit.repository != target_commit.repository:
241 if source_commit.repository != target_commit.repository:
237 msg = _(
242 msg = _(
238 "Repositories unrelated. "
243 "Repositories unrelated. "
239 "Cannot compare commit %(commit1)s from repository %(repo1)s "
244 "Cannot compare commit %(commit1)s from repository %(repo1)s "
240 "with commit %(commit2)s from repository %(repo2)s.") % {
245 "with commit %(commit2)s from repository %(repo2)s.") % {
241 'commit1': h.show_id(source_commit),
246 'commit1': h.show_id(source_commit),
242 'repo1': source_repo.repo_name,
247 'repo1': source_repo.repo_name,
243 'commit2': h.show_id(target_commit),
248 'commit2': h.show_id(target_commit),
244 'repo2': target_repo.repo_name,
249 'repo2': target_repo.repo_name,
245 }
250 }
246 h.flash(msg, category='error')
251 h.flash(msg, category='error')
247 raise HTTPBadRequest()
252 raise HTTPBadRequest()
248
253
249 txtdiff = source_repo.scm_instance().get_diff(
254 txtdiff = source_repo.scm_instance().get_diff(
250 commit1=source_commit, commit2=target_commit,
255 commit1=source_commit, commit2=target_commit,
251 path=target_path, path1=source_path)
256 path=target_path, path1=source_path)
252
257
253 diff_processor = diffs.DiffProcessor(
258 diff_processor = diffs.DiffProcessor(
254 txtdiff, format='newdiff', diff_limit=diff_limit,
259 txtdiff, format='newdiff', diff_limit=diff_limit,
255 file_limit=file_limit, show_full_diff=c.fulldiff)
260 file_limit=file_limit, show_full_diff=c.fulldiff)
256 _parsed = diff_processor.prepare()
261 _parsed = diff_processor.prepare()
257
262
258 def _node_getter(commit):
263 def _node_getter(commit):
259 """ Returns a function that returns a node for a commit or None """
264 """ Returns a function that returns a node for a commit or None """
260 def get_node(fname):
265 def get_node(fname):
261 try:
266 try:
262 return commit.get_node(fname)
267 return commit.get_node(fname)
263 except NodeDoesNotExistError:
268 except NodeDoesNotExistError:
264 return None
269 return None
265 return get_node
270 return get_node
266
271
267 c.diffset = codeblocks.DiffSet(
272 c.diffset = codeblocks.DiffSet(
268 repo_name=source_repo.repo_name,
273 repo_name=source_repo.repo_name,
269 source_node_getter=_node_getter(source_commit),
274 source_node_getter=_node_getter(source_commit),
270 target_node_getter=_node_getter(target_commit),
275 target_node_getter=_node_getter(target_commit),
271 ).render_patchset(_parsed, source_ref, target_ref)
276 ).render_patchset(_parsed, source_ref, target_ref)
272
277
273 c.preview_mode = merge
278 c.preview_mode = merge
274 c.source_commit = source_commit
279 c.source_commit = source_commit
275 c.target_commit = target_commit
280 c.target_commit = target_commit
276
281
277 return render('compare/compare_diff.html')
282 return render('compare/compare_diff.html')
@@ -1,1003 +1,1020 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 pull requests controller for rhodecode for initializing pull requests
22 pull requests controller for rhodecode for initializing pull requests
23 """
23 """
24 import types
24 import types
25
25
26 import peppercorn
26 import peppercorn
27 import formencode
27 import formencode
28 import logging
28 import logging
29
29 import collections
30
30
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pyramid.threadlocal import get_current_registry
35 from pyramid.threadlocal import get_current_registry
36 from sqlalchemy.sql import func
36 from sqlalchemy.sql import func
37 from sqlalchemy.sql.expression import or_
37 from sqlalchemy.sql.expression import or_
38
38
39 from rhodecode import events
39 from rhodecode import events
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.base import (
42 from rhodecode.lib.base import (
43 BaseRepoController, render, vcs_operation_context)
43 BaseRepoController, render, vcs_operation_context)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
46 HasAcceptedRepoType, XHRRequired)
46 HasAcceptedRepoType, XHRRequired)
47 from rhodecode.lib.channelstream import channelstream_request
47 from rhodecode.lib.channelstream import channelstream_request
48 from rhodecode.lib.compat import OrderedDict
49 from rhodecode.lib.utils import jsonify
48 from rhodecode.lib.utils import jsonify
50 from rhodecode.lib.utils2 import (
49 from rhodecode.lib.utils2 import (
51 safe_int, safe_str, str2bool, safe_unicode)
50 safe_int, safe_str, str2bool, safe_unicode)
52 from rhodecode.lib.vcs.backends.base import (
51 from rhodecode.lib.vcs.backends.base import (
53 EmptyCommit, UpdateFailureReason, EmptyRepository)
52 EmptyCommit, UpdateFailureReason, EmptyRepository)
54 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
55 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
54 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
56 NodeDoesNotExistError)
55 NodeDoesNotExistError)
57
56
58 from rhodecode.model.changeset_status import ChangesetStatusModel
57 from rhodecode.model.changeset_status import ChangesetStatusModel
59 from rhodecode.model.comment import ChangesetCommentsModel
58 from rhodecode.model.comment import ChangesetCommentsModel
60 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
59 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
61 Repository, PullRequestVersion)
60 Repository, PullRequestVersion)
62 from rhodecode.model.forms import PullRequestForm
61 from rhodecode.model.forms import PullRequestForm
63 from rhodecode.model.meta import Session
62 from rhodecode.model.meta import Session
64 from rhodecode.model.pull_request import PullRequestModel
63 from rhodecode.model.pull_request import PullRequestModel
65
64
66 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
67
66
68
67
69 class PullrequestsController(BaseRepoController):
68 class PullrequestsController(BaseRepoController):
70 def __before__(self):
69 def __before__(self):
71 super(PullrequestsController, self).__before__()
70 super(PullrequestsController, self).__before__()
72
71
73 def _load_compare_data(self, pull_request, inline_comments, enable_comments=True):
72 def _load_compare_data(self, pull_request, inline_comments):
74 """
73 """
75 Load context data needed for generating compare diff
74 Load context data needed for generating compare diff
76
75
77 :param pull_request: object related to the request
76 :param pull_request: object related to the request
78 :param enable_comments: flag to determine if comments are included
77 :param enable_comments: flag to determine if comments are included
79 """
78 """
80 source_repo = pull_request.source_repo
79 source_repo = pull_request.source_repo
81 source_ref_id = pull_request.source_ref_parts.commit_id
80 source_ref_id = pull_request.source_ref_parts.commit_id
82
81
83 target_repo = pull_request.target_repo
82 target_repo = pull_request.target_repo
84 target_ref_id = pull_request.target_ref_parts.commit_id
83 target_ref_id = pull_request.target_ref_parts.commit_id
85
84
86 # despite opening commits for bookmarks/branches/tags, we always
85 # despite opening commits for bookmarks/branches/tags, we always
87 # convert this to rev to prevent changes after bookmark or branch change
86 # convert this to rev to prevent changes after bookmark or branch change
88 c.source_ref_type = 'rev'
87 c.source_ref_type = 'rev'
89 c.source_ref = source_ref_id
88 c.source_ref = source_ref_id
90
89
91 c.target_ref_type = 'rev'
90 c.target_ref_type = 'rev'
92 c.target_ref = target_ref_id
91 c.target_ref = target_ref_id
93
92
94 c.source_repo = source_repo
93 c.source_repo = source_repo
95 c.target_repo = target_repo
94 c.target_repo = target_repo
96
95
97 c.fulldiff = bool(request.GET.get('fulldiff'))
96 c.fulldiff = bool(request.GET.get('fulldiff'))
98
97
99 # diff_limit is the old behavior, will cut off the whole diff
98 # diff_limit is the old behavior, will cut off the whole diff
100 # if the limit is applied otherwise will just hide the
99 # if the limit is applied otherwise will just hide the
101 # big files from the front-end
100 # big files from the front-end
102 diff_limit = self.cut_off_limit_diff
101 diff_limit = self.cut_off_limit_diff
103 file_limit = self.cut_off_limit_file
102 file_limit = self.cut_off_limit_file
104
103
105 pre_load = ["author", "branch", "date", "message"]
104 pre_load = ["author", "branch", "date", "message"]
106
105
107 c.commit_ranges = []
106 c.commit_ranges = []
108 source_commit = EmptyCommit()
107 source_commit = EmptyCommit()
109 target_commit = EmptyCommit()
108 target_commit = EmptyCommit()
110 c.missing_requirements = False
109 c.missing_requirements = False
111 try:
110 try:
112 c.commit_ranges = [
111 c.commit_ranges = [
113 source_repo.get_commit(commit_id=rev, pre_load=pre_load)
112 source_repo.get_commit(commit_id=rev, pre_load=pre_load)
114 for rev in pull_request.revisions]
113 for rev in pull_request.revisions]
115
114
116 c.statuses = source_repo.statuses(
115 c.statuses = source_repo.statuses(
117 [x.raw_id for x in c.commit_ranges])
116 [x.raw_id for x in c.commit_ranges])
118
117
119 target_commit = source_repo.get_commit(
118 target_commit = source_repo.get_commit(
120 commit_id=safe_str(target_ref_id))
119 commit_id=safe_str(target_ref_id))
121 source_commit = source_repo.get_commit(
120 source_commit = source_repo.get_commit(
122 commit_id=safe_str(source_ref_id))
121 commit_id=safe_str(source_ref_id))
123 except RepositoryRequirementError:
122 except RepositoryRequirementError:
124 c.missing_requirements = True
123 c.missing_requirements = True
125
124
125 # auto collapse if we have more than limit
126 collapse_limit = diffs.DiffProcessor._collapse_commits_over
127 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
128
126 c.changes = {}
129 c.changes = {}
127 c.missing_commits = False
130 c.missing_commits = False
128 if (c.missing_requirements or
131 if (c.missing_requirements or
129 isinstance(source_commit, EmptyCommit) or
132 isinstance(source_commit, EmptyCommit) or
130 source_commit == target_commit):
133 source_commit == target_commit):
131 _parsed = []
134 _parsed = []
132 c.missing_commits = True
135 c.missing_commits = True
133 else:
136 else:
134 vcs_diff = PullRequestModel().get_diff(pull_request)
137 vcs_diff = PullRequestModel().get_diff(pull_request)
135 diff_processor = diffs.DiffProcessor(
138 diff_processor = diffs.DiffProcessor(
136 vcs_diff, format='newdiff', diff_limit=diff_limit,
139 vcs_diff, format='newdiff', diff_limit=diff_limit,
137 file_limit=file_limit, show_full_diff=c.fulldiff)
140 file_limit=file_limit, show_full_diff=c.fulldiff)
138 _parsed = diff_processor.prepare()
139
141
140 commit_changes = OrderedDict()
141 _parsed = diff_processor.prepare()
142 _parsed = diff_processor.prepare()
142 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
143 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
143
144
144 _parsed = diff_processor.prepare()
145 included_files = {}
146 for f in _parsed:
147 included_files[f['filename']] = f['stats']
148
149 c.deleted_files = [fname for fname in inline_comments if
150 fname not in included_files]
151
152 c.deleted_files_comments = collections.defaultdict(dict)
153 for fname, per_line_comments in inline_comments.items():
154 if fname in c.deleted_files:
155 c.deleted_files_comments[fname]['stats'] = 0
156 c.deleted_files_comments[fname]['comments'] = list()
157 for lno, comments in per_line_comments.items():
158 c.deleted_files_comments[fname]['comments'].extend(comments)
145
159
146 def _node_getter(commit):
160 def _node_getter(commit):
147 def get_node(fname):
161 def get_node(fname):
148 try:
162 try:
149 return commit.get_node(fname)
163 return commit.get_node(fname)
150 except NodeDoesNotExistError:
164 except NodeDoesNotExistError:
151 return None
165 return None
152 return get_node
166 return get_node
153
167
154 c.diffset = codeblocks.DiffSet(
168 c.diffset = codeblocks.DiffSet(
155 repo_name=c.repo_name,
169 repo_name=c.repo_name,
156 source_repo_name=c.source_repo.repo_name,
170 source_repo_name=c.source_repo.repo_name,
157 source_node_getter=_node_getter(target_commit),
171 source_node_getter=_node_getter(target_commit),
158 target_node_getter=_node_getter(source_commit),
172 target_node_getter=_node_getter(source_commit),
159 comments=inline_comments
173 comments=inline_comments
160 ).render_patchset(_parsed, target_commit.raw_id, source_commit.raw_id)
174 ).render_patchset(_parsed, target_commit.raw_id, source_commit.raw_id)
161
175
162 c.included_files = []
163 c.deleted_files = []
164
165 for f in _parsed:
166 st = f['stats']
167 fid = h.FID('', f['filename'])
168 c.included_files.append(f['filename'])
169
170 def _extract_ordering(self, request):
176 def _extract_ordering(self, request):
171 column_index = safe_int(request.GET.get('order[0][column]'))
177 column_index = safe_int(request.GET.get('order[0][column]'))
172 order_dir = request.GET.get('order[0][dir]', 'desc')
178 order_dir = request.GET.get('order[0][dir]', 'desc')
173 order_by = request.GET.get(
179 order_by = request.GET.get(
174 'columns[%s][data][sort]' % column_index, 'name_raw')
180 'columns[%s][data][sort]' % column_index, 'name_raw')
175 return order_by, order_dir
181 return order_by, order_dir
176
182
177 @LoginRequired()
183 @LoginRequired()
178 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
184 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
179 'repository.admin')
185 'repository.admin')
180 @HasAcceptedRepoType('git', 'hg')
186 @HasAcceptedRepoType('git', 'hg')
181 def show_all(self, repo_name):
187 def show_all(self, repo_name):
182 # filter types
188 # filter types
183 c.active = 'open'
189 c.active = 'open'
184 c.source = str2bool(request.GET.get('source'))
190 c.source = str2bool(request.GET.get('source'))
185 c.closed = str2bool(request.GET.get('closed'))
191 c.closed = str2bool(request.GET.get('closed'))
186 c.my = str2bool(request.GET.get('my'))
192 c.my = str2bool(request.GET.get('my'))
187 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
193 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
188 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
194 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
189 c.repo_name = repo_name
195 c.repo_name = repo_name
190
196
191 opened_by = None
197 opened_by = None
192 if c.my:
198 if c.my:
193 c.active = 'my'
199 c.active = 'my'
194 opened_by = [c.rhodecode_user.user_id]
200 opened_by = [c.rhodecode_user.user_id]
195
201
196 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
202 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
197 if c.closed:
203 if c.closed:
198 c.active = 'closed'
204 c.active = 'closed'
199 statuses = [PullRequest.STATUS_CLOSED]
205 statuses = [PullRequest.STATUS_CLOSED]
200
206
201 if c.awaiting_review and not c.source:
207 if c.awaiting_review and not c.source:
202 c.active = 'awaiting'
208 c.active = 'awaiting'
203 if c.source and not c.awaiting_review:
209 if c.source and not c.awaiting_review:
204 c.active = 'source'
210 c.active = 'source'
205 if c.awaiting_my_review:
211 if c.awaiting_my_review:
206 c.active = 'awaiting_my'
212 c.active = 'awaiting_my'
207
213
208 data = self._get_pull_requests_list(
214 data = self._get_pull_requests_list(
209 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
215 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
210 if not request.is_xhr:
216 if not request.is_xhr:
211 c.data = json.dumps(data['data'])
217 c.data = json.dumps(data['data'])
212 c.records_total = data['recordsTotal']
218 c.records_total = data['recordsTotal']
213 return render('/pullrequests/pullrequests.html')
219 return render('/pullrequests/pullrequests.html')
214 else:
220 else:
215 return json.dumps(data)
221 return json.dumps(data)
216
222
217 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
223 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
218 # pagination
224 # pagination
219 start = safe_int(request.GET.get('start'), 0)
225 start = safe_int(request.GET.get('start'), 0)
220 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
226 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
221 order_by, order_dir = self._extract_ordering(request)
227 order_by, order_dir = self._extract_ordering(request)
222
228
223 if c.awaiting_review:
229 if c.awaiting_review:
224 pull_requests = PullRequestModel().get_awaiting_review(
230 pull_requests = PullRequestModel().get_awaiting_review(
225 repo_name, source=c.source, opened_by=opened_by,
231 repo_name, source=c.source, opened_by=opened_by,
226 statuses=statuses, offset=start, length=length,
232 statuses=statuses, offset=start, length=length,
227 order_by=order_by, order_dir=order_dir)
233 order_by=order_by, order_dir=order_dir)
228 pull_requests_total_count = PullRequestModel(
234 pull_requests_total_count = PullRequestModel(
229 ).count_awaiting_review(
235 ).count_awaiting_review(
230 repo_name, source=c.source, statuses=statuses,
236 repo_name, source=c.source, statuses=statuses,
231 opened_by=opened_by)
237 opened_by=opened_by)
232 elif c.awaiting_my_review:
238 elif c.awaiting_my_review:
233 pull_requests = PullRequestModel().get_awaiting_my_review(
239 pull_requests = PullRequestModel().get_awaiting_my_review(
234 repo_name, source=c.source, opened_by=opened_by,
240 repo_name, source=c.source, opened_by=opened_by,
235 user_id=c.rhodecode_user.user_id, statuses=statuses,
241 user_id=c.rhodecode_user.user_id, statuses=statuses,
236 offset=start, length=length, order_by=order_by,
242 offset=start, length=length, order_by=order_by,
237 order_dir=order_dir)
243 order_dir=order_dir)
238 pull_requests_total_count = PullRequestModel(
244 pull_requests_total_count = PullRequestModel(
239 ).count_awaiting_my_review(
245 ).count_awaiting_my_review(
240 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
246 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
241 statuses=statuses, opened_by=opened_by)
247 statuses=statuses, opened_by=opened_by)
242 else:
248 else:
243 pull_requests = PullRequestModel().get_all(
249 pull_requests = PullRequestModel().get_all(
244 repo_name, source=c.source, opened_by=opened_by,
250 repo_name, source=c.source, opened_by=opened_by,
245 statuses=statuses, offset=start, length=length,
251 statuses=statuses, offset=start, length=length,
246 order_by=order_by, order_dir=order_dir)
252 order_by=order_by, order_dir=order_dir)
247 pull_requests_total_count = PullRequestModel().count_all(
253 pull_requests_total_count = PullRequestModel().count_all(
248 repo_name, source=c.source, statuses=statuses,
254 repo_name, source=c.source, statuses=statuses,
249 opened_by=opened_by)
255 opened_by=opened_by)
250
256
251 from rhodecode.lib.utils import PartialRenderer
257 from rhodecode.lib.utils import PartialRenderer
252 _render = PartialRenderer('data_table/_dt_elements.html')
258 _render = PartialRenderer('data_table/_dt_elements.html')
253 data = []
259 data = []
254 for pr in pull_requests:
260 for pr in pull_requests:
255 comments = ChangesetCommentsModel().get_all_comments(
261 comments = ChangesetCommentsModel().get_all_comments(
256 c.rhodecode_db_repo.repo_id, pull_request=pr)
262 c.rhodecode_db_repo.repo_id, pull_request=pr)
257
263
258 data.append({
264 data.append({
259 'name': _render('pullrequest_name',
265 'name': _render('pullrequest_name',
260 pr.pull_request_id, pr.target_repo.repo_name),
266 pr.pull_request_id, pr.target_repo.repo_name),
261 'name_raw': pr.pull_request_id,
267 'name_raw': pr.pull_request_id,
262 'status': _render('pullrequest_status',
268 'status': _render('pullrequest_status',
263 pr.calculated_review_status()),
269 pr.calculated_review_status()),
264 'title': _render(
270 'title': _render(
265 'pullrequest_title', pr.title, pr.description),
271 'pullrequest_title', pr.title, pr.description),
266 'description': h.escape(pr.description),
272 'description': h.escape(pr.description),
267 'updated_on': _render('pullrequest_updated_on',
273 'updated_on': _render('pullrequest_updated_on',
268 h.datetime_to_time(pr.updated_on)),
274 h.datetime_to_time(pr.updated_on)),
269 'updated_on_raw': h.datetime_to_time(pr.updated_on),
275 'updated_on_raw': h.datetime_to_time(pr.updated_on),
270 'created_on': _render('pullrequest_updated_on',
276 'created_on': _render('pullrequest_updated_on',
271 h.datetime_to_time(pr.created_on)),
277 h.datetime_to_time(pr.created_on)),
272 'created_on_raw': h.datetime_to_time(pr.created_on),
278 'created_on_raw': h.datetime_to_time(pr.created_on),
273 'author': _render('pullrequest_author',
279 'author': _render('pullrequest_author',
274 pr.author.full_contact, ),
280 pr.author.full_contact, ),
275 'author_raw': pr.author.full_name,
281 'author_raw': pr.author.full_name,
276 'comments': _render('pullrequest_comments', len(comments)),
282 'comments': _render('pullrequest_comments', len(comments)),
277 'comments_raw': len(comments),
283 'comments_raw': len(comments),
278 'closed': pr.is_closed(),
284 'closed': pr.is_closed(),
279 })
285 })
280 # json used to render the grid
286 # json used to render the grid
281 data = ({
287 data = ({
282 'data': data,
288 'data': data,
283 'recordsTotal': pull_requests_total_count,
289 'recordsTotal': pull_requests_total_count,
284 'recordsFiltered': pull_requests_total_count,
290 'recordsFiltered': pull_requests_total_count,
285 })
291 })
286 return data
292 return data
287
293
288 @LoginRequired()
294 @LoginRequired()
289 @NotAnonymous()
295 @NotAnonymous()
290 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
291 'repository.admin')
297 'repository.admin')
292 @HasAcceptedRepoType('git', 'hg')
298 @HasAcceptedRepoType('git', 'hg')
293 def index(self):
299 def index(self):
294 source_repo = c.rhodecode_db_repo
300 source_repo = c.rhodecode_db_repo
295
301
296 try:
302 try:
297 source_repo.scm_instance().get_commit()
303 source_repo.scm_instance().get_commit()
298 except EmptyRepositoryError:
304 except EmptyRepositoryError:
299 h.flash(h.literal(_('There are no commits yet')),
305 h.flash(h.literal(_('There are no commits yet')),
300 category='warning')
306 category='warning')
301 redirect(url('summary_home', repo_name=source_repo.repo_name))
307 redirect(url('summary_home', repo_name=source_repo.repo_name))
302
308
303 commit_id = request.GET.get('commit')
309 commit_id = request.GET.get('commit')
304 branch_ref = request.GET.get('branch')
310 branch_ref = request.GET.get('branch')
305 bookmark_ref = request.GET.get('bookmark')
311 bookmark_ref = request.GET.get('bookmark')
306
312
307 try:
313 try:
308 source_repo_data = PullRequestModel().generate_repo_data(
314 source_repo_data = PullRequestModel().generate_repo_data(
309 source_repo, commit_id=commit_id,
315 source_repo, commit_id=commit_id,
310 branch=branch_ref, bookmark=bookmark_ref)
316 branch=branch_ref, bookmark=bookmark_ref)
311 except CommitDoesNotExistError as e:
317 except CommitDoesNotExistError as e:
312 log.exception(e)
318 log.exception(e)
313 h.flash(_('Commit does not exist'), 'error')
319 h.flash(_('Commit does not exist'), 'error')
314 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
320 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
315
321
316 default_target_repo = source_repo
322 default_target_repo = source_repo
317
323
318 if source_repo.parent:
324 if source_repo.parent:
319 parent_vcs_obj = source_repo.parent.scm_instance()
325 parent_vcs_obj = source_repo.parent.scm_instance()
320 if parent_vcs_obj and not parent_vcs_obj.is_empty():
326 if parent_vcs_obj and not parent_vcs_obj.is_empty():
321 # change default if we have a parent repo
327 # change default if we have a parent repo
322 default_target_repo = source_repo.parent
328 default_target_repo = source_repo.parent
323
329
324 target_repo_data = PullRequestModel().generate_repo_data(
330 target_repo_data = PullRequestModel().generate_repo_data(
325 default_target_repo)
331 default_target_repo)
326
332
327 selected_source_ref = source_repo_data['refs']['selected_ref']
333 selected_source_ref = source_repo_data['refs']['selected_ref']
328
334
329 title_source_ref = selected_source_ref.split(':', 2)[1]
335 title_source_ref = selected_source_ref.split(':', 2)[1]
330 c.default_title = PullRequestModel().generate_pullrequest_title(
336 c.default_title = PullRequestModel().generate_pullrequest_title(
331 source=source_repo.repo_name,
337 source=source_repo.repo_name,
332 source_ref=title_source_ref,
338 source_ref=title_source_ref,
333 target=default_target_repo.repo_name
339 target=default_target_repo.repo_name
334 )
340 )
335
341
336 c.default_repo_data = {
342 c.default_repo_data = {
337 'source_repo_name': source_repo.repo_name,
343 'source_repo_name': source_repo.repo_name,
338 'source_refs_json': json.dumps(source_repo_data),
344 'source_refs_json': json.dumps(source_repo_data),
339 'target_repo_name': default_target_repo.repo_name,
345 'target_repo_name': default_target_repo.repo_name,
340 'target_refs_json': json.dumps(target_repo_data),
346 'target_refs_json': json.dumps(target_repo_data),
341 }
347 }
342 c.default_source_ref = selected_source_ref
348 c.default_source_ref = selected_source_ref
343
349
344 return render('/pullrequests/pullrequest.html')
350 return render('/pullrequests/pullrequest.html')
345
351
346 @LoginRequired()
352 @LoginRequired()
347 @NotAnonymous()
353 @NotAnonymous()
348 @XHRRequired()
354 @XHRRequired()
349 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
355 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
350 'repository.admin')
356 'repository.admin')
351 @jsonify
357 @jsonify
352 def get_repo_refs(self, repo_name, target_repo_name):
358 def get_repo_refs(self, repo_name, target_repo_name):
353 repo = Repository.get_by_repo_name(target_repo_name)
359 repo = Repository.get_by_repo_name(target_repo_name)
354 if not repo:
360 if not repo:
355 raise HTTPNotFound
361 raise HTTPNotFound
356 return PullRequestModel().generate_repo_data(repo)
362 return PullRequestModel().generate_repo_data(repo)
357
363
358 @LoginRequired()
364 @LoginRequired()
359 @NotAnonymous()
365 @NotAnonymous()
360 @XHRRequired()
366 @XHRRequired()
361 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
367 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
362 'repository.admin')
368 'repository.admin')
363 @jsonify
369 @jsonify
364 def get_repo_destinations(self, repo_name):
370 def get_repo_destinations(self, repo_name):
365 repo = Repository.get_by_repo_name(repo_name)
371 repo = Repository.get_by_repo_name(repo_name)
366 if not repo:
372 if not repo:
367 raise HTTPNotFound
373 raise HTTPNotFound
368 filter_query = request.GET.get('query')
374 filter_query = request.GET.get('query')
369
375
370 query = Repository.query() \
376 query = Repository.query() \
371 .order_by(func.length(Repository.repo_name)) \
377 .order_by(func.length(Repository.repo_name)) \
372 .filter(or_(
378 .filter(or_(
373 Repository.repo_name == repo.repo_name,
379 Repository.repo_name == repo.repo_name,
374 Repository.fork_id == repo.repo_id))
380 Repository.fork_id == repo.repo_id))
375
381
376 if filter_query:
382 if filter_query:
377 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
383 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
378 query = query.filter(
384 query = query.filter(
379 Repository.repo_name.ilike(ilike_expression))
385 Repository.repo_name.ilike(ilike_expression))
380
386
381 add_parent = False
387 add_parent = False
382 if repo.parent:
388 if repo.parent:
383 if filter_query in repo.parent.repo_name:
389 if filter_query in repo.parent.repo_name:
384 parent_vcs_obj = repo.parent.scm_instance()
390 parent_vcs_obj = repo.parent.scm_instance()
385 if parent_vcs_obj and not parent_vcs_obj.is_empty():
391 if parent_vcs_obj and not parent_vcs_obj.is_empty():
386 add_parent = True
392 add_parent = True
387
393
388 limit = 20 - 1 if add_parent else 20
394 limit = 20 - 1 if add_parent else 20
389 all_repos = query.limit(limit).all()
395 all_repos = query.limit(limit).all()
390 if add_parent:
396 if add_parent:
391 all_repos += [repo.parent]
397 all_repos += [repo.parent]
392
398
393 repos = []
399 repos = []
394 for obj in self.scm_model.get_repos(all_repos):
400 for obj in self.scm_model.get_repos(all_repos):
395 repos.append({
401 repos.append({
396 'id': obj['name'],
402 'id': obj['name'],
397 'text': obj['name'],
403 'text': obj['name'],
398 'type': 'repo',
404 'type': 'repo',
399 'obj': obj['dbrepo']
405 'obj': obj['dbrepo']
400 })
406 })
401
407
402 data = {
408 data = {
403 'more': False,
409 'more': False,
404 'results': [{
410 'results': [{
405 'text': _('Repositories'),
411 'text': _('Repositories'),
406 'children': repos
412 'children': repos
407 }] if repos else []
413 }] if repos else []
408 }
414 }
409 return data
415 return data
410
416
411 @LoginRequired()
417 @LoginRequired()
412 @NotAnonymous()
418 @NotAnonymous()
413 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
419 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
414 'repository.admin')
420 'repository.admin')
415 @HasAcceptedRepoType('git', 'hg')
421 @HasAcceptedRepoType('git', 'hg')
416 @auth.CSRFRequired()
422 @auth.CSRFRequired()
417 def create(self, repo_name):
423 def create(self, repo_name):
418 repo = Repository.get_by_repo_name(repo_name)
424 repo = Repository.get_by_repo_name(repo_name)
419 if not repo:
425 if not repo:
420 raise HTTPNotFound
426 raise HTTPNotFound
421
427
422 controls = peppercorn.parse(request.POST.items())
428 controls = peppercorn.parse(request.POST.items())
423
429
424 try:
430 try:
425 _form = PullRequestForm(repo.repo_id)().to_python(controls)
431 _form = PullRequestForm(repo.repo_id)().to_python(controls)
426 except formencode.Invalid as errors:
432 except formencode.Invalid as errors:
427 if errors.error_dict.get('revisions'):
433 if errors.error_dict.get('revisions'):
428 msg = 'Revisions: %s' % errors.error_dict['revisions']
434 msg = 'Revisions: %s' % errors.error_dict['revisions']
429 elif errors.error_dict.get('pullrequest_title'):
435 elif errors.error_dict.get('pullrequest_title'):
430 msg = _('Pull request requires a title with min. 3 chars')
436 msg = _('Pull request requires a title with min. 3 chars')
431 else:
437 else:
432 msg = _('Error creating pull request: {}').format(errors)
438 msg = _('Error creating pull request: {}').format(errors)
433 log.exception(msg)
439 log.exception(msg)
434 h.flash(msg, 'error')
440 h.flash(msg, 'error')
435
441
436 # would rather just go back to form ...
442 # would rather just go back to form ...
437 return redirect(url('pullrequest_home', repo_name=repo_name))
443 return redirect(url('pullrequest_home', repo_name=repo_name))
438
444
439 source_repo = _form['source_repo']
445 source_repo = _form['source_repo']
440 source_ref = _form['source_ref']
446 source_ref = _form['source_ref']
441 target_repo = _form['target_repo']
447 target_repo = _form['target_repo']
442 target_ref = _form['target_ref']
448 target_ref = _form['target_ref']
443 commit_ids = _form['revisions'][::-1]
449 commit_ids = _form['revisions'][::-1]
444 reviewers = [
450 reviewers = [
445 (r['user_id'], r['reasons']) for r in _form['review_members']]
451 (r['user_id'], r['reasons']) for r in _form['review_members']]
446
452
447 # find the ancestor for this pr
453 # find the ancestor for this pr
448 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
454 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
449 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
455 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
450
456
451 source_scm = source_db_repo.scm_instance()
457 source_scm = source_db_repo.scm_instance()
452 target_scm = target_db_repo.scm_instance()
458 target_scm = target_db_repo.scm_instance()
453
459
454 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
460 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
455 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
461 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
456
462
457 ancestor = source_scm.get_common_ancestor(
463 ancestor = source_scm.get_common_ancestor(
458 source_commit.raw_id, target_commit.raw_id, target_scm)
464 source_commit.raw_id, target_commit.raw_id, target_scm)
459
465
460 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
466 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
461 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
467 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
462
468
463 pullrequest_title = _form['pullrequest_title']
469 pullrequest_title = _form['pullrequest_title']
464 title_source_ref = source_ref.split(':', 2)[1]
470 title_source_ref = source_ref.split(':', 2)[1]
465 if not pullrequest_title:
471 if not pullrequest_title:
466 pullrequest_title = PullRequestModel().generate_pullrequest_title(
472 pullrequest_title = PullRequestModel().generate_pullrequest_title(
467 source=source_repo,
473 source=source_repo,
468 source_ref=title_source_ref,
474 source_ref=title_source_ref,
469 target=target_repo
475 target=target_repo
470 )
476 )
471
477
472 description = _form['pullrequest_desc']
478 description = _form['pullrequest_desc']
473 try:
479 try:
474 pull_request = PullRequestModel().create(
480 pull_request = PullRequestModel().create(
475 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
481 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
476 target_ref, commit_ids, reviewers, pullrequest_title,
482 target_ref, commit_ids, reviewers, pullrequest_title,
477 description
483 description
478 )
484 )
479 Session().commit()
485 Session().commit()
480 h.flash(_('Successfully opened new pull request'),
486 h.flash(_('Successfully opened new pull request'),
481 category='success')
487 category='success')
482 except Exception as e:
488 except Exception as e:
483 msg = _('Error occurred during sending pull request')
489 msg = _('Error occurred during sending pull request')
484 log.exception(msg)
490 log.exception(msg)
485 h.flash(msg, category='error')
491 h.flash(msg, category='error')
486 return redirect(url('pullrequest_home', repo_name=repo_name))
492 return redirect(url('pullrequest_home', repo_name=repo_name))
487
493
488 return redirect(url('pullrequest_show', repo_name=target_repo,
494 return redirect(url('pullrequest_show', repo_name=target_repo,
489 pull_request_id=pull_request.pull_request_id))
495 pull_request_id=pull_request.pull_request_id))
490
496
491 @LoginRequired()
497 @LoginRequired()
492 @NotAnonymous()
498 @NotAnonymous()
493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
499 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
494 'repository.admin')
500 'repository.admin')
495 @auth.CSRFRequired()
501 @auth.CSRFRequired()
496 @jsonify
502 @jsonify
497 def update(self, repo_name, pull_request_id):
503 def update(self, repo_name, pull_request_id):
498 pull_request_id = safe_int(pull_request_id)
504 pull_request_id = safe_int(pull_request_id)
499 pull_request = PullRequest.get_or_404(pull_request_id)
505 pull_request = PullRequest.get_or_404(pull_request_id)
500 # only owner or admin can update it
506 # only owner or admin can update it
501 allowed_to_update = PullRequestModel().check_user_update(
507 allowed_to_update = PullRequestModel().check_user_update(
502 pull_request, c.rhodecode_user)
508 pull_request, c.rhodecode_user)
503 if allowed_to_update:
509 if allowed_to_update:
504 controls = peppercorn.parse(request.POST.items())
510 controls = peppercorn.parse(request.POST.items())
505
511
506 if 'review_members' in controls:
512 if 'review_members' in controls:
507 self._update_reviewers(
513 self._update_reviewers(
508 pull_request_id, controls['review_members'])
514 pull_request_id, controls['review_members'])
509 elif str2bool(request.POST.get('update_commits', 'false')):
515 elif str2bool(request.POST.get('update_commits', 'false')):
510 self._update_commits(pull_request)
516 self._update_commits(pull_request)
511 elif str2bool(request.POST.get('close_pull_request', 'false')):
517 elif str2bool(request.POST.get('close_pull_request', 'false')):
512 self._reject_close(pull_request)
518 self._reject_close(pull_request)
513 elif str2bool(request.POST.get('edit_pull_request', 'false')):
519 elif str2bool(request.POST.get('edit_pull_request', 'false')):
514 self._edit_pull_request(pull_request)
520 self._edit_pull_request(pull_request)
515 else:
521 else:
516 raise HTTPBadRequest()
522 raise HTTPBadRequest()
517 return True
523 return True
518 raise HTTPForbidden()
524 raise HTTPForbidden()
519
525
520 def _edit_pull_request(self, pull_request):
526 def _edit_pull_request(self, pull_request):
521 try:
527 try:
522 PullRequestModel().edit(
528 PullRequestModel().edit(
523 pull_request, request.POST.get('title'),
529 pull_request, request.POST.get('title'),
524 request.POST.get('description'))
530 request.POST.get('description'))
525 except ValueError:
531 except ValueError:
526 msg = _(u'Cannot update closed pull requests.')
532 msg = _(u'Cannot update closed pull requests.')
527 h.flash(msg, category='error')
533 h.flash(msg, category='error')
528 return
534 return
529 else:
535 else:
530 Session().commit()
536 Session().commit()
531
537
532 msg = _(u'Pull request title & description updated.')
538 msg = _(u'Pull request title & description updated.')
533 h.flash(msg, category='success')
539 h.flash(msg, category='success')
534 return
540 return
535
541
536 def _update_commits(self, pull_request):
542 def _update_commits(self, pull_request):
537 resp = PullRequestModel().update_commits(pull_request)
543 resp = PullRequestModel().update_commits(pull_request)
538
544
539 if resp.executed:
545 if resp.executed:
540 msg = _(
546 msg = _(
541 u'Pull request updated to "{source_commit_id}" with '
547 u'Pull request updated to "{source_commit_id}" with '
542 u'{count_added} added, {count_removed} removed commits.')
548 u'{count_added} added, {count_removed} removed commits.')
543 msg = msg.format(
549 msg = msg.format(
544 source_commit_id=pull_request.source_ref_parts.commit_id,
550 source_commit_id=pull_request.source_ref_parts.commit_id,
545 count_added=len(resp.changes.added),
551 count_added=len(resp.changes.added),
546 count_removed=len(resp.changes.removed))
552 count_removed=len(resp.changes.removed))
547 h.flash(msg, category='success')
553 h.flash(msg, category='success')
548
554
549 registry = get_current_registry()
555 registry = get_current_registry()
550 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
556 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
551 channelstream_config = rhodecode_plugins.get('channelstream', {})
557 channelstream_config = rhodecode_plugins.get('channelstream', {})
552 if channelstream_config.get('enabled'):
558 if channelstream_config.get('enabled'):
553 message = msg + (
559 message = msg + (
554 ' - <a onclick="window.location.reload()">'
560 ' - <a onclick="window.location.reload()">'
555 '<strong>{}</strong></a>'.format(_('Reload page')))
561 '<strong>{}</strong></a>'.format(_('Reload page')))
556 channel = '/repo${}$/pr/{}'.format(
562 channel = '/repo${}$/pr/{}'.format(
557 pull_request.target_repo.repo_name,
563 pull_request.target_repo.repo_name,
558 pull_request.pull_request_id
564 pull_request.pull_request_id
559 )
565 )
560 payload = {
566 payload = {
561 'type': 'message',
567 'type': 'message',
562 'user': 'system',
568 'user': 'system',
563 'exclude_users': [request.user.username],
569 'exclude_users': [request.user.username],
564 'channel': channel,
570 'channel': channel,
565 'message': {
571 'message': {
566 'message': message,
572 'message': message,
567 'level': 'success',
573 'level': 'success',
568 'topic': '/notifications'
574 'topic': '/notifications'
569 }
575 }
570 }
576 }
571 channelstream_request(
577 channelstream_request(
572 channelstream_config, [payload], '/message',
578 channelstream_config, [payload], '/message',
573 raise_exc=False)
579 raise_exc=False)
574 else:
580 else:
575 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
581 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
576 warning_reasons = [
582 warning_reasons = [
577 UpdateFailureReason.NO_CHANGE,
583 UpdateFailureReason.NO_CHANGE,
578 UpdateFailureReason.WRONG_REF_TPYE,
584 UpdateFailureReason.WRONG_REF_TPYE,
579 ]
585 ]
580 category = 'warning' if resp.reason in warning_reasons else 'error'
586 category = 'warning' if resp.reason in warning_reasons else 'error'
581 h.flash(msg, category=category)
587 h.flash(msg, category=category)
582
588
583 @auth.CSRFRequired()
589 @auth.CSRFRequired()
584 @LoginRequired()
590 @LoginRequired()
585 @NotAnonymous()
591 @NotAnonymous()
586 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
592 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
587 'repository.admin')
593 'repository.admin')
588 def merge(self, repo_name, pull_request_id):
594 def merge(self, repo_name, pull_request_id):
589 """
595 """
590 POST /{repo_name}/pull-request/{pull_request_id}
596 POST /{repo_name}/pull-request/{pull_request_id}
591
597
592 Merge will perform a server-side merge of the specified
598 Merge will perform a server-side merge of the specified
593 pull request, if the pull request is approved and mergeable.
599 pull request, if the pull request is approved and mergeable.
594 After succesfull merging, the pull request is automatically
600 After succesfull merging, the pull request is automatically
595 closed, with a relevant comment.
601 closed, with a relevant comment.
596 """
602 """
597 pull_request_id = safe_int(pull_request_id)
603 pull_request_id = safe_int(pull_request_id)
598 pull_request = PullRequest.get_or_404(pull_request_id)
604 pull_request = PullRequest.get_or_404(pull_request_id)
599 user = c.rhodecode_user
605 user = c.rhodecode_user
600
606
601 if self._meets_merge_pre_conditions(pull_request, user):
607 if self._meets_merge_pre_conditions(pull_request, user):
602 log.debug("Pre-conditions checked, trying to merge.")
608 log.debug("Pre-conditions checked, trying to merge.")
603 extras = vcs_operation_context(
609 extras = vcs_operation_context(
604 request.environ, repo_name=pull_request.target_repo.repo_name,
610 request.environ, repo_name=pull_request.target_repo.repo_name,
605 username=user.username, action='push',
611 username=user.username, action='push',
606 scm=pull_request.target_repo.repo_type)
612 scm=pull_request.target_repo.repo_type)
607 self._merge_pull_request(pull_request, user, extras)
613 self._merge_pull_request(pull_request, user, extras)
608
614
609 return redirect(url(
615 return redirect(url(
610 'pullrequest_show',
616 'pullrequest_show',
611 repo_name=pull_request.target_repo.repo_name,
617 repo_name=pull_request.target_repo.repo_name,
612 pull_request_id=pull_request.pull_request_id))
618 pull_request_id=pull_request.pull_request_id))
613
619
614 def _meets_merge_pre_conditions(self, pull_request, user):
620 def _meets_merge_pre_conditions(self, pull_request, user):
615 if not PullRequestModel().check_user_merge(pull_request, user):
621 if not PullRequestModel().check_user_merge(pull_request, user):
616 raise HTTPForbidden()
622 raise HTTPForbidden()
617
623
618 merge_status, msg = PullRequestModel().merge_status(pull_request)
624 merge_status, msg = PullRequestModel().merge_status(pull_request)
619 if not merge_status:
625 if not merge_status:
620 log.debug("Cannot merge, not mergeable.")
626 log.debug("Cannot merge, not mergeable.")
621 h.flash(msg, category='error')
627 h.flash(msg, category='error')
622 return False
628 return False
623
629
624 if (pull_request.calculated_review_status()
630 if (pull_request.calculated_review_status()
625 is not ChangesetStatus.STATUS_APPROVED):
631 is not ChangesetStatus.STATUS_APPROVED):
626 log.debug("Cannot merge, approval is pending.")
632 log.debug("Cannot merge, approval is pending.")
627 msg = _('Pull request reviewer approval is pending.')
633 msg = _('Pull request reviewer approval is pending.')
628 h.flash(msg, category='error')
634 h.flash(msg, category='error')
629 return False
635 return False
630 return True
636 return True
631
637
632 def _merge_pull_request(self, pull_request, user, extras):
638 def _merge_pull_request(self, pull_request, user, extras):
633 merge_resp = PullRequestModel().merge(
639 merge_resp = PullRequestModel().merge(
634 pull_request, user, extras=extras)
640 pull_request, user, extras=extras)
635
641
636 if merge_resp.executed:
642 if merge_resp.executed:
637 log.debug("The merge was successful, closing the pull request.")
643 log.debug("The merge was successful, closing the pull request.")
638 PullRequestModel().close_pull_request(
644 PullRequestModel().close_pull_request(
639 pull_request.pull_request_id, user)
645 pull_request.pull_request_id, user)
640 Session().commit()
646 Session().commit()
641 msg = _('Pull request was successfully merged and closed.')
647 msg = _('Pull request was successfully merged and closed.')
642 h.flash(msg, category='success')
648 h.flash(msg, category='success')
643 else:
649 else:
644 log.debug(
650 log.debug(
645 "The merge was not successful. Merge response: %s",
651 "The merge was not successful. Merge response: %s",
646 merge_resp)
652 merge_resp)
647 msg = PullRequestModel().merge_status_message(
653 msg = PullRequestModel().merge_status_message(
648 merge_resp.failure_reason)
654 merge_resp.failure_reason)
649 h.flash(msg, category='error')
655 h.flash(msg, category='error')
650
656
651 def _update_reviewers(self, pull_request_id, review_members):
657 def _update_reviewers(self, pull_request_id, review_members):
652 reviewers = [
658 reviewers = [
653 (int(r['user_id']), r['reasons']) for r in review_members]
659 (int(r['user_id']), r['reasons']) for r in review_members]
654 PullRequestModel().update_reviewers(pull_request_id, reviewers)
660 PullRequestModel().update_reviewers(pull_request_id, reviewers)
655 Session().commit()
661 Session().commit()
656
662
657 def _reject_close(self, pull_request):
663 def _reject_close(self, pull_request):
658 if pull_request.is_closed():
664 if pull_request.is_closed():
659 raise HTTPForbidden()
665 raise HTTPForbidden()
660
666
661 PullRequestModel().close_pull_request_with_comment(
667 PullRequestModel().close_pull_request_with_comment(
662 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
668 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
663 Session().commit()
669 Session().commit()
664
670
665 @LoginRequired()
671 @LoginRequired()
666 @NotAnonymous()
672 @NotAnonymous()
667 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
673 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
668 'repository.admin')
674 'repository.admin')
669 @auth.CSRFRequired()
675 @auth.CSRFRequired()
670 @jsonify
676 @jsonify
671 def delete(self, repo_name, pull_request_id):
677 def delete(self, repo_name, pull_request_id):
672 pull_request_id = safe_int(pull_request_id)
678 pull_request_id = safe_int(pull_request_id)
673 pull_request = PullRequest.get_or_404(pull_request_id)
679 pull_request = PullRequest.get_or_404(pull_request_id)
674 # only owner can delete it !
680 # only owner can delete it !
675 if pull_request.author.user_id == c.rhodecode_user.user_id:
681 if pull_request.author.user_id == c.rhodecode_user.user_id:
676 PullRequestModel().delete(pull_request)
682 PullRequestModel().delete(pull_request)
677 Session().commit()
683 Session().commit()
678 h.flash(_('Successfully deleted pull request'),
684 h.flash(_('Successfully deleted pull request'),
679 category='success')
685 category='success')
680 return redirect(url('my_account_pullrequests'))
686 return redirect(url('my_account_pullrequests'))
681 raise HTTPForbidden()
687 raise HTTPForbidden()
682
688
683 def _get_pr_version(self, pull_request_id, version=None):
689 def _get_pr_version(self, pull_request_id, version=None):
684 pull_request_id = safe_int(pull_request_id)
690 pull_request_id = safe_int(pull_request_id)
685 at_version = None
691 at_version = None
686
692
687 if version and version == 'latest':
693 if version and version == 'latest':
688 pull_request_ver = PullRequest.get(pull_request_id)
694 pull_request_ver = PullRequest.get(pull_request_id)
689 pull_request_obj = pull_request_ver
695 pull_request_obj = pull_request_ver
690 _org_pull_request_obj = pull_request_obj
696 _org_pull_request_obj = pull_request_obj
691 at_version = 'latest'
697 at_version = 'latest'
692 elif version:
698 elif version:
693 pull_request_ver = PullRequestVersion.get_or_404(version)
699 pull_request_ver = PullRequestVersion.get_or_404(version)
694 pull_request_obj = pull_request_ver
700 pull_request_obj = pull_request_ver
695 _org_pull_request_obj = pull_request_ver.pull_request
701 _org_pull_request_obj = pull_request_ver.pull_request
696 at_version = pull_request_ver.pull_request_version_id
702 at_version = pull_request_ver.pull_request_version_id
697 else:
703 else:
698 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id)
704 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id)
699
705
700 pull_request_display_obj = PullRequest.get_pr_display_object(
706 pull_request_display_obj = PullRequest.get_pr_display_object(
701 pull_request_obj, _org_pull_request_obj)
707 pull_request_obj, _org_pull_request_obj)
702 return _org_pull_request_obj, pull_request_obj, \
708 return _org_pull_request_obj, pull_request_obj, \
703 pull_request_display_obj, at_version
709 pull_request_display_obj, at_version
704
710
705 def _get_pr_version_changes(self, version, pull_request_latest):
711 def _get_pr_version_changes(self, version, pull_request_latest):
706 """
712 """
707 Generate changes commits, and diff data based on the current pr version
713 Generate changes commits, and diff data based on the current pr version
708 """
714 """
709
715
710 #TODO(marcink): save those changes as JSON metadata for chaching later.
716 #TODO(marcink): save those changes as JSON metadata for chaching later.
711
717
712 # fake the version to add the "initial" state object
718 # fake the version to add the "initial" state object
713 pull_request_initial = PullRequest.get_pr_display_object(
719 pull_request_initial = PullRequest.get_pr_display_object(
714 pull_request_latest, pull_request_latest,
720 pull_request_latest, pull_request_latest,
715 internal_methods=['get_commit', 'versions'])
721 internal_methods=['get_commit', 'versions'])
716 pull_request_initial.revisions = []
722 pull_request_initial.revisions = []
717 pull_request_initial.source_repo.get_commit = types.MethodType(
723 pull_request_initial.source_repo.get_commit = types.MethodType(
718 lambda *a, **k: EmptyCommit(), pull_request_initial)
724 lambda *a, **k: EmptyCommit(), pull_request_initial)
719 pull_request_initial.source_repo.scm_instance = types.MethodType(
725 pull_request_initial.source_repo.scm_instance = types.MethodType(
720 lambda *a, **k: EmptyRepository(), pull_request_initial)
726 lambda *a, **k: EmptyRepository(), pull_request_initial)
721
727
722 _changes_versions = [pull_request_latest] + \
728 _changes_versions = [pull_request_latest] + \
723 list(reversed(c.versions)) + \
729 list(reversed(c.versions)) + \
724 [pull_request_initial]
730 [pull_request_initial]
725
731
726 if version == 'latest':
732 if version == 'latest':
727 index = 0
733 index = 0
728 else:
734 else:
729 for pos, prver in enumerate(_changes_versions):
735 for pos, prver in enumerate(_changes_versions):
730 ver = getattr(prver, 'pull_request_version_id', -1)
736 ver = getattr(prver, 'pull_request_version_id', -1)
731 if ver == safe_int(version):
737 if ver == safe_int(version):
732 index = pos
738 index = pos
733 break
739 break
734 else:
740 else:
735 index = 0
741 index = 0
736
742
737 cur_obj = _changes_versions[index]
743 cur_obj = _changes_versions[index]
738 prev_obj = _changes_versions[index + 1]
744 prev_obj = _changes_versions[index + 1]
739
745
740 old_commit_ids = set(prev_obj.revisions)
746 old_commit_ids = set(prev_obj.revisions)
741 new_commit_ids = set(cur_obj.revisions)
747 new_commit_ids = set(cur_obj.revisions)
742
748
743 changes = PullRequestModel()._calculate_commit_id_changes(
749 changes = PullRequestModel()._calculate_commit_id_changes(
744 old_commit_ids, new_commit_ids)
750 old_commit_ids, new_commit_ids)
745
751
746 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
752 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
747 cur_obj, prev_obj)
753 cur_obj, prev_obj)
748 file_changes = PullRequestModel()._calculate_file_changes(
754 file_changes = PullRequestModel()._calculate_file_changes(
749 old_diff_data, new_diff_data)
755 old_diff_data, new_diff_data)
750 return changes, file_changes
756 return changes, file_changes
751
757
752 @LoginRequired()
758 @LoginRequired()
753 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
759 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
754 'repository.admin')
760 'repository.admin')
755 def show(self, repo_name, pull_request_id):
761 def show(self, repo_name, pull_request_id):
756 pull_request_id = safe_int(pull_request_id)
762 pull_request_id = safe_int(pull_request_id)
757 version = request.GET.get('version')
763 version = request.GET.get('version')
758
764
759 (pull_request_latest,
765 (pull_request_latest,
760 pull_request_at_ver,
766 pull_request_at_ver,
761 pull_request_display_obj,
767 pull_request_display_obj,
762 at_version) = self._get_pr_version(pull_request_id, version=version)
768 at_version) = self._get_pr_version(pull_request_id, version=version)
763
769
764 c.template_context['pull_request_data']['pull_request_id'] = \
770 c.template_context['pull_request_data']['pull_request_id'] = \
765 pull_request_id
771 pull_request_id
766
772
767 # pull_requests repo_name we opened it against
773 # pull_requests repo_name we opened it against
768 # ie. target_repo must match
774 # ie. target_repo must match
769 if repo_name != pull_request_at_ver.target_repo.repo_name:
775 if repo_name != pull_request_at_ver.target_repo.repo_name:
770 raise HTTPNotFound
776 raise HTTPNotFound
771
777
772 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
778 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
773 pull_request_at_ver)
779 pull_request_at_ver)
774
780
775 pr_closed = pull_request_latest.is_closed()
781 pr_closed = pull_request_latest.is_closed()
776 if at_version and not at_version == 'latest':
782 if at_version and not at_version == 'latest':
777 c.allowed_to_change_status = False
783 c.allowed_to_change_status = False
778 c.allowed_to_update = False
784 c.allowed_to_update = False
779 c.allowed_to_merge = False
785 c.allowed_to_merge = False
780 c.allowed_to_delete = False
786 c.allowed_to_delete = False
781 c.allowed_to_comment = False
787 c.allowed_to_comment = False
782 else:
788 else:
783 c.allowed_to_change_status = PullRequestModel(). \
789 c.allowed_to_change_status = PullRequestModel(). \
784 check_user_change_status(pull_request_at_ver, c.rhodecode_user)
790 check_user_change_status(pull_request_at_ver, c.rhodecode_user)
785 c.allowed_to_update = PullRequestModel().check_user_update(
791 c.allowed_to_update = PullRequestModel().check_user_update(
786 pull_request_latest, c.rhodecode_user) and not pr_closed
792 pull_request_latest, c.rhodecode_user) and not pr_closed
787 c.allowed_to_merge = PullRequestModel().check_user_merge(
793 c.allowed_to_merge = PullRequestModel().check_user_merge(
788 pull_request_latest, c.rhodecode_user) and not pr_closed
794 pull_request_latest, c.rhodecode_user) and not pr_closed
789 c.allowed_to_delete = PullRequestModel().check_user_delete(
795 c.allowed_to_delete = PullRequestModel().check_user_delete(
790 pull_request_latest, c.rhodecode_user) and not pr_closed
796 pull_request_latest, c.rhodecode_user) and not pr_closed
791 c.allowed_to_comment = not pr_closed
797 c.allowed_to_comment = not pr_closed
792
798
793 cc_model = ChangesetCommentsModel()
799 cc_model = ChangesetCommentsModel()
794
800
795 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
801 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
796 c.pull_request_review_status = pull_request_at_ver.calculated_review_status()
802 c.pull_request_review_status = pull_request_at_ver.calculated_review_status()
797 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
803 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
798 pull_request_at_ver)
804 pull_request_at_ver)
799 c.approval_msg = None
805 c.approval_msg = None
800 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
806 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
801 c.approval_msg = _('Reviewer approval is pending.')
807 c.approval_msg = _('Reviewer approval is pending.')
802 c.pr_merge_status = False
808 c.pr_merge_status = False
803
809
804 # inline comments
810 # inline comments
805 c.inline_comments = cc_model.get_inline_comments(
811 inline_comments = cc_model.get_inline_comments(
806 c.rhodecode_db_repo.repo_id,
812 c.rhodecode_db_repo.repo_id, pull_request=pull_request_id)
807 pull_request=pull_request_id)
813
814 _inline_cnt, c.inline_versions = cc_model.get_inline_comments_count(
815 inline_comments, version=at_version, include_aggregates=True)
816
817 c.at_version_num = at_version if at_version and at_version != 'latest' else None
818 is_outdated = lambda co: \
819 not c.at_version_num \
820 or co.pull_request_version_id <= c.at_version_num
808
821
809 c.inline_cnt = cc_model.get_inline_comments_count(
822 # inline_comments_until_version
810 c.inline_comments, version=at_version)
823 if c.at_version_num:
811
824 # if we use version, then do not show later comments
812 # load compare data into template context
825 # than current version
813 enable_comments = not pr_closed
826 paths = collections.defaultdict(lambda: collections.defaultdict(list))
814 self._load_compare_data(
827 for fname, per_line_comments in inline_comments.iteritems():
815 pull_request_at_ver,
828 for lno, comments in per_line_comments.iteritems():
816 c.inline_comments, enable_comments=enable_comments)
829 for co in comments:
830 if co.pull_request_version_id and is_outdated(co):
831 paths[co.f_path][co.line_no].append(co)
832 inline_comments = paths
817
833
818 # outdated comments
834 # outdated comments
819 c.outdated_comments = {}
820 c.outdated_cnt = 0
835 c.outdated_cnt = 0
821
822 if ChangesetCommentsModel.use_outdated_comments(pull_request_latest):
836 if ChangesetCommentsModel.use_outdated_comments(pull_request_latest):
823 c.outdated_comments = cc_model.get_outdated_comments(
837 outdated_comments = cc_model.get_outdated_comments(
824 c.rhodecode_db_repo.repo_id,
838 c.rhodecode_db_repo.repo_id,
825 pull_request=pull_request_at_ver)
839 pull_request=pull_request_at_ver)
826
840
827 # Count outdated comments and check for deleted files
841 # Count outdated comments and check for deleted files
828 for file_name, lines in c.outdated_comments.iteritems():
842 is_outdated = lambda co: \
843 not c.at_version_num \
844 or co.pull_request_version_id < c.at_version_num
845 for file_name, lines in outdated_comments.iteritems():
829 for comments in lines.values():
846 for comments in lines.values():
830 comments = [comm for comm in comments
847 comments = [comm for comm in comments if is_outdated(comm)]
831 if comm.outdated_at_version(at_version)]
832 c.outdated_cnt += len(comments)
848 c.outdated_cnt += len(comments)
833 if file_name not in c.included_files:
849
834 c.deleted_files.append(file_name)
850 # load compare data into template context
851 self._load_compare_data(pull_request_at_ver, inline_comments)
835
852
836 # this is a hack to properly display links, when creating PR, the
853 # this is a hack to properly display links, when creating PR, the
837 # compare view and others uses different notation, and
854 # compare view and others uses different notation, and
838 # compare_commits.html renders links based on the target_repo.
855 # compare_commits.html renders links based on the target_repo.
839 # We need to swap that here to generate it properly on the html side
856 # We need to swap that here to generate it properly on the html side
840 c.target_repo = c.source_repo
857 c.target_repo = c.source_repo
841
858
842 # comments
859 # general comments
843 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
860 c.comments = cc_model.get_comments(
844 pull_request=pull_request_id)
861 c.rhodecode_db_repo.repo_id, pull_request=pull_request_id)
845
862
846 if c.allowed_to_update:
863 if c.allowed_to_update:
847 force_close = ('forced_closed', _('Close Pull Request'))
864 force_close = ('forced_closed', _('Close Pull Request'))
848 statuses = ChangesetStatus.STATUSES + [force_close]
865 statuses = ChangesetStatus.STATUSES + [force_close]
849 else:
866 else:
850 statuses = ChangesetStatus.STATUSES
867 statuses = ChangesetStatus.STATUSES
851 c.commit_statuses = statuses
868 c.commit_statuses = statuses
852
869
853 c.ancestor = None # TODO: add ancestor here
870 c.ancestor = None # TODO: add ancestor here
854 c.pull_request = pull_request_display_obj
871 c.pull_request = pull_request_display_obj
855 c.pull_request_latest = pull_request_latest
872 c.pull_request_latest = pull_request_latest
856 c.at_version = at_version
873 c.at_version = at_version
857
874
858 c.versions = pull_request_display_obj.versions()
875 c.versions = pull_request_display_obj.versions()
859 c.changes = None
876 c.changes = None
860 c.file_changes = None
877 c.file_changes = None
861
878
862 c.show_version_changes = 1
879 c.show_version_changes = 1 # control flag, not used yet
863
880
864 if at_version and c.show_version_changes:
881 if at_version and c.show_version_changes:
865 c.changes, c.file_changes = self._get_pr_version_changes(
882 c.changes, c.file_changes = self._get_pr_version_changes(
866 version, pull_request_latest)
883 version, pull_request_latest)
867
884
868 return render('/pullrequests/pullrequest_show.html')
885 return render('/pullrequests/pullrequest_show.html')
869
886
870 @LoginRequired()
887 @LoginRequired()
871 @NotAnonymous()
888 @NotAnonymous()
872 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
889 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
873 'repository.admin')
890 'repository.admin')
874 @auth.CSRFRequired()
891 @auth.CSRFRequired()
875 @jsonify
892 @jsonify
876 def comment(self, repo_name, pull_request_id):
893 def comment(self, repo_name, pull_request_id):
877 pull_request_id = safe_int(pull_request_id)
894 pull_request_id = safe_int(pull_request_id)
878 pull_request = PullRequest.get_or_404(pull_request_id)
895 pull_request = PullRequest.get_or_404(pull_request_id)
879 if pull_request.is_closed():
896 if pull_request.is_closed():
880 raise HTTPForbidden()
897 raise HTTPForbidden()
881
898
882 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
899 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
883 # as a changeset status, still we want to send it in one value.
900 # as a changeset status, still we want to send it in one value.
884 status = request.POST.get('changeset_status', None)
901 status = request.POST.get('changeset_status', None)
885 text = request.POST.get('text')
902 text = request.POST.get('text')
886 if status and '_closed' in status:
903 if status and '_closed' in status:
887 close_pr = True
904 close_pr = True
888 status = status.replace('_closed', '')
905 status = status.replace('_closed', '')
889 else:
906 else:
890 close_pr = False
907 close_pr = False
891
908
892 forced = (status == 'forced')
909 forced = (status == 'forced')
893 if forced:
910 if forced:
894 status = 'rejected'
911 status = 'rejected'
895
912
896 allowed_to_change_status = PullRequestModel().check_user_change_status(
913 allowed_to_change_status = PullRequestModel().check_user_change_status(
897 pull_request, c.rhodecode_user)
914 pull_request, c.rhodecode_user)
898
915
899 if status and allowed_to_change_status:
916 if status and allowed_to_change_status:
900 message = (_('Status change %(transition_icon)s %(status)s')
917 message = (_('Status change %(transition_icon)s %(status)s')
901 % {'transition_icon': '>',
918 % {'transition_icon': '>',
902 'status': ChangesetStatus.get_status_lbl(status)})
919 'status': ChangesetStatus.get_status_lbl(status)})
903 if close_pr:
920 if close_pr:
904 message = _('Closing with') + ' ' + message
921 message = _('Closing with') + ' ' + message
905 text = text or message
922 text = text or message
906 comm = ChangesetCommentsModel().create(
923 comm = ChangesetCommentsModel().create(
907 text=text,
924 text=text,
908 repo=c.rhodecode_db_repo.repo_id,
925 repo=c.rhodecode_db_repo.repo_id,
909 user=c.rhodecode_user.user_id,
926 user=c.rhodecode_user.user_id,
910 pull_request=pull_request_id,
927 pull_request=pull_request_id,
911 f_path=request.POST.get('f_path'),
928 f_path=request.POST.get('f_path'),
912 line_no=request.POST.get('line'),
929 line_no=request.POST.get('line'),
913 status_change=(ChangesetStatus.get_status_lbl(status)
930 status_change=(ChangesetStatus.get_status_lbl(status)
914 if status and allowed_to_change_status else None),
931 if status and allowed_to_change_status else None),
915 status_change_type=(status
932 status_change_type=(status
916 if status and allowed_to_change_status else None),
933 if status and allowed_to_change_status else None),
917 closing_pr=close_pr
934 closing_pr=close_pr
918 )
935 )
919
936
920 if allowed_to_change_status:
937 if allowed_to_change_status:
921 old_calculated_status = pull_request.calculated_review_status()
938 old_calculated_status = pull_request.calculated_review_status()
922 # get status if set !
939 # get status if set !
923 if status:
940 if status:
924 ChangesetStatusModel().set_status(
941 ChangesetStatusModel().set_status(
925 c.rhodecode_db_repo.repo_id,
942 c.rhodecode_db_repo.repo_id,
926 status,
943 status,
927 c.rhodecode_user.user_id,
944 c.rhodecode_user.user_id,
928 comm,
945 comm,
929 pull_request=pull_request_id
946 pull_request=pull_request_id
930 )
947 )
931
948
932 Session().flush()
949 Session().flush()
933 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
950 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
934 # we now calculate the status of pull request, and based on that
951 # we now calculate the status of pull request, and based on that
935 # calculation we set the commits status
952 # calculation we set the commits status
936 calculated_status = pull_request.calculated_review_status()
953 calculated_status = pull_request.calculated_review_status()
937 if old_calculated_status != calculated_status:
954 if old_calculated_status != calculated_status:
938 PullRequestModel()._trigger_pull_request_hook(
955 PullRequestModel()._trigger_pull_request_hook(
939 pull_request, c.rhodecode_user, 'review_status_change')
956 pull_request, c.rhodecode_user, 'review_status_change')
940
957
941 calculated_status_lbl = ChangesetStatus.get_status_lbl(
958 calculated_status_lbl = ChangesetStatus.get_status_lbl(
942 calculated_status)
959 calculated_status)
943
960
944 if close_pr:
961 if close_pr:
945 status_completed = (
962 status_completed = (
946 calculated_status in [ChangesetStatus.STATUS_APPROVED,
963 calculated_status in [ChangesetStatus.STATUS_APPROVED,
947 ChangesetStatus.STATUS_REJECTED])
964 ChangesetStatus.STATUS_REJECTED])
948 if forced or status_completed:
965 if forced or status_completed:
949 PullRequestModel().close_pull_request(
966 PullRequestModel().close_pull_request(
950 pull_request_id, c.rhodecode_user)
967 pull_request_id, c.rhodecode_user)
951 else:
968 else:
952 h.flash(_('Closing pull request on other statuses than '
969 h.flash(_('Closing pull request on other statuses than '
953 'rejected or approved is forbidden. '
970 'rejected or approved is forbidden. '
954 'Calculated status from all reviewers '
971 'Calculated status from all reviewers '
955 'is currently: %s') % calculated_status_lbl,
972 'is currently: %s') % calculated_status_lbl,
956 category='warning')
973 category='warning')
957
974
958 Session().commit()
975 Session().commit()
959
976
960 if not request.is_xhr:
977 if not request.is_xhr:
961 return redirect(h.url('pullrequest_show', repo_name=repo_name,
978 return redirect(h.url('pullrequest_show', repo_name=repo_name,
962 pull_request_id=pull_request_id))
979 pull_request_id=pull_request_id))
963
980
964 data = {
981 data = {
965 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
982 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
966 }
983 }
967 if comm:
984 if comm:
968 c.co = comm
985 c.co = comm
969 data.update(comm.get_dict())
986 data.update(comm.get_dict())
970 data.update({'rendered_text':
987 data.update({'rendered_text':
971 render('changeset/changeset_comment_block.html')})
988 render('changeset/changeset_comment_block.html')})
972
989
973 return data
990 return data
974
991
975 @LoginRequired()
992 @LoginRequired()
976 @NotAnonymous()
993 @NotAnonymous()
977 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
994 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
978 'repository.admin')
995 'repository.admin')
979 @auth.CSRFRequired()
996 @auth.CSRFRequired()
980 @jsonify
997 @jsonify
981 def delete_comment(self, repo_name, comment_id):
998 def delete_comment(self, repo_name, comment_id):
982 return self._delete_comment(comment_id)
999 return self._delete_comment(comment_id)
983
1000
984 def _delete_comment(self, comment_id):
1001 def _delete_comment(self, comment_id):
985 comment_id = safe_int(comment_id)
1002 comment_id = safe_int(comment_id)
986 co = ChangesetComment.get_or_404(comment_id)
1003 co = ChangesetComment.get_or_404(comment_id)
987 if co.pull_request.is_closed():
1004 if co.pull_request.is_closed():
988 # don't allow deleting comments on closed pull request
1005 # don't allow deleting comments on closed pull request
989 raise HTTPForbidden()
1006 raise HTTPForbidden()
990
1007
991 is_owner = co.author.user_id == c.rhodecode_user.user_id
1008 is_owner = co.author.user_id == c.rhodecode_user.user_id
992 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1009 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
993 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1010 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
994 old_calculated_status = co.pull_request.calculated_review_status()
1011 old_calculated_status = co.pull_request.calculated_review_status()
995 ChangesetCommentsModel().delete(comment=co)
1012 ChangesetCommentsModel().delete(comment=co)
996 Session().commit()
1013 Session().commit()
997 calculated_status = co.pull_request.calculated_review_status()
1014 calculated_status = co.pull_request.calculated_review_status()
998 if old_calculated_status != calculated_status:
1015 if old_calculated_status != calculated_status:
999 PullRequestModel()._trigger_pull_request_hook(
1016 PullRequestModel()._trigger_pull_request_hook(
1000 co.pull_request, c.rhodecode_user, 'review_status_change')
1017 co.pull_request, c.rhodecode_user, 'review_status_change')
1001 return True
1018 return True
1002 else:
1019 else:
1003 raise HTTPForbidden()
1020 raise HTTPForbidden()
@@ -1,1161 +1,1164 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-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 """
22 """
23 Set of diffing helpers, previously part of vcs
23 Set of diffing helpers, previously part of vcs
24 """
24 """
25
25
26 import collections
26 import collections
27 import re
27 import re
28 import difflib
28 import difflib
29 import logging
29 import logging
30
30
31 from itertools import tee, imap
31 from itertools import tee, imap
32
32
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from rhodecode.lib.vcs.exceptions import VCSError
35 from rhodecode.lib.vcs.exceptions import VCSError
36 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
36 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 from rhodecode.lib.helpers import escape
38 from rhodecode.lib.helpers import escape
39 from rhodecode.lib.utils2 import safe_unicode
39 from rhodecode.lib.utils2 import safe_unicode
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43 # define max context, a file with more than this numbers of lines is unusable
43 # define max context, a file with more than this numbers of lines is unusable
44 # in browser anyway
44 # in browser anyway
45 MAX_CONTEXT = 1024 * 1014
45 MAX_CONTEXT = 1024 * 1014
46
46
47
47
48 class OPS(object):
48 class OPS(object):
49 ADD = 'A'
49 ADD = 'A'
50 MOD = 'M'
50 MOD = 'M'
51 DEL = 'D'
51 DEL = 'D'
52
52
53
53
54 def wrap_to_table(str_):
54 def wrap_to_table(str_):
55 return '''<table class="code-difftable">
55 return '''<table class="code-difftable">
56 <tr class="line no-comment">
56 <tr class="line no-comment">
57 <td class="add-comment-line tooltip" title="%s"><span class="add-comment-content"></span></td>
57 <td class="add-comment-line tooltip" title="%s"><span class="add-comment-content"></span></td>
58 <td></td>
58 <td></td>
59 <td class="lineno new"></td>
59 <td class="lineno new"></td>
60 <td class="code no-comment"><pre>%s</pre></td>
60 <td class="code no-comment"><pre>%s</pre></td>
61 </tr>
61 </tr>
62 </table>''' % (_('Click to comment'), str_)
62 </table>''' % (_('Click to comment'), str_)
63
63
64
64
65 def wrapped_diff(filenode_old, filenode_new, diff_limit=None, file_limit=None,
65 def wrapped_diff(filenode_old, filenode_new, diff_limit=None, file_limit=None,
66 show_full_diff=False, ignore_whitespace=True, line_context=3,
66 show_full_diff=False, ignore_whitespace=True, line_context=3,
67 enable_comments=False):
67 enable_comments=False):
68 """
68 """
69 returns a wrapped diff into a table, checks for cut_off_limit for file and
69 returns a wrapped diff into a table, checks for cut_off_limit for file and
70 whole diff and presents proper message
70 whole diff and presents proper message
71 """
71 """
72
72
73 if filenode_old is None:
73 if filenode_old is None:
74 filenode_old = FileNode(filenode_new.path, '', EmptyCommit())
74 filenode_old = FileNode(filenode_new.path, '', EmptyCommit())
75
75
76 if filenode_old.is_binary or filenode_new.is_binary:
76 if filenode_old.is_binary or filenode_new.is_binary:
77 diff = wrap_to_table(_('Binary file'))
77 diff = wrap_to_table(_('Binary file'))
78 stats = None
78 stats = None
79 size = 0
79 size = 0
80 data = None
80 data = None
81
81
82 elif diff_limit != -1 and (diff_limit is None or
82 elif diff_limit != -1 and (diff_limit is None or
83 (filenode_old.size < diff_limit and filenode_new.size < diff_limit)):
83 (filenode_old.size < diff_limit and filenode_new.size < diff_limit)):
84
84
85 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
85 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
86 ignore_whitespace=ignore_whitespace,
86 ignore_whitespace=ignore_whitespace,
87 context=line_context)
87 context=line_context)
88 diff_processor = DiffProcessor(
88 diff_processor = DiffProcessor(
89 f_gitdiff, format='gitdiff', diff_limit=diff_limit,
89 f_gitdiff, format='gitdiff', diff_limit=diff_limit,
90 file_limit=file_limit, show_full_diff=show_full_diff)
90 file_limit=file_limit, show_full_diff=show_full_diff)
91 _parsed = diff_processor.prepare()
91 _parsed = diff_processor.prepare()
92
92
93 diff = diff_processor.as_html(enable_comments=enable_comments)
93 diff = diff_processor.as_html(enable_comments=enable_comments)
94 stats = _parsed[0]['stats'] if _parsed else None
94 stats = _parsed[0]['stats'] if _parsed else None
95 size = len(diff or '')
95 size = len(diff or '')
96 data = _parsed[0] if _parsed else None
96 data = _parsed[0] if _parsed else None
97 else:
97 else:
98 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
98 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
99 'diff menu to display this diff'))
99 'diff menu to display this diff'))
100 stats = None
100 stats = None
101 size = 0
101 size = 0
102 data = None
102 data = None
103 if not diff:
103 if not diff:
104 submodules = filter(lambda o: isinstance(o, SubModuleNode),
104 submodules = filter(lambda o: isinstance(o, SubModuleNode),
105 [filenode_new, filenode_old])
105 [filenode_new, filenode_old])
106 if submodules:
106 if submodules:
107 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
107 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
108 else:
108 else:
109 diff = wrap_to_table(_('No changes detected'))
109 diff = wrap_to_table(_('No changes detected'))
110
110
111 cs1 = filenode_old.commit.raw_id
111 cs1 = filenode_old.commit.raw_id
112 cs2 = filenode_new.commit.raw_id
112 cs2 = filenode_new.commit.raw_id
113
113
114 return size, cs1, cs2, diff, stats, data
114 return size, cs1, cs2, diff, stats, data
115
115
116
116
117 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
117 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
118 """
118 """
119 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
119 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
120
120
121 :param ignore_whitespace: ignore whitespaces in diff
121 :param ignore_whitespace: ignore whitespaces in diff
122 """
122 """
123 # make sure we pass in default context
123 # make sure we pass in default context
124 context = context or 3
124 context = context or 3
125 # protect against IntOverflow when passing HUGE context
125 # protect against IntOverflow when passing HUGE context
126 if context > MAX_CONTEXT:
126 if context > MAX_CONTEXT:
127 context = MAX_CONTEXT
127 context = MAX_CONTEXT
128
128
129 submodules = filter(lambda o: isinstance(o, SubModuleNode),
129 submodules = filter(lambda o: isinstance(o, SubModuleNode),
130 [filenode_new, filenode_old])
130 [filenode_new, filenode_old])
131 if submodules:
131 if submodules:
132 return ''
132 return ''
133
133
134 for filenode in (filenode_old, filenode_new):
134 for filenode in (filenode_old, filenode_new):
135 if not isinstance(filenode, FileNode):
135 if not isinstance(filenode, FileNode):
136 raise VCSError(
136 raise VCSError(
137 "Given object should be FileNode object, not %s"
137 "Given object should be FileNode object, not %s"
138 % filenode.__class__)
138 % filenode.__class__)
139
139
140 repo = filenode_new.commit.repository
140 repo = filenode_new.commit.repository
141 old_commit = filenode_old.commit or repo.EMPTY_COMMIT
141 old_commit = filenode_old.commit or repo.EMPTY_COMMIT
142 new_commit = filenode_new.commit
142 new_commit = filenode_new.commit
143
143
144 vcs_gitdiff = repo.get_diff(
144 vcs_gitdiff = repo.get_diff(
145 old_commit, new_commit, filenode_new.path,
145 old_commit, new_commit, filenode_new.path,
146 ignore_whitespace, context, path1=filenode_old.path)
146 ignore_whitespace, context, path1=filenode_old.path)
147 return vcs_gitdiff
147 return vcs_gitdiff
148
148
149 NEW_FILENODE = 1
149 NEW_FILENODE = 1
150 DEL_FILENODE = 2
150 DEL_FILENODE = 2
151 MOD_FILENODE = 3
151 MOD_FILENODE = 3
152 RENAMED_FILENODE = 4
152 RENAMED_FILENODE = 4
153 COPIED_FILENODE = 5
153 COPIED_FILENODE = 5
154 CHMOD_FILENODE = 6
154 CHMOD_FILENODE = 6
155 BIN_FILENODE = 7
155 BIN_FILENODE = 7
156
156
157
157
158 class LimitedDiffContainer(object):
158 class LimitedDiffContainer(object):
159
159
160 def __init__(self, diff_limit, cur_diff_size, diff):
160 def __init__(self, diff_limit, cur_diff_size, diff):
161 self.diff = diff
161 self.diff = diff
162 self.diff_limit = diff_limit
162 self.diff_limit = diff_limit
163 self.cur_diff_size = cur_diff_size
163 self.cur_diff_size = cur_diff_size
164
164
165 def __getitem__(self, key):
165 def __getitem__(self, key):
166 return self.diff.__getitem__(key)
166 return self.diff.__getitem__(key)
167
167
168 def __iter__(self):
168 def __iter__(self):
169 for l in self.diff:
169 for l in self.diff:
170 yield l
170 yield l
171
171
172
172
173 class Action(object):
173 class Action(object):
174 """
174 """
175 Contains constants for the action value of the lines in a parsed diff.
175 Contains constants for the action value of the lines in a parsed diff.
176 """
176 """
177
177
178 ADD = 'add'
178 ADD = 'add'
179 DELETE = 'del'
179 DELETE = 'del'
180 UNMODIFIED = 'unmod'
180 UNMODIFIED = 'unmod'
181
181
182 CONTEXT = 'context'
182 CONTEXT = 'context'
183 OLD_NO_NL = 'old-no-nl'
183 OLD_NO_NL = 'old-no-nl'
184 NEW_NO_NL = 'new-no-nl'
184 NEW_NO_NL = 'new-no-nl'
185
185
186
186
187 class DiffProcessor(object):
187 class DiffProcessor(object):
188 """
188 """
189 Give it a unified or git diff and it returns a list of the files that were
189 Give it a unified or git diff and it returns a list of the files that were
190 mentioned in the diff together with a dict of meta information that
190 mentioned in the diff together with a dict of meta information that
191 can be used to render it in a HTML template.
191 can be used to render it in a HTML template.
192
192
193 .. note:: Unicode handling
193 .. note:: Unicode handling
194
194
195 The original diffs are a byte sequence and can contain filenames
195 The original diffs are a byte sequence and can contain filenames
196 in mixed encodings. This class generally returns `unicode` objects
196 in mixed encodings. This class generally returns `unicode` objects
197 since the result is intended for presentation to the user.
197 since the result is intended for presentation to the user.
198
198
199 """
199 """
200 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
200 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
201 _newline_marker = re.compile(r'^\\ No newline at end of file')
201 _newline_marker = re.compile(r'^\\ No newline at end of file')
202
202
203 # used for inline highlighter word split
203 # used for inline highlighter word split
204 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
204 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
205
205
206 # collapse ranges of commits over given number
207 _collapse_commits_over = 5
208
206 def __init__(self, diff, format='gitdiff', diff_limit=None,
209 def __init__(self, diff, format='gitdiff', diff_limit=None,
207 file_limit=None, show_full_diff=True):
210 file_limit=None, show_full_diff=True):
208 """
211 """
209 :param diff: A `Diff` object representing a diff from a vcs backend
212 :param diff: A `Diff` object representing a diff from a vcs backend
210 :param format: format of diff passed, `udiff` or `gitdiff`
213 :param format: format of diff passed, `udiff` or `gitdiff`
211 :param diff_limit: define the size of diff that is considered "big"
214 :param diff_limit: define the size of diff that is considered "big"
212 based on that parameter cut off will be triggered, set to None
215 based on that parameter cut off will be triggered, set to None
213 to show full diff
216 to show full diff
214 """
217 """
215 self._diff = diff
218 self._diff = diff
216 self._format = format
219 self._format = format
217 self.adds = 0
220 self.adds = 0
218 self.removes = 0
221 self.removes = 0
219 # calculate diff size
222 # calculate diff size
220 self.diff_limit = diff_limit
223 self.diff_limit = diff_limit
221 self.file_limit = file_limit
224 self.file_limit = file_limit
222 self.show_full_diff = show_full_diff
225 self.show_full_diff = show_full_diff
223 self.cur_diff_size = 0
226 self.cur_diff_size = 0
224 self.parsed = False
227 self.parsed = False
225 self.parsed_diff = []
228 self.parsed_diff = []
226
229
227 if format == 'gitdiff':
230 if format == 'gitdiff':
228 self.differ = self._highlight_line_difflib
231 self.differ = self._highlight_line_difflib
229 self._parser = self._parse_gitdiff
232 self._parser = self._parse_gitdiff
230 else:
233 else:
231 self.differ = self._highlight_line_udiff
234 self.differ = self._highlight_line_udiff
232 self._parser = self._new_parse_gitdiff
235 self._parser = self._new_parse_gitdiff
233
236
234 def _copy_iterator(self):
237 def _copy_iterator(self):
235 """
238 """
236 make a fresh copy of generator, we should not iterate thru
239 make a fresh copy of generator, we should not iterate thru
237 an original as it's needed for repeating operations on
240 an original as it's needed for repeating operations on
238 this instance of DiffProcessor
241 this instance of DiffProcessor
239 """
242 """
240 self.__udiff, iterator_copy = tee(self.__udiff)
243 self.__udiff, iterator_copy = tee(self.__udiff)
241 return iterator_copy
244 return iterator_copy
242
245
243 def _escaper(self, string):
246 def _escaper(self, string):
244 """
247 """
245 Escaper for diff escapes special chars and checks the diff limit
248 Escaper for diff escapes special chars and checks the diff limit
246
249
247 :param string:
250 :param string:
248 """
251 """
249
252
250 self.cur_diff_size += len(string)
253 self.cur_diff_size += len(string)
251
254
252 if not self.show_full_diff and (self.cur_diff_size > self.diff_limit):
255 if not self.show_full_diff and (self.cur_diff_size > self.diff_limit):
253 raise DiffLimitExceeded('Diff Limit Exceeded')
256 raise DiffLimitExceeded('Diff Limit Exceeded')
254
257
255 return safe_unicode(string)\
258 return safe_unicode(string)\
256 .replace('&', '&amp;')\
259 .replace('&', '&amp;')\
257 .replace('<', '&lt;')\
260 .replace('<', '&lt;')\
258 .replace('>', '&gt;')
261 .replace('>', '&gt;')
259
262
260 def _line_counter(self, l):
263 def _line_counter(self, l):
261 """
264 """
262 Checks each line and bumps total adds/removes for this diff
265 Checks each line and bumps total adds/removes for this diff
263
266
264 :param l:
267 :param l:
265 """
268 """
266 if l.startswith('+') and not l.startswith('+++'):
269 if l.startswith('+') and not l.startswith('+++'):
267 self.adds += 1
270 self.adds += 1
268 elif l.startswith('-') and not l.startswith('---'):
271 elif l.startswith('-') and not l.startswith('---'):
269 self.removes += 1
272 self.removes += 1
270 return safe_unicode(l)
273 return safe_unicode(l)
271
274
272 def _highlight_line_difflib(self, line, next_):
275 def _highlight_line_difflib(self, line, next_):
273 """
276 """
274 Highlight inline changes in both lines.
277 Highlight inline changes in both lines.
275 """
278 """
276
279
277 if line['action'] == Action.DELETE:
280 if line['action'] == Action.DELETE:
278 old, new = line, next_
281 old, new = line, next_
279 else:
282 else:
280 old, new = next_, line
283 old, new = next_, line
281
284
282 oldwords = self._token_re.split(old['line'])
285 oldwords = self._token_re.split(old['line'])
283 newwords = self._token_re.split(new['line'])
286 newwords = self._token_re.split(new['line'])
284 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
287 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
285
288
286 oldfragments, newfragments = [], []
289 oldfragments, newfragments = [], []
287 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
290 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
288 oldfrag = ''.join(oldwords[i1:i2])
291 oldfrag = ''.join(oldwords[i1:i2])
289 newfrag = ''.join(newwords[j1:j2])
292 newfrag = ''.join(newwords[j1:j2])
290 if tag != 'equal':
293 if tag != 'equal':
291 if oldfrag:
294 if oldfrag:
292 oldfrag = '<del>%s</del>' % oldfrag
295 oldfrag = '<del>%s</del>' % oldfrag
293 if newfrag:
296 if newfrag:
294 newfrag = '<ins>%s</ins>' % newfrag
297 newfrag = '<ins>%s</ins>' % newfrag
295 oldfragments.append(oldfrag)
298 oldfragments.append(oldfrag)
296 newfragments.append(newfrag)
299 newfragments.append(newfrag)
297
300
298 old['line'] = "".join(oldfragments)
301 old['line'] = "".join(oldfragments)
299 new['line'] = "".join(newfragments)
302 new['line'] = "".join(newfragments)
300
303
301 def _highlight_line_udiff(self, line, next_):
304 def _highlight_line_udiff(self, line, next_):
302 """
305 """
303 Highlight inline changes in both lines.
306 Highlight inline changes in both lines.
304 """
307 """
305 start = 0
308 start = 0
306 limit = min(len(line['line']), len(next_['line']))
309 limit = min(len(line['line']), len(next_['line']))
307 while start < limit and line['line'][start] == next_['line'][start]:
310 while start < limit and line['line'][start] == next_['line'][start]:
308 start += 1
311 start += 1
309 end = -1
312 end = -1
310 limit -= start
313 limit -= start
311 while -end <= limit and line['line'][end] == next_['line'][end]:
314 while -end <= limit and line['line'][end] == next_['line'][end]:
312 end -= 1
315 end -= 1
313 end += 1
316 end += 1
314 if start or end:
317 if start or end:
315 def do(l):
318 def do(l):
316 last = end + len(l['line'])
319 last = end + len(l['line'])
317 if l['action'] == Action.ADD:
320 if l['action'] == Action.ADD:
318 tag = 'ins'
321 tag = 'ins'
319 else:
322 else:
320 tag = 'del'
323 tag = 'del'
321 l['line'] = '%s<%s>%s</%s>%s' % (
324 l['line'] = '%s<%s>%s</%s>%s' % (
322 l['line'][:start],
325 l['line'][:start],
323 tag,
326 tag,
324 l['line'][start:last],
327 l['line'][start:last],
325 tag,
328 tag,
326 l['line'][last:]
329 l['line'][last:]
327 )
330 )
328 do(line)
331 do(line)
329 do(next_)
332 do(next_)
330
333
331 def _clean_line(self, line, command):
334 def _clean_line(self, line, command):
332 if command in ['+', '-', ' ']:
335 if command in ['+', '-', ' ']:
333 # only modify the line if it's actually a diff thing
336 # only modify the line if it's actually a diff thing
334 line = line[1:]
337 line = line[1:]
335 return line
338 return line
336
339
337 def _parse_gitdiff(self, inline_diff=True):
340 def _parse_gitdiff(self, inline_diff=True):
338 _files = []
341 _files = []
339 diff_container = lambda arg: arg
342 diff_container = lambda arg: arg
340
343
341 for chunk in self._diff.chunks():
344 for chunk in self._diff.chunks():
342 head = chunk.header
345 head = chunk.header
343
346
344 diff = imap(self._escaper, chunk.diff.splitlines(1))
347 diff = imap(self._escaper, chunk.diff.splitlines(1))
345 raw_diff = chunk.raw
348 raw_diff = chunk.raw
346 limited_diff = False
349 limited_diff = False
347 exceeds_limit = False
350 exceeds_limit = False
348
351
349 op = None
352 op = None
350 stats = {
353 stats = {
351 'added': 0,
354 'added': 0,
352 'deleted': 0,
355 'deleted': 0,
353 'binary': False,
356 'binary': False,
354 'ops': {},
357 'ops': {},
355 }
358 }
356
359
357 if head['deleted_file_mode']:
360 if head['deleted_file_mode']:
358 op = OPS.DEL
361 op = OPS.DEL
359 stats['binary'] = True
362 stats['binary'] = True
360 stats['ops'][DEL_FILENODE] = 'deleted file'
363 stats['ops'][DEL_FILENODE] = 'deleted file'
361
364
362 elif head['new_file_mode']:
365 elif head['new_file_mode']:
363 op = OPS.ADD
366 op = OPS.ADD
364 stats['binary'] = True
367 stats['binary'] = True
365 stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
368 stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
366 else: # modify operation, can be copy, rename or chmod
369 else: # modify operation, can be copy, rename or chmod
367
370
368 # CHMOD
371 # CHMOD
369 if head['new_mode'] and head['old_mode']:
372 if head['new_mode'] and head['old_mode']:
370 op = OPS.MOD
373 op = OPS.MOD
371 stats['binary'] = True
374 stats['binary'] = True
372 stats['ops'][CHMOD_FILENODE] = (
375 stats['ops'][CHMOD_FILENODE] = (
373 'modified file chmod %s => %s' % (
376 'modified file chmod %s => %s' % (
374 head['old_mode'], head['new_mode']))
377 head['old_mode'], head['new_mode']))
375 # RENAME
378 # RENAME
376 if head['rename_from'] != head['rename_to']:
379 if head['rename_from'] != head['rename_to']:
377 op = OPS.MOD
380 op = OPS.MOD
378 stats['binary'] = True
381 stats['binary'] = True
379 stats['ops'][RENAMED_FILENODE] = (
382 stats['ops'][RENAMED_FILENODE] = (
380 'file renamed from %s to %s' % (
383 'file renamed from %s to %s' % (
381 head['rename_from'], head['rename_to']))
384 head['rename_from'], head['rename_to']))
382 # COPY
385 # COPY
383 if head.get('copy_from') and head.get('copy_to'):
386 if head.get('copy_from') and head.get('copy_to'):
384 op = OPS.MOD
387 op = OPS.MOD
385 stats['binary'] = True
388 stats['binary'] = True
386 stats['ops'][COPIED_FILENODE] = (
389 stats['ops'][COPIED_FILENODE] = (
387 'file copied from %s to %s' % (
390 'file copied from %s to %s' % (
388 head['copy_from'], head['copy_to']))
391 head['copy_from'], head['copy_to']))
389
392
390 # If our new parsed headers didn't match anything fallback to
393 # If our new parsed headers didn't match anything fallback to
391 # old style detection
394 # old style detection
392 if op is None:
395 if op is None:
393 if not head['a_file'] and head['b_file']:
396 if not head['a_file'] and head['b_file']:
394 op = OPS.ADD
397 op = OPS.ADD
395 stats['binary'] = True
398 stats['binary'] = True
396 stats['ops'][NEW_FILENODE] = 'new file'
399 stats['ops'][NEW_FILENODE] = 'new file'
397
400
398 elif head['a_file'] and not head['b_file']:
401 elif head['a_file'] and not head['b_file']:
399 op = OPS.DEL
402 op = OPS.DEL
400 stats['binary'] = True
403 stats['binary'] = True
401 stats['ops'][DEL_FILENODE] = 'deleted file'
404 stats['ops'][DEL_FILENODE] = 'deleted file'
402
405
403 # it's not ADD not DELETE
406 # it's not ADD not DELETE
404 if op is None:
407 if op is None:
405 op = OPS.MOD
408 op = OPS.MOD
406 stats['binary'] = True
409 stats['binary'] = True
407 stats['ops'][MOD_FILENODE] = 'modified file'
410 stats['ops'][MOD_FILENODE] = 'modified file'
408
411
409 # a real non-binary diff
412 # a real non-binary diff
410 if head['a_file'] or head['b_file']:
413 if head['a_file'] or head['b_file']:
411 try:
414 try:
412 raw_diff, chunks, _stats = self._parse_lines(diff)
415 raw_diff, chunks, _stats = self._parse_lines(diff)
413 stats['binary'] = False
416 stats['binary'] = False
414 stats['added'] = _stats[0]
417 stats['added'] = _stats[0]
415 stats['deleted'] = _stats[1]
418 stats['deleted'] = _stats[1]
416 # explicit mark that it's a modified file
419 # explicit mark that it's a modified file
417 if op == OPS.MOD:
420 if op == OPS.MOD:
418 stats['ops'][MOD_FILENODE] = 'modified file'
421 stats['ops'][MOD_FILENODE] = 'modified file'
419 exceeds_limit = len(raw_diff) > self.file_limit
422 exceeds_limit = len(raw_diff) > self.file_limit
420
423
421 # changed from _escaper function so we validate size of
424 # changed from _escaper function so we validate size of
422 # each file instead of the whole diff
425 # each file instead of the whole diff
423 # diff will hide big files but still show small ones
426 # diff will hide big files but still show small ones
424 # from my tests, big files are fairly safe to be parsed
427 # from my tests, big files are fairly safe to be parsed
425 # but the browser is the bottleneck
428 # but the browser is the bottleneck
426 if not self.show_full_diff and exceeds_limit:
429 if not self.show_full_diff and exceeds_limit:
427 raise DiffLimitExceeded('File Limit Exceeded')
430 raise DiffLimitExceeded('File Limit Exceeded')
428
431
429 except DiffLimitExceeded:
432 except DiffLimitExceeded:
430 diff_container = lambda _diff: \
433 diff_container = lambda _diff: \
431 LimitedDiffContainer(
434 LimitedDiffContainer(
432 self.diff_limit, self.cur_diff_size, _diff)
435 self.diff_limit, self.cur_diff_size, _diff)
433
436
434 exceeds_limit = len(raw_diff) > self.file_limit
437 exceeds_limit = len(raw_diff) > self.file_limit
435 limited_diff = True
438 limited_diff = True
436 chunks = []
439 chunks = []
437
440
438 else: # GIT format binary patch, or possibly empty diff
441 else: # GIT format binary patch, or possibly empty diff
439 if head['bin_patch']:
442 if head['bin_patch']:
440 # we have operation already extracted, but we mark simply
443 # we have operation already extracted, but we mark simply
441 # it's a diff we wont show for binary files
444 # it's a diff we wont show for binary files
442 stats['ops'][BIN_FILENODE] = 'binary diff hidden'
445 stats['ops'][BIN_FILENODE] = 'binary diff hidden'
443 chunks = []
446 chunks = []
444
447
445 if chunks and not self.show_full_diff and op == OPS.DEL:
448 if chunks and not self.show_full_diff and op == OPS.DEL:
446 # if not full diff mode show deleted file contents
449 # if not full diff mode show deleted file contents
447 # TODO: anderson: if the view is not too big, there is no way
450 # TODO: anderson: if the view is not too big, there is no way
448 # to see the content of the file
451 # to see the content of the file
449 chunks = []
452 chunks = []
450
453
451 chunks.insert(0, [{
454 chunks.insert(0, [{
452 'old_lineno': '',
455 'old_lineno': '',
453 'new_lineno': '',
456 'new_lineno': '',
454 'action': Action.CONTEXT,
457 'action': Action.CONTEXT,
455 'line': msg,
458 'line': msg,
456 } for _op, msg in stats['ops'].iteritems()
459 } for _op, msg in stats['ops'].iteritems()
457 if _op not in [MOD_FILENODE]])
460 if _op not in [MOD_FILENODE]])
458
461
459 _files.append({
462 _files.append({
460 'filename': safe_unicode(head['b_path']),
463 'filename': safe_unicode(head['b_path']),
461 'old_revision': head['a_blob_id'],
464 'old_revision': head['a_blob_id'],
462 'new_revision': head['b_blob_id'],
465 'new_revision': head['b_blob_id'],
463 'chunks': chunks,
466 'chunks': chunks,
464 'raw_diff': safe_unicode(raw_diff),
467 'raw_diff': safe_unicode(raw_diff),
465 'operation': op,
468 'operation': op,
466 'stats': stats,
469 'stats': stats,
467 'exceeds_limit': exceeds_limit,
470 'exceeds_limit': exceeds_limit,
468 'is_limited_diff': limited_diff,
471 'is_limited_diff': limited_diff,
469 })
472 })
470
473
471 sorter = lambda info: {OPS.ADD: 0, OPS.MOD: 1,
474 sorter = lambda info: {OPS.ADD: 0, OPS.MOD: 1,
472 OPS.DEL: 2}.get(info['operation'])
475 OPS.DEL: 2}.get(info['operation'])
473
476
474 if not inline_diff:
477 if not inline_diff:
475 return diff_container(sorted(_files, key=sorter))
478 return diff_container(sorted(_files, key=sorter))
476
479
477 # highlight inline changes
480 # highlight inline changes
478 for diff_data in _files:
481 for diff_data in _files:
479 for chunk in diff_data['chunks']:
482 for chunk in diff_data['chunks']:
480 lineiter = iter(chunk)
483 lineiter = iter(chunk)
481 try:
484 try:
482 while 1:
485 while 1:
483 line = lineiter.next()
486 line = lineiter.next()
484 if line['action'] not in (
487 if line['action'] not in (
485 Action.UNMODIFIED, Action.CONTEXT):
488 Action.UNMODIFIED, Action.CONTEXT):
486 nextline = lineiter.next()
489 nextline = lineiter.next()
487 if nextline['action'] in ['unmod', 'context'] or \
490 if nextline['action'] in ['unmod', 'context'] or \
488 nextline['action'] == line['action']:
491 nextline['action'] == line['action']:
489 continue
492 continue
490 self.differ(line, nextline)
493 self.differ(line, nextline)
491 except StopIteration:
494 except StopIteration:
492 pass
495 pass
493
496
494 return diff_container(sorted(_files, key=sorter))
497 return diff_container(sorted(_files, key=sorter))
495
498
496
499
497 # FIXME: NEWDIFFS: dan: this replaces the old _escaper function
500 # FIXME: NEWDIFFS: dan: this replaces the old _escaper function
498 def _process_line(self, string):
501 def _process_line(self, string):
499 """
502 """
500 Process a diff line, checks the diff limit
503 Process a diff line, checks the diff limit
501
504
502 :param string:
505 :param string:
503 """
506 """
504
507
505 self.cur_diff_size += len(string)
508 self.cur_diff_size += len(string)
506
509
507 if not self.show_full_diff and (self.cur_diff_size > self.diff_limit):
510 if not self.show_full_diff and (self.cur_diff_size > self.diff_limit):
508 raise DiffLimitExceeded('Diff Limit Exceeded')
511 raise DiffLimitExceeded('Diff Limit Exceeded')
509
512
510 return safe_unicode(string)
513 return safe_unicode(string)
511
514
512 # FIXME: NEWDIFFS: dan: this replaces _parse_gitdiff
515 # FIXME: NEWDIFFS: dan: this replaces _parse_gitdiff
513 def _new_parse_gitdiff(self, inline_diff=True):
516 def _new_parse_gitdiff(self, inline_diff=True):
514 _files = []
517 _files = []
515 diff_container = lambda arg: arg
518 diff_container = lambda arg: arg
516 for chunk in self._diff.chunks():
519 for chunk in self._diff.chunks():
517 head = chunk.header
520 head = chunk.header
518 log.debug('parsing diff %r' % head)
521 log.debug('parsing diff %r' % head)
519
522
520 diff = imap(self._process_line, chunk.diff.splitlines(1))
523 diff = imap(self._process_line, chunk.diff.splitlines(1))
521 raw_diff = chunk.raw
524 raw_diff = chunk.raw
522 limited_diff = False
525 limited_diff = False
523 exceeds_limit = False
526 exceeds_limit = False
524 # if 'empty_file_to_modify_and_rename' in head['a_path']:
527 # if 'empty_file_to_modify_and_rename' in head['a_path']:
525 # 1/0
528 # 1/0
526 op = None
529 op = None
527 stats = {
530 stats = {
528 'added': 0,
531 'added': 0,
529 'deleted': 0,
532 'deleted': 0,
530 'binary': False,
533 'binary': False,
531 'old_mode': None,
534 'old_mode': None,
532 'new_mode': None,
535 'new_mode': None,
533 'ops': {},
536 'ops': {},
534 }
537 }
535 if head['old_mode']:
538 if head['old_mode']:
536 stats['old_mode'] = head['old_mode']
539 stats['old_mode'] = head['old_mode']
537 if head['new_mode']:
540 if head['new_mode']:
538 stats['new_mode'] = head['new_mode']
541 stats['new_mode'] = head['new_mode']
539 if head['b_mode']:
542 if head['b_mode']:
540 stats['new_mode'] = head['b_mode']
543 stats['new_mode'] = head['b_mode']
541
544
542 if head['deleted_file_mode']:
545 if head['deleted_file_mode']:
543 op = OPS.DEL
546 op = OPS.DEL
544 stats['binary'] = True
547 stats['binary'] = True
545 stats['ops'][DEL_FILENODE] = 'deleted file'
548 stats['ops'][DEL_FILENODE] = 'deleted file'
546
549
547 elif head['new_file_mode']:
550 elif head['new_file_mode']:
548 op = OPS.ADD
551 op = OPS.ADD
549 stats['binary'] = True
552 stats['binary'] = True
550 stats['old_mode'] = None
553 stats['old_mode'] = None
551 stats['new_mode'] = head['new_file_mode']
554 stats['new_mode'] = head['new_file_mode']
552 stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
555 stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
553 else: # modify operation, can be copy, rename or chmod
556 else: # modify operation, can be copy, rename or chmod
554
557
555 # CHMOD
558 # CHMOD
556 if head['new_mode'] and head['old_mode']:
559 if head['new_mode'] and head['old_mode']:
557 op = OPS.MOD
560 op = OPS.MOD
558 stats['binary'] = True
561 stats['binary'] = True
559 stats['ops'][CHMOD_FILENODE] = (
562 stats['ops'][CHMOD_FILENODE] = (
560 'modified file chmod %s => %s' % (
563 'modified file chmod %s => %s' % (
561 head['old_mode'], head['new_mode']))
564 head['old_mode'], head['new_mode']))
562
565
563 # RENAME
566 # RENAME
564 if head['rename_from'] != head['rename_to']:
567 if head['rename_from'] != head['rename_to']:
565 op = OPS.MOD
568 op = OPS.MOD
566 stats['binary'] = True
569 stats['binary'] = True
567 stats['renamed'] = (head['rename_from'], head['rename_to'])
570 stats['renamed'] = (head['rename_from'], head['rename_to'])
568 stats['ops'][RENAMED_FILENODE] = (
571 stats['ops'][RENAMED_FILENODE] = (
569 'file renamed from %s to %s' % (
572 'file renamed from %s to %s' % (
570 head['rename_from'], head['rename_to']))
573 head['rename_from'], head['rename_to']))
571 # COPY
574 # COPY
572 if head.get('copy_from') and head.get('copy_to'):
575 if head.get('copy_from') and head.get('copy_to'):
573 op = OPS.MOD
576 op = OPS.MOD
574 stats['binary'] = True
577 stats['binary'] = True
575 stats['copied'] = (head['copy_from'], head['copy_to'])
578 stats['copied'] = (head['copy_from'], head['copy_to'])
576 stats['ops'][COPIED_FILENODE] = (
579 stats['ops'][COPIED_FILENODE] = (
577 'file copied from %s to %s' % (
580 'file copied from %s to %s' % (
578 head['copy_from'], head['copy_to']))
581 head['copy_from'], head['copy_to']))
579
582
580 # If our new parsed headers didn't match anything fallback to
583 # If our new parsed headers didn't match anything fallback to
581 # old style detection
584 # old style detection
582 if op is None:
585 if op is None:
583 if not head['a_file'] and head['b_file']:
586 if not head['a_file'] and head['b_file']:
584 op = OPS.ADD
587 op = OPS.ADD
585 stats['binary'] = True
588 stats['binary'] = True
586 stats['new_file'] = True
589 stats['new_file'] = True
587 stats['ops'][NEW_FILENODE] = 'new file'
590 stats['ops'][NEW_FILENODE] = 'new file'
588
591
589 elif head['a_file'] and not head['b_file']:
592 elif head['a_file'] and not head['b_file']:
590 op = OPS.DEL
593 op = OPS.DEL
591 stats['binary'] = True
594 stats['binary'] = True
592 stats['ops'][DEL_FILENODE] = 'deleted file'
595 stats['ops'][DEL_FILENODE] = 'deleted file'
593
596
594 # it's not ADD not DELETE
597 # it's not ADD not DELETE
595 if op is None:
598 if op is None:
596 op = OPS.MOD
599 op = OPS.MOD
597 stats['binary'] = True
600 stats['binary'] = True
598 stats['ops'][MOD_FILENODE] = 'modified file'
601 stats['ops'][MOD_FILENODE] = 'modified file'
599
602
600 # a real non-binary diff
603 # a real non-binary diff
601 if head['a_file'] or head['b_file']:
604 if head['a_file'] or head['b_file']:
602 try:
605 try:
603 raw_diff, chunks, _stats = self._new_parse_lines(diff)
606 raw_diff, chunks, _stats = self._new_parse_lines(diff)
604 stats['binary'] = False
607 stats['binary'] = False
605 stats['added'] = _stats[0]
608 stats['added'] = _stats[0]
606 stats['deleted'] = _stats[1]
609 stats['deleted'] = _stats[1]
607 # explicit mark that it's a modified file
610 # explicit mark that it's a modified file
608 if op == OPS.MOD:
611 if op == OPS.MOD:
609 stats['ops'][MOD_FILENODE] = 'modified file'
612 stats['ops'][MOD_FILENODE] = 'modified file'
610 exceeds_limit = len(raw_diff) > self.file_limit
613 exceeds_limit = len(raw_diff) > self.file_limit
611
614
612 # changed from _escaper function so we validate size of
615 # changed from _escaper function so we validate size of
613 # each file instead of the whole diff
616 # each file instead of the whole diff
614 # diff will hide big files but still show small ones
617 # diff will hide big files but still show small ones
615 # from my tests, big files are fairly safe to be parsed
618 # from my tests, big files are fairly safe to be parsed
616 # but the browser is the bottleneck
619 # but the browser is the bottleneck
617 if not self.show_full_diff and exceeds_limit:
620 if not self.show_full_diff and exceeds_limit:
618 raise DiffLimitExceeded('File Limit Exceeded')
621 raise DiffLimitExceeded('File Limit Exceeded')
619
622
620 except DiffLimitExceeded:
623 except DiffLimitExceeded:
621 diff_container = lambda _diff: \
624 diff_container = lambda _diff: \
622 LimitedDiffContainer(
625 LimitedDiffContainer(
623 self.diff_limit, self.cur_diff_size, _diff)
626 self.diff_limit, self.cur_diff_size, _diff)
624
627
625 exceeds_limit = len(raw_diff) > self.file_limit
628 exceeds_limit = len(raw_diff) > self.file_limit
626 limited_diff = True
629 limited_diff = True
627 chunks = []
630 chunks = []
628
631
629 else: # GIT format binary patch, or possibly empty diff
632 else: # GIT format binary patch, or possibly empty diff
630 if head['bin_patch']:
633 if head['bin_patch']:
631 # we have operation already extracted, but we mark simply
634 # we have operation already extracted, but we mark simply
632 # it's a diff we wont show for binary files
635 # it's a diff we wont show for binary files
633 stats['ops'][BIN_FILENODE] = 'binary diff hidden'
636 stats['ops'][BIN_FILENODE] = 'binary diff hidden'
634 chunks = []
637 chunks = []
635
638
636 if chunks and not self.show_full_diff and op == OPS.DEL:
639 if chunks and not self.show_full_diff and op == OPS.DEL:
637 # if not full diff mode show deleted file contents
640 # if not full diff mode show deleted file contents
638 # TODO: anderson: if the view is not too big, there is no way
641 # TODO: anderson: if the view is not too big, there is no way
639 # to see the content of the file
642 # to see the content of the file
640 chunks = []
643 chunks = []
641
644
642 chunks.insert(0, [{
645 chunks.insert(0, [{
643 'old_lineno': '',
646 'old_lineno': '',
644 'new_lineno': '',
647 'new_lineno': '',
645 'action': Action.CONTEXT,
648 'action': Action.CONTEXT,
646 'line': msg,
649 'line': msg,
647 } for _op, msg in stats['ops'].iteritems()
650 } for _op, msg in stats['ops'].iteritems()
648 if _op not in [MOD_FILENODE]])
651 if _op not in [MOD_FILENODE]])
649
652
650 original_filename = safe_unicode(head['a_path'])
653 original_filename = safe_unicode(head['a_path'])
651 _files.append({
654 _files.append({
652 'original_filename': original_filename,
655 'original_filename': original_filename,
653 'filename': safe_unicode(head['b_path']),
656 'filename': safe_unicode(head['b_path']),
654 'old_revision': head['a_blob_id'],
657 'old_revision': head['a_blob_id'],
655 'new_revision': head['b_blob_id'],
658 'new_revision': head['b_blob_id'],
656 'chunks': chunks,
659 'chunks': chunks,
657 'raw_diff': safe_unicode(raw_diff),
660 'raw_diff': safe_unicode(raw_diff),
658 'operation': op,
661 'operation': op,
659 'stats': stats,
662 'stats': stats,
660 'exceeds_limit': exceeds_limit,
663 'exceeds_limit': exceeds_limit,
661 'is_limited_diff': limited_diff,
664 'is_limited_diff': limited_diff,
662 })
665 })
663
666
664
667
665 sorter = lambda info: {OPS.ADD: 0, OPS.MOD: 1,
668 sorter = lambda info: {OPS.ADD: 0, OPS.MOD: 1,
666 OPS.DEL: 2}.get(info['operation'])
669 OPS.DEL: 2}.get(info['operation'])
667
670
668 return diff_container(sorted(_files, key=sorter))
671 return diff_container(sorted(_files, key=sorter))
669
672
670 # FIXME: NEWDIFFS: dan: this gets replaced by _new_parse_lines
673 # FIXME: NEWDIFFS: dan: this gets replaced by _new_parse_lines
671 def _parse_lines(self, diff):
674 def _parse_lines(self, diff):
672 """
675 """
673 Parse the diff an return data for the template.
676 Parse the diff an return data for the template.
674 """
677 """
675
678
676 lineiter = iter(diff)
679 lineiter = iter(diff)
677 stats = [0, 0]
680 stats = [0, 0]
678 chunks = []
681 chunks = []
679 raw_diff = []
682 raw_diff = []
680
683
681 try:
684 try:
682 line = lineiter.next()
685 line = lineiter.next()
683
686
684 while line:
687 while line:
685 raw_diff.append(line)
688 raw_diff.append(line)
686 lines = []
689 lines = []
687 chunks.append(lines)
690 chunks.append(lines)
688
691
689 match = self._chunk_re.match(line)
692 match = self._chunk_re.match(line)
690
693
691 if not match:
694 if not match:
692 break
695 break
693
696
694 gr = match.groups()
697 gr = match.groups()
695 (old_line, old_end,
698 (old_line, old_end,
696 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
699 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
697 old_line -= 1
700 old_line -= 1
698 new_line -= 1
701 new_line -= 1
699
702
700 context = len(gr) == 5
703 context = len(gr) == 5
701 old_end += old_line
704 old_end += old_line
702 new_end += new_line
705 new_end += new_line
703
706
704 if context:
707 if context:
705 # skip context only if it's first line
708 # skip context only if it's first line
706 if int(gr[0]) > 1:
709 if int(gr[0]) > 1:
707 lines.append({
710 lines.append({
708 'old_lineno': '...',
711 'old_lineno': '...',
709 'new_lineno': '...',
712 'new_lineno': '...',
710 'action': Action.CONTEXT,
713 'action': Action.CONTEXT,
711 'line': line,
714 'line': line,
712 })
715 })
713
716
714 line = lineiter.next()
717 line = lineiter.next()
715
718
716 while old_line < old_end or new_line < new_end:
719 while old_line < old_end or new_line < new_end:
717 command = ' '
720 command = ' '
718 if line:
721 if line:
719 command = line[0]
722 command = line[0]
720
723
721 affects_old = affects_new = False
724 affects_old = affects_new = False
722
725
723 # ignore those if we don't expect them
726 # ignore those if we don't expect them
724 if command in '#@':
727 if command in '#@':
725 continue
728 continue
726 elif command == '+':
729 elif command == '+':
727 affects_new = True
730 affects_new = True
728 action = Action.ADD
731 action = Action.ADD
729 stats[0] += 1
732 stats[0] += 1
730 elif command == '-':
733 elif command == '-':
731 affects_old = True
734 affects_old = True
732 action = Action.DELETE
735 action = Action.DELETE
733 stats[1] += 1
736 stats[1] += 1
734 else:
737 else:
735 affects_old = affects_new = True
738 affects_old = affects_new = True
736 action = Action.UNMODIFIED
739 action = Action.UNMODIFIED
737
740
738 if not self._newline_marker.match(line):
741 if not self._newline_marker.match(line):
739 old_line += affects_old
742 old_line += affects_old
740 new_line += affects_new
743 new_line += affects_new
741 lines.append({
744 lines.append({
742 'old_lineno': affects_old and old_line or '',
745 'old_lineno': affects_old and old_line or '',
743 'new_lineno': affects_new and new_line or '',
746 'new_lineno': affects_new and new_line or '',
744 'action': action,
747 'action': action,
745 'line': self._clean_line(line, command)
748 'line': self._clean_line(line, command)
746 })
749 })
747 raw_diff.append(line)
750 raw_diff.append(line)
748
751
749 line = lineiter.next()
752 line = lineiter.next()
750
753
751 if self._newline_marker.match(line):
754 if self._newline_marker.match(line):
752 # we need to append to lines, since this is not
755 # we need to append to lines, since this is not
753 # counted in the line specs of diff
756 # counted in the line specs of diff
754 lines.append({
757 lines.append({
755 'old_lineno': '...',
758 'old_lineno': '...',
756 'new_lineno': '...',
759 'new_lineno': '...',
757 'action': Action.CONTEXT,
760 'action': Action.CONTEXT,
758 'line': self._clean_line(line, command)
761 'line': self._clean_line(line, command)
759 })
762 })
760
763
761 except StopIteration:
764 except StopIteration:
762 pass
765 pass
763 return ''.join(raw_diff), chunks, stats
766 return ''.join(raw_diff), chunks, stats
764
767
765 # FIXME: NEWDIFFS: dan: this replaces _parse_lines
768 # FIXME: NEWDIFFS: dan: this replaces _parse_lines
766 def _new_parse_lines(self, diff):
769 def _new_parse_lines(self, diff):
767 """
770 """
768 Parse the diff an return data for the template.
771 Parse the diff an return data for the template.
769 """
772 """
770
773
771 lineiter = iter(diff)
774 lineiter = iter(diff)
772 stats = [0, 0]
775 stats = [0, 0]
773 chunks = []
776 chunks = []
774 raw_diff = []
777 raw_diff = []
775
778
776 try:
779 try:
777 line = lineiter.next()
780 line = lineiter.next()
778
781
779 while line:
782 while line:
780 raw_diff.append(line)
783 raw_diff.append(line)
781 match = self._chunk_re.match(line)
784 match = self._chunk_re.match(line)
782
785
783 if not match:
786 if not match:
784 break
787 break
785
788
786 gr = match.groups()
789 gr = match.groups()
787 (old_line, old_end,
790 (old_line, old_end,
788 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
791 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
789
792
790 lines = []
793 lines = []
791 hunk = {
794 hunk = {
792 'section_header': gr[-1],
795 'section_header': gr[-1],
793 'source_start': old_line,
796 'source_start': old_line,
794 'source_length': old_end,
797 'source_length': old_end,
795 'target_start': new_line,
798 'target_start': new_line,
796 'target_length': new_end,
799 'target_length': new_end,
797 'lines': lines,
800 'lines': lines,
798 }
801 }
799 chunks.append(hunk)
802 chunks.append(hunk)
800
803
801 old_line -= 1
804 old_line -= 1
802 new_line -= 1
805 new_line -= 1
803
806
804 context = len(gr) == 5
807 context = len(gr) == 5
805 old_end += old_line
808 old_end += old_line
806 new_end += new_line
809 new_end += new_line
807
810
808 line = lineiter.next()
811 line = lineiter.next()
809
812
810 while old_line < old_end or new_line < new_end:
813 while old_line < old_end or new_line < new_end:
811 command = ' '
814 command = ' '
812 if line:
815 if line:
813 command = line[0]
816 command = line[0]
814
817
815 affects_old = affects_new = False
818 affects_old = affects_new = False
816
819
817 # ignore those if we don't expect them
820 # ignore those if we don't expect them
818 if command in '#@':
821 if command in '#@':
819 continue
822 continue
820 elif command == '+':
823 elif command == '+':
821 affects_new = True
824 affects_new = True
822 action = Action.ADD
825 action = Action.ADD
823 stats[0] += 1
826 stats[0] += 1
824 elif command == '-':
827 elif command == '-':
825 affects_old = True
828 affects_old = True
826 action = Action.DELETE
829 action = Action.DELETE
827 stats[1] += 1
830 stats[1] += 1
828 else:
831 else:
829 affects_old = affects_new = True
832 affects_old = affects_new = True
830 action = Action.UNMODIFIED
833 action = Action.UNMODIFIED
831
834
832 if not self._newline_marker.match(line):
835 if not self._newline_marker.match(line):
833 old_line += affects_old
836 old_line += affects_old
834 new_line += affects_new
837 new_line += affects_new
835 lines.append({
838 lines.append({
836 'old_lineno': affects_old and old_line or '',
839 'old_lineno': affects_old and old_line or '',
837 'new_lineno': affects_new and new_line or '',
840 'new_lineno': affects_new and new_line or '',
838 'action': action,
841 'action': action,
839 'line': self._clean_line(line, command)
842 'line': self._clean_line(line, command)
840 })
843 })
841 raw_diff.append(line)
844 raw_diff.append(line)
842
845
843 line = lineiter.next()
846 line = lineiter.next()
844
847
845 if self._newline_marker.match(line):
848 if self._newline_marker.match(line):
846 # we need to append to lines, since this is not
849 # we need to append to lines, since this is not
847 # counted in the line specs of diff
850 # counted in the line specs of diff
848 if affects_old:
851 if affects_old:
849 action = Action.OLD_NO_NL
852 action = Action.OLD_NO_NL
850 elif affects_new:
853 elif affects_new:
851 action = Action.NEW_NO_NL
854 action = Action.NEW_NO_NL
852 else:
855 else:
853 raise Exception('invalid context for no newline')
856 raise Exception('invalid context for no newline')
854
857
855 lines.append({
858 lines.append({
856 'old_lineno': None,
859 'old_lineno': None,
857 'new_lineno': None,
860 'new_lineno': None,
858 'action': action,
861 'action': action,
859 'line': self._clean_line(line, command)
862 'line': self._clean_line(line, command)
860 })
863 })
861
864
862 except StopIteration:
865 except StopIteration:
863 pass
866 pass
864 return ''.join(raw_diff), chunks, stats
867 return ''.join(raw_diff), chunks, stats
865
868
866 def _safe_id(self, idstring):
869 def _safe_id(self, idstring):
867 """Make a string safe for including in an id attribute.
870 """Make a string safe for including in an id attribute.
868
871
869 The HTML spec says that id attributes 'must begin with
872 The HTML spec says that id attributes 'must begin with
870 a letter ([A-Za-z]) and may be followed by any number
873 a letter ([A-Za-z]) and may be followed by any number
871 of letters, digits ([0-9]), hyphens ("-"), underscores
874 of letters, digits ([0-9]), hyphens ("-"), underscores
872 ("_"), colons (":"), and periods (".")'. These regexps
875 ("_"), colons (":"), and periods (".")'. These regexps
873 are slightly over-zealous, in that they remove colons
876 are slightly over-zealous, in that they remove colons
874 and periods unnecessarily.
877 and periods unnecessarily.
875
878
876 Whitespace is transformed into underscores, and then
879 Whitespace is transformed into underscores, and then
877 anything which is not a hyphen or a character that
880 anything which is not a hyphen or a character that
878 matches \w (alphanumerics and underscore) is removed.
881 matches \w (alphanumerics and underscore) is removed.
879
882
880 """
883 """
881 # Transform all whitespace to underscore
884 # Transform all whitespace to underscore
882 idstring = re.sub(r'\s', "_", '%s' % idstring)
885 idstring = re.sub(r'\s', "_", '%s' % idstring)
883 # Remove everything that is not a hyphen or a member of \w
886 # Remove everything that is not a hyphen or a member of \w
884 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
887 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
885 return idstring
888 return idstring
886
889
887 def prepare(self, inline_diff=True):
890 def prepare(self, inline_diff=True):
888 """
891 """
889 Prepare the passed udiff for HTML rendering.
892 Prepare the passed udiff for HTML rendering.
890
893
891 :return: A list of dicts with diff information.
894 :return: A list of dicts with diff information.
892 """
895 """
893 parsed = self._parser(inline_diff=inline_diff)
896 parsed = self._parser(inline_diff=inline_diff)
894 self.parsed = True
897 self.parsed = True
895 self.parsed_diff = parsed
898 self.parsed_diff = parsed
896 return parsed
899 return parsed
897
900
898 def as_raw(self, diff_lines=None):
901 def as_raw(self, diff_lines=None):
899 """
902 """
900 Returns raw diff as a byte string
903 Returns raw diff as a byte string
901 """
904 """
902 return self._diff.raw
905 return self._diff.raw
903
906
904 def as_html(self, table_class='code-difftable', line_class='line',
907 def as_html(self, table_class='code-difftable', line_class='line',
905 old_lineno_class='lineno old', new_lineno_class='lineno new',
908 old_lineno_class='lineno old', new_lineno_class='lineno new',
906 code_class='code', enable_comments=False, parsed_lines=None):
909 code_class='code', enable_comments=False, parsed_lines=None):
907 """
910 """
908 Return given diff as html table with customized css classes
911 Return given diff as html table with customized css classes
909 """
912 """
910 def _link_to_if(condition, label, url):
913 def _link_to_if(condition, label, url):
911 """
914 """
912 Generates a link if condition is meet or just the label if not.
915 Generates a link if condition is meet or just the label if not.
913 """
916 """
914
917
915 if condition:
918 if condition:
916 return '''<a href="%(url)s" class="tooltip"
919 return '''<a href="%(url)s" class="tooltip"
917 title="%(title)s">%(label)s</a>''' % {
920 title="%(title)s">%(label)s</a>''' % {
918 'title': _('Click to select line'),
921 'title': _('Click to select line'),
919 'url': url,
922 'url': url,
920 'label': label
923 'label': label
921 }
924 }
922 else:
925 else:
923 return label
926 return label
924 if not self.parsed:
927 if not self.parsed:
925 self.prepare()
928 self.prepare()
926
929
927 diff_lines = self.parsed_diff
930 diff_lines = self.parsed_diff
928 if parsed_lines:
931 if parsed_lines:
929 diff_lines = parsed_lines
932 diff_lines = parsed_lines
930
933
931 _html_empty = True
934 _html_empty = True
932 _html = []
935 _html = []
933 _html.append('''<table class="%(table_class)s">\n''' % {
936 _html.append('''<table class="%(table_class)s">\n''' % {
934 'table_class': table_class
937 'table_class': table_class
935 })
938 })
936
939
937 for diff in diff_lines:
940 for diff in diff_lines:
938 for line in diff['chunks']:
941 for line in diff['chunks']:
939 _html_empty = False
942 _html_empty = False
940 for change in line:
943 for change in line:
941 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
944 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
942 'lc': line_class,
945 'lc': line_class,
943 'action': change['action']
946 'action': change['action']
944 })
947 })
945 anchor_old_id = ''
948 anchor_old_id = ''
946 anchor_new_id = ''
949 anchor_new_id = ''
947 anchor_old = "%(filename)s_o%(oldline_no)s" % {
950 anchor_old = "%(filename)s_o%(oldline_no)s" % {
948 'filename': self._safe_id(diff['filename']),
951 'filename': self._safe_id(diff['filename']),
949 'oldline_no': change['old_lineno']
952 'oldline_no': change['old_lineno']
950 }
953 }
951 anchor_new = "%(filename)s_n%(oldline_no)s" % {
954 anchor_new = "%(filename)s_n%(oldline_no)s" % {
952 'filename': self._safe_id(diff['filename']),
955 'filename': self._safe_id(diff['filename']),
953 'oldline_no': change['new_lineno']
956 'oldline_no': change['new_lineno']
954 }
957 }
955 cond_old = (change['old_lineno'] != '...' and
958 cond_old = (change['old_lineno'] != '...' and
956 change['old_lineno'])
959 change['old_lineno'])
957 cond_new = (change['new_lineno'] != '...' and
960 cond_new = (change['new_lineno'] != '...' and
958 change['new_lineno'])
961 change['new_lineno'])
959 if cond_old:
962 if cond_old:
960 anchor_old_id = 'id="%s"' % anchor_old
963 anchor_old_id = 'id="%s"' % anchor_old
961 if cond_new:
964 if cond_new:
962 anchor_new_id = 'id="%s"' % anchor_new
965 anchor_new_id = 'id="%s"' % anchor_new
963
966
964 if change['action'] != Action.CONTEXT:
967 if change['action'] != Action.CONTEXT:
965 anchor_link = True
968 anchor_link = True
966 else:
969 else:
967 anchor_link = False
970 anchor_link = False
968
971
969 ###########################################################
972 ###########################################################
970 # COMMENT ICONS
973 # COMMENT ICONS
971 ###########################################################
974 ###########################################################
972 _html.append('''\t<td class="add-comment-line"><span class="add-comment-content">''')
975 _html.append('''\t<td class="add-comment-line"><span class="add-comment-content">''')
973
976
974 if enable_comments and change['action'] != Action.CONTEXT:
977 if enable_comments and change['action'] != Action.CONTEXT:
975 _html.append('''<a href="#"><span class="icon-comment-add"></span></a>''')
978 _html.append('''<a href="#"><span class="icon-comment-add"></span></a>''')
976
979
977 _html.append('''</span></td><td class="comment-toggle tooltip" title="Toggle Comment Thread"><i class="icon-comment"></i></td>\n''')
980 _html.append('''</span></td><td class="comment-toggle tooltip" title="Toggle Comment Thread"><i class="icon-comment"></i></td>\n''')
978
981
979 ###########################################################
982 ###########################################################
980 # OLD LINE NUMBER
983 # OLD LINE NUMBER
981 ###########################################################
984 ###########################################################
982 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
985 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
983 'a_id': anchor_old_id,
986 'a_id': anchor_old_id,
984 'olc': old_lineno_class
987 'olc': old_lineno_class
985 })
988 })
986
989
987 _html.append('''%(link)s''' % {
990 _html.append('''%(link)s''' % {
988 'link': _link_to_if(anchor_link, change['old_lineno'],
991 'link': _link_to_if(anchor_link, change['old_lineno'],
989 '#%s' % anchor_old)
992 '#%s' % anchor_old)
990 })
993 })
991 _html.append('''</td>\n''')
994 _html.append('''</td>\n''')
992 ###########################################################
995 ###########################################################
993 # NEW LINE NUMBER
996 # NEW LINE NUMBER
994 ###########################################################
997 ###########################################################
995
998
996 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
999 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
997 'a_id': anchor_new_id,
1000 'a_id': anchor_new_id,
998 'nlc': new_lineno_class
1001 'nlc': new_lineno_class
999 })
1002 })
1000
1003
1001 _html.append('''%(link)s''' % {
1004 _html.append('''%(link)s''' % {
1002 'link': _link_to_if(anchor_link, change['new_lineno'],
1005 'link': _link_to_if(anchor_link, change['new_lineno'],
1003 '#%s' % anchor_new)
1006 '#%s' % anchor_new)
1004 })
1007 })
1005 _html.append('''</td>\n''')
1008 _html.append('''</td>\n''')
1006 ###########################################################
1009 ###########################################################
1007 # CODE
1010 # CODE
1008 ###########################################################
1011 ###########################################################
1009 code_classes = [code_class]
1012 code_classes = [code_class]
1010 if (not enable_comments or
1013 if (not enable_comments or
1011 change['action'] == Action.CONTEXT):
1014 change['action'] == Action.CONTEXT):
1012 code_classes.append('no-comment')
1015 code_classes.append('no-comment')
1013 _html.append('\t<td class="%s">' % ' '.join(code_classes))
1016 _html.append('\t<td class="%s">' % ' '.join(code_classes))
1014 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
1017 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
1015 'code': change['line']
1018 'code': change['line']
1016 })
1019 })
1017
1020
1018 _html.append('''\t</td>''')
1021 _html.append('''\t</td>''')
1019 _html.append('''\n</tr>\n''')
1022 _html.append('''\n</tr>\n''')
1020 _html.append('''</table>''')
1023 _html.append('''</table>''')
1021 if _html_empty:
1024 if _html_empty:
1022 return None
1025 return None
1023 return ''.join(_html)
1026 return ''.join(_html)
1024
1027
1025 def stat(self):
1028 def stat(self):
1026 """
1029 """
1027 Returns tuple of added, and removed lines for this instance
1030 Returns tuple of added, and removed lines for this instance
1028 """
1031 """
1029 return self.adds, self.removes
1032 return self.adds, self.removes
1030
1033
1031 def get_context_of_line(
1034 def get_context_of_line(
1032 self, path, diff_line=None, context_before=3, context_after=3):
1035 self, path, diff_line=None, context_before=3, context_after=3):
1033 """
1036 """
1034 Returns the context lines for the specified diff line.
1037 Returns the context lines for the specified diff line.
1035
1038
1036 :type diff_line: :class:`DiffLineNumber`
1039 :type diff_line: :class:`DiffLineNumber`
1037 """
1040 """
1038 assert self.parsed, "DiffProcessor is not initialized."
1041 assert self.parsed, "DiffProcessor is not initialized."
1039
1042
1040 if None not in diff_line:
1043 if None not in diff_line:
1041 raise ValueError(
1044 raise ValueError(
1042 "Cannot specify both line numbers: {}".format(diff_line))
1045 "Cannot specify both line numbers: {}".format(diff_line))
1043
1046
1044 file_diff = self._get_file_diff(path)
1047 file_diff = self._get_file_diff(path)
1045 chunk, idx = self._find_chunk_line_index(file_diff, diff_line)
1048 chunk, idx = self._find_chunk_line_index(file_diff, diff_line)
1046
1049
1047 first_line_to_include = max(idx - context_before, 0)
1050 first_line_to_include = max(idx - context_before, 0)
1048 first_line_after_context = idx + context_after + 1
1051 first_line_after_context = idx + context_after + 1
1049 context_lines = chunk[first_line_to_include:first_line_after_context]
1052 context_lines = chunk[first_line_to_include:first_line_after_context]
1050
1053
1051 line_contents = [
1054 line_contents = [
1052 _context_line(line) for line in context_lines
1055 _context_line(line) for line in context_lines
1053 if _is_diff_content(line)]
1056 if _is_diff_content(line)]
1054 # TODO: johbo: Interim fixup, the diff chunks drop the final newline.
1057 # TODO: johbo: Interim fixup, the diff chunks drop the final newline.
1055 # Once they are fixed, we can drop this line here.
1058 # Once they are fixed, we can drop this line here.
1056 if line_contents:
1059 if line_contents:
1057 line_contents[-1] = (
1060 line_contents[-1] = (
1058 line_contents[-1][0], line_contents[-1][1].rstrip('\n') + '\n')
1061 line_contents[-1][0], line_contents[-1][1].rstrip('\n') + '\n')
1059 return line_contents
1062 return line_contents
1060
1063
1061 def find_context(self, path, context, offset=0):
1064 def find_context(self, path, context, offset=0):
1062 """
1065 """
1063 Finds the given `context` inside of the diff.
1066 Finds the given `context` inside of the diff.
1064
1067
1065 Use the parameter `offset` to specify which offset the target line has
1068 Use the parameter `offset` to specify which offset the target line has
1066 inside of the given `context`. This way the correct diff line will be
1069 inside of the given `context`. This way the correct diff line will be
1067 returned.
1070 returned.
1068
1071
1069 :param offset: Shall be used to specify the offset of the main line
1072 :param offset: Shall be used to specify the offset of the main line
1070 within the given `context`.
1073 within the given `context`.
1071 """
1074 """
1072 if offset < 0 or offset >= len(context):
1075 if offset < 0 or offset >= len(context):
1073 raise ValueError(
1076 raise ValueError(
1074 "Only positive values up to the length of the context "
1077 "Only positive values up to the length of the context "
1075 "minus one are allowed.")
1078 "minus one are allowed.")
1076
1079
1077 matches = []
1080 matches = []
1078 file_diff = self._get_file_diff(path)
1081 file_diff = self._get_file_diff(path)
1079
1082
1080 for chunk in file_diff['chunks']:
1083 for chunk in file_diff['chunks']:
1081 context_iter = iter(context)
1084 context_iter = iter(context)
1082 for line_idx, line in enumerate(chunk):
1085 for line_idx, line in enumerate(chunk):
1083 try:
1086 try:
1084 if _context_line(line) == context_iter.next():
1087 if _context_line(line) == context_iter.next():
1085 continue
1088 continue
1086 except StopIteration:
1089 except StopIteration:
1087 matches.append((line_idx, chunk))
1090 matches.append((line_idx, chunk))
1088 context_iter = iter(context)
1091 context_iter = iter(context)
1089
1092
1090 # Increment position and triger StopIteration
1093 # Increment position and triger StopIteration
1091 # if we had a match at the end
1094 # if we had a match at the end
1092 line_idx += 1
1095 line_idx += 1
1093 try:
1096 try:
1094 context_iter.next()
1097 context_iter.next()
1095 except StopIteration:
1098 except StopIteration:
1096 matches.append((line_idx, chunk))
1099 matches.append((line_idx, chunk))
1097
1100
1098 effective_offset = len(context) - offset
1101 effective_offset = len(context) - offset
1099 found_at_diff_lines = [
1102 found_at_diff_lines = [
1100 _line_to_diff_line_number(chunk[idx - effective_offset])
1103 _line_to_diff_line_number(chunk[idx - effective_offset])
1101 for idx, chunk in matches]
1104 for idx, chunk in matches]
1102
1105
1103 return found_at_diff_lines
1106 return found_at_diff_lines
1104
1107
1105 def _get_file_diff(self, path):
1108 def _get_file_diff(self, path):
1106 for file_diff in self.parsed_diff:
1109 for file_diff in self.parsed_diff:
1107 if file_diff['filename'] == path:
1110 if file_diff['filename'] == path:
1108 break
1111 break
1109 else:
1112 else:
1110 raise FileNotInDiffException("File {} not in diff".format(path))
1113 raise FileNotInDiffException("File {} not in diff".format(path))
1111 return file_diff
1114 return file_diff
1112
1115
1113 def _find_chunk_line_index(self, file_diff, diff_line):
1116 def _find_chunk_line_index(self, file_diff, diff_line):
1114 for chunk in file_diff['chunks']:
1117 for chunk in file_diff['chunks']:
1115 for idx, line in enumerate(chunk):
1118 for idx, line in enumerate(chunk):
1116 if line['old_lineno'] == diff_line.old:
1119 if line['old_lineno'] == diff_line.old:
1117 return chunk, idx
1120 return chunk, idx
1118 if line['new_lineno'] == diff_line.new:
1121 if line['new_lineno'] == diff_line.new:
1119 return chunk, idx
1122 return chunk, idx
1120 raise LineNotInDiffException(
1123 raise LineNotInDiffException(
1121 "The line {} is not part of the diff.".format(diff_line))
1124 "The line {} is not part of the diff.".format(diff_line))
1122
1125
1123
1126
1124 def _is_diff_content(line):
1127 def _is_diff_content(line):
1125 return line['action'] in (
1128 return line['action'] in (
1126 Action.UNMODIFIED, Action.ADD, Action.DELETE)
1129 Action.UNMODIFIED, Action.ADD, Action.DELETE)
1127
1130
1128
1131
1129 def _context_line(line):
1132 def _context_line(line):
1130 return (line['action'], line['line'])
1133 return (line['action'], line['line'])
1131
1134
1132
1135
1133 DiffLineNumber = collections.namedtuple('DiffLineNumber', ['old', 'new'])
1136 DiffLineNumber = collections.namedtuple('DiffLineNumber', ['old', 'new'])
1134
1137
1135
1138
1136 def _line_to_diff_line_number(line):
1139 def _line_to_diff_line_number(line):
1137 new_line_no = line['new_lineno'] or None
1140 new_line_no = line['new_lineno'] or None
1138 old_line_no = line['old_lineno'] or None
1141 old_line_no = line['old_lineno'] or None
1139 return DiffLineNumber(old=old_line_no, new=new_line_no)
1142 return DiffLineNumber(old=old_line_no, new=new_line_no)
1140
1143
1141
1144
1142 class FileNotInDiffException(Exception):
1145 class FileNotInDiffException(Exception):
1143 """
1146 """
1144 Raised when the context for a missing file is requested.
1147 Raised when the context for a missing file is requested.
1145
1148
1146 If you request the context for a line in a file which is not part of the
1149 If you request the context for a line in a file which is not part of the
1147 given diff, then this exception is raised.
1150 given diff, then this exception is raised.
1148 """
1151 """
1149
1152
1150
1153
1151 class LineNotInDiffException(Exception):
1154 class LineNotInDiffException(Exception):
1152 """
1155 """
1153 Raised when the context for a missing line is requested.
1156 Raised when the context for a missing line is requested.
1154
1157
1155 If you request the context for a line in a file and this line is not
1158 If you request the context for a line in a file and this line is not
1156 part of the given diff, then this exception is raised.
1159 part of the given diff, then this exception is raised.
1157 """
1160 """
1158
1161
1159
1162
1160 class DiffLimitExceeded(Exception):
1163 class DiffLimitExceeded(Exception):
1161 pass
1164 pass
@@ -1,525 +1,530 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-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 comments model for RhodeCode
22 comments model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import collections
27 import collections
28
28
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pyramid.threadlocal import get_current_registry
32 from pyramid.threadlocal import get_current_registry
33 from sqlalchemy.sql.expression import null
33 from sqlalchemy.sql.expression import null
34 from sqlalchemy.sql.functions import coalesce
34 from sqlalchemy.sql.functions import coalesce
35
35
36 from rhodecode.lib import helpers as h, diffs
36 from rhodecode.lib import helpers as h, diffs
37 from rhodecode.lib.channelstream import channelstream_request
37 from rhodecode.lib.channelstream import channelstream_request
38 from rhodecode.lib.utils import action_logger
38 from rhodecode.lib.utils import action_logger
39 from rhodecode.lib.utils2 import extract_mentioned_users
39 from rhodecode.lib.utils2 import extract_mentioned_users
40 from rhodecode.model import BaseModel
40 from rhodecode.model import BaseModel
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 ChangesetComment, User, Notification, PullRequest)
42 ChangesetComment, User, Notification, PullRequest)
43 from rhodecode.model.notification import NotificationModel
43 from rhodecode.model.notification import NotificationModel
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.settings import VcsSettingsModel
45 from rhodecode.model.settings import VcsSettingsModel
46 from rhodecode.model.notification import EmailNotificationModel
46 from rhodecode.model.notification import EmailNotificationModel
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class ChangesetCommentsModel(BaseModel):
51 class ChangesetCommentsModel(BaseModel):
52
52
53 cls = ChangesetComment
53 cls = ChangesetComment
54
54
55 DIFF_CONTEXT_BEFORE = 3
55 DIFF_CONTEXT_BEFORE = 3
56 DIFF_CONTEXT_AFTER = 3
56 DIFF_CONTEXT_AFTER = 3
57
57
58 def __get_commit_comment(self, changeset_comment):
58 def __get_commit_comment(self, changeset_comment):
59 return self._get_instance(ChangesetComment, changeset_comment)
59 return self._get_instance(ChangesetComment, changeset_comment)
60
60
61 def __get_pull_request(self, pull_request):
61 def __get_pull_request(self, pull_request):
62 return self._get_instance(PullRequest, pull_request)
62 return self._get_instance(PullRequest, pull_request)
63
63
64 def _extract_mentions(self, s):
64 def _extract_mentions(self, s):
65 user_objects = []
65 user_objects = []
66 for username in extract_mentioned_users(s):
66 for username in extract_mentioned_users(s):
67 user_obj = User.get_by_username(username, case_insensitive=True)
67 user_obj = User.get_by_username(username, case_insensitive=True)
68 if user_obj:
68 if user_obj:
69 user_objects.append(user_obj)
69 user_objects.append(user_obj)
70 return user_objects
70 return user_objects
71
71
72 def _get_renderer(self, global_renderer='rst'):
72 def _get_renderer(self, global_renderer='rst'):
73 try:
73 try:
74 # try reading from visual context
74 # try reading from visual context
75 from pylons import tmpl_context
75 from pylons import tmpl_context
76 global_renderer = tmpl_context.visual.default_renderer
76 global_renderer = tmpl_context.visual.default_renderer
77 except AttributeError:
77 except AttributeError:
78 log.debug("Renderer not set, falling back "
78 log.debug("Renderer not set, falling back "
79 "to default renderer '%s'", global_renderer)
79 "to default renderer '%s'", global_renderer)
80 except Exception:
80 except Exception:
81 log.error(traceback.format_exc())
81 log.error(traceback.format_exc())
82 return global_renderer
82 return global_renderer
83
83
84 def create(self, text, repo, user, revision=None, pull_request=None,
84 def create(self, text, repo, user, revision=None, pull_request=None,
85 f_path=None, line_no=None, status_change=None,
85 f_path=None, line_no=None, status_change=None,
86 status_change_type=None, closing_pr=False,
86 status_change_type=None, closing_pr=False,
87 send_email=True, renderer=None):
87 send_email=True, renderer=None):
88 """
88 """
89 Creates new comment for commit or pull request.
89 Creates new comment for commit or pull request.
90 IF status_change is not none this comment is associated with a
90 IF status_change is not none this comment is associated with a
91 status change of commit or commit associated with pull request
91 status change of commit or commit associated with pull request
92
92
93 :param text:
93 :param text:
94 :param repo:
94 :param repo:
95 :param user:
95 :param user:
96 :param revision:
96 :param revision:
97 :param pull_request:
97 :param pull_request:
98 :param f_path:
98 :param f_path:
99 :param line_no:
99 :param line_no:
100 :param status_change: Label for status change
100 :param status_change: Label for status change
101 :param status_change_type: type of status change
101 :param status_change_type: type of status change
102 :param closing_pr:
102 :param closing_pr:
103 :param send_email:
103 :param send_email:
104 """
104 """
105 if not text:
105 if not text:
106 log.warning('Missing text for comment, skipping...')
106 log.warning('Missing text for comment, skipping...')
107 return
107 return
108
108
109 if not renderer:
109 if not renderer:
110 renderer = self._get_renderer()
110 renderer = self._get_renderer()
111
111
112 repo = self._get_repo(repo)
112 repo = self._get_repo(repo)
113 user = self._get_user(user)
113 user = self._get_user(user)
114 comment = ChangesetComment()
114 comment = ChangesetComment()
115 comment.renderer = renderer
115 comment.renderer = renderer
116 comment.repo = repo
116 comment.repo = repo
117 comment.author = user
117 comment.author = user
118 comment.text = text
118 comment.text = text
119 comment.f_path = f_path
119 comment.f_path = f_path
120 comment.line_no = line_no
120 comment.line_no = line_no
121
121
122 #TODO (marcink): fix this and remove revision as param
122 #TODO (marcink): fix this and remove revision as param
123 commit_id = revision
123 commit_id = revision
124 pull_request_id = pull_request
124 pull_request_id = pull_request
125
125
126 commit_obj = None
126 commit_obj = None
127 pull_request_obj = None
127 pull_request_obj = None
128
128
129 if commit_id:
129 if commit_id:
130 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
130 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
131 # do a lookup, so we don't pass something bad here
131 # do a lookup, so we don't pass something bad here
132 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
132 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
133 comment.revision = commit_obj.raw_id
133 comment.revision = commit_obj.raw_id
134
134
135 elif pull_request_id:
135 elif pull_request_id:
136 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
136 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
137 pull_request_obj = self.__get_pull_request(pull_request_id)
137 pull_request_obj = self.__get_pull_request(pull_request_id)
138 comment.pull_request = pull_request_obj
138 comment.pull_request = pull_request_obj
139 else:
139 else:
140 raise Exception('Please specify commit or pull_request_id')
140 raise Exception('Please specify commit or pull_request_id')
141
141
142 Session().add(comment)
142 Session().add(comment)
143 Session().flush()
143 Session().flush()
144 kwargs = {
144 kwargs = {
145 'user': user,
145 'user': user,
146 'renderer_type': renderer,
146 'renderer_type': renderer,
147 'repo_name': repo.repo_name,
147 'repo_name': repo.repo_name,
148 'status_change': status_change,
148 'status_change': status_change,
149 'status_change_type': status_change_type,
149 'status_change_type': status_change_type,
150 'comment_body': text,
150 'comment_body': text,
151 'comment_file': f_path,
151 'comment_file': f_path,
152 'comment_line': line_no,
152 'comment_line': line_no,
153 }
153 }
154
154
155 if commit_obj:
155 if commit_obj:
156 recipients = ChangesetComment.get_users(
156 recipients = ChangesetComment.get_users(
157 revision=commit_obj.raw_id)
157 revision=commit_obj.raw_id)
158 # add commit author if it's in RhodeCode system
158 # add commit author if it's in RhodeCode system
159 cs_author = User.get_from_cs_author(commit_obj.author)
159 cs_author = User.get_from_cs_author(commit_obj.author)
160 if not cs_author:
160 if not cs_author:
161 # use repo owner if we cannot extract the author correctly
161 # use repo owner if we cannot extract the author correctly
162 cs_author = repo.user
162 cs_author = repo.user
163 recipients += [cs_author]
163 recipients += [cs_author]
164
164
165 commit_comment_url = self.get_url(comment)
165 commit_comment_url = self.get_url(comment)
166
166
167 target_repo_url = h.link_to(
167 target_repo_url = h.link_to(
168 repo.repo_name,
168 repo.repo_name,
169 h.url('summary_home',
169 h.url('summary_home',
170 repo_name=repo.repo_name, qualified=True))
170 repo_name=repo.repo_name, qualified=True))
171
171
172 # commit specifics
172 # commit specifics
173 kwargs.update({
173 kwargs.update({
174 'commit': commit_obj,
174 'commit': commit_obj,
175 'commit_message': commit_obj.message,
175 'commit_message': commit_obj.message,
176 'commit_target_repo': target_repo_url,
176 'commit_target_repo': target_repo_url,
177 'commit_comment_url': commit_comment_url,
177 'commit_comment_url': commit_comment_url,
178 })
178 })
179
179
180 elif pull_request_obj:
180 elif pull_request_obj:
181 # get the current participants of this pull request
181 # get the current participants of this pull request
182 recipients = ChangesetComment.get_users(
182 recipients = ChangesetComment.get_users(
183 pull_request_id=pull_request_obj.pull_request_id)
183 pull_request_id=pull_request_obj.pull_request_id)
184 # add pull request author
184 # add pull request author
185 recipients += [pull_request_obj.author]
185 recipients += [pull_request_obj.author]
186
186
187 # add the reviewers to notification
187 # add the reviewers to notification
188 recipients += [x.user for x in pull_request_obj.reviewers]
188 recipients += [x.user for x in pull_request_obj.reviewers]
189
189
190 pr_target_repo = pull_request_obj.target_repo
190 pr_target_repo = pull_request_obj.target_repo
191 pr_source_repo = pull_request_obj.source_repo
191 pr_source_repo = pull_request_obj.source_repo
192
192
193 pr_comment_url = h.url(
193 pr_comment_url = h.url(
194 'pullrequest_show',
194 'pullrequest_show',
195 repo_name=pr_target_repo.repo_name,
195 repo_name=pr_target_repo.repo_name,
196 pull_request_id=pull_request_obj.pull_request_id,
196 pull_request_id=pull_request_obj.pull_request_id,
197 anchor='comment-%s' % comment.comment_id,
197 anchor='comment-%s' % comment.comment_id,
198 qualified=True,)
198 qualified=True,)
199
199
200 # set some variables for email notification
200 # set some variables for email notification
201 pr_target_repo_url = h.url(
201 pr_target_repo_url = h.url(
202 'summary_home', repo_name=pr_target_repo.repo_name,
202 'summary_home', repo_name=pr_target_repo.repo_name,
203 qualified=True)
203 qualified=True)
204
204
205 pr_source_repo_url = h.url(
205 pr_source_repo_url = h.url(
206 'summary_home', repo_name=pr_source_repo.repo_name,
206 'summary_home', repo_name=pr_source_repo.repo_name,
207 qualified=True)
207 qualified=True)
208
208
209 # pull request specifics
209 # pull request specifics
210 kwargs.update({
210 kwargs.update({
211 'pull_request': pull_request_obj,
211 'pull_request': pull_request_obj,
212 'pr_id': pull_request_obj.pull_request_id,
212 'pr_id': pull_request_obj.pull_request_id,
213 'pr_target_repo': pr_target_repo,
213 'pr_target_repo': pr_target_repo,
214 'pr_target_repo_url': pr_target_repo_url,
214 'pr_target_repo_url': pr_target_repo_url,
215 'pr_source_repo': pr_source_repo,
215 'pr_source_repo': pr_source_repo,
216 'pr_source_repo_url': pr_source_repo_url,
216 'pr_source_repo_url': pr_source_repo_url,
217 'pr_comment_url': pr_comment_url,
217 'pr_comment_url': pr_comment_url,
218 'pr_closing': closing_pr,
218 'pr_closing': closing_pr,
219 })
219 })
220 if send_email:
220 if send_email:
221 # pre-generate the subject for notification itself
221 # pre-generate the subject for notification itself
222 (subject,
222 (subject,
223 _h, _e, # we don't care about those
223 _h, _e, # we don't care about those
224 body_plaintext) = EmailNotificationModel().render_email(
224 body_plaintext) = EmailNotificationModel().render_email(
225 notification_type, **kwargs)
225 notification_type, **kwargs)
226
226
227 mention_recipients = set(
227 mention_recipients = set(
228 self._extract_mentions(text)).difference(recipients)
228 self._extract_mentions(text)).difference(recipients)
229
229
230 # create notification objects, and emails
230 # create notification objects, and emails
231 NotificationModel().create(
231 NotificationModel().create(
232 created_by=user,
232 created_by=user,
233 notification_subject=subject,
233 notification_subject=subject,
234 notification_body=body_plaintext,
234 notification_body=body_plaintext,
235 notification_type=notification_type,
235 notification_type=notification_type,
236 recipients=recipients,
236 recipients=recipients,
237 mention_recipients=mention_recipients,
237 mention_recipients=mention_recipients,
238 email_kwargs=kwargs,
238 email_kwargs=kwargs,
239 )
239 )
240
240
241 action = (
241 action = (
242 'user_commented_pull_request:{}'.format(
242 'user_commented_pull_request:{}'.format(
243 comment.pull_request.pull_request_id)
243 comment.pull_request.pull_request_id)
244 if comment.pull_request
244 if comment.pull_request
245 else 'user_commented_revision:{}'.format(comment.revision)
245 else 'user_commented_revision:{}'.format(comment.revision)
246 )
246 )
247 action_logger(user, action, comment.repo)
247 action_logger(user, action, comment.repo)
248
248
249 registry = get_current_registry()
249 registry = get_current_registry()
250 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
250 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
251 channelstream_config = rhodecode_plugins.get('channelstream', {})
251 channelstream_config = rhodecode_plugins.get('channelstream', {})
252 msg_url = ''
252 msg_url = ''
253 if commit_obj:
253 if commit_obj:
254 msg_url = commit_comment_url
254 msg_url = commit_comment_url
255 repo_name = repo.repo_name
255 repo_name = repo.repo_name
256 elif pull_request_obj:
256 elif pull_request_obj:
257 msg_url = pr_comment_url
257 msg_url = pr_comment_url
258 repo_name = pr_target_repo.repo_name
258 repo_name = pr_target_repo.repo_name
259
259
260 if channelstream_config.get('enabled'):
260 if channelstream_config.get('enabled'):
261 message = '<strong>{}</strong> {} - ' \
261 message = '<strong>{}</strong> {} - ' \
262 '<a onclick="window.location=\'{}\';' \
262 '<a onclick="window.location=\'{}\';' \
263 'window.location.reload()">' \
263 'window.location.reload()">' \
264 '<strong>{}</strong></a>'
264 '<strong>{}</strong></a>'
265 message = message.format(
265 message = message.format(
266 user.username, _('made a comment'), msg_url,
266 user.username, _('made a comment'), msg_url,
267 _('Show it now'))
267 _('Show it now'))
268 channel = '/repo${}$/pr/{}'.format(
268 channel = '/repo${}$/pr/{}'.format(
269 repo_name,
269 repo_name,
270 pull_request_id
270 pull_request_id
271 )
271 )
272 payload = {
272 payload = {
273 'type': 'message',
273 'type': 'message',
274 'timestamp': datetime.utcnow(),
274 'timestamp': datetime.utcnow(),
275 'user': 'system',
275 'user': 'system',
276 'exclude_users': [user.username],
276 'exclude_users': [user.username],
277 'channel': channel,
277 'channel': channel,
278 'message': {
278 'message': {
279 'message': message,
279 'message': message,
280 'level': 'info',
280 'level': 'info',
281 'topic': '/notifications'
281 'topic': '/notifications'
282 }
282 }
283 }
283 }
284 channelstream_request(channelstream_config, [payload],
284 channelstream_request(channelstream_config, [payload],
285 '/message', raise_exc=False)
285 '/message', raise_exc=False)
286
286
287 return comment
287 return comment
288
288
289 def delete(self, comment):
289 def delete(self, comment):
290 """
290 """
291 Deletes given comment
291 Deletes given comment
292
292
293 :param comment_id:
293 :param comment_id:
294 """
294 """
295 comment = self.__get_commit_comment(comment)
295 comment = self.__get_commit_comment(comment)
296 Session().delete(comment)
296 Session().delete(comment)
297
297
298 return comment
298 return comment
299
299
300 def get_all_comments(self, repo_id, revision=None, pull_request=None):
300 def get_all_comments(self, repo_id, revision=None, pull_request=None):
301 q = ChangesetComment.query()\
301 q = ChangesetComment.query()\
302 .filter(ChangesetComment.repo_id == repo_id)
302 .filter(ChangesetComment.repo_id == repo_id)
303 if revision:
303 if revision:
304 q = q.filter(ChangesetComment.revision == revision)
304 q = q.filter(ChangesetComment.revision == revision)
305 elif pull_request:
305 elif pull_request:
306 pull_request = self.__get_pull_request(pull_request)
306 pull_request = self.__get_pull_request(pull_request)
307 q = q.filter(ChangesetComment.pull_request == pull_request)
307 q = q.filter(ChangesetComment.pull_request == pull_request)
308 else:
308 else:
309 raise Exception('Please specify commit or pull_request')
309 raise Exception('Please specify commit or pull_request')
310 q = q.order_by(ChangesetComment.created_on)
310 q = q.order_by(ChangesetComment.created_on)
311 return q.all()
311 return q.all()
312
312
313 def get_url(self, comment):
313 def get_url(self, comment):
314 comment = self.__get_commit_comment(comment)
314 comment = self.__get_commit_comment(comment)
315 if comment.pull_request:
315 if comment.pull_request:
316 return h.url(
316 return h.url(
317 'pullrequest_show',
317 'pullrequest_show',
318 repo_name=comment.pull_request.target_repo.repo_name,
318 repo_name=comment.pull_request.target_repo.repo_name,
319 pull_request_id=comment.pull_request.pull_request_id,
319 pull_request_id=comment.pull_request.pull_request_id,
320 anchor='comment-%s' % comment.comment_id,
320 anchor='comment-%s' % comment.comment_id,
321 qualified=True,)
321 qualified=True,)
322 else:
322 else:
323 return h.url(
323 return h.url(
324 'changeset_home',
324 'changeset_home',
325 repo_name=comment.repo.repo_name,
325 repo_name=comment.repo.repo_name,
326 revision=comment.revision,
326 revision=comment.revision,
327 anchor='comment-%s' % comment.comment_id,
327 anchor='comment-%s' % comment.comment_id,
328 qualified=True,)
328 qualified=True,)
329
329
330 def get_comments(self, repo_id, revision=None, pull_request=None):
330 def get_comments(self, repo_id, revision=None, pull_request=None):
331 """
331 """
332 Gets main comments based on revision or pull_request_id
332 Gets main comments based on revision or pull_request_id
333
333
334 :param repo_id:
334 :param repo_id:
335 :param revision:
335 :param revision:
336 :param pull_request:
336 :param pull_request:
337 """
337 """
338
338
339 q = ChangesetComment.query()\
339 q = ChangesetComment.query()\
340 .filter(ChangesetComment.repo_id == repo_id)\
340 .filter(ChangesetComment.repo_id == repo_id)\
341 .filter(ChangesetComment.line_no == None)\
341 .filter(ChangesetComment.line_no == None)\
342 .filter(ChangesetComment.f_path == None)
342 .filter(ChangesetComment.f_path == None)
343 if revision:
343 if revision:
344 q = q.filter(ChangesetComment.revision == revision)
344 q = q.filter(ChangesetComment.revision == revision)
345 elif pull_request:
345 elif pull_request:
346 pull_request = self.__get_pull_request(pull_request)
346 pull_request = self.__get_pull_request(pull_request)
347 q = q.filter(ChangesetComment.pull_request == pull_request)
347 q = q.filter(ChangesetComment.pull_request == pull_request)
348 else:
348 else:
349 raise Exception('Please specify commit or pull_request')
349 raise Exception('Please specify commit or pull_request')
350 q = q.order_by(ChangesetComment.created_on)
350 q = q.order_by(ChangesetComment.created_on)
351 return q.all()
351 return q.all()
352
352
353 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
353 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
354 q = self._get_inline_comments_query(repo_id, revision, pull_request)
354 q = self._get_inline_comments_query(repo_id, revision, pull_request)
355 return self._group_comments_by_path_and_line_number(q)
355 return self._group_comments_by_path_and_line_number(q)
356
356
357 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
357 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
358 version=None):
358 version=None, include_aggregates=False):
359 version_aggregates = collections.defaultdict(list)
359 inline_cnt = 0
360 inline_cnt = 0
360 for fname, per_line_comments in inline_comments.iteritems():
361 for fname, per_line_comments in inline_comments.iteritems():
361 for lno, comments in per_line_comments.iteritems():
362 for lno, comments in per_line_comments.iteritems():
362 inline_cnt += len(
363 for comm in comments:
363 [comm for comm in comments
364 version_aggregates[comm.pull_request_version_id].append(comm)
364 if (not comm.outdated_at_version(version) and skip_outdated)])
365 if not comm.outdated_at_version(version) and skip_outdated:
366 inline_cnt += 1
367
368 if include_aggregates:
369 return inline_cnt, version_aggregates
365 return inline_cnt
370 return inline_cnt
366
371
367 def get_outdated_comments(self, repo_id, pull_request):
372 def get_outdated_comments(self, repo_id, pull_request):
368 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
373 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
369 # of a pull request.
374 # of a pull request.
370 q = self._all_inline_comments_of_pull_request(pull_request)
375 q = self._all_inline_comments_of_pull_request(pull_request)
371 q = q.filter(
376 q = q.filter(
372 ChangesetComment.display_state ==
377 ChangesetComment.display_state ==
373 ChangesetComment.COMMENT_OUTDATED
378 ChangesetComment.COMMENT_OUTDATED
374 ).order_by(ChangesetComment.comment_id.asc())
379 ).order_by(ChangesetComment.comment_id.asc())
375
380
376 return self._group_comments_by_path_and_line_number(q)
381 return self._group_comments_by_path_and_line_number(q)
377
382
378 def _get_inline_comments_query(self, repo_id, revision, pull_request):
383 def _get_inline_comments_query(self, repo_id, revision, pull_request):
379 # TODO: johbo: Split this into two methods: One for PR and one for
384 # TODO: johbo: Split this into two methods: One for PR and one for
380 # commit.
385 # commit.
381 if revision:
386 if revision:
382 q = Session().query(ChangesetComment).filter(
387 q = Session().query(ChangesetComment).filter(
383 ChangesetComment.repo_id == repo_id,
388 ChangesetComment.repo_id == repo_id,
384 ChangesetComment.line_no != null(),
389 ChangesetComment.line_no != null(),
385 ChangesetComment.f_path != null(),
390 ChangesetComment.f_path != null(),
386 ChangesetComment.revision == revision)
391 ChangesetComment.revision == revision)
387
392
388 elif pull_request:
393 elif pull_request:
389 pull_request = self.__get_pull_request(pull_request)
394 pull_request = self.__get_pull_request(pull_request)
390 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
395 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
391 q = self._visible_inline_comments_of_pull_request(pull_request)
396 q = self._visible_inline_comments_of_pull_request(pull_request)
392 else:
397 else:
393 q = self._all_inline_comments_of_pull_request(pull_request)
398 q = self._all_inline_comments_of_pull_request(pull_request)
394
399
395 else:
400 else:
396 raise Exception('Please specify commit or pull_request_id')
401 raise Exception('Please specify commit or pull_request_id')
397 q = q.order_by(ChangesetComment.comment_id.asc())
402 q = q.order_by(ChangesetComment.comment_id.asc())
398 return q
403 return q
399
404
400 def _group_comments_by_path_and_line_number(self, q):
405 def _group_comments_by_path_and_line_number(self, q):
401 comments = q.all()
406 comments = q.all()
402 paths = collections.defaultdict(lambda: collections.defaultdict(list))
407 paths = collections.defaultdict(lambda: collections.defaultdict(list))
403 for co in comments:
408 for co in comments:
404 paths[co.f_path][co.line_no].append(co)
409 paths[co.f_path][co.line_no].append(co)
405 return paths
410 return paths
406
411
407 @classmethod
412 @classmethod
408 def needed_extra_diff_context(cls):
413 def needed_extra_diff_context(cls):
409 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
414 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
410
415
411 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
416 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
412 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
417 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
413 return
418 return
414
419
415 comments = self._visible_inline_comments_of_pull_request(pull_request)
420 comments = self._visible_inline_comments_of_pull_request(pull_request)
416 comments_to_outdate = comments.all()
421 comments_to_outdate = comments.all()
417
422
418 for comment in comments_to_outdate:
423 for comment in comments_to_outdate:
419 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
424 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
420
425
421 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
426 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
422 diff_line = _parse_comment_line_number(comment.line_no)
427 diff_line = _parse_comment_line_number(comment.line_no)
423
428
424 try:
429 try:
425 old_context = old_diff_proc.get_context_of_line(
430 old_context = old_diff_proc.get_context_of_line(
426 path=comment.f_path, diff_line=diff_line)
431 path=comment.f_path, diff_line=diff_line)
427 new_context = new_diff_proc.get_context_of_line(
432 new_context = new_diff_proc.get_context_of_line(
428 path=comment.f_path, diff_line=diff_line)
433 path=comment.f_path, diff_line=diff_line)
429 except (diffs.LineNotInDiffException,
434 except (diffs.LineNotInDiffException,
430 diffs.FileNotInDiffException):
435 diffs.FileNotInDiffException):
431 comment.display_state = ChangesetComment.COMMENT_OUTDATED
436 comment.display_state = ChangesetComment.COMMENT_OUTDATED
432 return
437 return
433
438
434 if old_context == new_context:
439 if old_context == new_context:
435 return
440 return
436
441
437 if self._should_relocate_diff_line(diff_line):
442 if self._should_relocate_diff_line(diff_line):
438 new_diff_lines = new_diff_proc.find_context(
443 new_diff_lines = new_diff_proc.find_context(
439 path=comment.f_path, context=old_context,
444 path=comment.f_path, context=old_context,
440 offset=self.DIFF_CONTEXT_BEFORE)
445 offset=self.DIFF_CONTEXT_BEFORE)
441 if not new_diff_lines:
446 if not new_diff_lines:
442 comment.display_state = ChangesetComment.COMMENT_OUTDATED
447 comment.display_state = ChangesetComment.COMMENT_OUTDATED
443 else:
448 else:
444 new_diff_line = self._choose_closest_diff_line(
449 new_diff_line = self._choose_closest_diff_line(
445 diff_line, new_diff_lines)
450 diff_line, new_diff_lines)
446 comment.line_no = _diff_to_comment_line_number(new_diff_line)
451 comment.line_no = _diff_to_comment_line_number(new_diff_line)
447 else:
452 else:
448 comment.display_state = ChangesetComment.COMMENT_OUTDATED
453 comment.display_state = ChangesetComment.COMMENT_OUTDATED
449
454
450 def _should_relocate_diff_line(self, diff_line):
455 def _should_relocate_diff_line(self, diff_line):
451 """
456 """
452 Checks if relocation shall be tried for the given `diff_line`.
457 Checks if relocation shall be tried for the given `diff_line`.
453
458
454 If a comment points into the first lines, then we can have a situation
459 If a comment points into the first lines, then we can have a situation
455 that after an update another line has been added on top. In this case
460 that after an update another line has been added on top. In this case
456 we would find the context still and move the comment around. This
461 we would find the context still and move the comment around. This
457 would be wrong.
462 would be wrong.
458 """
463 """
459 should_relocate = (
464 should_relocate = (
460 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
465 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
461 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
466 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
462 return should_relocate
467 return should_relocate
463
468
464 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
469 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
465 candidate = new_diff_lines[0]
470 candidate = new_diff_lines[0]
466 best_delta = _diff_line_delta(diff_line, candidate)
471 best_delta = _diff_line_delta(diff_line, candidate)
467 for new_diff_line in new_diff_lines[1:]:
472 for new_diff_line in new_diff_lines[1:]:
468 delta = _diff_line_delta(diff_line, new_diff_line)
473 delta = _diff_line_delta(diff_line, new_diff_line)
469 if delta < best_delta:
474 if delta < best_delta:
470 candidate = new_diff_line
475 candidate = new_diff_line
471 best_delta = delta
476 best_delta = delta
472 return candidate
477 return candidate
473
478
474 def _visible_inline_comments_of_pull_request(self, pull_request):
479 def _visible_inline_comments_of_pull_request(self, pull_request):
475 comments = self._all_inline_comments_of_pull_request(pull_request)
480 comments = self._all_inline_comments_of_pull_request(pull_request)
476 comments = comments.filter(
481 comments = comments.filter(
477 coalesce(ChangesetComment.display_state, '') !=
482 coalesce(ChangesetComment.display_state, '') !=
478 ChangesetComment.COMMENT_OUTDATED)
483 ChangesetComment.COMMENT_OUTDATED)
479 return comments
484 return comments
480
485
481 def _all_inline_comments_of_pull_request(self, pull_request):
486 def _all_inline_comments_of_pull_request(self, pull_request):
482 comments = Session().query(ChangesetComment)\
487 comments = Session().query(ChangesetComment)\
483 .filter(ChangesetComment.line_no != None)\
488 .filter(ChangesetComment.line_no != None)\
484 .filter(ChangesetComment.f_path != None)\
489 .filter(ChangesetComment.f_path != None)\
485 .filter(ChangesetComment.pull_request == pull_request)
490 .filter(ChangesetComment.pull_request == pull_request)
486 return comments
491 return comments
487
492
488 @staticmethod
493 @staticmethod
489 def use_outdated_comments(pull_request):
494 def use_outdated_comments(pull_request):
490 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
495 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
491 settings = settings_model.get_general_settings()
496 settings = settings_model.get_general_settings()
492 return settings.get('rhodecode_use_outdated_comments', False)
497 return settings.get('rhodecode_use_outdated_comments', False)
493
498
494
499
495 def _parse_comment_line_number(line_no):
500 def _parse_comment_line_number(line_no):
496 """
501 """
497 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
502 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
498 """
503 """
499 old_line = None
504 old_line = None
500 new_line = None
505 new_line = None
501 if line_no.startswith('o'):
506 if line_no.startswith('o'):
502 old_line = int(line_no[1:])
507 old_line = int(line_no[1:])
503 elif line_no.startswith('n'):
508 elif line_no.startswith('n'):
504 new_line = int(line_no[1:])
509 new_line = int(line_no[1:])
505 else:
510 else:
506 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
511 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
507 return diffs.DiffLineNumber(old_line, new_line)
512 return diffs.DiffLineNumber(old_line, new_line)
508
513
509
514
510 def _diff_to_comment_line_number(diff_line):
515 def _diff_to_comment_line_number(diff_line):
511 if diff_line.new is not None:
516 if diff_line.new is not None:
512 return u'n{}'.format(diff_line.new)
517 return u'n{}'.format(diff_line.new)
513 elif diff_line.old is not None:
518 elif diff_line.old is not None:
514 return u'o{}'.format(diff_line.old)
519 return u'o{}'.format(diff_line.old)
515 return u''
520 return u''
516
521
517
522
518 def _diff_line_delta(a, b):
523 def _diff_line_delta(a, b):
519 if None not in (a.new, b.new):
524 if None not in (a.new, b.new):
520 return abs(a.new - b.new)
525 return abs(a.new - b.new)
521 elif None not in (a.old, b.old):
526 elif None not in (a.old, b.old):
522 return abs(a.old - b.old)
527 return abs(a.old - b.old)
523 else:
528 else:
524 raise ValueError(
529 raise ValueError(
525 "Cannot compute delta between {} and {}".format(a, b))
530 "Cannot compute delta between {} and {}".format(a, b))
@@ -1,1187 +1,1199 b''
1 // Default styles
1 // Default styles
2
2
3 .diff-collapse {
3 .diff-collapse {
4 margin: @padding 0;
4 margin: @padding 0;
5 text-align: right;
5 text-align: right;
6 }
6 }
7
7
8 .diff-container {
8 .diff-container {
9 margin-bottom: @space;
9 margin-bottom: @space;
10
10
11 .diffblock {
11 .diffblock {
12 margin-bottom: @space;
12 margin-bottom: @space;
13 }
13 }
14
14
15 &.hidden {
15 &.hidden {
16 display: none;
16 display: none;
17 overflow: hidden;
17 overflow: hidden;
18 }
18 }
19 }
19 }
20
20
21 .compare_view_files {
21 .compare_view_files {
22
22
23 .diff-container {
23 .diff-container {
24
24
25 .diffblock {
25 .diffblock {
26 margin-bottom: 0;
26 margin-bottom: 0;
27 }
27 }
28 }
28 }
29 }
29 }
30
30
31 div.diffblock .sidebyside {
31 div.diffblock .sidebyside {
32 background: #ffffff;
32 background: #ffffff;
33 }
33 }
34
34
35 div.diffblock {
35 div.diffblock {
36 overflow-x: auto;
36 overflow-x: auto;
37 overflow-y: hidden;
37 overflow-y: hidden;
38 clear: both;
38 clear: both;
39 padding: 0px;
39 padding: 0px;
40 background: @grey6;
40 background: @grey6;
41 border: @border-thickness solid @grey5;
41 border: @border-thickness solid @grey5;
42 -webkit-border-radius: @border-radius @border-radius 0px 0px;
42 -webkit-border-radius: @border-radius @border-radius 0px 0px;
43 border-radius: @border-radius @border-radius 0px 0px;
43 border-radius: @border-radius @border-radius 0px 0px;
44
44
45
45
46 .comments-number {
46 .comments-number {
47 float: right;
47 float: right;
48 }
48 }
49
49
50 // BEGIN CODE-HEADER STYLES
50 // BEGIN CODE-HEADER STYLES
51
51
52 .code-header {
52 .code-header {
53 background: @grey6;
53 background: @grey6;
54 padding: 10px 0 10px 0;
54 padding: 10px 0 10px 0;
55 height: auto;
55 height: auto;
56 width: 100%;
56 width: 100%;
57
57
58 .hash {
58 .hash {
59 float: left;
59 float: left;
60 padding: 2px 0 0 2px;
60 padding: 2px 0 0 2px;
61 }
61 }
62
62
63 .date {
63 .date {
64 float: left;
64 float: left;
65 text-transform: uppercase;
65 text-transform: uppercase;
66 padding: 4px 0px 0px 2px;
66 padding: 4px 0px 0px 2px;
67 }
67 }
68
68
69 div {
69 div {
70 margin-left: 4px;
70 margin-left: 4px;
71 }
71 }
72
72
73 div.compare_header {
73 div.compare_header {
74 min-height: 40px;
74 min-height: 40px;
75 margin: 0;
75 margin: 0;
76 padding: 0 @padding;
76 padding: 0 @padding;
77
77
78 .drop-menu {
78 .drop-menu {
79 float:left;
79 float:left;
80 display: block;
80 display: block;
81 margin:0 0 @padding 0;
81 margin:0 0 @padding 0;
82 }
82 }
83
83
84 .compare-label {
84 .compare-label {
85 float: left;
85 float: left;
86 clear: both;
86 clear: both;
87 display: inline-block;
87 display: inline-block;
88 min-width: 5em;
88 min-width: 5em;
89 margin: 0;
89 margin: 0;
90 padding: @button-padding @button-padding @button-padding 0;
90 padding: @button-padding @button-padding @button-padding 0;
91 font-family: @text-semibold;
91 font-family: @text-semibold;
92 }
92 }
93
93
94 .compare-buttons {
94 .compare-buttons {
95 float: left;
95 float: left;
96 margin: 0;
96 margin: 0;
97 padding: 0 0 @padding;
97 padding: 0 0 @padding;
98
98
99 .btn {
99 .btn {
100 margin: 0 @padding 0 0;
100 margin: 0 @padding 0 0;
101 }
101 }
102 }
102 }
103 }
103 }
104
104
105 }
105 }
106
106
107 .parents {
107 .parents {
108 float: left;
108 float: left;
109 width: 100px;
109 width: 100px;
110 font-weight: 400;
110 font-weight: 400;
111 vertical-align: middle;
111 vertical-align: middle;
112 padding: 0px 2px 0px 2px;
112 padding: 0px 2px 0px 2px;
113 background-color: @grey6;
113 background-color: @grey6;
114
114
115 #parent_link {
115 #parent_link {
116 margin: 00px 2px;
116 margin: 00px 2px;
117
117
118 &.double {
118 &.double {
119 margin: 0px 2px;
119 margin: 0px 2px;
120 }
120 }
121
121
122 &.disabled{
122 &.disabled{
123 margin-right: @padding;
123 margin-right: @padding;
124 }
124 }
125 }
125 }
126 }
126 }
127
127
128 .children {
128 .children {
129 float: right;
129 float: right;
130 width: 100px;
130 width: 100px;
131 font-weight: 400;
131 font-weight: 400;
132 vertical-align: middle;
132 vertical-align: middle;
133 text-align: right;
133 text-align: right;
134 padding: 0px 2px 0px 2px;
134 padding: 0px 2px 0px 2px;
135 background-color: @grey6;
135 background-color: @grey6;
136
136
137 #child_link {
137 #child_link {
138 margin: 0px 2px;
138 margin: 0px 2px;
139
139
140 &.double {
140 &.double {
141 margin: 0px 2px;
141 margin: 0px 2px;
142 }
142 }
143
143
144 &.disabled{
144 &.disabled{
145 margin-right: @padding;
145 margin-right: @padding;
146 }
146 }
147 }
147 }
148 }
148 }
149
149
150 .changeset_header {
150 .changeset_header {
151 height: 16px;
151 height: 16px;
152
152
153 & > div{
153 & > div{
154 margin-right: @padding;
154 margin-right: @padding;
155 }
155 }
156 }
156 }
157
157
158 .changeset_file {
158 .changeset_file {
159 text-align: left;
159 text-align: left;
160 float: left;
160 float: left;
161 padding: 0;
161 padding: 0;
162
162
163 a{
163 a{
164 display: inline-block;
164 display: inline-block;
165 margin-right: 0.5em;
165 margin-right: 0.5em;
166 }
166 }
167
167
168 #selected_mode{
168 #selected_mode{
169 margin-left: 0;
169 margin-left: 0;
170 }
170 }
171 }
171 }
172
172
173 .diff-menu-wrapper {
173 .diff-menu-wrapper {
174 float: left;
174 float: left;
175 }
175 }
176
176
177 .diff-menu {
177 .diff-menu {
178 position: absolute;
178 position: absolute;
179 background: none repeat scroll 0 0 #FFFFFF;
179 background: none repeat scroll 0 0 #FFFFFF;
180 border-color: #003367 @grey3 @grey3;
180 border-color: #003367 @grey3 @grey3;
181 border-right: 1px solid @grey3;
181 border-right: 1px solid @grey3;
182 border-style: solid solid solid;
182 border-style: solid solid solid;
183 border-width: @border-thickness;
183 border-width: @border-thickness;
184 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
184 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
185 margin-top: 5px;
185 margin-top: 5px;
186 margin-left: 1px;
186 margin-left: 1px;
187 }
187 }
188
188
189 .diff-actions, .editor-actions {
189 .diff-actions, .editor-actions {
190 float: left;
190 float: left;
191
191
192 input{
192 input{
193 margin: 0 0.5em 0 0;
193 margin: 0 0.5em 0 0;
194 }
194 }
195 }
195 }
196
196
197 // END CODE-HEADER STYLES
197 // END CODE-HEADER STYLES
198
198
199 // BEGIN CODE-BODY STYLES
199 // BEGIN CODE-BODY STYLES
200
200
201 .code-body {
201 .code-body {
202 background: white;
202 background: white;
203 padding: 0;
203 padding: 0;
204 background-color: #ffffff;
204 background-color: #ffffff;
205 position: relative;
205 position: relative;
206 max-width: none;
206 max-width: none;
207 box-sizing: border-box;
207 box-sizing: border-box;
208 // TODO: johbo: Parent has overflow: auto, this forces the child here
208 // TODO: johbo: Parent has overflow: auto, this forces the child here
209 // to have the intended size and to scroll. Should be simplified.
209 // to have the intended size and to scroll. Should be simplified.
210 width: 100%;
210 width: 100%;
211 overflow-x: auto;
211 overflow-x: auto;
212 }
212 }
213
213
214 pre.raw {
214 pre.raw {
215 background: white;
215 background: white;
216 color: @grey1;
216 color: @grey1;
217 }
217 }
218 // END CODE-BODY STYLES
218 // END CODE-BODY STYLES
219
219
220 }
220 }
221
221
222
222
223 table.code-difftable {
223 table.code-difftable {
224 border-collapse: collapse;
224 border-collapse: collapse;
225 width: 99%;
225 width: 99%;
226 border-radius: 0px !important;
226 border-radius: 0px !important;
227
227
228 td {
228 td {
229 padding: 0 !important;
229 padding: 0 !important;
230 background: none !important;
230 background: none !important;
231 border: 0 !important;
231 border: 0 !important;
232 }
232 }
233
233
234 .context {
234 .context {
235 background: none repeat scroll 0 0 #DDE7EF;
235 background: none repeat scroll 0 0 #DDE7EF;
236 }
236 }
237
237
238 .add {
238 .add {
239 background: none repeat scroll 0 0 #DDFFDD;
239 background: none repeat scroll 0 0 #DDFFDD;
240
240
241 ins {
241 ins {
242 background: none repeat scroll 0 0 #AAFFAA;
242 background: none repeat scroll 0 0 #AAFFAA;
243 text-decoration: none;
243 text-decoration: none;
244 }
244 }
245 }
245 }
246
246
247 .del {
247 .del {
248 background: none repeat scroll 0 0 #FFDDDD;
248 background: none repeat scroll 0 0 #FFDDDD;
249
249
250 del {
250 del {
251 background: none repeat scroll 0 0 #FFAAAA;
251 background: none repeat scroll 0 0 #FFAAAA;
252 text-decoration: none;
252 text-decoration: none;
253 }
253 }
254 }
254 }
255
255
256 /** LINE NUMBERS **/
256 /** LINE NUMBERS **/
257 .lineno {
257 .lineno {
258 padding-left: 2px !important;
258 padding-left: 2px !important;
259 padding-right: 2px;
259 padding-right: 2px;
260 text-align: right;
260 text-align: right;
261 width: 32px;
261 width: 32px;
262 -moz-user-select: none;
262 -moz-user-select: none;
263 -webkit-user-select: none;
263 -webkit-user-select: none;
264 border-right: @border-thickness solid @grey5 !important;
264 border-right: @border-thickness solid @grey5 !important;
265 border-left: 0px solid #CCC !important;
265 border-left: 0px solid #CCC !important;
266 border-top: 0px solid #CCC !important;
266 border-top: 0px solid #CCC !important;
267 border-bottom: none !important;
267 border-bottom: none !important;
268
268
269 a {
269 a {
270 &:extend(pre);
270 &:extend(pre);
271 text-align: right;
271 text-align: right;
272 padding-right: 2px;
272 padding-right: 2px;
273 cursor: pointer;
273 cursor: pointer;
274 display: block;
274 display: block;
275 width: 32px;
275 width: 32px;
276 }
276 }
277 }
277 }
278
278
279 .context {
279 .context {
280 cursor: auto;
280 cursor: auto;
281 &:extend(pre);
281 &:extend(pre);
282 }
282 }
283
283
284 .lineno-inline {
284 .lineno-inline {
285 background: none repeat scroll 0 0 #FFF !important;
285 background: none repeat scroll 0 0 #FFF !important;
286 padding-left: 2px;
286 padding-left: 2px;
287 padding-right: 2px;
287 padding-right: 2px;
288 text-align: right;
288 text-align: right;
289 width: 30px;
289 width: 30px;
290 -moz-user-select: none;
290 -moz-user-select: none;
291 -webkit-user-select: none;
291 -webkit-user-select: none;
292 }
292 }
293
293
294 /** CODE **/
294 /** CODE **/
295 .code {
295 .code {
296 display: block;
296 display: block;
297 width: 100%;
297 width: 100%;
298
298
299 td {
299 td {
300 margin: 0;
300 margin: 0;
301 padding: 0;
301 padding: 0;
302 }
302 }
303
303
304 pre {
304 pre {
305 margin: 0;
305 margin: 0;
306 padding: 0;
306 padding: 0;
307 margin-left: .5em;
307 margin-left: .5em;
308 }
308 }
309 }
309 }
310 }
310 }
311
311
312
312
313 // Comments
313 // Comments
314
314
315 div.comment:target {
315 div.comment:target {
316 border-left: 6px solid @comment-highlight-color !important;
316 border-left: 6px solid @comment-highlight-color !important;
317 padding-left: 3px;
317 padding-left: 3px;
318 margin-left: -9px;
318 margin-left: -9px;
319 }
319 }
320
320
321 //TODO: anderson: can't get an absolute number out of anything, so had to put the
321 //TODO: anderson: can't get an absolute number out of anything, so had to put the
322 //current values that might change. But to make it clear I put as a calculation
322 //current values that might change. But to make it clear I put as a calculation
323 @comment-max-width: 1065px;
323 @comment-max-width: 1065px;
324 @pr-extra-margin: 34px;
324 @pr-extra-margin: 34px;
325 @pr-border-spacing: 4px;
325 @pr-border-spacing: 4px;
326 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
326 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
327
327
328 // Pull Request
328 // Pull Request
329 .cs_files .code-difftable {
329 .cs_files .code-difftable {
330 border: @border-thickness solid @grey5; //borders only on PRs
330 border: @border-thickness solid @grey5; //borders only on PRs
331
331
332 .comment-inline-form,
332 .comment-inline-form,
333 div.comment {
333 div.comment {
334 width: @pr-comment-width;
334 width: @pr-comment-width;
335 }
335 }
336 }
336 }
337
337
338 // Changeset
338 // Changeset
339 .code-difftable {
339 .code-difftable {
340 .comment-inline-form,
340 .comment-inline-form,
341 div.comment {
341 div.comment {
342 width: @comment-max-width;
342 width: @comment-max-width;
343 }
343 }
344 }
344 }
345
345
346 //Style page
346 //Style page
347 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
347 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
348 #style-page .code-difftable{
348 #style-page .code-difftable{
349 .comment-inline-form,
349 .comment-inline-form,
350 div.comment {
350 div.comment {
351 width: @comment-max-width - @style-extra-margin;
351 width: @comment-max-width - @style-extra-margin;
352 }
352 }
353 }
353 }
354
354
355 #context-bar > h2 {
355 #context-bar > h2 {
356 font-size: 20px;
356 font-size: 20px;
357 }
357 }
358
358
359 #context-bar > h2> a {
359 #context-bar > h2> a {
360 font-size: 20px;
360 font-size: 20px;
361 }
361 }
362 // end of defaults
362 // end of defaults
363
363
364 .file_diff_buttons {
364 .file_diff_buttons {
365 padding: 0 0 @padding;
365 padding: 0 0 @padding;
366
366
367 .drop-menu {
367 .drop-menu {
368 float: left;
368 float: left;
369 margin: 0 @padding 0 0;
369 margin: 0 @padding 0 0;
370 }
370 }
371 .btn {
371 .btn {
372 margin: 0 @padding 0 0;
372 margin: 0 @padding 0 0;
373 }
373 }
374 }
374 }
375
375
376 .code-body.textarea.editor {
376 .code-body.textarea.editor {
377 max-width: none;
377 max-width: none;
378 padding: 15px;
378 padding: 15px;
379 }
379 }
380
380
381 td.injected_diff{
381 td.injected_diff{
382 max-width: 1178px;
382 max-width: 1178px;
383 overflow-x: auto;
383 overflow-x: auto;
384 overflow-y: hidden;
384 overflow-y: hidden;
385
385
386 div.diff-container,
386 div.diff-container,
387 div.diffblock{
387 div.diffblock{
388 max-width: 100%;
388 max-width: 100%;
389 }
389 }
390
390
391 div.code-body {
391 div.code-body {
392 max-width: 1124px;
392 max-width: 1124px;
393 overflow-x: auto;
393 overflow-x: auto;
394 overflow-y: hidden;
394 overflow-y: hidden;
395 padding: 0;
395 padding: 0;
396 }
396 }
397 div.diffblock {
397 div.diffblock {
398 border: none;
398 border: none;
399 }
399 }
400
400
401 &.inline-form {
401 &.inline-form {
402 width: 99%
402 width: 99%
403 }
403 }
404 }
404 }
405
405
406
406
407 table.code-difftable {
407 table.code-difftable {
408 width: 100%;
408 width: 100%;
409 }
409 }
410
410
411 /** PYGMENTS COLORING **/
411 /** PYGMENTS COLORING **/
412 div.codeblock {
412 div.codeblock {
413
413
414 // TODO: johbo: Added interim to get rid of the margin around
414 // TODO: johbo: Added interim to get rid of the margin around
415 // Select2 widgets. This needs further cleanup.
415 // Select2 widgets. This needs further cleanup.
416 margin-top: @padding;
416 margin-top: @padding;
417
417
418 overflow: auto;
418 overflow: auto;
419 padding: 0px;
419 padding: 0px;
420 border: @border-thickness solid @grey5;
420 border: @border-thickness solid @grey5;
421 background: @grey6;
421 background: @grey6;
422 .border-radius(@border-radius);
422 .border-radius(@border-radius);
423
423
424 #remove_gist {
424 #remove_gist {
425 float: right;
425 float: right;
426 }
426 }
427
427
428 .author {
428 .author {
429 clear: both;
429 clear: both;
430 vertical-align: middle;
430 vertical-align: middle;
431 font-family: @text-bold;
431 font-family: @text-bold;
432 }
432 }
433
433
434 .btn-mini {
434 .btn-mini {
435 float: left;
435 float: left;
436 margin: 0 5px 0 0;
436 margin: 0 5px 0 0;
437 }
437 }
438
438
439 .code-header {
439 .code-header {
440 padding: @padding;
440 padding: @padding;
441 border-bottom: @border-thickness solid @grey5;
441 border-bottom: @border-thickness solid @grey5;
442
442
443 .rc-user {
443 .rc-user {
444 min-width: 0;
444 min-width: 0;
445 margin-right: .5em;
445 margin-right: .5em;
446 }
446 }
447
447
448 .stats {
448 .stats {
449 clear: both;
449 clear: both;
450 margin: 0 0 @padding 0;
450 margin: 0 0 @padding 0;
451 padding: 0;
451 padding: 0;
452 .left {
452 .left {
453 float: left;
453 float: left;
454 clear: left;
454 clear: left;
455 max-width: 75%;
455 max-width: 75%;
456 margin: 0 0 @padding 0;
456 margin: 0 0 @padding 0;
457
457
458 &.item {
458 &.item {
459 margin-right: @padding;
459 margin-right: @padding;
460 &.last { border-right: none; }
460 &.last { border-right: none; }
461 }
461 }
462 }
462 }
463 .buttons { float: right; }
463 .buttons { float: right; }
464 .author {
464 .author {
465 height: 25px; margin-left: 15px; font-weight: bold;
465 height: 25px; margin-left: 15px; font-weight: bold;
466 }
466 }
467 }
467 }
468
468
469 .commit {
469 .commit {
470 margin: 5px 0 0 26px;
470 margin: 5px 0 0 26px;
471 font-weight: normal;
471 font-weight: normal;
472 white-space: pre-wrap;
472 white-space: pre-wrap;
473 }
473 }
474 }
474 }
475
475
476 .message {
476 .message {
477 position: relative;
477 position: relative;
478 margin: @padding;
478 margin: @padding;
479
479
480 .codeblock-label {
480 .codeblock-label {
481 margin: 0 0 1em 0;
481 margin: 0 0 1em 0;
482 }
482 }
483 }
483 }
484
484
485 .code-body {
485 .code-body {
486 padding: @padding;
486 padding: @padding;
487 background-color: #ffffff;
487 background-color: #ffffff;
488 min-width: 100%;
488 min-width: 100%;
489 box-sizing: border-box;
489 box-sizing: border-box;
490 // TODO: johbo: Parent has overflow: auto, this forces the child here
490 // TODO: johbo: Parent has overflow: auto, this forces the child here
491 // to have the intended size and to scroll. Should be simplified.
491 // to have the intended size and to scroll. Should be simplified.
492 width: 100%;
492 width: 100%;
493 overflow-x: auto;
493 overflow-x: auto;
494 }
494 }
495 }
495 }
496
496
497 .code-highlighttable,
497 .code-highlighttable,
498 div.codeblock {
498 div.codeblock {
499
499
500 &.readme {
500 &.readme {
501 background-color: white;
501 background-color: white;
502 }
502 }
503
503
504 .markdown-block table {
504 .markdown-block table {
505 border-collapse: collapse;
505 border-collapse: collapse;
506
506
507 th,
507 th,
508 td {
508 td {
509 padding: .5em;
509 padding: .5em;
510 border: @border-thickness solid @border-default-color;
510 border: @border-thickness solid @border-default-color;
511 }
511 }
512 }
512 }
513
513
514 table {
514 table {
515 border: 0px;
515 border: 0px;
516 margin: 0;
516 margin: 0;
517 letter-spacing: normal;
517 letter-spacing: normal;
518
518
519
519
520 td {
520 td {
521 border: 0px;
521 border: 0px;
522 vertical-align: top;
522 vertical-align: top;
523 }
523 }
524 }
524 }
525 }
525 }
526
526
527 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
527 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
528 div.search-code-body {
528 div.search-code-body {
529 background-color: #ffffff; padding: 5px 0 5px 10px;
529 background-color: #ffffff; padding: 5px 0 5px 10px;
530 pre {
530 pre {
531 .match { background-color: #faffa6;}
531 .match { background-color: #faffa6;}
532 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
532 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
533 }
533 }
534 .code-highlighttable {
534 .code-highlighttable {
535 border-collapse: collapse;
535 border-collapse: collapse;
536
536
537 tr:hover {
537 tr:hover {
538 background: #fafafa;
538 background: #fafafa;
539 }
539 }
540 td.code {
540 td.code {
541 padding-left: 10px;
541 padding-left: 10px;
542 }
542 }
543 td.line {
543 td.line {
544 border-right: 1px solid #ccc !important;
544 border-right: 1px solid #ccc !important;
545 padding-right: 10px;
545 padding-right: 10px;
546 text-align: right;
546 text-align: right;
547 font-family: "Lucida Console",Monaco,monospace;
547 font-family: "Lucida Console",Monaco,monospace;
548 span {
548 span {
549 white-space: pre-wrap;
549 white-space: pre-wrap;
550 color: #666666;
550 color: #666666;
551 }
551 }
552 }
552 }
553 }
553 }
554 }
554 }
555
555
556 div.annotatediv { margin-left: 2px; margin-right: 4px; }
556 div.annotatediv { margin-left: 2px; margin-right: 4px; }
557 .code-highlight {
557 .code-highlight {
558 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
558 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
559 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
559 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
560 pre div:target {background-color: @comment-highlight-color !important;}
560 pre div:target {background-color: @comment-highlight-color !important;}
561 }
561 }
562
562
563 .linenos a { text-decoration: none; }
563 .linenos a { text-decoration: none; }
564
564
565 .CodeMirror-selected { background: @rchighlightblue; }
565 .CodeMirror-selected { background: @rchighlightblue; }
566 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
566 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
567 .CodeMirror ::selection { background: @rchighlightblue; }
567 .CodeMirror ::selection { background: @rchighlightblue; }
568 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
568 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
569
569
570 .code { display: block; border:0px !important; }
570 .code { display: block; border:0px !important; }
571 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
571 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
572 .codehilite {
572 .codehilite {
573 .hll { background-color: #ffffcc }
573 .hll { background-color: #ffffcc }
574 .c { color: #408080; font-style: italic } /* Comment */
574 .c { color: #408080; font-style: italic } /* Comment */
575 .err, .codehilite .err { border: @border-thickness solid #FF0000 } /* Error */
575 .err, .codehilite .err { border: @border-thickness solid #FF0000 } /* Error */
576 .k { color: #008000; font-weight: bold } /* Keyword */
576 .k { color: #008000; font-weight: bold } /* Keyword */
577 .o { color: #666666 } /* Operator */
577 .o { color: #666666 } /* Operator */
578 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
578 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
579 .cp { color: #BC7A00 } /* Comment.Preproc */
579 .cp { color: #BC7A00 } /* Comment.Preproc */
580 .c1 { color: #408080; font-style: italic } /* Comment.Single */
580 .c1 { color: #408080; font-style: italic } /* Comment.Single */
581 .cs { color: #408080; font-style: italic } /* Comment.Special */
581 .cs { color: #408080; font-style: italic } /* Comment.Special */
582 .gd { color: #A00000 } /* Generic.Deleted */
582 .gd { color: #A00000 } /* Generic.Deleted */
583 .ge { font-style: italic } /* Generic.Emph */
583 .ge { font-style: italic } /* Generic.Emph */
584 .gr { color: #FF0000 } /* Generic.Error */
584 .gr { color: #FF0000 } /* Generic.Error */
585 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
585 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
586 .gi { color: #00A000 } /* Generic.Inserted */
586 .gi { color: #00A000 } /* Generic.Inserted */
587 .go { color: #808080 } /* Generic.Output */
587 .go { color: #808080 } /* Generic.Output */
588 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
588 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
589 .gs { font-weight: bold } /* Generic.Strong */
589 .gs { font-weight: bold } /* Generic.Strong */
590 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
590 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
591 .gt { color: #0040D0 } /* Generic.Traceback */
591 .gt { color: #0040D0 } /* Generic.Traceback */
592 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
592 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
593 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
593 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
594 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
594 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
595 .kp { color: #008000 } /* Keyword.Pseudo */
595 .kp { color: #008000 } /* Keyword.Pseudo */
596 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
596 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
597 .kt { color: #B00040 } /* Keyword.Type */
597 .kt { color: #B00040 } /* Keyword.Type */
598 .m { color: #666666 } /* Literal.Number */
598 .m { color: #666666 } /* Literal.Number */
599 .s { color: #BA2121 } /* Literal.String */
599 .s { color: #BA2121 } /* Literal.String */
600 .na { color: #7D9029 } /* Name.Attribute */
600 .na { color: #7D9029 } /* Name.Attribute */
601 .nb { color: #008000 } /* Name.Builtin */
601 .nb { color: #008000 } /* Name.Builtin */
602 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
602 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
603 .no { color: #880000 } /* Name.Constant */
603 .no { color: #880000 } /* Name.Constant */
604 .nd { color: #AA22FF } /* Name.Decorator */
604 .nd { color: #AA22FF } /* Name.Decorator */
605 .ni { color: #999999; font-weight: bold } /* Name.Entity */
605 .ni { color: #999999; font-weight: bold } /* Name.Entity */
606 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
606 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
607 .nf { color: #0000FF } /* Name.Function */
607 .nf { color: #0000FF } /* Name.Function */
608 .nl { color: #A0A000 } /* Name.Label */
608 .nl { color: #A0A000 } /* Name.Label */
609 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
609 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
610 .nt { color: #008000; font-weight: bold } /* Name.Tag */
610 .nt { color: #008000; font-weight: bold } /* Name.Tag */
611 .nv { color: #19177C } /* Name.Variable */
611 .nv { color: #19177C } /* Name.Variable */
612 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
612 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
613 .w { color: #bbbbbb } /* Text.Whitespace */
613 .w { color: #bbbbbb } /* Text.Whitespace */
614 .mf { color: #666666 } /* Literal.Number.Float */
614 .mf { color: #666666 } /* Literal.Number.Float */
615 .mh { color: #666666 } /* Literal.Number.Hex */
615 .mh { color: #666666 } /* Literal.Number.Hex */
616 .mi { color: #666666 } /* Literal.Number.Integer */
616 .mi { color: #666666 } /* Literal.Number.Integer */
617 .mo { color: #666666 } /* Literal.Number.Oct */
617 .mo { color: #666666 } /* Literal.Number.Oct */
618 .sb { color: #BA2121 } /* Literal.String.Backtick */
618 .sb { color: #BA2121 } /* Literal.String.Backtick */
619 .sc { color: #BA2121 } /* Literal.String.Char */
619 .sc { color: #BA2121 } /* Literal.String.Char */
620 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
620 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
621 .s2 { color: #BA2121 } /* Literal.String.Double */
621 .s2 { color: #BA2121 } /* Literal.String.Double */
622 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
622 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
623 .sh { color: #BA2121 } /* Literal.String.Heredoc */
623 .sh { color: #BA2121 } /* Literal.String.Heredoc */
624 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
624 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
625 .sx { color: #008000 } /* Literal.String.Other */
625 .sx { color: #008000 } /* Literal.String.Other */
626 .sr { color: #BB6688 } /* Literal.String.Regex */
626 .sr { color: #BB6688 } /* Literal.String.Regex */
627 .s1 { color: #BA2121 } /* Literal.String.Single */
627 .s1 { color: #BA2121 } /* Literal.String.Single */
628 .ss { color: #19177C } /* Literal.String.Symbol */
628 .ss { color: #19177C } /* Literal.String.Symbol */
629 .bp { color: #008000 } /* Name.Builtin.Pseudo */
629 .bp { color: #008000 } /* Name.Builtin.Pseudo */
630 .vc { color: #19177C } /* Name.Variable.Class */
630 .vc { color: #19177C } /* Name.Variable.Class */
631 .vg { color: #19177C } /* Name.Variable.Global */
631 .vg { color: #19177C } /* Name.Variable.Global */
632 .vi { color: #19177C } /* Name.Variable.Instance */
632 .vi { color: #19177C } /* Name.Variable.Instance */
633 .il { color: #666666 } /* Literal.Number.Integer.Long */
633 .il { color: #666666 } /* Literal.Number.Integer.Long */
634 }
634 }
635
635
636 /* customized pre blocks for markdown/rst */
636 /* customized pre blocks for markdown/rst */
637 pre.literal-block, .codehilite pre{
637 pre.literal-block, .codehilite pre{
638 padding: @padding;
638 padding: @padding;
639 border: 1px solid @grey6;
639 border: 1px solid @grey6;
640 .border-radius(@border-radius);
640 .border-radius(@border-radius);
641 background-color: @grey7;
641 background-color: @grey7;
642 }
642 }
643
643
644
644
645 /* START NEW CODE BLOCK CSS */
645 /* START NEW CODE BLOCK CSS */
646
646
647 @cb-line-height: 18px;
647 @cb-line-height: 18px;
648 @cb-line-code-padding: 10px;
648 @cb-line-code-padding: 10px;
649 @cb-text-padding: 5px;
649 @cb-text-padding: 5px;
650
650
651 @pill-padding: 2px 7px;
651 @pill-padding: 2px 7px;
652
652
653 input.filediff-collapse-state {
653 input.filediff-collapse-state {
654 display: none;
654 display: none;
655
655
656 &:checked + .filediff { /* file diff is collapsed */
656 &:checked + .filediff { /* file diff is collapsed */
657 .cb {
657 .cb {
658 display: none
658 display: none
659 }
659 }
660 .filediff-collapse-indicator {
660 .filediff-collapse-indicator {
661 border-width: 9px 0 9px 15.6px;
661 width: 0;
662 border-color: transparent transparent transparent #ccc;
662 height: 0;
663 border-style: solid;
664 border-width: 6.5px 0 6.5px 11.3px;
665 border-color: transparent transparent transparent #ccc;
663 }
666 }
664 .filediff-menu {
667 .filediff-menu {
665 display: none;
668 display: none;
666 }
669 }
667 margin: -1px 0 0 0;
670 margin: 10px 0 0 0;
668 }
671 }
669
672
670 &+ .filediff { /* file diff is expanded */
673 &+ .filediff { /* file diff is expanded */
671 .filediff-collapse-indicator {
674 .filediff-collapse-indicator {
672 border-width: 15.6px 9px 0 9px;
675 width: 0;
676 height: 0;
677 border-style: solid;
678 border-width: 11.3px 6.5px 0 6.5px;
673 border-color: #ccc transparent transparent transparent;
679 border-color: #ccc transparent transparent transparent;
674 }
680 }
675 .filediff-menu {
681 .filediff-menu {
676 display: block;
682 display: block;
677 }
683 }
678 margin: 20px 0;
684 margin: 10px 0;
679 &:nth-child(2) {
685 &:nth-child(2) {
680 margin: 0;
686 margin: 0;
681 }
687 }
682 }
688 }
683 }
689 }
684 .cs_files {
690 .cs_files {
685 clear: both;
691 clear: both;
686 }
692 }
687
693
688 .diffset-menu {
694 .diffset-menu {
689 margin-bottom: 20px;
695 margin-bottom: 20px;
690 }
696 }
691 .diffset {
697 .diffset {
692 margin: 20px auto;
698 margin: 20px auto;
693 .diffset-heading {
699 .diffset-heading {
694 border: 1px solid @grey5;
700 border: 1px solid @grey5;
695 margin-bottom: -1px;
701 margin-bottom: -1px;
696 // margin-top: 20px;
702 // margin-top: 20px;
697 h2 {
703 h2 {
698 margin: 0;
704 margin: 0;
699 line-height: 38px;
705 line-height: 38px;
700 padding-left: 10px;
706 padding-left: 10px;
701 }
707 }
702 .btn {
708 .btn {
703 margin: 0;
709 margin: 0;
704 }
710 }
705 background: @grey6;
711 background: @grey6;
706 display: block;
712 display: block;
707 padding: 5px;
713 padding: 5px;
708 }
714 }
709 .diffset-heading-warning {
715 .diffset-heading-warning {
710 background: @alert3-inner;
716 background: @alert3-inner;
711 border: 1px solid @alert3;
717 border: 1px solid @alert3;
712 }
718 }
713 &.diffset-comments-disabled {
719 &.diffset-comments-disabled {
714 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
720 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
715 display: none !important;
721 display: none !important;
716 }
722 }
717 }
723 }
718 }
724 }
719
725
720 .pill {
726 .pill {
721 display: block;
727 display: block;
722 float: left;
728 float: left;
723 padding: @pill-padding;
729 padding: @pill-padding;
724 }
730 }
725 .pill-group {
731 .pill-group {
726 .pill {
732 .pill {
727 opacity: .8;
733 opacity: .8;
728 &:first-child {
734 &:first-child {
729 border-radius: @border-radius 0 0 @border-radius;
735 border-radius: @border-radius 0 0 @border-radius;
730 }
736 }
731 &:last-child {
737 &:last-child {
732 border-radius: 0 @border-radius @border-radius 0;
738 border-radius: 0 @border-radius @border-radius 0;
733 }
739 }
734 &:only-child {
740 &:only-child {
735 border-radius: @border-radius;
741 border-radius: @border-radius;
736 }
742 }
737 }
743 }
738 }
744 }
739
745
740 /* Main comments*/
746 /* Main comments*/
741 #comments {
747 #comments {
742 .comment-selected {
748 .comment-selected {
743 border-left: 6px solid @comment-highlight-color;
749 border-left: 6px solid @comment-highlight-color;
744 padding-left: 3px;
750 padding-left: 3px;
745 margin-left: -9px;
751 margin-left: -9px;
746 }
752 }
747 }
753 }
748
754
749 .filediff {
755 .filediff {
750 border: 1px solid @grey5;
756 border: 1px solid @grey5;
751
757
752 /* START OVERRIDES */
758 /* START OVERRIDES */
753 .code-highlight {
759 .code-highlight {
754 border: none; // TODO: remove this border from the global
760 border: none; // TODO: remove this border from the global
755 // .code-highlight, it doesn't belong there
761 // .code-highlight, it doesn't belong there
756 }
762 }
757 label {
763 label {
758 margin: 0; // TODO: remove this margin definition from global label
764 margin: 0; // TODO: remove this margin definition from global label
759 // it doesn't belong there - if margin on labels
765 // it doesn't belong there - if margin on labels
760 // are needed for a form they should be defined
766 // are needed for a form they should be defined
761 // in the form's class
767 // in the form's class
762 }
768 }
763 /* END OVERRIDES */
769 /* END OVERRIDES */
764
770
765 * {
771 * {
766 box-sizing: border-box;
772 box-sizing: border-box;
767 }
773 }
768 .filediff-anchor {
774 .filediff-anchor {
769 visibility: hidden;
775 visibility: hidden;
770 }
776 }
771 &:hover {
777 &:hover {
772 .filediff-anchor {
778 .filediff-anchor {
773 visibility: visible;
779 visibility: visible;
774 }
780 }
775 }
781 }
776
782
777 .filediff-collapse-indicator {
783 .filediff-collapse-indicator {
778 width: 0;
779 height: 0;
780 border-style: solid;
784 border-style: solid;
781 float: left;
785 float: left;
782 margin: 2px 2px 0 0;
786 margin: 4px 0px 0 0;
783 cursor: pointer;
787 cursor: pointer;
784 }
788 }
785
789
786 .filediff-heading {
790 .filediff-heading {
787 background: @grey7;
791 background: @grey7;
788 cursor: pointer;
792 cursor: pointer;
789 display: block;
793 display: block;
790 padding: 5px 10px;
794 padding: 5px 10px;
791 }
795 }
792 .filediff-heading:after {
796 .filediff-heading:after {
793 content: "";
797 content: "";
794 display: table;
798 display: table;
795 clear: both;
799 clear: both;
796 }
800 }
797 .filediff-heading:hover {
801 .filediff-heading:hover {
798 background: #e1e9f4 !important;
802 background: #e1e9f4 !important;
799 }
803 }
800
804
801 .filediff-menu {
805 .filediff-menu {
802 float: right;
806 float: right;
807 text-align: right;
808 padding: 5px 5px 5px 0px;
803
809
804 &> a, &> span {
810 &> a,
805 padding: 5px;
811 &> span {
806 display: block;
812 padding: 1px;
807 float: left
808 }
813 }
809 }
814 }
810
815
811 .pill {
816 .pill {
812 &[op="name"] {
817 &[op="name"] {
813 background: none;
818 background: none;
814 color: @grey2;
819 color: @grey2;
815 opacity: 1;
820 opacity: 1;
816 color: white;
821 color: white;
817 }
822 }
818 &[op="limited"] {
823 &[op="limited"] {
819 background: @grey2;
824 background: @grey2;
820 color: white;
825 color: white;
821 }
826 }
822 &[op="binary"] {
827 &[op="binary"] {
823 background: @color7;
828 background: @color7;
824 color: white;
829 color: white;
825 }
830 }
826 &[op="modified"] {
831 &[op="modified"] {
827 background: @alert1;
832 background: @alert1;
828 color: white;
833 color: white;
829 }
834 }
830 &[op="renamed"] {
835 &[op="renamed"] {
831 background: @color4;
836 background: @color4;
832 color: white;
837 color: white;
833 }
838 }
834 &[op="mode"] {
839 &[op="mode"] {
835 background: @grey3;
840 background: @grey3;
836 color: white;
841 color: white;
837 }
842 }
838 &[op="symlink"] {
843 &[op="symlink"] {
839 background: @color8;
844 background: @color8;
840 color: white;
845 color: white;
841 }
846 }
842
847
843 &[op="added"] { /* added lines */
848 &[op="added"] { /* added lines */
844 background: @alert1;
849 background: @alert1;
845 color: white;
850 color: white;
846 }
851 }
847 &[op="deleted"] { /* deleted lines */
852 &[op="deleted"] { /* deleted lines */
848 background: @alert2;
853 background: @alert2;
849 color: white;
854 color: white;
850 }
855 }
851
856
852 &[op="created"] { /* created file */
857 &[op="created"] { /* created file */
853 background: @alert1;
858 background: @alert1;
854 color: white;
859 color: white;
855 }
860 }
856 &[op="removed"] { /* deleted file */
861 &[op="removed"] { /* deleted file */
857 background: @color5;
862 background: @color5;
858 color: white;
863 color: white;
859 }
864 }
860 }
865 }
861
866
862 .filediff-collapse-button, .filediff-expand-button {
867 .filediff-collapse-button, .filediff-expand-button {
863 cursor: pointer;
868 cursor: pointer;
864 }
869 }
865 .filediff-collapse-button {
870 .filediff-collapse-button {
866 display: inline;
871 display: inline;
867 }
872 }
868 .filediff-expand-button {
873 .filediff-expand-button {
869 display: none;
874 display: none;
870 }
875 }
871 .filediff-collapsed .filediff-collapse-button {
876 .filediff-collapsed .filediff-collapse-button {
872 display: none;
877 display: none;
873 }
878 }
874 .filediff-collapsed .filediff-expand-button {
879 .filediff-collapsed .filediff-expand-button {
875 display: inline;
880 display: inline;
876 }
881 }
877
882
878 @comment-padding: 5px;
883 @comment-padding: 5px;
879
884
880 /**** COMMENTS ****/
885 /**** COMMENTS ****/
881
886
882 .filediff-menu {
887 .filediff-menu {
883 .show-comment-button {
888 .show-comment-button {
884 display: none;
889 display: none;
885 }
890 }
886 }
891 }
887 &.hide-comments {
892 &.hide-comments {
888 .inline-comments {
893 .inline-comments {
889 display: none;
894 display: none;
890 }
895 }
891 .filediff-menu {
896 .filediff-menu {
892 .show-comment-button {
897 .show-comment-button {
893 display: inline;
898 display: inline;
894 }
899 }
895 .hide-comment-button {
900 .hide-comment-button {
896 display: none;
901 display: none;
897 }
902 }
898 }
903 }
899 }
904 }
900
905
901 .hide-line-comments {
906 .hide-line-comments {
902 .inline-comments {
907 .inline-comments {
903 display: none;
908 display: none;
904 }
909 }
905 }
910 }
906
911
907 .inline-comments {
912 .inline-comments {
908 border-radius: @border-radius;
913 border-radius: @border-radius;
909 background: @grey6;
914 background: @grey6;
910 .comment {
915 .comment {
911 margin: 0;
916 margin: 0;
912 border-radius: @border-radius;
917 border-radius: @border-radius;
913 }
918 }
914 .comment-outdated {
919 .comment-outdated {
915 opacity: 0.5;
920 opacity: 0.5;
916 }
921 }
917
922
918 .comment-inline {
923 .comment-inline {
919 background: white;
924 background: white;
920 padding: (@comment-padding + 3px) @comment-padding;
925 padding: (@comment-padding + 3px) @comment-padding;
921 border: @comment-padding solid @grey6;
926 border: @comment-padding solid @grey6;
922
927
923 .text {
928 .text {
924 border: none;
929 border: none;
925 }
930 }
926 .meta {
931 .meta {
927 border-bottom: 1px solid @grey6;
932 border-bottom: 1px solid @grey6;
928 padding-bottom: 10px;
933 padding-bottom: 10px;
929 }
934 }
930 }
935 }
931 .comment-selected {
936 .comment-selected {
932 border-left: 6px solid @comment-highlight-color;
937 border-left: 6px solid @comment-highlight-color;
933 }
938 }
934 .comment-inline-form {
939 .comment-inline-form {
935 padding: @comment-padding;
940 padding: @comment-padding;
936 display: none;
941 display: none;
937 }
942 }
938 .cb-comment-add-button {
943 .cb-comment-add-button {
939 margin: @comment-padding;
944 margin: @comment-padding;
940 }
945 }
941 /* hide add comment button when form is open */
946 /* hide add comment button when form is open */
942 .comment-inline-form-open ~ .cb-comment-add-button {
947 .comment-inline-form-open ~ .cb-comment-add-button {
943 display: none;
948 display: none;
944 }
949 }
945 .comment-inline-form-open {
950 .comment-inline-form-open {
946 display: block;
951 display: block;
947 }
952 }
948 /* hide add comment button when form but no comments */
953 /* hide add comment button when form but no comments */
949 .comment-inline-form:first-child + .cb-comment-add-button {
954 .comment-inline-form:first-child + .cb-comment-add-button {
950 display: none;
955 display: none;
951 }
956 }
952 /* hide add comment button when no comments or form */
957 /* hide add comment button when no comments or form */
953 .cb-comment-add-button:first-child {
958 .cb-comment-add-button:first-child {
954 display: none;
959 display: none;
955 }
960 }
956 /* hide add comment button when only comment is being deleted */
961 /* hide add comment button when only comment is being deleted */
957 .comment-deleting:first-child + .cb-comment-add-button {
962 .comment-deleting:first-child + .cb-comment-add-button {
958 display: none;
963 display: none;
959 }
964 }
960 }
965 }
961 /**** END COMMENTS ****/
966 /**** END COMMENTS ****/
962
967
963 }
968 }
964
969
970 .filediff-outdated {
971 padding: 8px 0;
972
973 .filediff-heading {
974 opacity: .5;
975 }
976 }
965
977
966 table.cb {
978 table.cb {
967 width: 100%;
979 width: 100%;
968 border-collapse: collapse;
980 border-collapse: collapse;
969
981
970 .cb-text {
982 .cb-text {
971 padding: @cb-text-padding;
983 padding: @cb-text-padding;
972 }
984 }
973 .cb-hunk {
985 .cb-hunk {
974 padding: @cb-text-padding;
986 padding: @cb-text-padding;
975 }
987 }
976 .cb-expand {
988 .cb-expand {
977 display: none;
989 display: none;
978 }
990 }
979 .cb-collapse {
991 .cb-collapse {
980 display: inline;
992 display: inline;
981 }
993 }
982 &.cb-collapsed {
994 &.cb-collapsed {
983 .cb-line {
995 .cb-line {
984 display: none;
996 display: none;
985 }
997 }
986 .cb-expand {
998 .cb-expand {
987 display: inline;
999 display: inline;
988 }
1000 }
989 .cb-collapse {
1001 .cb-collapse {
990 display: none;
1002 display: none;
991 }
1003 }
992 }
1004 }
993
1005
994 /* intentionally general selector since .cb-line-selected must override it
1006 /* intentionally general selector since .cb-line-selected must override it
995 and they both use !important since the td itself may have a random color
1007 and they both use !important since the td itself may have a random color
996 generated by annotation blocks. TLDR: if you change it, make sure
1008 generated by annotation blocks. TLDR: if you change it, make sure
997 annotated block selection and line selection in file view still work */
1009 annotated block selection and line selection in file view still work */
998 .cb-line-fresh .cb-content {
1010 .cb-line-fresh .cb-content {
999 background: white !important;
1011 background: white !important;
1000 }
1012 }
1001 .cb-warning {
1013 .cb-warning {
1002 background: #fff4dd;
1014 background: #fff4dd;
1003 }
1015 }
1004
1016
1005 &.cb-diff-sideside {
1017 &.cb-diff-sideside {
1006 td {
1018 td {
1007 &.cb-content {
1019 &.cb-content {
1008 width: 50%;
1020 width: 50%;
1009 }
1021 }
1010 }
1022 }
1011 }
1023 }
1012
1024
1013 tr {
1025 tr {
1014 &.cb-annotate {
1026 &.cb-annotate {
1015 border-top: 1px solid #eee;
1027 border-top: 1px solid #eee;
1016
1028
1017 &+ .cb-line {
1029 &+ .cb-line {
1018 border-top: 1px solid #eee;
1030 border-top: 1px solid #eee;
1019 }
1031 }
1020
1032
1021 &:first-child {
1033 &:first-child {
1022 border-top: none;
1034 border-top: none;
1023 &+ .cb-line {
1035 &+ .cb-line {
1024 border-top: none;
1036 border-top: none;
1025 }
1037 }
1026 }
1038 }
1027 }
1039 }
1028
1040
1029 &.cb-hunk {
1041 &.cb-hunk {
1030 font-family: @font-family-monospace;
1042 font-family: @font-family-monospace;
1031 color: rgba(0, 0, 0, 0.3);
1043 color: rgba(0, 0, 0, 0.3);
1032
1044
1033 td {
1045 td {
1034 &:first-child {
1046 &:first-child {
1035 background: #edf2f9;
1047 background: #edf2f9;
1036 }
1048 }
1037 &:last-child {
1049 &:last-child {
1038 background: #f4f7fb;
1050 background: #f4f7fb;
1039 }
1051 }
1040 }
1052 }
1041 }
1053 }
1042 }
1054 }
1043
1055
1044
1056
1045 td {
1057 td {
1046 vertical-align: top;
1058 vertical-align: top;
1047 padding: 0;
1059 padding: 0;
1048
1060
1049 &.cb-content {
1061 &.cb-content {
1050 font-size: 12.35px;
1062 font-size: 12.35px;
1051
1063
1052 &.cb-line-selected .cb-code {
1064 &.cb-line-selected .cb-code {
1053 background: @comment-highlight-color !important;
1065 background: @comment-highlight-color !important;
1054 }
1066 }
1055
1067
1056 span.cb-code {
1068 span.cb-code {
1057 line-height: @cb-line-height;
1069 line-height: @cb-line-height;
1058 padding-left: @cb-line-code-padding;
1070 padding-left: @cb-line-code-padding;
1059 padding-right: @cb-line-code-padding;
1071 padding-right: @cb-line-code-padding;
1060 display: block;
1072 display: block;
1061 white-space: pre-wrap;
1073 white-space: pre-wrap;
1062 font-family: @font-family-monospace;
1074 font-family: @font-family-monospace;
1063 word-break: break-word;
1075 word-break: break-word;
1064 .nonl {
1076 .nonl {
1065 color: @color5;
1077 color: @color5;
1066 }
1078 }
1067 }
1079 }
1068
1080
1069 &> button.cb-comment-box-opener {
1081 &> button.cb-comment-box-opener {
1070
1082
1071 padding: 2px 5px 1px 5px;
1083 padding: 2px 2px 1px 3px;
1072 margin-left: 0px;
1084 margin-left: -6px;
1073 margin-top: -1px;
1085 margin-top: -1px;
1074
1086
1075 border-radius: @border-radius;
1087 border-radius: @border-radius;
1076 position: absolute;
1088 position: absolute;
1077 display: none;
1089 display: none;
1078 }
1090 }
1079 .cb-comment {
1091 .cb-comment {
1080 margin-top: 10px;
1092 margin-top: 10px;
1081 white-space: normal;
1093 white-space: normal;
1082 }
1094 }
1083 }
1095 }
1084 &:hover {
1096 &:hover {
1085 button.cb-comment-box-opener {
1097 button.cb-comment-box-opener {
1086 display: block;
1098 display: block;
1087 }
1099 }
1088 &+ td button.cb-comment-box-opener {
1100 &+ td button.cb-comment-box-opener {
1089 display: block
1101 display: block
1090 }
1102 }
1091 }
1103 }
1092
1104
1093 &.cb-data {
1105 &.cb-data {
1094 text-align: right;
1106 text-align: right;
1095 width: 30px;
1107 width: 30px;
1096 font-family: @font-family-monospace;
1108 font-family: @font-family-monospace;
1097
1109
1098 .icon-comment {
1110 .icon-comment {
1099 cursor: pointer;
1111 cursor: pointer;
1100 }
1112 }
1101 &.cb-line-selected > div {
1113 &.cb-line-selected > div {
1102 display: block;
1114 display: block;
1103 background: @comment-highlight-color !important;
1115 background: @comment-highlight-color !important;
1104 line-height: @cb-line-height;
1116 line-height: @cb-line-height;
1105 color: rgba(0, 0, 0, 0.3);
1117 color: rgba(0, 0, 0, 0.3);
1106 }
1118 }
1107 }
1119 }
1108
1120
1109 &.cb-lineno {
1121 &.cb-lineno {
1110 padding: 0;
1122 padding: 0;
1111 width: 50px;
1123 width: 50px;
1112 color: rgba(0, 0, 0, 0.3);
1124 color: rgba(0, 0, 0, 0.3);
1113 text-align: right;
1125 text-align: right;
1114 border-right: 1px solid #eee;
1126 border-right: 1px solid #eee;
1115 font-family: @font-family-monospace;
1127 font-family: @font-family-monospace;
1116
1128
1117 a::before {
1129 a::before {
1118 content: attr(data-line-no);
1130 content: attr(data-line-no);
1119 }
1131 }
1120 &.cb-line-selected a {
1132 &.cb-line-selected a {
1121 background: @comment-highlight-color !important;
1133 background: @comment-highlight-color !important;
1122 }
1134 }
1123
1135
1124 a {
1136 a {
1125 display: block;
1137 display: block;
1126 padding-right: @cb-line-code-padding;
1138 padding-right: @cb-line-code-padding;
1127 padding-left: @cb-line-code-padding;
1139 padding-left: @cb-line-code-padding;
1128 line-height: @cb-line-height;
1140 line-height: @cb-line-height;
1129 color: rgba(0, 0, 0, 0.3);
1141 color: rgba(0, 0, 0, 0.3);
1130 }
1142 }
1131 }
1143 }
1132
1144
1133 &.cb-empty {
1145 &.cb-empty {
1134 background: @grey7;
1146 background: @grey7;
1135 }
1147 }
1136
1148
1137 ins {
1149 ins {
1138 color: black;
1150 color: black;
1139 background: #a6f3a6;
1151 background: #a6f3a6;
1140 text-decoration: none;
1152 text-decoration: none;
1141 }
1153 }
1142 del {
1154 del {
1143 color: black;
1155 color: black;
1144 background: #f8cbcb;
1156 background: #f8cbcb;
1145 text-decoration: none;
1157 text-decoration: none;
1146 }
1158 }
1147 &.cb-addition {
1159 &.cb-addition {
1148 background: #ecffec;
1160 background: #ecffec;
1149
1161
1150 &.blob-lineno {
1162 &.blob-lineno {
1151 background: #ddffdd;
1163 background: #ddffdd;
1152 }
1164 }
1153 }
1165 }
1154 &.cb-deletion {
1166 &.cb-deletion {
1155 background: #ffecec;
1167 background: #ffecec;
1156
1168
1157 &.blob-lineno {
1169 &.blob-lineno {
1158 background: #ffdddd;
1170 background: #ffdddd;
1159 }
1171 }
1160 }
1172 }
1161
1173
1162 &.cb-annotate-info {
1174 &.cb-annotate-info {
1163 width: 320px;
1175 width: 320px;
1164 min-width: 320px;
1176 min-width: 320px;
1165 max-width: 320px;
1177 max-width: 320px;
1166 padding: 5px 2px;
1178 padding: 5px 2px;
1167 font-size: 13px;
1179 font-size: 13px;
1168
1180
1169 strong.cb-annotate-message {
1181 strong.cb-annotate-message {
1170 padding: 5px 0;
1182 padding: 5px 0;
1171 white-space: pre-line;
1183 white-space: pre-line;
1172 display: inline-block;
1184 display: inline-block;
1173 }
1185 }
1174 .rc-user {
1186 .rc-user {
1175 float: none;
1187 float: none;
1176 padding: 0 6px 0 17px;
1188 padding: 0 6px 0 17px;
1177 min-width: auto;
1189 min-width: auto;
1178 min-height: auto;
1190 min-height: auto;
1179 }
1191 }
1180 }
1192 }
1181
1193
1182 &.cb-annotate-revision {
1194 &.cb-annotate-revision {
1183 cursor: pointer;
1195 cursor: pointer;
1184 text-align: right;
1196 text-align: right;
1185 }
1197 }
1186 }
1198 }
1187 }
1199 }
@@ -1,2217 +1,2223 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'fonts';
8 @import 'fonts';
9 @import 'variables';
9 @import 'variables';
10 @import 'bootstrap-variables';
10 @import 'bootstrap-variables';
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29
29
30 //--- BASE ------------------//
30 //--- BASE ------------------//
31 .noscript-error {
31 .noscript-error {
32 top: 0;
32 top: 0;
33 left: 0;
33 left: 0;
34 width: 100%;
34 width: 100%;
35 z-index: 101;
35 z-index: 101;
36 text-align: center;
36 text-align: center;
37 font-family: @text-semibold;
37 font-family: @text-semibold;
38 font-size: 120%;
38 font-size: 120%;
39 color: white;
39 color: white;
40 background-color: @alert2;
40 background-color: @alert2;
41 padding: 5px 0 5px 0;
41 padding: 5px 0 5px 0;
42 }
42 }
43
43
44 html {
44 html {
45 display: table;
45 display: table;
46 height: 100%;
46 height: 100%;
47 width: 100%;
47 width: 100%;
48 }
48 }
49
49
50 body {
50 body {
51 display: table-cell;
51 display: table-cell;
52 width: 100%;
52 width: 100%;
53 }
53 }
54
54
55 //--- LAYOUT ------------------//
55 //--- LAYOUT ------------------//
56
56
57 .hidden{
57 .hidden{
58 display: none !important;
58 display: none !important;
59 }
59 }
60
60
61 .box{
61 .box{
62 float: left;
62 float: left;
63 width: 100%;
63 width: 100%;
64 }
64 }
65
65
66 .browser-header {
66 .browser-header {
67 clear: both;
67 clear: both;
68 }
68 }
69 .main {
69 .main {
70 clear: both;
70 clear: both;
71 padding:0 0 @pagepadding;
71 padding:0 0 @pagepadding;
72 height: auto;
72 height: auto;
73
73
74 &:after { //clearfix
74 &:after { //clearfix
75 content:"";
75 content:"";
76 clear:both;
76 clear:both;
77 width:100%;
77 width:100%;
78 display:block;
78 display:block;
79 }
79 }
80 }
80 }
81
81
82 .action-link{
82 .action-link{
83 margin-left: @padding;
83 margin-left: @padding;
84 padding-left: @padding;
84 padding-left: @padding;
85 border-left: @border-thickness solid @border-default-color;
85 border-left: @border-thickness solid @border-default-color;
86 }
86 }
87
87
88 input + .action-link, .action-link.first{
88 input + .action-link, .action-link.first{
89 border-left: none;
89 border-left: none;
90 }
90 }
91
91
92 .action-link.last{
92 .action-link.last{
93 margin-right: @padding;
93 margin-right: @padding;
94 padding-right: @padding;
94 padding-right: @padding;
95 }
95 }
96
96
97 .action-link.active,
97 .action-link.active,
98 .action-link.active a{
98 .action-link.active a{
99 color: @grey4;
99 color: @grey4;
100 }
100 }
101
101
102 ul.simple-list{
102 ul.simple-list{
103 list-style: none;
103 list-style: none;
104 margin: 0;
104 margin: 0;
105 padding: 0;
105 padding: 0;
106 }
106 }
107
107
108 .main-content {
108 .main-content {
109 padding-bottom: @pagepadding;
109 padding-bottom: @pagepadding;
110 }
110 }
111
111
112 .wide-mode-wrapper {
112 .wide-mode-wrapper {
113 max-width:2400px !important;
113 max-width:4000px !important;
114 }
114 }
115
115
116 .wrapper {
116 .wrapper {
117 position: relative;
117 position: relative;
118 max-width: @wrapper-maxwidth;
118 max-width: @wrapper-maxwidth;
119 margin: 0 auto;
119 margin: 0 auto;
120 }
120 }
121
121
122 #content {
122 #content {
123 clear: both;
123 clear: both;
124 padding: 0 @contentpadding;
124 padding: 0 @contentpadding;
125 }
125 }
126
126
127 .advanced-settings-fields{
127 .advanced-settings-fields{
128 input{
128 input{
129 margin-left: @textmargin;
129 margin-left: @textmargin;
130 margin-right: @padding/2;
130 margin-right: @padding/2;
131 }
131 }
132 }
132 }
133
133
134 .cs_files_title {
134 .cs_files_title {
135 margin: @pagepadding 0 0;
135 margin: @pagepadding 0 0;
136 }
136 }
137
137
138 input.inline[type="file"] {
138 input.inline[type="file"] {
139 display: inline;
139 display: inline;
140 }
140 }
141
141
142 .error_page {
142 .error_page {
143 margin: 10% auto;
143 margin: 10% auto;
144
144
145 h1 {
145 h1 {
146 color: @grey2;
146 color: @grey2;
147 }
147 }
148
148
149 .alert {
149 .alert {
150 margin: @padding 0;
150 margin: @padding 0;
151 }
151 }
152
152
153 .error-branding {
153 .error-branding {
154 font-family: @text-semibold;
154 font-family: @text-semibold;
155 color: @grey4;
155 color: @grey4;
156 }
156 }
157
157
158 .error_message {
158 .error_message {
159 font-family: @text-regular;
159 font-family: @text-regular;
160 }
160 }
161
161
162 .sidebar {
162 .sidebar {
163 min-height: 275px;
163 min-height: 275px;
164 margin: 0;
164 margin: 0;
165 padding: 0 0 @sidebarpadding @sidebarpadding;
165 padding: 0 0 @sidebarpadding @sidebarpadding;
166 border: none;
166 border: none;
167 }
167 }
168
168
169 .main-content {
169 .main-content {
170 position: relative;
170 position: relative;
171 margin: 0 @sidebarpadding @sidebarpadding;
171 margin: 0 @sidebarpadding @sidebarpadding;
172 padding: 0 0 0 @sidebarpadding;
172 padding: 0 0 0 @sidebarpadding;
173 border-left: @border-thickness solid @grey5;
173 border-left: @border-thickness solid @grey5;
174
174
175 @media (max-width:767px) {
175 @media (max-width:767px) {
176 clear: both;
176 clear: both;
177 width: 100%;
177 width: 100%;
178 margin: 0;
178 margin: 0;
179 border: none;
179 border: none;
180 }
180 }
181 }
181 }
182
182
183 .inner-column {
183 .inner-column {
184 float: left;
184 float: left;
185 width: 29.75%;
185 width: 29.75%;
186 min-height: 150px;
186 min-height: 150px;
187 margin: @sidebarpadding 2% 0 0;
187 margin: @sidebarpadding 2% 0 0;
188 padding: 0 2% 0 0;
188 padding: 0 2% 0 0;
189 border-right: @border-thickness solid @grey5;
189 border-right: @border-thickness solid @grey5;
190
190
191 @media (max-width:767px) {
191 @media (max-width:767px) {
192 clear: both;
192 clear: both;
193 width: 100%;
193 width: 100%;
194 border: none;
194 border: none;
195 }
195 }
196
196
197 ul {
197 ul {
198 padding-left: 1.25em;
198 padding-left: 1.25em;
199 }
199 }
200
200
201 &:last-child {
201 &:last-child {
202 margin: @sidebarpadding 0 0;
202 margin: @sidebarpadding 0 0;
203 border: none;
203 border: none;
204 }
204 }
205
205
206 h4 {
206 h4 {
207 margin: 0 0 @padding;
207 margin: 0 0 @padding;
208 font-family: @text-semibold;
208 font-family: @text-semibold;
209 }
209 }
210 }
210 }
211 }
211 }
212 .error-page-logo {
212 .error-page-logo {
213 width: 130px;
213 width: 130px;
214 height: 160px;
214 height: 160px;
215 }
215 }
216
216
217 // HEADER
217 // HEADER
218 .header {
218 .header {
219
219
220 // TODO: johbo: Fix login pages, so that they work without a min-height
220 // TODO: johbo: Fix login pages, so that they work without a min-height
221 // for the header and then remove the min-height. I chose a smaller value
221 // for the header and then remove the min-height. I chose a smaller value
222 // intentionally here to avoid rendering issues in the main navigation.
222 // intentionally here to avoid rendering issues in the main navigation.
223 min-height: 49px;
223 min-height: 49px;
224
224
225 position: relative;
225 position: relative;
226 vertical-align: bottom;
226 vertical-align: bottom;
227 padding: 0 @header-padding;
227 padding: 0 @header-padding;
228 background-color: @grey2;
228 background-color: @grey2;
229 color: @grey5;
229 color: @grey5;
230
230
231 .title {
231 .title {
232 overflow: visible;
232 overflow: visible;
233 }
233 }
234
234
235 &:before,
235 &:before,
236 &:after {
236 &:after {
237 content: "";
237 content: "";
238 clear: both;
238 clear: both;
239 width: 100%;
239 width: 100%;
240 }
240 }
241
241
242 // TODO: johbo: Avoids breaking "Repositories" chooser
242 // TODO: johbo: Avoids breaking "Repositories" chooser
243 .select2-container .select2-choice .select2-arrow {
243 .select2-container .select2-choice .select2-arrow {
244 display: none;
244 display: none;
245 }
245 }
246 }
246 }
247
247
248 #header-inner {
248 #header-inner {
249 &.title {
249 &.title {
250 margin: 0;
250 margin: 0;
251 }
251 }
252 &:before,
252 &:before,
253 &:after {
253 &:after {
254 content: "";
254 content: "";
255 clear: both;
255 clear: both;
256 }
256 }
257 }
257 }
258
258
259 // Gists
259 // Gists
260 #files_data {
260 #files_data {
261 clear: both; //for firefox
261 clear: both; //for firefox
262 }
262 }
263 #gistid {
263 #gistid {
264 margin-right: @padding;
264 margin-right: @padding;
265 }
265 }
266
266
267 // Global Settings Editor
267 // Global Settings Editor
268 .textarea.editor {
268 .textarea.editor {
269 float: left;
269 float: left;
270 position: relative;
270 position: relative;
271 max-width: @texteditor-width;
271 max-width: @texteditor-width;
272
272
273 select {
273 select {
274 position: absolute;
274 position: absolute;
275 top:10px;
275 top:10px;
276 right:0;
276 right:0;
277 }
277 }
278
278
279 .CodeMirror {
279 .CodeMirror {
280 margin: 0;
280 margin: 0;
281 }
281 }
282
282
283 .help-block {
283 .help-block {
284 margin: 0 0 @padding;
284 margin: 0 0 @padding;
285 padding:.5em;
285 padding:.5em;
286 background-color: @grey6;
286 background-color: @grey6;
287 }
287 }
288 }
288 }
289
289
290 ul.auth_plugins {
290 ul.auth_plugins {
291 margin: @padding 0 @padding @legend-width;
291 margin: @padding 0 @padding @legend-width;
292 padding: 0;
292 padding: 0;
293
293
294 li {
294 li {
295 margin-bottom: @padding;
295 margin-bottom: @padding;
296 line-height: 1em;
296 line-height: 1em;
297 list-style-type: none;
297 list-style-type: none;
298
298
299 .auth_buttons .btn {
299 .auth_buttons .btn {
300 margin-right: @padding;
300 margin-right: @padding;
301 }
301 }
302
302
303 &:before { content: none; }
303 &:before { content: none; }
304 }
304 }
305 }
305 }
306
306
307
307
308 // My Account PR list
308 // My Account PR list
309
309
310 #show_closed {
310 #show_closed {
311 margin: 0 1em 0 0;
311 margin: 0 1em 0 0;
312 }
312 }
313
313
314 .pullrequestlist {
314 .pullrequestlist {
315 .closed {
315 .closed {
316 background-color: @grey6;
316 background-color: @grey6;
317 }
317 }
318 .td-status {
318 .td-status {
319 padding-left: .5em;
319 padding-left: .5em;
320 }
320 }
321 .log-container .truncate {
321 .log-container .truncate {
322 height: 2.75em;
322 height: 2.75em;
323 white-space: pre-line;
323 white-space: pre-line;
324 }
324 }
325 table.rctable .user {
325 table.rctable .user {
326 padding-left: 0;
326 padding-left: 0;
327 }
327 }
328 table.rctable {
328 table.rctable {
329 td.td-description,
329 td.td-description,
330 .rc-user {
330 .rc-user {
331 min-width: auto;
331 min-width: auto;
332 }
332 }
333 }
333 }
334 }
334 }
335
335
336 // Pull Requests
336 // Pull Requests
337
337
338 .pullrequests_section_head {
338 .pullrequests_section_head {
339 display: block;
339 display: block;
340 clear: both;
340 clear: both;
341 margin: @padding 0;
341 margin: @padding 0;
342 font-family: @text-bold;
342 font-family: @text-bold;
343 }
343 }
344
344
345 .pr-origininfo, .pr-targetinfo {
345 .pr-origininfo, .pr-targetinfo {
346 position: relative;
346 position: relative;
347
347
348 .tag {
348 .tag {
349 display: inline-block;
349 display: inline-block;
350 margin: 0 1em .5em 0;
350 margin: 0 1em .5em 0;
351 }
351 }
352
352
353 .clone-url {
353 .clone-url {
354 display: inline-block;
354 display: inline-block;
355 margin: 0 0 .5em 0;
355 margin: 0 0 .5em 0;
356 padding: 0;
356 padding: 0;
357 line-height: 1.2em;
357 line-height: 1.2em;
358 }
358 }
359 }
359 }
360
360
361 .pr-pullinfo {
361 .pr-pullinfo {
362 clear: both;
362 clear: both;
363 margin: .5em 0;
363 margin: .5em 0;
364 }
364 }
365
365
366 #pr-title-input {
366 #pr-title-input {
367 width: 72%;
367 width: 72%;
368 font-size: 1em;
368 font-size: 1em;
369 font-family: @text-bold;
369 font-family: @text-bold;
370 margin: 0;
370 margin: 0;
371 padding: 0 0 0 @padding/4;
371 padding: 0 0 0 @padding/4;
372 line-height: 1.7em;
372 line-height: 1.7em;
373 color: @text-color;
373 color: @text-color;
374 letter-spacing: .02em;
374 letter-spacing: .02em;
375 }
375 }
376
376
377 #pullrequest_title {
377 #pullrequest_title {
378 width: 100%;
378 width: 100%;
379 box-sizing: border-box;
379 box-sizing: border-box;
380 }
380 }
381
381
382 #pr_open_message {
382 #pr_open_message {
383 border: @border-thickness solid #fff;
383 border: @border-thickness solid #fff;
384 border-radius: @border-radius;
384 border-radius: @border-radius;
385 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
385 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
386 text-align: right;
386 text-align: right;
387 overflow: hidden;
387 overflow: hidden;
388 }
388 }
389
389
390 .pr-submit-button {
390 .pr-submit-button {
391 float: right;
391 float: right;
392 margin: 0 0 0 5px;
392 margin: 0 0 0 5px;
393 }
393 }
394
394
395 .pr-spacing-container {
395 .pr-spacing-container {
396 padding: 20px;
396 padding: 20px;
397 clear: both
397 clear: both
398 }
398 }
399
399
400 #pr-description-input {
400 #pr-description-input {
401 margin-bottom: 0;
401 margin-bottom: 0;
402 }
402 }
403
403
404 .pr-description-label {
404 .pr-description-label {
405 vertical-align: top;
405 vertical-align: top;
406 }
406 }
407
407
408 .perms_section_head {
408 .perms_section_head {
409 min-width: 625px;
409 min-width: 625px;
410
410
411 h2 {
411 h2 {
412 margin-bottom: 0;
412 margin-bottom: 0;
413 }
413 }
414
414
415 .label-checkbox {
415 .label-checkbox {
416 float: left;
416 float: left;
417 }
417 }
418
418
419 &.field {
419 &.field {
420 margin: @space 0 @padding;
420 margin: @space 0 @padding;
421 }
421 }
422
422
423 &:first-child.field {
423 &:first-child.field {
424 margin-top: 0;
424 margin-top: 0;
425
425
426 .label {
426 .label {
427 margin-top: 0;
427 margin-top: 0;
428 padding-top: 0;
428 padding-top: 0;
429 }
429 }
430
430
431 .radios {
431 .radios {
432 padding-top: 0;
432 padding-top: 0;
433 }
433 }
434 }
434 }
435
435
436 .radios {
436 .radios {
437 float: right;
437 float: right;
438 position: relative;
438 position: relative;
439 width: 405px;
439 width: 405px;
440 }
440 }
441 }
441 }
442
442
443 //--- MODULES ------------------//
443 //--- MODULES ------------------//
444
444
445
445
446 // Server Announcement
446 // Server Announcement
447 #server-announcement {
447 #server-announcement {
448 width: 95%;
448 width: 95%;
449 margin: @padding auto;
449 margin: @padding auto;
450 padding: @padding;
450 padding: @padding;
451 border-width: 2px;
451 border-width: 2px;
452 border-style: solid;
452 border-style: solid;
453 .border-radius(2px);
453 .border-radius(2px);
454 font-family: @text-bold;
454 font-family: @text-bold;
455
455
456 &.info { border-color: @alert4; background-color: @alert4-inner; }
456 &.info { border-color: @alert4; background-color: @alert4-inner; }
457 &.warning { border-color: @alert3; background-color: @alert3-inner; }
457 &.warning { border-color: @alert3; background-color: @alert3-inner; }
458 &.error { border-color: @alert2; background-color: @alert2-inner; }
458 &.error { border-color: @alert2; background-color: @alert2-inner; }
459 &.success { border-color: @alert1; background-color: @alert1-inner; }
459 &.success { border-color: @alert1; background-color: @alert1-inner; }
460 &.neutral { border-color: @grey3; background-color: @grey6; }
460 &.neutral { border-color: @grey3; background-color: @grey6; }
461 }
461 }
462
462
463 // Fixed Sidebar Column
463 // Fixed Sidebar Column
464 .sidebar-col-wrapper {
464 .sidebar-col-wrapper {
465 padding-left: @sidebar-all-width;
465 padding-left: @sidebar-all-width;
466
466
467 .sidebar {
467 .sidebar {
468 width: @sidebar-width;
468 width: @sidebar-width;
469 margin-left: -@sidebar-all-width;
469 margin-left: -@sidebar-all-width;
470 }
470 }
471 }
471 }
472
472
473 .sidebar-col-wrapper.scw-small {
473 .sidebar-col-wrapper.scw-small {
474 padding-left: @sidebar-small-all-width;
474 padding-left: @sidebar-small-all-width;
475
475
476 .sidebar {
476 .sidebar {
477 width: @sidebar-small-width;
477 width: @sidebar-small-width;
478 margin-left: -@sidebar-small-all-width;
478 margin-left: -@sidebar-small-all-width;
479 }
479 }
480 }
480 }
481
481
482
482
483 // FOOTER
483 // FOOTER
484 #footer {
484 #footer {
485 padding: 0;
485 padding: 0;
486 text-align: center;
486 text-align: center;
487 vertical-align: middle;
487 vertical-align: middle;
488 color: @grey2;
488 color: @grey2;
489 background-color: @grey6;
489 background-color: @grey6;
490
490
491 p {
491 p {
492 margin: 0;
492 margin: 0;
493 padding: 1em;
493 padding: 1em;
494 line-height: 1em;
494 line-height: 1em;
495 }
495 }
496
496
497 .server-instance { //server instance
497 .server-instance { //server instance
498 display: none;
498 display: none;
499 }
499 }
500
500
501 .title {
501 .title {
502 float: none;
502 float: none;
503 margin: 0 auto;
503 margin: 0 auto;
504 }
504 }
505 }
505 }
506
506
507 button.close {
507 button.close {
508 padding: 0;
508 padding: 0;
509 cursor: pointer;
509 cursor: pointer;
510 background: transparent;
510 background: transparent;
511 border: 0;
511 border: 0;
512 .box-shadow(none);
512 .box-shadow(none);
513 -webkit-appearance: none;
513 -webkit-appearance: none;
514 }
514 }
515
515
516 .close {
516 .close {
517 float: right;
517 float: right;
518 font-size: 21px;
518 font-size: 21px;
519 font-family: @text-bootstrap;
519 font-family: @text-bootstrap;
520 line-height: 1em;
520 line-height: 1em;
521 font-weight: bold;
521 font-weight: bold;
522 color: @grey2;
522 color: @grey2;
523
523
524 &:hover,
524 &:hover,
525 &:focus {
525 &:focus {
526 color: @grey1;
526 color: @grey1;
527 text-decoration: none;
527 text-decoration: none;
528 cursor: pointer;
528 cursor: pointer;
529 }
529 }
530 }
530 }
531
531
532 // GRID
532 // GRID
533 .sorting,
533 .sorting,
534 .sorting_desc,
534 .sorting_desc,
535 .sorting_asc {
535 .sorting_asc {
536 cursor: pointer;
536 cursor: pointer;
537 }
537 }
538 .sorting_desc:after {
538 .sorting_desc:after {
539 content: "\00A0\25B2";
539 content: "\00A0\25B2";
540 font-size: .75em;
540 font-size: .75em;
541 }
541 }
542 .sorting_asc:after {
542 .sorting_asc:after {
543 content: "\00A0\25BC";
543 content: "\00A0\25BC";
544 font-size: .68em;
544 font-size: .68em;
545 }
545 }
546
546
547
547
548 .user_auth_tokens {
548 .user_auth_tokens {
549
549
550 &.truncate {
550 &.truncate {
551 white-space: nowrap;
551 white-space: nowrap;
552 overflow: hidden;
552 overflow: hidden;
553 text-overflow: ellipsis;
553 text-overflow: ellipsis;
554 }
554 }
555
555
556 .fields .field .input {
556 .fields .field .input {
557 margin: 0;
557 margin: 0;
558 }
558 }
559
559
560 input#description {
560 input#description {
561 width: 100px;
561 width: 100px;
562 margin: 0;
562 margin: 0;
563 }
563 }
564
564
565 .drop-menu {
565 .drop-menu {
566 // TODO: johbo: Remove this, should work out of the box when
566 // TODO: johbo: Remove this, should work out of the box when
567 // having multiple inputs inline
567 // having multiple inputs inline
568 margin: 0 0 0 5px;
568 margin: 0 0 0 5px;
569 }
569 }
570 }
570 }
571 #user_list_table {
571 #user_list_table {
572 .closed {
572 .closed {
573 background-color: @grey6;
573 background-color: @grey6;
574 }
574 }
575 }
575 }
576
576
577
577
578 input {
578 input {
579 &.disabled {
579 &.disabled {
580 opacity: .5;
580 opacity: .5;
581 }
581 }
582 }
582 }
583
583
584 // remove extra padding in firefox
584 // remove extra padding in firefox
585 input::-moz-focus-inner { border:0; padding:0 }
585 input::-moz-focus-inner { border:0; padding:0 }
586
586
587 .adjacent input {
587 .adjacent input {
588 margin-bottom: @padding;
588 margin-bottom: @padding;
589 }
589 }
590
590
591 .permissions_boxes {
591 .permissions_boxes {
592 display: block;
592 display: block;
593 }
593 }
594
594
595 //TODO: lisa: this should be in tables
595 //TODO: lisa: this should be in tables
596 .show_more_col {
596 .show_more_col {
597 width: 20px;
597 width: 20px;
598 }
598 }
599
599
600 //FORMS
600 //FORMS
601
601
602 .medium-inline,
602 .medium-inline,
603 input#description.medium-inline {
603 input#description.medium-inline {
604 display: inline;
604 display: inline;
605 width: @medium-inline-input-width;
605 width: @medium-inline-input-width;
606 min-width: 100px;
606 min-width: 100px;
607 }
607 }
608
608
609 select {
609 select {
610 //reset
610 //reset
611 -webkit-appearance: none;
611 -webkit-appearance: none;
612 -moz-appearance: none;
612 -moz-appearance: none;
613
613
614 display: inline-block;
614 display: inline-block;
615 height: 28px;
615 height: 28px;
616 width: auto;
616 width: auto;
617 margin: 0 @padding @padding 0;
617 margin: 0 @padding @padding 0;
618 padding: 0 18px 0 8px;
618 padding: 0 18px 0 8px;
619 line-height:1em;
619 line-height:1em;
620 font-size: @basefontsize;
620 font-size: @basefontsize;
621 border: @border-thickness solid @rcblue;
621 border: @border-thickness solid @rcblue;
622 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
622 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
623 color: @rcblue;
623 color: @rcblue;
624
624
625 &:after {
625 &:after {
626 content: "\00A0\25BE";
626 content: "\00A0\25BE";
627 }
627 }
628
628
629 &:focus {
629 &:focus {
630 outline: none;
630 outline: none;
631 }
631 }
632 }
632 }
633
633
634 option {
634 option {
635 &:focus {
635 &:focus {
636 outline: none;
636 outline: none;
637 }
637 }
638 }
638 }
639
639
640 input,
640 input,
641 textarea {
641 textarea {
642 padding: @input-padding;
642 padding: @input-padding;
643 border: @input-border-thickness solid @border-highlight-color;
643 border: @input-border-thickness solid @border-highlight-color;
644 .border-radius (@border-radius);
644 .border-radius (@border-radius);
645 font-family: @text-light;
645 font-family: @text-light;
646 font-size: @basefontsize;
646 font-size: @basefontsize;
647
647
648 &.input-sm {
648 &.input-sm {
649 padding: 5px;
649 padding: 5px;
650 }
650 }
651
651
652 &#description {
652 &#description {
653 min-width: @input-description-minwidth;
653 min-width: @input-description-minwidth;
654 min-height: 1em;
654 min-height: 1em;
655 padding: 10px;
655 padding: 10px;
656 }
656 }
657 }
657 }
658
658
659 .field-sm {
659 .field-sm {
660 input,
660 input,
661 textarea {
661 textarea {
662 padding: 5px;
662 padding: 5px;
663 }
663 }
664 }
664 }
665
665
666 textarea {
666 textarea {
667 display: block;
667 display: block;
668 clear: both;
668 clear: both;
669 width: 100%;
669 width: 100%;
670 min-height: 100px;
670 min-height: 100px;
671 margin-bottom: @padding;
671 margin-bottom: @padding;
672 .box-sizing(border-box);
672 .box-sizing(border-box);
673 overflow: auto;
673 overflow: auto;
674 }
674 }
675
675
676 label {
676 label {
677 font-family: @text-light;
677 font-family: @text-light;
678 }
678 }
679
679
680 // GRAVATARS
680 // GRAVATARS
681 // centers gravatar on username to the right
681 // centers gravatar on username to the right
682
682
683 .gravatar {
683 .gravatar {
684 display: inline;
684 display: inline;
685 min-width: 16px;
685 min-width: 16px;
686 min-height: 16px;
686 min-height: 16px;
687 margin: -5px 0;
687 margin: -5px 0;
688 padding: 0;
688 padding: 0;
689 line-height: 1em;
689 line-height: 1em;
690 border: 1px solid @grey4;
690 border: 1px solid @grey4;
691
691
692 &.gravatar-large {
692 &.gravatar-large {
693 margin: -0.5em .25em -0.5em 0;
693 margin: -0.5em .25em -0.5em 0;
694 }
694 }
695
695
696 & + .user {
696 & + .user {
697 display: inline;
697 display: inline;
698 margin: 0;
698 margin: 0;
699 padding: 0 0 0 .17em;
699 padding: 0 0 0 .17em;
700 line-height: 1em;
700 line-height: 1em;
701 }
701 }
702 }
702 }
703
703
704 .user-inline-data {
704 .user-inline-data {
705 display: inline-block;
705 display: inline-block;
706 float: left;
706 float: left;
707 padding-left: .5em;
707 padding-left: .5em;
708 line-height: 1.3em;
708 line-height: 1.3em;
709 }
709 }
710
710
711 .rc-user { // gravatar + user wrapper
711 .rc-user { // gravatar + user wrapper
712 float: left;
712 float: left;
713 position: relative;
713 position: relative;
714 min-width: 100px;
714 min-width: 100px;
715 max-width: 200px;
715 max-width: 200px;
716 min-height: (@gravatar-size + @border-thickness * 2); // account for border
716 min-height: (@gravatar-size + @border-thickness * 2); // account for border
717 display: block;
717 display: block;
718 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
718 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
719
719
720
720
721 .gravatar {
721 .gravatar {
722 display: block;
722 display: block;
723 position: absolute;
723 position: absolute;
724 top: 0;
724 top: 0;
725 left: 0;
725 left: 0;
726 min-width: @gravatar-size;
726 min-width: @gravatar-size;
727 min-height: @gravatar-size;
727 min-height: @gravatar-size;
728 margin: 0;
728 margin: 0;
729 }
729 }
730
730
731 .user {
731 .user {
732 display: block;
732 display: block;
733 max-width: 175px;
733 max-width: 175px;
734 padding-top: 2px;
734 padding-top: 2px;
735 overflow: hidden;
735 overflow: hidden;
736 text-overflow: ellipsis;
736 text-overflow: ellipsis;
737 }
737 }
738 }
738 }
739
739
740 .gist-gravatar,
740 .gist-gravatar,
741 .journal_container {
741 .journal_container {
742 .gravatar-large {
742 .gravatar-large {
743 margin: 0 .5em -10px 0;
743 margin: 0 .5em -10px 0;
744 }
744 }
745 }
745 }
746
746
747
747
748 // ADMIN SETTINGS
748 // ADMIN SETTINGS
749
749
750 // Tag Patterns
750 // Tag Patterns
751 .tag_patterns {
751 .tag_patterns {
752 .tag_input {
752 .tag_input {
753 margin-bottom: @padding;
753 margin-bottom: @padding;
754 }
754 }
755 }
755 }
756
756
757 .locked_input {
757 .locked_input {
758 position: relative;
758 position: relative;
759
759
760 input {
760 input {
761 display: inline;
761 display: inline;
762 margin-top: 3px;
762 margin-top: 3px;
763 }
763 }
764
764
765 br {
765 br {
766 display: none;
766 display: none;
767 }
767 }
768
768
769 .error-message {
769 .error-message {
770 float: left;
770 float: left;
771 width: 100%;
771 width: 100%;
772 }
772 }
773
773
774 .lock_input_button {
774 .lock_input_button {
775 display: inline;
775 display: inline;
776 }
776 }
777
777
778 .help-block {
778 .help-block {
779 clear: both;
779 clear: both;
780 }
780 }
781 }
781 }
782
782
783 // Notifications
783 // Notifications
784
784
785 .notifications_buttons {
785 .notifications_buttons {
786 margin: 0 0 @space 0;
786 margin: 0 0 @space 0;
787 padding: 0;
787 padding: 0;
788
788
789 .btn {
789 .btn {
790 display: inline-block;
790 display: inline-block;
791 }
791 }
792 }
792 }
793
793
794 .notification-list {
794 .notification-list {
795
795
796 div {
796 div {
797 display: inline-block;
797 display: inline-block;
798 vertical-align: middle;
798 vertical-align: middle;
799 }
799 }
800
800
801 .container {
801 .container {
802 display: block;
802 display: block;
803 margin: 0 0 @padding 0;
803 margin: 0 0 @padding 0;
804 }
804 }
805
805
806 .delete-notifications {
806 .delete-notifications {
807 margin-left: @padding;
807 margin-left: @padding;
808 text-align: right;
808 text-align: right;
809 cursor: pointer;
809 cursor: pointer;
810 }
810 }
811
811
812 .read-notifications {
812 .read-notifications {
813 margin-left: @padding/2;
813 margin-left: @padding/2;
814 text-align: right;
814 text-align: right;
815 width: 35px;
815 width: 35px;
816 cursor: pointer;
816 cursor: pointer;
817 }
817 }
818
818
819 .icon-minus-sign {
819 .icon-minus-sign {
820 color: @alert2;
820 color: @alert2;
821 }
821 }
822
822
823 .icon-ok-sign {
823 .icon-ok-sign {
824 color: @alert1;
824 color: @alert1;
825 }
825 }
826 }
826 }
827
827
828 .user_settings {
828 .user_settings {
829 float: left;
829 float: left;
830 clear: both;
830 clear: both;
831 display: block;
831 display: block;
832 width: 100%;
832 width: 100%;
833
833
834 .gravatar_box {
834 .gravatar_box {
835 margin-bottom: @padding;
835 margin-bottom: @padding;
836
836
837 &:after {
837 &:after {
838 content: " ";
838 content: " ";
839 clear: both;
839 clear: both;
840 width: 100%;
840 width: 100%;
841 }
841 }
842 }
842 }
843
843
844 .fields .field {
844 .fields .field {
845 clear: both;
845 clear: both;
846 }
846 }
847 }
847 }
848
848
849 .advanced_settings {
849 .advanced_settings {
850 margin-bottom: @space;
850 margin-bottom: @space;
851
851
852 .help-block {
852 .help-block {
853 margin-left: 0;
853 margin-left: 0;
854 }
854 }
855
855
856 button + .help-block {
856 button + .help-block {
857 margin-top: @padding;
857 margin-top: @padding;
858 }
858 }
859 }
859 }
860
860
861 // admin settings radio buttons and labels
861 // admin settings radio buttons and labels
862 .label-2 {
862 .label-2 {
863 float: left;
863 float: left;
864 width: @label2-width;
864 width: @label2-width;
865
865
866 label {
866 label {
867 color: @grey1;
867 color: @grey1;
868 }
868 }
869 }
869 }
870 .checkboxes {
870 .checkboxes {
871 float: left;
871 float: left;
872 width: @checkboxes-width;
872 width: @checkboxes-width;
873 margin-bottom: @padding;
873 margin-bottom: @padding;
874
874
875 .checkbox {
875 .checkbox {
876 width: 100%;
876 width: 100%;
877
877
878 label {
878 label {
879 margin: 0;
879 margin: 0;
880 padding: 0;
880 padding: 0;
881 }
881 }
882 }
882 }
883
883
884 .checkbox + .checkbox {
884 .checkbox + .checkbox {
885 display: inline-block;
885 display: inline-block;
886 }
886 }
887
887
888 label {
888 label {
889 margin-right: 1em;
889 margin-right: 1em;
890 }
890 }
891 }
891 }
892
892
893 // CHANGELOG
893 // CHANGELOG
894 .container_header {
894 .container_header {
895 float: left;
895 float: left;
896 display: block;
896 display: block;
897 width: 100%;
897 width: 100%;
898 margin: @padding 0 @padding;
898 margin: @padding 0 @padding;
899
899
900 #filter_changelog {
900 #filter_changelog {
901 float: left;
901 float: left;
902 margin-right: @padding;
902 margin-right: @padding;
903 }
903 }
904
904
905 .breadcrumbs_light {
905 .breadcrumbs_light {
906 display: inline-block;
906 display: inline-block;
907 }
907 }
908 }
908 }
909
909
910 .info_box {
910 .info_box {
911 float: right;
911 float: right;
912 }
912 }
913
913
914
914
915 #graph_nodes {
915 #graph_nodes {
916 padding-top: 43px;
916 padding-top: 43px;
917 }
917 }
918
918
919 #graph_content{
919 #graph_content{
920
920
921 // adjust for table headers so that graph renders properly
921 // adjust for table headers so that graph renders properly
922 // #graph_nodes padding - table cell padding
922 // #graph_nodes padding - table cell padding
923 padding-top: (@space - (@basefontsize * 2.4));
923 padding-top: (@space - (@basefontsize * 2.4));
924
924
925 &.graph_full_width {
925 &.graph_full_width {
926 width: 100%;
926 width: 100%;
927 max-width: 100%;
927 max-width: 100%;
928 }
928 }
929 }
929 }
930
930
931 #graph {
931 #graph {
932 .flag_status {
932 .flag_status {
933 margin: 0;
933 margin: 0;
934 }
934 }
935
935
936 .pagination-left {
936 .pagination-left {
937 float: left;
937 float: left;
938 clear: both;
938 clear: both;
939 }
939 }
940
940
941 .log-container {
941 .log-container {
942 max-width: 345px;
942 max-width: 345px;
943
943
944 .message{
944 .message{
945 max-width: 340px;
945 max-width: 340px;
946 }
946 }
947 }
947 }
948
948
949 .graph-col-wrapper {
949 .graph-col-wrapper {
950 padding-left: 110px;
950 padding-left: 110px;
951
951
952 #graph_nodes {
952 #graph_nodes {
953 width: 100px;
953 width: 100px;
954 margin-left: -110px;
954 margin-left: -110px;
955 float: left;
955 float: left;
956 clear: left;
956 clear: left;
957 }
957 }
958 }
958 }
959 }
959 }
960
960
961 #filter_changelog {
961 #filter_changelog {
962 float: left;
962 float: left;
963 }
963 }
964
964
965
965
966 //--- THEME ------------------//
966 //--- THEME ------------------//
967
967
968 #logo {
968 #logo {
969 float: left;
969 float: left;
970 margin: 9px 0 0 0;
970 margin: 9px 0 0 0;
971
971
972 .header {
972 .header {
973 background-color: transparent;
973 background-color: transparent;
974 }
974 }
975
975
976 a {
976 a {
977 display: inline-block;
977 display: inline-block;
978 }
978 }
979
979
980 img {
980 img {
981 height:30px;
981 height:30px;
982 }
982 }
983 }
983 }
984
984
985 .logo-wrapper {
985 .logo-wrapper {
986 float:left;
986 float:left;
987 }
987 }
988
988
989 .branding{
989 .branding{
990 float: left;
990 float: left;
991 padding: 9px 2px;
991 padding: 9px 2px;
992 line-height: 1em;
992 line-height: 1em;
993 font-size: @navigation-fontsize;
993 font-size: @navigation-fontsize;
994 }
994 }
995
995
996 img {
996 img {
997 border: none;
997 border: none;
998 outline: none;
998 outline: none;
999 }
999 }
1000 user-profile-header
1000 user-profile-header
1001 label {
1001 label {
1002
1002
1003 input[type="checkbox"] {
1003 input[type="checkbox"] {
1004 margin-right: 1em;
1004 margin-right: 1em;
1005 }
1005 }
1006 input[type="radio"] {
1006 input[type="radio"] {
1007 margin-right: 1em;
1007 margin-right: 1em;
1008 }
1008 }
1009 }
1009 }
1010
1010
1011 .flag_status {
1011 .flag_status {
1012 margin: 2px 8px 6px 2px;
1012 margin: 2px 8px 6px 2px;
1013 &.under_review {
1013 &.under_review {
1014 .circle(5px, @alert3);
1014 .circle(5px, @alert3);
1015 }
1015 }
1016 &.approved {
1016 &.approved {
1017 .circle(5px, @alert1);
1017 .circle(5px, @alert1);
1018 }
1018 }
1019 &.rejected,
1019 &.rejected,
1020 &.forced_closed{
1020 &.forced_closed{
1021 .circle(5px, @alert2);
1021 .circle(5px, @alert2);
1022 }
1022 }
1023 &.not_reviewed {
1023 &.not_reviewed {
1024 .circle(5px, @grey5);
1024 .circle(5px, @grey5);
1025 }
1025 }
1026 }
1026 }
1027
1027
1028 .flag_status_comment_box {
1028 .flag_status_comment_box {
1029 margin: 5px 6px 0px 2px;
1029 margin: 5px 6px 0px 2px;
1030 }
1030 }
1031 .test_pattern_preview {
1031 .test_pattern_preview {
1032 margin: @space 0;
1032 margin: @space 0;
1033
1033
1034 p {
1034 p {
1035 margin-bottom: 0;
1035 margin-bottom: 0;
1036 border-bottom: @border-thickness solid @border-default-color;
1036 border-bottom: @border-thickness solid @border-default-color;
1037 color: @grey3;
1037 color: @grey3;
1038 }
1038 }
1039
1039
1040 .btn {
1040 .btn {
1041 margin-bottom: @padding;
1041 margin-bottom: @padding;
1042 }
1042 }
1043 }
1043 }
1044 #test_pattern_result {
1044 #test_pattern_result {
1045 display: none;
1045 display: none;
1046 &:extend(pre);
1046 &:extend(pre);
1047 padding: .9em;
1047 padding: .9em;
1048 color: @grey3;
1048 color: @grey3;
1049 background-color: @grey7;
1049 background-color: @grey7;
1050 border-right: @border-thickness solid @border-default-color;
1050 border-right: @border-thickness solid @border-default-color;
1051 border-bottom: @border-thickness solid @border-default-color;
1051 border-bottom: @border-thickness solid @border-default-color;
1052 border-left: @border-thickness solid @border-default-color;
1052 border-left: @border-thickness solid @border-default-color;
1053 }
1053 }
1054
1054
1055 #repo_vcs_settings {
1055 #repo_vcs_settings {
1056 #inherit_overlay_vcs_default {
1056 #inherit_overlay_vcs_default {
1057 display: none;
1057 display: none;
1058 }
1058 }
1059 #inherit_overlay_vcs_custom {
1059 #inherit_overlay_vcs_custom {
1060 display: custom;
1060 display: custom;
1061 }
1061 }
1062 &.inherited {
1062 &.inherited {
1063 #inherit_overlay_vcs_default {
1063 #inherit_overlay_vcs_default {
1064 display: block;
1064 display: block;
1065 }
1065 }
1066 #inherit_overlay_vcs_custom {
1066 #inherit_overlay_vcs_custom {
1067 display: none;
1067 display: none;
1068 }
1068 }
1069 }
1069 }
1070 }
1070 }
1071
1071
1072 .issue-tracker-link {
1072 .issue-tracker-link {
1073 color: @rcblue;
1073 color: @rcblue;
1074 }
1074 }
1075
1075
1076 // Issue Tracker Table Show/Hide
1076 // Issue Tracker Table Show/Hide
1077 #repo_issue_tracker {
1077 #repo_issue_tracker {
1078 #inherit_overlay {
1078 #inherit_overlay {
1079 display: none;
1079 display: none;
1080 }
1080 }
1081 #custom_overlay {
1081 #custom_overlay {
1082 display: custom;
1082 display: custom;
1083 }
1083 }
1084 &.inherited {
1084 &.inherited {
1085 #inherit_overlay {
1085 #inherit_overlay {
1086 display: block;
1086 display: block;
1087 }
1087 }
1088 #custom_overlay {
1088 #custom_overlay {
1089 display: none;
1089 display: none;
1090 }
1090 }
1091 }
1091 }
1092 }
1092 }
1093 table.issuetracker {
1093 table.issuetracker {
1094 &.readonly {
1094 &.readonly {
1095 tr, td {
1095 tr, td {
1096 color: @grey3;
1096 color: @grey3;
1097 }
1097 }
1098 }
1098 }
1099 .edit {
1099 .edit {
1100 display: none;
1100 display: none;
1101 }
1101 }
1102 .editopen {
1102 .editopen {
1103 .edit {
1103 .edit {
1104 display: inline;
1104 display: inline;
1105 }
1105 }
1106 .entry {
1106 .entry {
1107 display: none;
1107 display: none;
1108 }
1108 }
1109 }
1109 }
1110 tr td.td-action {
1110 tr td.td-action {
1111 min-width: 117px;
1111 min-width: 117px;
1112 }
1112 }
1113 td input {
1113 td input {
1114 max-width: none;
1114 max-width: none;
1115 min-width: 30px;
1115 min-width: 30px;
1116 width: 80%;
1116 width: 80%;
1117 }
1117 }
1118 .issuetracker_pref input {
1118 .issuetracker_pref input {
1119 width: 40%;
1119 width: 40%;
1120 }
1120 }
1121 input.edit_issuetracker_update {
1121 input.edit_issuetracker_update {
1122 margin-right: 0;
1122 margin-right: 0;
1123 width: auto;
1123 width: auto;
1124 }
1124 }
1125 }
1125 }
1126
1126
1127 table.integrations {
1127 table.integrations {
1128 .td-icon {
1128 .td-icon {
1129 width: 20px;
1129 width: 20px;
1130 .integration-icon {
1130 .integration-icon {
1131 height: 20px;
1131 height: 20px;
1132 width: 20px;
1132 width: 20px;
1133 }
1133 }
1134 }
1134 }
1135 }
1135 }
1136
1136
1137 .integrations {
1137 .integrations {
1138 a.integration-box {
1138 a.integration-box {
1139 color: @text-color;
1139 color: @text-color;
1140 &:hover {
1140 &:hover {
1141 .panel {
1141 .panel {
1142 background: #fbfbfb;
1142 background: #fbfbfb;
1143 }
1143 }
1144 }
1144 }
1145 .integration-icon {
1145 .integration-icon {
1146 width: 30px;
1146 width: 30px;
1147 height: 30px;
1147 height: 30px;
1148 margin-right: 20px;
1148 margin-right: 20px;
1149 float: left;
1149 float: left;
1150 }
1150 }
1151
1151
1152 .panel-body {
1152 .panel-body {
1153 padding: 10px;
1153 padding: 10px;
1154 }
1154 }
1155 .panel {
1155 .panel {
1156 margin-bottom: 10px;
1156 margin-bottom: 10px;
1157 }
1157 }
1158 h2 {
1158 h2 {
1159 display: inline-block;
1159 display: inline-block;
1160 margin: 0;
1160 margin: 0;
1161 min-width: 140px;
1161 min-width: 140px;
1162 }
1162 }
1163 }
1163 }
1164 }
1164 }
1165
1165
1166 //Permissions Settings
1166 //Permissions Settings
1167 #add_perm {
1167 #add_perm {
1168 margin: 0 0 @padding;
1168 margin: 0 0 @padding;
1169 cursor: pointer;
1169 cursor: pointer;
1170 }
1170 }
1171
1171
1172 .perm_ac {
1172 .perm_ac {
1173 input {
1173 input {
1174 width: 95%;
1174 width: 95%;
1175 }
1175 }
1176 }
1176 }
1177
1177
1178 .autocomplete-suggestions {
1178 .autocomplete-suggestions {
1179 width: auto !important; // overrides autocomplete.js
1179 width: auto !important; // overrides autocomplete.js
1180 margin: 0;
1180 margin: 0;
1181 border: @border-thickness solid @rcblue;
1181 border: @border-thickness solid @rcblue;
1182 border-radius: @border-radius;
1182 border-radius: @border-radius;
1183 color: @rcblue;
1183 color: @rcblue;
1184 background-color: white;
1184 background-color: white;
1185 }
1185 }
1186 .autocomplete-selected {
1186 .autocomplete-selected {
1187 background: #F0F0F0;
1187 background: #F0F0F0;
1188 }
1188 }
1189 .ac-container-wrap {
1189 .ac-container-wrap {
1190 margin: 0;
1190 margin: 0;
1191 padding: 8px;
1191 padding: 8px;
1192 border-bottom: @border-thickness solid @rclightblue;
1192 border-bottom: @border-thickness solid @rclightblue;
1193 list-style-type: none;
1193 list-style-type: none;
1194 cursor: pointer;
1194 cursor: pointer;
1195
1195
1196 &:hover {
1196 &:hover {
1197 background-color: @rclightblue;
1197 background-color: @rclightblue;
1198 }
1198 }
1199
1199
1200 img {
1200 img {
1201 height: @gravatar-size;
1201 height: @gravatar-size;
1202 width: @gravatar-size;
1202 width: @gravatar-size;
1203 margin-right: 1em;
1203 margin-right: 1em;
1204 }
1204 }
1205
1205
1206 strong {
1206 strong {
1207 font-weight: normal;
1207 font-weight: normal;
1208 }
1208 }
1209 }
1209 }
1210
1210
1211 // Settings Dropdown
1211 // Settings Dropdown
1212 .user-menu .container {
1212 .user-menu .container {
1213 padding: 0 4px;
1213 padding: 0 4px;
1214 margin: 0;
1214 margin: 0;
1215 }
1215 }
1216
1216
1217 .user-menu .gravatar {
1217 .user-menu .gravatar {
1218 cursor: pointer;
1218 cursor: pointer;
1219 }
1219 }
1220
1220
1221 .codeblock {
1221 .codeblock {
1222 margin-bottom: @padding;
1222 margin-bottom: @padding;
1223 clear: both;
1223 clear: both;
1224
1224
1225 .stats{
1225 .stats{
1226 overflow: hidden;
1226 overflow: hidden;
1227 }
1227 }
1228
1228
1229 .message{
1229 .message{
1230 textarea{
1230 textarea{
1231 margin: 0;
1231 margin: 0;
1232 }
1232 }
1233 }
1233 }
1234
1234
1235 .code-header {
1235 .code-header {
1236 .stats {
1236 .stats {
1237 line-height: 2em;
1237 line-height: 2em;
1238
1238
1239 .revision_id {
1239 .revision_id {
1240 margin-left: 0;
1240 margin-left: 0;
1241 }
1241 }
1242 .buttons {
1242 .buttons {
1243 padding-right: 0;
1243 padding-right: 0;
1244 }
1244 }
1245 }
1245 }
1246
1246
1247 .item{
1247 .item{
1248 margin-right: 0.5em;
1248 margin-right: 0.5em;
1249 }
1249 }
1250 }
1250 }
1251
1251
1252 #editor_container{
1252 #editor_container{
1253 position: relative;
1253 position: relative;
1254 margin: @padding;
1254 margin: @padding;
1255 }
1255 }
1256 }
1256 }
1257
1257
1258 #file_history_container {
1258 #file_history_container {
1259 display: none;
1259 display: none;
1260 }
1260 }
1261
1261
1262 .file-history-inner {
1262 .file-history-inner {
1263 margin-bottom: 10px;
1263 margin-bottom: 10px;
1264 }
1264 }
1265
1265
1266 // Pull Requests
1266 // Pull Requests
1267 .summary-details {
1267 .summary-details {
1268 width: 72%;
1268 width: 72%;
1269 }
1269 }
1270 .pr-summary {
1270 .pr-summary {
1271 border-bottom: @border-thickness solid @grey5;
1271 border-bottom: @border-thickness solid @grey5;
1272 margin-bottom: @space;
1272 margin-bottom: @space;
1273 }
1273 }
1274 .reviewers-title {
1274 .reviewers-title {
1275 width: 25%;
1275 width: 25%;
1276 min-width: 200px;
1276 min-width: 200px;
1277 }
1277 }
1278 .reviewers {
1278 .reviewers {
1279 width: 25%;
1279 width: 25%;
1280 min-width: 200px;
1280 min-width: 200px;
1281 }
1281 }
1282 .reviewers ul li {
1282 .reviewers ul li {
1283 position: relative;
1283 position: relative;
1284 width: 100%;
1284 width: 100%;
1285 margin-bottom: 8px;
1285 margin-bottom: 8px;
1286 }
1286 }
1287 .reviewers_member {
1287 .reviewers_member {
1288 width: 100%;
1288 width: 100%;
1289 overflow: auto;
1289 overflow: auto;
1290 }
1290 }
1291 .reviewer_reason {
1291 .reviewer_reason {
1292 padding-left: 20px;
1292 padding-left: 20px;
1293 }
1293 }
1294 .reviewer_status {
1294 .reviewer_status {
1295 display: inline-block;
1295 display: inline-block;
1296 vertical-align: top;
1296 vertical-align: top;
1297 width: 7%;
1297 width: 7%;
1298 min-width: 20px;
1298 min-width: 20px;
1299 height: 1.2em;
1299 height: 1.2em;
1300 margin-top: 3px;
1300 margin-top: 3px;
1301 line-height: 1em;
1301 line-height: 1em;
1302 }
1302 }
1303
1303
1304 .reviewer_name {
1304 .reviewer_name {
1305 display: inline-block;
1305 display: inline-block;
1306 max-width: 83%;
1306 max-width: 83%;
1307 padding-right: 20px;
1307 padding-right: 20px;
1308 vertical-align: middle;
1308 vertical-align: middle;
1309 line-height: 1;
1309 line-height: 1;
1310
1310
1311 .rc-user {
1311 .rc-user {
1312 min-width: 0;
1312 min-width: 0;
1313 margin: -2px 1em 0 0;
1313 margin: -2px 1em 0 0;
1314 }
1314 }
1315
1315
1316 .reviewer {
1316 .reviewer {
1317 float: left;
1317 float: left;
1318 }
1318 }
1319
1319
1320 &.to-delete {
1320 &.to-delete {
1321 .user,
1321 .user,
1322 .reviewer {
1322 .reviewer {
1323 text-decoration: line-through;
1323 text-decoration: line-through;
1324 }
1324 }
1325 }
1325 }
1326 }
1326 }
1327
1327
1328 .reviewer_member_remove {
1328 .reviewer_member_remove {
1329 position: absolute;
1329 position: absolute;
1330 right: 0;
1330 right: 0;
1331 top: 0;
1331 top: 0;
1332 width: 16px;
1332 width: 16px;
1333 margin-bottom: 10px;
1333 margin-bottom: 10px;
1334 padding: 0;
1334 padding: 0;
1335 color: black;
1335 color: black;
1336 }
1336 }
1337 .reviewer_member_status {
1337 .reviewer_member_status {
1338 margin-top: 5px;
1338 margin-top: 5px;
1339 }
1339 }
1340 .pr-summary #summary{
1340 .pr-summary #summary{
1341 width: 100%;
1341 width: 100%;
1342 }
1342 }
1343 .pr-summary .action_button:hover {
1343 .pr-summary .action_button:hover {
1344 border: 0;
1344 border: 0;
1345 cursor: pointer;
1345 cursor: pointer;
1346 }
1346 }
1347 .pr-details-title {
1347 .pr-details-title {
1348 padding-bottom: 8px;
1348 padding-bottom: 8px;
1349 border-bottom: @border-thickness solid @grey5;
1349 border-bottom: @border-thickness solid @grey5;
1350
1350
1351 .action_button.disabled {
1351 .action_button.disabled {
1352 color: @grey4;
1352 color: @grey4;
1353 cursor: inherit;
1353 cursor: inherit;
1354 }
1354 }
1355 .action_button {
1355 .action_button {
1356 color: @rcblue;
1356 color: @rcblue;
1357 }
1357 }
1358 }
1358 }
1359 .pr-details-content {
1359 .pr-details-content {
1360 margin-top: @textmargin;
1360 margin-top: @textmargin;
1361 margin-bottom: @textmargin;
1361 margin-bottom: @textmargin;
1362 }
1362 }
1363 .pr-description {
1363 .pr-description {
1364 white-space:pre-wrap;
1364 white-space:pre-wrap;
1365 }
1365 }
1366 .group_members {
1366 .group_members {
1367 margin-top: 0;
1367 margin-top: 0;
1368 padding: 0;
1368 padding: 0;
1369 list-style: outside none none;
1369 list-style: outside none none;
1370
1370
1371 img {
1371 img {
1372 height: @gravatar-size;
1372 height: @gravatar-size;
1373 width: @gravatar-size;
1373 width: @gravatar-size;
1374 margin-right: .5em;
1374 margin-right: .5em;
1375 margin-left: 3px;
1375 margin-left: 3px;
1376 }
1376 }
1377
1377
1378 .to-delete {
1378 .to-delete {
1379 .user {
1379 .user {
1380 text-decoration: line-through;
1380 text-decoration: line-through;
1381 }
1381 }
1382 }
1382 }
1383 }
1383 }
1384
1384
1385 .compare_view_commits_title {
1385 .compare_view_commits_title {
1386 .disabled {
1386 .disabled {
1387 cursor: inherit;
1387 cursor: inherit;
1388 &:hover{
1388 &:hover{
1389 background-color: inherit;
1389 background-color: inherit;
1390 color: inherit;
1390 color: inherit;
1391 }
1391 }
1392 }
1392 }
1393 }
1393 }
1394
1394
1395 // new entry in group_members
1395 // new entry in group_members
1396 .td-author-new-entry {
1396 .td-author-new-entry {
1397 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1397 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1398 }
1398 }
1399
1399
1400 .usergroup_member_remove {
1400 .usergroup_member_remove {
1401 width: 16px;
1401 width: 16px;
1402 margin-bottom: 10px;
1402 margin-bottom: 10px;
1403 padding: 0;
1403 padding: 0;
1404 color: black !important;
1404 color: black !important;
1405 cursor: pointer;
1405 cursor: pointer;
1406 }
1406 }
1407
1407
1408 .reviewer_ac .ac-input {
1408 .reviewer_ac .ac-input {
1409 width: 92%;
1409 width: 92%;
1410 margin-bottom: 1em;
1410 margin-bottom: 1em;
1411 }
1411 }
1412
1412
1413 .compare_view_commits tr{
1413 .compare_view_commits tr{
1414 height: 20px;
1414 height: 20px;
1415 }
1415 }
1416 .compare_view_commits td {
1416 .compare_view_commits td {
1417 vertical-align: top;
1417 vertical-align: top;
1418 padding-top: 10px;
1418 padding-top: 10px;
1419 }
1419 }
1420 .compare_view_commits .author {
1420 .compare_view_commits .author {
1421 margin-left: 5px;
1421 margin-left: 5px;
1422 }
1422 }
1423
1423
1424 .compare_view_files {
1424 .compare_view_files {
1425 width: 100%;
1425 width: 100%;
1426
1426
1427 td {
1427 td {
1428 vertical-align: middle;
1428 vertical-align: middle;
1429 }
1429 }
1430 }
1430 }
1431
1431
1432 .compare_view_filepath {
1432 .compare_view_filepath {
1433 color: @grey1;
1433 color: @grey1;
1434 }
1434 }
1435
1435
1436 .show_more {
1436 .show_more {
1437 display: inline-block;
1437 display: inline-block;
1438 position: relative;
1438 position: relative;
1439 vertical-align: middle;
1439 vertical-align: middle;
1440 width: 4px;
1440 width: 4px;
1441 height: @basefontsize;
1441 height: @basefontsize;
1442
1442
1443 &:after {
1443 &:after {
1444 content: "\00A0\25BE";
1444 content: "\00A0\25BE";
1445 display: inline-block;
1445 display: inline-block;
1446 width:10px;
1446 width:10px;
1447 line-height: 5px;
1447 line-height: 5px;
1448 font-size: 12px;
1448 font-size: 12px;
1449 cursor: pointer;
1449 cursor: pointer;
1450 }
1450 }
1451 }
1451 }
1452
1452
1453 .journal_more .show_more {
1453 .journal_more .show_more {
1454 display: inline;
1454 display: inline;
1455
1455
1456 &:after {
1456 &:after {
1457 content: none;
1457 content: none;
1458 }
1458 }
1459 }
1459 }
1460
1460
1461 .open .show_more:after,
1461 .open .show_more:after,
1462 .select2-dropdown-open .show_more:after {
1462 .select2-dropdown-open .show_more:after {
1463 .rotate(180deg);
1463 .rotate(180deg);
1464 margin-left: 4px;
1464 margin-left: 4px;
1465 }
1465 }
1466
1466
1467
1467
1468 .compare_view_commits .collapse_commit:after {
1468 .compare_view_commits .collapse_commit:after {
1469 cursor: pointer;
1469 cursor: pointer;
1470 content: "\00A0\25B4";
1470 content: "\00A0\25B4";
1471 margin-left: -3px;
1471 margin-left: -3px;
1472 font-size: 17px;
1472 font-size: 17px;
1473 color: @grey4;
1473 color: @grey4;
1474 }
1474 }
1475
1475
1476 .diff_links {
1476 .diff_links {
1477 margin-left: 8px;
1477 margin-left: 8px;
1478 }
1478 }
1479
1479
1480 div.ancestor {
1480 div.ancestor {
1481 margin: @padding 0;
1481 margin: @padding 0;
1482 line-height: 3.0em;
1482 line-height: 3.0em;
1483 }
1483 }
1484
1484
1485 .cs_icon_td input[type="checkbox"] {
1485 .cs_icon_td input[type="checkbox"] {
1486 display: none;
1486 display: none;
1487 }
1487 }
1488
1488
1489 .cs_icon_td .expand_file_icon:after {
1489 .cs_icon_td .expand_file_icon:after {
1490 cursor: pointer;
1490 cursor: pointer;
1491 content: "\00A0\25B6";
1491 content: "\00A0\25B6";
1492 font-size: 12px;
1492 font-size: 12px;
1493 color: @grey4;
1493 color: @grey4;
1494 }
1494 }
1495
1495
1496 .cs_icon_td .collapse_file_icon:after {
1496 .cs_icon_td .collapse_file_icon:after {
1497 cursor: pointer;
1497 cursor: pointer;
1498 content: "\00A0\25BC";
1498 content: "\00A0\25BC";
1499 font-size: 12px;
1499 font-size: 12px;
1500 color: @grey4;
1500 color: @grey4;
1501 }
1501 }
1502
1502
1503 /*new binary
1503 /*new binary
1504 NEW_FILENODE = 1
1504 NEW_FILENODE = 1
1505 DEL_FILENODE = 2
1505 DEL_FILENODE = 2
1506 MOD_FILENODE = 3
1506 MOD_FILENODE = 3
1507 RENAMED_FILENODE = 4
1507 RENAMED_FILENODE = 4
1508 COPIED_FILENODE = 5
1508 COPIED_FILENODE = 5
1509 CHMOD_FILENODE = 6
1509 CHMOD_FILENODE = 6
1510 BIN_FILENODE = 7
1510 BIN_FILENODE = 7
1511 */
1511 */
1512 .cs_files_expand {
1512 .cs_files_expand {
1513 font-size: @basefontsize + 5px;
1513 font-size: @basefontsize + 5px;
1514 line-height: 1.8em;
1514 line-height: 1.8em;
1515 float: right;
1515 float: right;
1516 }
1516 }
1517
1517
1518 .cs_files_expand span{
1518 .cs_files_expand span{
1519 color: @rcblue;
1519 color: @rcblue;
1520 cursor: pointer;
1520 cursor: pointer;
1521 }
1521 }
1522 .cs_files {
1522 .cs_files {
1523 clear: both;
1523 clear: both;
1524 padding-bottom: @padding;
1524 padding-bottom: @padding;
1525
1525
1526 .cur_cs {
1526 .cur_cs {
1527 margin: 10px 2px;
1527 margin: 10px 2px;
1528 font-weight: bold;
1528 font-weight: bold;
1529 }
1529 }
1530
1530
1531 .node {
1531 .node {
1532 float: left;
1532 float: left;
1533 }
1533 }
1534
1534
1535 .changes {
1535 .changes {
1536 float: right;
1536 float: right;
1537 color: white;
1537 color: white;
1538 font-size: @basefontsize - 4px;
1538 font-size: @basefontsize - 4px;
1539 margin-top: 4px;
1539 margin-top: 4px;
1540 opacity: 0.6;
1540 opacity: 0.6;
1541 filter: Alpha(opacity=60); /* IE8 and earlier */
1541 filter: Alpha(opacity=60); /* IE8 and earlier */
1542
1542
1543 .added {
1543 .added {
1544 background-color: @alert1;
1544 background-color: @alert1;
1545 float: left;
1545 float: left;
1546 text-align: center;
1546 text-align: center;
1547 }
1547 }
1548
1548
1549 .deleted {
1549 .deleted {
1550 background-color: @alert2;
1550 background-color: @alert2;
1551 float: left;
1551 float: left;
1552 text-align: center;
1552 text-align: center;
1553 }
1553 }
1554
1554
1555 .bin {
1555 .bin {
1556 background-color: @alert1;
1556 background-color: @alert1;
1557 text-align: center;
1557 text-align: center;
1558 }
1558 }
1559
1559
1560 /*new binary*/
1560 /*new binary*/
1561 .bin.bin1 {
1561 .bin.bin1 {
1562 background-color: @alert1;
1562 background-color: @alert1;
1563 text-align: center;
1563 text-align: center;
1564 }
1564 }
1565
1565
1566 /*deleted binary*/
1566 /*deleted binary*/
1567 .bin.bin2 {
1567 .bin.bin2 {
1568 background-color: @alert2;
1568 background-color: @alert2;
1569 text-align: center;
1569 text-align: center;
1570 }
1570 }
1571
1571
1572 /*mod binary*/
1572 /*mod binary*/
1573 .bin.bin3 {
1573 .bin.bin3 {
1574 background-color: @grey2;
1574 background-color: @grey2;
1575 text-align: center;
1575 text-align: center;
1576 }
1576 }
1577
1577
1578 /*rename file*/
1578 /*rename file*/
1579 .bin.bin4 {
1579 .bin.bin4 {
1580 background-color: @alert4;
1580 background-color: @alert4;
1581 text-align: center;
1581 text-align: center;
1582 }
1582 }
1583
1583
1584 /*copied file*/
1584 /*copied file*/
1585 .bin.bin5 {
1585 .bin.bin5 {
1586 background-color: @alert4;
1586 background-color: @alert4;
1587 text-align: center;
1587 text-align: center;
1588 }
1588 }
1589
1589
1590 /*chmod file*/
1590 /*chmod file*/
1591 .bin.bin6 {
1591 .bin.bin6 {
1592 background-color: @grey2;
1592 background-color: @grey2;
1593 text-align: center;
1593 text-align: center;
1594 }
1594 }
1595 }
1595 }
1596 }
1596 }
1597
1597
1598 .cs_files .cs_added, .cs_files .cs_A,
1598 .cs_files .cs_added, .cs_files .cs_A,
1599 .cs_files .cs_added, .cs_files .cs_M,
1599 .cs_files .cs_added, .cs_files .cs_M,
1600 .cs_files .cs_added, .cs_files .cs_D {
1600 .cs_files .cs_added, .cs_files .cs_D {
1601 height: 16px;
1601 height: 16px;
1602 padding-right: 10px;
1602 padding-right: 10px;
1603 margin-top: 7px;
1603 margin-top: 7px;
1604 text-align: left;
1604 text-align: left;
1605 }
1605 }
1606
1606
1607 .cs_icon_td {
1607 .cs_icon_td {
1608 min-width: 16px;
1608 min-width: 16px;
1609 width: 16px;
1609 width: 16px;
1610 }
1610 }
1611
1611
1612 .pull-request-merge {
1612 .pull-request-merge {
1613 padding: 10px 0;
1613 padding: 10px 0;
1614 margin-top: 10px;
1614 margin-top: 10px;
1615 margin-bottom: 20px;
1615 margin-bottom: 20px;
1616 }
1616 }
1617
1617
1618 .pull-request-merge .pull-request-wrap {
1618 .pull-request-merge .pull-request-wrap {
1619 height: 25px;
1619 height: 25px;
1620 padding: 5px 0;
1620 padding: 5px 0;
1621 }
1621 }
1622
1622
1623 .pull-request-merge span {
1623 .pull-request-merge span {
1624 margin-right: 10px;
1624 margin-right: 10px;
1625 }
1625 }
1626
1627 .pr-versions {
1628 position: relative;
1629 top: 6px;
1630 }
1631
1626 #close_pull_request {
1632 #close_pull_request {
1627 margin-right: 0px;
1633 margin-right: 0px;
1628 }
1634 }
1629
1635
1630 .empty_data {
1636 .empty_data {
1631 color: @grey4;
1637 color: @grey4;
1632 }
1638 }
1633
1639
1634 #changeset_compare_view_content {
1640 #changeset_compare_view_content {
1635 margin-bottom: @space;
1641 margin-bottom: @space;
1636 clear: both;
1642 clear: both;
1637 width: 100%;
1643 width: 100%;
1638 box-sizing: border-box;
1644 box-sizing: border-box;
1639 .border-radius(@border-radius);
1645 .border-radius(@border-radius);
1640
1646
1641 .help-block {
1647 .help-block {
1642 margin: @padding 0;
1648 margin: @padding 0;
1643 color: @text-color;
1649 color: @text-color;
1644 }
1650 }
1645
1651
1646 .empty_data {
1652 .empty_data {
1647 margin: @padding 0;
1653 margin: @padding 0;
1648 }
1654 }
1649
1655
1650 .alert {
1656 .alert {
1651 margin-bottom: @space;
1657 margin-bottom: @space;
1652 }
1658 }
1653 }
1659 }
1654
1660
1655 .table_disp {
1661 .table_disp {
1656 .status {
1662 .status {
1657 width: auto;
1663 width: auto;
1658
1664
1659 .flag_status {
1665 .flag_status {
1660 float: left;
1666 float: left;
1661 }
1667 }
1662 }
1668 }
1663 }
1669 }
1664
1670
1665 .status_box_menu {
1671 .status_box_menu {
1666 margin: 0;
1672 margin: 0;
1667 }
1673 }
1668
1674
1669 .notification-table{
1675 .notification-table{
1670 margin-bottom: @space;
1676 margin-bottom: @space;
1671 display: table;
1677 display: table;
1672 width: 100%;
1678 width: 100%;
1673
1679
1674 .container{
1680 .container{
1675 display: table-row;
1681 display: table-row;
1676
1682
1677 .notification-header{
1683 .notification-header{
1678 border-bottom: @border-thickness solid @border-default-color;
1684 border-bottom: @border-thickness solid @border-default-color;
1679 }
1685 }
1680
1686
1681 .notification-subject{
1687 .notification-subject{
1682 display: table-cell;
1688 display: table-cell;
1683 }
1689 }
1684 }
1690 }
1685 }
1691 }
1686
1692
1687 // Notifications
1693 // Notifications
1688 .notification-header{
1694 .notification-header{
1689 display: table;
1695 display: table;
1690 width: 100%;
1696 width: 100%;
1691 padding: floor(@basefontsize/2) 0;
1697 padding: floor(@basefontsize/2) 0;
1692 line-height: 1em;
1698 line-height: 1em;
1693
1699
1694 .desc, .delete-notifications, .read-notifications{
1700 .desc, .delete-notifications, .read-notifications{
1695 display: table-cell;
1701 display: table-cell;
1696 text-align: left;
1702 text-align: left;
1697 }
1703 }
1698
1704
1699 .desc{
1705 .desc{
1700 width: 1163px;
1706 width: 1163px;
1701 }
1707 }
1702
1708
1703 .delete-notifications, .read-notifications{
1709 .delete-notifications, .read-notifications{
1704 width: 35px;
1710 width: 35px;
1705 min-width: 35px; //fixes when only one button is displayed
1711 min-width: 35px; //fixes when only one button is displayed
1706 }
1712 }
1707 }
1713 }
1708
1714
1709 .notification-body {
1715 .notification-body {
1710 .markdown-block,
1716 .markdown-block,
1711 .rst-block {
1717 .rst-block {
1712 padding: @padding 0;
1718 padding: @padding 0;
1713 }
1719 }
1714
1720
1715 .notification-subject {
1721 .notification-subject {
1716 padding: @textmargin 0;
1722 padding: @textmargin 0;
1717 border-bottom: @border-thickness solid @border-default-color;
1723 border-bottom: @border-thickness solid @border-default-color;
1718 }
1724 }
1719 }
1725 }
1720
1726
1721
1727
1722 .notifications_buttons{
1728 .notifications_buttons{
1723 float: right;
1729 float: right;
1724 }
1730 }
1725
1731
1726 #notification-status{
1732 #notification-status{
1727 display: inline;
1733 display: inline;
1728 }
1734 }
1729
1735
1730 // Repositories
1736 // Repositories
1731
1737
1732 #summary.fields{
1738 #summary.fields{
1733 display: table;
1739 display: table;
1734
1740
1735 .field{
1741 .field{
1736 display: table-row;
1742 display: table-row;
1737
1743
1738 .label-summary{
1744 .label-summary{
1739 display: table-cell;
1745 display: table-cell;
1740 min-width: @label-summary-minwidth;
1746 min-width: @label-summary-minwidth;
1741 padding-top: @padding/2;
1747 padding-top: @padding/2;
1742 padding-bottom: @padding/2;
1748 padding-bottom: @padding/2;
1743 padding-right: @padding/2;
1749 padding-right: @padding/2;
1744 }
1750 }
1745
1751
1746 .input{
1752 .input{
1747 display: table-cell;
1753 display: table-cell;
1748 padding: @padding/2;
1754 padding: @padding/2;
1749
1755
1750 input{
1756 input{
1751 min-width: 29em;
1757 min-width: 29em;
1752 padding: @padding/4;
1758 padding: @padding/4;
1753 }
1759 }
1754 }
1760 }
1755 .statistics, .downloads{
1761 .statistics, .downloads{
1756 .disabled{
1762 .disabled{
1757 color: @grey4;
1763 color: @grey4;
1758 }
1764 }
1759 }
1765 }
1760 }
1766 }
1761 }
1767 }
1762
1768
1763 #summary{
1769 #summary{
1764 width: 70%;
1770 width: 70%;
1765 }
1771 }
1766
1772
1767
1773
1768 // Journal
1774 // Journal
1769 .journal.title {
1775 .journal.title {
1770 h5 {
1776 h5 {
1771 float: left;
1777 float: left;
1772 margin: 0;
1778 margin: 0;
1773 width: 70%;
1779 width: 70%;
1774 }
1780 }
1775
1781
1776 ul {
1782 ul {
1777 float: right;
1783 float: right;
1778 display: inline-block;
1784 display: inline-block;
1779 margin: 0;
1785 margin: 0;
1780 width: 30%;
1786 width: 30%;
1781 text-align: right;
1787 text-align: right;
1782
1788
1783 li {
1789 li {
1784 display: inline;
1790 display: inline;
1785 font-size: @journal-fontsize;
1791 font-size: @journal-fontsize;
1786 line-height: 1em;
1792 line-height: 1em;
1787
1793
1788 &:before { content: none; }
1794 &:before { content: none; }
1789 }
1795 }
1790 }
1796 }
1791 }
1797 }
1792
1798
1793 .filterexample {
1799 .filterexample {
1794 position: absolute;
1800 position: absolute;
1795 top: 95px;
1801 top: 95px;
1796 left: @contentpadding;
1802 left: @contentpadding;
1797 color: @rcblue;
1803 color: @rcblue;
1798 font-size: 11px;
1804 font-size: 11px;
1799 font-family: @text-regular;
1805 font-family: @text-regular;
1800 cursor: help;
1806 cursor: help;
1801
1807
1802 &:hover {
1808 &:hover {
1803 color: @rcdarkblue;
1809 color: @rcdarkblue;
1804 }
1810 }
1805
1811
1806 @media (max-width:768px) {
1812 @media (max-width:768px) {
1807 position: relative;
1813 position: relative;
1808 top: auto;
1814 top: auto;
1809 left: auto;
1815 left: auto;
1810 display: block;
1816 display: block;
1811 }
1817 }
1812 }
1818 }
1813
1819
1814
1820
1815 #journal{
1821 #journal{
1816 margin-bottom: @space;
1822 margin-bottom: @space;
1817
1823
1818 .journal_day{
1824 .journal_day{
1819 margin-bottom: @textmargin/2;
1825 margin-bottom: @textmargin/2;
1820 padding-bottom: @textmargin/2;
1826 padding-bottom: @textmargin/2;
1821 font-size: @journal-fontsize;
1827 font-size: @journal-fontsize;
1822 border-bottom: @border-thickness solid @border-default-color;
1828 border-bottom: @border-thickness solid @border-default-color;
1823 }
1829 }
1824
1830
1825 .journal_container{
1831 .journal_container{
1826 margin-bottom: @space;
1832 margin-bottom: @space;
1827
1833
1828 .journal_user{
1834 .journal_user{
1829 display: inline-block;
1835 display: inline-block;
1830 }
1836 }
1831 .journal_action_container{
1837 .journal_action_container{
1832 display: block;
1838 display: block;
1833 margin-top: @textmargin;
1839 margin-top: @textmargin;
1834
1840
1835 div{
1841 div{
1836 display: inline;
1842 display: inline;
1837 }
1843 }
1838
1844
1839 div.journal_action_params{
1845 div.journal_action_params{
1840 display: block;
1846 display: block;
1841 }
1847 }
1842
1848
1843 div.journal_repo:after{
1849 div.journal_repo:after{
1844 content: "\A";
1850 content: "\A";
1845 white-space: pre;
1851 white-space: pre;
1846 }
1852 }
1847
1853
1848 div.date{
1854 div.date{
1849 display: block;
1855 display: block;
1850 margin-bottom: @textmargin;
1856 margin-bottom: @textmargin;
1851 }
1857 }
1852 }
1858 }
1853 }
1859 }
1854 }
1860 }
1855
1861
1856 // Files
1862 // Files
1857 .edit-file-title {
1863 .edit-file-title {
1858 border-bottom: @border-thickness solid @border-default-color;
1864 border-bottom: @border-thickness solid @border-default-color;
1859
1865
1860 .breadcrumbs {
1866 .breadcrumbs {
1861 margin-bottom: 0;
1867 margin-bottom: 0;
1862 }
1868 }
1863 }
1869 }
1864
1870
1865 .edit-file-fieldset {
1871 .edit-file-fieldset {
1866 margin-top: @sidebarpadding;
1872 margin-top: @sidebarpadding;
1867
1873
1868 .fieldset {
1874 .fieldset {
1869 .left-label {
1875 .left-label {
1870 width: 13%;
1876 width: 13%;
1871 }
1877 }
1872 .right-content {
1878 .right-content {
1873 width: 87%;
1879 width: 87%;
1874 max-width: 100%;
1880 max-width: 100%;
1875 }
1881 }
1876 .filename-label {
1882 .filename-label {
1877 margin-top: 13px;
1883 margin-top: 13px;
1878 }
1884 }
1879 .commit-message-label {
1885 .commit-message-label {
1880 margin-top: 4px;
1886 margin-top: 4px;
1881 }
1887 }
1882 .file-upload-input {
1888 .file-upload-input {
1883 input {
1889 input {
1884 display: none;
1890 display: none;
1885 }
1891 }
1886 }
1892 }
1887 p {
1893 p {
1888 margin-top: 5px;
1894 margin-top: 5px;
1889 }
1895 }
1890
1896
1891 }
1897 }
1892 .custom-path-link {
1898 .custom-path-link {
1893 margin-left: 5px;
1899 margin-left: 5px;
1894 }
1900 }
1895 #commit {
1901 #commit {
1896 resize: vertical;
1902 resize: vertical;
1897 }
1903 }
1898 }
1904 }
1899
1905
1900 .delete-file-preview {
1906 .delete-file-preview {
1901 max-height: 250px;
1907 max-height: 250px;
1902 }
1908 }
1903
1909
1904 .new-file,
1910 .new-file,
1905 #filter_activate,
1911 #filter_activate,
1906 #filter_deactivate {
1912 #filter_deactivate {
1907 float: left;
1913 float: left;
1908 margin: 0 0 0 15px;
1914 margin: 0 0 0 15px;
1909 }
1915 }
1910
1916
1911 h3.files_location{
1917 h3.files_location{
1912 line-height: 2.4em;
1918 line-height: 2.4em;
1913 }
1919 }
1914
1920
1915 .browser-nav {
1921 .browser-nav {
1916 display: table;
1922 display: table;
1917 margin-bottom: @space;
1923 margin-bottom: @space;
1918
1924
1919
1925
1920 .info_box {
1926 .info_box {
1921 display: inline-table;
1927 display: inline-table;
1922 height: 2.5em;
1928 height: 2.5em;
1923
1929
1924 .browser-cur-rev, .info_box_elem {
1930 .browser-cur-rev, .info_box_elem {
1925 display: table-cell;
1931 display: table-cell;
1926 vertical-align: middle;
1932 vertical-align: middle;
1927 }
1933 }
1928
1934
1929 .info_box_elem {
1935 .info_box_elem {
1930 border-top: @border-thickness solid @rcblue;
1936 border-top: @border-thickness solid @rcblue;
1931 border-bottom: @border-thickness solid @rcblue;
1937 border-bottom: @border-thickness solid @rcblue;
1932
1938
1933 #at_rev, a {
1939 #at_rev, a {
1934 padding: 0.6em 0.9em;
1940 padding: 0.6em 0.9em;
1935 margin: 0;
1941 margin: 0;
1936 .box-shadow(none);
1942 .box-shadow(none);
1937 border: 0;
1943 border: 0;
1938 height: 12px;
1944 height: 12px;
1939 }
1945 }
1940
1946
1941 input#at_rev {
1947 input#at_rev {
1942 max-width: 50px;
1948 max-width: 50px;
1943 text-align: right;
1949 text-align: right;
1944 }
1950 }
1945
1951
1946 &.previous {
1952 &.previous {
1947 border: @border-thickness solid @rcblue;
1953 border: @border-thickness solid @rcblue;
1948 .disabled {
1954 .disabled {
1949 color: @grey4;
1955 color: @grey4;
1950 cursor: not-allowed;
1956 cursor: not-allowed;
1951 }
1957 }
1952 }
1958 }
1953
1959
1954 &.next {
1960 &.next {
1955 border: @border-thickness solid @rcblue;
1961 border: @border-thickness solid @rcblue;
1956 .disabled {
1962 .disabled {
1957 color: @grey4;
1963 color: @grey4;
1958 cursor: not-allowed;
1964 cursor: not-allowed;
1959 }
1965 }
1960 }
1966 }
1961 }
1967 }
1962
1968
1963 .browser-cur-rev {
1969 .browser-cur-rev {
1964
1970
1965 span{
1971 span{
1966 margin: 0;
1972 margin: 0;
1967 color: @rcblue;
1973 color: @rcblue;
1968 height: 12px;
1974 height: 12px;
1969 display: inline-block;
1975 display: inline-block;
1970 padding: 0.7em 1em ;
1976 padding: 0.7em 1em ;
1971 border: @border-thickness solid @rcblue;
1977 border: @border-thickness solid @rcblue;
1972 margin-right: @padding;
1978 margin-right: @padding;
1973 }
1979 }
1974 }
1980 }
1975 }
1981 }
1976
1982
1977 .search_activate {
1983 .search_activate {
1978 display: table-cell;
1984 display: table-cell;
1979 vertical-align: middle;
1985 vertical-align: middle;
1980
1986
1981 input, label{
1987 input, label{
1982 margin: 0;
1988 margin: 0;
1983 padding: 0;
1989 padding: 0;
1984 }
1990 }
1985
1991
1986 input{
1992 input{
1987 margin-left: @textmargin;
1993 margin-left: @textmargin;
1988 }
1994 }
1989
1995
1990 }
1996 }
1991 }
1997 }
1992
1998
1993 .browser-cur-rev{
1999 .browser-cur-rev{
1994 margin-bottom: @textmargin;
2000 margin-bottom: @textmargin;
1995 }
2001 }
1996
2002
1997 #node_filter_box_loading{
2003 #node_filter_box_loading{
1998 .info_text;
2004 .info_text;
1999 }
2005 }
2000
2006
2001 .browser-search {
2007 .browser-search {
2002 margin: -25px 0px 5px 0px;
2008 margin: -25px 0px 5px 0px;
2003 }
2009 }
2004
2010
2005 .node-filter {
2011 .node-filter {
2006 font-size: @repo-title-fontsize;
2012 font-size: @repo-title-fontsize;
2007 padding: 4px 0px 0px 0px;
2013 padding: 4px 0px 0px 0px;
2008
2014
2009 .node-filter-path {
2015 .node-filter-path {
2010 float: left;
2016 float: left;
2011 color: @grey4;
2017 color: @grey4;
2012 }
2018 }
2013 .node-filter-input {
2019 .node-filter-input {
2014 float: left;
2020 float: left;
2015 margin: -2px 0px 0px 2px;
2021 margin: -2px 0px 0px 2px;
2016 input {
2022 input {
2017 padding: 2px;
2023 padding: 2px;
2018 border: none;
2024 border: none;
2019 font-size: @repo-title-fontsize;
2025 font-size: @repo-title-fontsize;
2020 }
2026 }
2021 }
2027 }
2022 }
2028 }
2023
2029
2024
2030
2025 .browser-result{
2031 .browser-result{
2026 td a{
2032 td a{
2027 margin-left: 0.5em;
2033 margin-left: 0.5em;
2028 display: inline-block;
2034 display: inline-block;
2029
2035
2030 em{
2036 em{
2031 font-family: @text-bold;
2037 font-family: @text-bold;
2032 }
2038 }
2033 }
2039 }
2034 }
2040 }
2035
2041
2036 .browser-highlight{
2042 .browser-highlight{
2037 background-color: @grey5-alpha;
2043 background-color: @grey5-alpha;
2038 }
2044 }
2039
2045
2040
2046
2041 // Search
2047 // Search
2042
2048
2043 .search-form{
2049 .search-form{
2044 #q {
2050 #q {
2045 width: @search-form-width;
2051 width: @search-form-width;
2046 }
2052 }
2047 .fields{
2053 .fields{
2048 margin: 0 0 @space;
2054 margin: 0 0 @space;
2049 }
2055 }
2050
2056
2051 label{
2057 label{
2052 display: inline-block;
2058 display: inline-block;
2053 margin-right: @textmargin;
2059 margin-right: @textmargin;
2054 padding-top: 0.25em;
2060 padding-top: 0.25em;
2055 }
2061 }
2056
2062
2057
2063
2058 .results{
2064 .results{
2059 clear: both;
2065 clear: both;
2060 margin: 0 0 @padding;
2066 margin: 0 0 @padding;
2061 }
2067 }
2062 }
2068 }
2063
2069
2064 div.search-feedback-items {
2070 div.search-feedback-items {
2065 display: inline-block;
2071 display: inline-block;
2066 padding:0px 0px 0px 96px;
2072 padding:0px 0px 0px 96px;
2067 }
2073 }
2068
2074
2069 div.search-code-body {
2075 div.search-code-body {
2070 background-color: #ffffff; padding: 5px 0 5px 10px;
2076 background-color: #ffffff; padding: 5px 0 5px 10px;
2071 pre {
2077 pre {
2072 .match { background-color: #faffa6;}
2078 .match { background-color: #faffa6;}
2073 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2079 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2074 }
2080 }
2075 }
2081 }
2076
2082
2077 .expand_commit.search {
2083 .expand_commit.search {
2078 .show_more.open {
2084 .show_more.open {
2079 height: auto;
2085 height: auto;
2080 max-height: none;
2086 max-height: none;
2081 }
2087 }
2082 }
2088 }
2083
2089
2084 .search-results {
2090 .search-results {
2085
2091
2086 h2 {
2092 h2 {
2087 margin-bottom: 0;
2093 margin-bottom: 0;
2088 }
2094 }
2089 .codeblock {
2095 .codeblock {
2090 border: none;
2096 border: none;
2091 background: transparent;
2097 background: transparent;
2092 }
2098 }
2093
2099
2094 .codeblock-header {
2100 .codeblock-header {
2095 border: none;
2101 border: none;
2096 background: transparent;
2102 background: transparent;
2097 }
2103 }
2098
2104
2099 .code-body {
2105 .code-body {
2100 border: @border-thickness solid @border-default-color;
2106 border: @border-thickness solid @border-default-color;
2101 .border-radius(@border-radius);
2107 .border-radius(@border-radius);
2102 }
2108 }
2103
2109
2104 .td-commit {
2110 .td-commit {
2105 &:extend(pre);
2111 &:extend(pre);
2106 border-bottom: @border-thickness solid @border-default-color;
2112 border-bottom: @border-thickness solid @border-default-color;
2107 }
2113 }
2108
2114
2109 .message {
2115 .message {
2110 height: auto;
2116 height: auto;
2111 max-width: 350px;
2117 max-width: 350px;
2112 white-space: normal;
2118 white-space: normal;
2113 text-overflow: initial;
2119 text-overflow: initial;
2114 overflow: visible;
2120 overflow: visible;
2115
2121
2116 .match { background-color: #faffa6;}
2122 .match { background-color: #faffa6;}
2117 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2123 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2118 }
2124 }
2119
2125
2120 }
2126 }
2121
2127
2122 table.rctable td.td-search-results div {
2128 table.rctable td.td-search-results div {
2123 max-width: 100%;
2129 max-width: 100%;
2124 }
2130 }
2125
2131
2126 #tip-box, .tip-box{
2132 #tip-box, .tip-box{
2127 padding: @menupadding/2;
2133 padding: @menupadding/2;
2128 display: block;
2134 display: block;
2129 border: @border-thickness solid @border-highlight-color;
2135 border: @border-thickness solid @border-highlight-color;
2130 .border-radius(@border-radius);
2136 .border-radius(@border-radius);
2131 background-color: white;
2137 background-color: white;
2132 z-index: 99;
2138 z-index: 99;
2133 white-space: pre-wrap;
2139 white-space: pre-wrap;
2134 }
2140 }
2135
2141
2136 #linktt {
2142 #linktt {
2137 width: 79px;
2143 width: 79px;
2138 }
2144 }
2139
2145
2140 #help_kb .modal-content{
2146 #help_kb .modal-content{
2141 max-width: 750px;
2147 max-width: 750px;
2142 margin: 10% auto;
2148 margin: 10% auto;
2143
2149
2144 table{
2150 table{
2145 td,th{
2151 td,th{
2146 border-bottom: none;
2152 border-bottom: none;
2147 line-height: 2.5em;
2153 line-height: 2.5em;
2148 }
2154 }
2149 th{
2155 th{
2150 padding-bottom: @textmargin/2;
2156 padding-bottom: @textmargin/2;
2151 }
2157 }
2152 td.keys{
2158 td.keys{
2153 text-align: center;
2159 text-align: center;
2154 }
2160 }
2155 }
2161 }
2156
2162
2157 .block-left{
2163 .block-left{
2158 width: 45%;
2164 width: 45%;
2159 margin-right: 5%;
2165 margin-right: 5%;
2160 }
2166 }
2161 .modal-footer{
2167 .modal-footer{
2162 clear: both;
2168 clear: both;
2163 }
2169 }
2164 .key.tag{
2170 .key.tag{
2165 padding: 0.5em;
2171 padding: 0.5em;
2166 background-color: @rcblue;
2172 background-color: @rcblue;
2167 color: white;
2173 color: white;
2168 border-color: @rcblue;
2174 border-color: @rcblue;
2169 .box-shadow(none);
2175 .box-shadow(none);
2170 }
2176 }
2171 }
2177 }
2172
2178
2173
2179
2174
2180
2175 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2181 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2176
2182
2177 @import 'statistics-graph';
2183 @import 'statistics-graph';
2178 @import 'tables';
2184 @import 'tables';
2179 @import 'forms';
2185 @import 'forms';
2180 @import 'diff';
2186 @import 'diff';
2181 @import 'summary';
2187 @import 'summary';
2182 @import 'navigation';
2188 @import 'navigation';
2183
2189
2184 //--- SHOW/HIDE SECTIONS --//
2190 //--- SHOW/HIDE SECTIONS --//
2185
2191
2186 .btn-collapse {
2192 .btn-collapse {
2187 float: right;
2193 float: right;
2188 text-align: right;
2194 text-align: right;
2189 font-family: @text-light;
2195 font-family: @text-light;
2190 font-size: @basefontsize;
2196 font-size: @basefontsize;
2191 cursor: pointer;
2197 cursor: pointer;
2192 border: none;
2198 border: none;
2193 color: @rcblue;
2199 color: @rcblue;
2194 }
2200 }
2195
2201
2196 table.rctable,
2202 table.rctable,
2197 table.dataTable {
2203 table.dataTable {
2198 .btn-collapse {
2204 .btn-collapse {
2199 float: right;
2205 float: right;
2200 text-align: right;
2206 text-align: right;
2201 }
2207 }
2202 }
2208 }
2203
2209
2204
2210
2205 // TODO: johbo: Fix for IE10, this avoids that we see a border
2211 // TODO: johbo: Fix for IE10, this avoids that we see a border
2206 // and padding around checkboxes and radio boxes. Move to the right place,
2212 // and padding around checkboxes and radio boxes. Move to the right place,
2207 // or better: Remove this once we did the form refactoring.
2213 // or better: Remove this once we did the form refactoring.
2208 input[type=checkbox],
2214 input[type=checkbox],
2209 input[type=radio] {
2215 input[type=radio] {
2210 padding: 0;
2216 padding: 0;
2211 border: none;
2217 border: none;
2212 }
2218 }
2213
2219
2214 .toggle-ajax-spinner{
2220 .toggle-ajax-spinner{
2215 height: 16px;
2221 height: 16px;
2216 width: 16px;
2222 width: 16px;
2217 }
2223 }
@@ -1,530 +1,541 b''
1
1
2 // tables.less
2 // tables.less
3 // For use in RhodeCode application tables;
3 // For use in RhodeCode application tables;
4 // see style guide documentation for guidelines.
4 // see style guide documentation for guidelines.
5
5
6 // TABLES
6 // TABLES
7
7
8 .rctable,
8 .rctable,
9 table.rctable,
9 table.rctable,
10 table.dataTable {
10 table.dataTable {
11 clear:both;
11 clear:both;
12 width: 100%;
12 width: 100%;
13 margin: 0 auto @padding;
13 margin: 0 auto @padding;
14 padding: 0;
14 padding: 0;
15 vertical-align: baseline;
15 vertical-align: baseline;
16 line-height:1.5em;
16 line-height:1.5em;
17 border: none;
17 border: none;
18 outline: none;
18 outline: none;
19 border-collapse: collapse;
19 border-collapse: collapse;
20 border-spacing: 0;
20 border-spacing: 0;
21 color: @grey2;
21 color: @grey2;
22
22
23 b {
23 b {
24 font-weight: normal;
24 font-weight: normal;
25 }
25 }
26
26
27 em {
27 em {
28 font-weight: bold;
28 font-weight: bold;
29 font-style: normal;
29 font-style: normal;
30 }
30 }
31
31
32 th,
32 th,
33 td {
33 td {
34 height: auto;
34 height: auto;
35 max-width: 20%;
35 max-width: 20%;
36 padding: .65em 1em .65em 0;
36 padding: .65em 1em .65em 0;
37 vertical-align: middle;
37 vertical-align: middle;
38 border-bottom: @border-thickness solid @grey5;
38 border-bottom: @border-thickness solid @grey5;
39 white-space: normal;
39 white-space: normal;
40
40
41 &.td-radio,
41 &.td-radio,
42 &.td-checkbox {
42 &.td-checkbox {
43 padding-right: 0;
43 padding-right: 0;
44 text-align: center;
44 text-align: center;
45
45
46 input {
46 input {
47 margin: 0 1em;
47 margin: 0 1em;
48 }
48 }
49 }
49 }
50
50
51 &.truncate-wrap {
51 &.truncate-wrap {
52 white-space: nowrap !important;
52 white-space: nowrap !important;
53 }
53 }
54
54
55 pre {
55 pre {
56 margin: 0;
56 margin: 0;
57 }
57 }
58
58
59 .show_more {
59 .show_more {
60 height: inherit;
60 height: inherit;
61 }
61 }
62 }
62 }
63
63
64 .expired td {
64 .expired td {
65 background-color: @grey7;
65 background-color: @grey7;
66 }
66 }
67
67
68 .td-radio + .td-owner {
68 .td-radio + .td-owner {
69 padding-left: 1em;
69 padding-left: 1em;
70 }
70 }
71
71
72
72
73 th {
73 th {
74 text-align: left;
74 text-align: left;
75 font-family: @text-semibold;
75 font-family: @text-semibold;
76 }
76 }
77
77
78 .hl {
78 .hl {
79 td {
79 td {
80 background-color: lighten(@alert4,25%);
80 background-color: lighten(@alert4,25%);
81 }
81 }
82 }
82 }
83
83
84 // Special Data Cell Types
84 // Special Data Cell Types
85 // See style guide for desciptions and examples.
85 // See style guide for desciptions and examples.
86
86
87 td {
87 td {
88
88
89 &.user {
89 &.user {
90 padding-left: 1em;
90 padding-left: 1em;
91 }
91 }
92
92
93 &.td-rss {
93 &.td-rss {
94 width: 20px;
94 width: 20px;
95 min-width: 0;
95 min-width: 0;
96 margin: 0;
96 margin: 0;
97 }
97 }
98
98
99 &.quick_repo_menu {
99 &.quick_repo_menu {
100 width: 15px;
100 width: 15px;
101 text-align: center;
101 text-align: center;
102
102
103 &:hover {
103 &:hover {
104 background-color: @grey5;
104 background-color: @grey5;
105 }
105 }
106 }
106 }
107
107
108 &.td-hash {
108 &.td-hash {
109 min-width: 80px;
109 min-width: 80px;
110 width: 200px;
110 width: 200px;
111 }
111 }
112
112
113 &.td-time {
113 &.td-time {
114 width: 160px;
114 width: 160px;
115 white-space: nowrap;
115 white-space: nowrap;
116 }
116 }
117
117
118 &.annotate{
118 &.annotate{
119 padding-right: 0;
119 padding-right: 0;
120
120
121 div.annotatediv{
121 div.annotatediv{
122 margin: 0 0.7em;
122 margin: 0 0.7em;
123 }
123 }
124 }
124 }
125
125
126 &.tags-col {
126 &.tags-col {
127 padding-right: 0;
127 padding-right: 0;
128 }
128 }
129
129
130 &.td-description {
130 &.td-description {
131 min-width: 350px;
131 min-width: 350px;
132
132
133 &.truncate, .truncate-wrap {
133 &.truncate, .truncate-wrap {
134 white-space: nowrap;
134 white-space: nowrap;
135 overflow: hidden;
135 overflow: hidden;
136 text-overflow: ellipsis;
136 text-overflow: ellipsis;
137 max-width: 450px;
137 max-width: 450px;
138 }
138 }
139 }
139 }
140
140
141 &.td-componentname {
141 &.td-componentname {
142 white-space: nowrap;
142 white-space: nowrap;
143 }
143 }
144
144
145 &.td-journalaction {
145 &.td-journalaction {
146 min-width: 300px;
146 min-width: 300px;
147
147
148 .journal_action_params {
148 .journal_action_params {
149 // waiting for feedback
149 // waiting for feedback
150 }
150 }
151 }
151 }
152
152
153 &.td-active {
153 &.td-active {
154 padding-left: .65em;
154 padding-left: .65em;
155 }
155 }
156
156
157 &.td-url {
157 &.td-url {
158 white-space: nowrap;
158 white-space: nowrap;
159 }
159 }
160
160
161 &.td-comments {
161 &.td-comments {
162 min-width: 3em;
162 min-width: 3em;
163 }
163 }
164
164
165 &.td-buttons {
165 &.td-buttons {
166 padding: .3em 0;
166 padding: .3em 0;
167 }
167 }
168
168
169 &.td-action {
169 &.td-action {
170 // this is for the remove/delete/edit buttons
170 // this is for the remove/delete/edit buttons
171 padding-right: 0;
171 padding-right: 0;
172 min-width: 95px;
172 min-width: 95px;
173 text-transform: capitalize;
173 text-transform: capitalize;
174
174
175 i {
175 i {
176 display: none;
176 display: none;
177 }
177 }
178 }
178 }
179
179
180 // TODO: lisa: this needs to be cleaned up with the buttons
180 // TODO: lisa: this needs to be cleaned up with the buttons
181 .grid_edit,
181 .grid_edit,
182 .grid_delete {
182 .grid_delete {
183 display: inline-block;
183 display: inline-block;
184 margin: 0 @padding/3 0 0;
184 margin: 0 @padding/3 0 0;
185 font-family: @text-light;
185 font-family: @text-light;
186
186
187 i {
187 i {
188 display: none;
188 display: none;
189 }
189 }
190 }
190 }
191
191
192 .grid_edit + .grid_delete {
192 .grid_edit + .grid_delete {
193 border-left: @border-thickness solid @grey5;
193 border-left: @border-thickness solid @grey5;
194 padding-left: @padding/2;
194 padding-left: @padding/2;
195 }
195 }
196
196
197 &.td-compare {
197 &.td-compare {
198
198
199 input {
199 input {
200 margin-right: 1em;
200 margin-right: 1em;
201 }
201 }
202
202
203 .compare-radio-button {
203 .compare-radio-button {
204 margin: 0 1em 0 0;
204 margin: 0 1em 0 0;
205 }
205 }
206
206
207
207
208 }
208 }
209
209
210 &.td-tags {
210 &.td-tags {
211 padding: .5em 1em .5em 0;
211 padding: .5em 1em .5em 0;
212 width: 140px;
212 width: 140px;
213
213
214 .tag {
214 .tag {
215 margin: 1px;
215 margin: 1px;
216 float: left;
216 float: left;
217 }
217 }
218 }
218 }
219
219
220 .icon-svn, .icon-hg, .icon-git {
220 .icon-svn, .icon-hg, .icon-git {
221 font-size: 1.4em;
221 font-size: 1.4em;
222 }
222 }
223
223
224 &.collapse_commit,
224 &.collapse_commit,
225 &.expand_commit {
225 &.expand_commit {
226 padding-right: 0;
226 padding-right: 0;
227 padding-left: 1em;
227 padding-left: 1em;
228 }
228 }
229 }
229 }
230
230
231 .perm_admin_row {
231 .perm_admin_row {
232 color: @grey4;
232 color: @grey4;
233 background-color: @grey6;
233 background-color: @grey6;
234 }
234 }
235
235
236 .noborder {
236 .noborder {
237 border: none;
237 border: none;
238
238
239 td {
239 td {
240 border: none;
240 border: none;
241 }
241 }
242 }
242 }
243 }
243 }
244
244
245 // TRUNCATING
245 // TRUNCATING
246 // TODO: lisaq: should this possibly be moved out of tables.less?
246 // TODO: lisaq: should this possibly be moved out of tables.less?
247 // for truncated text
247 // for truncated text
248 // used inside of table cells and in code block headers
248 // used inside of table cells and in code block headers
249 .truncate-wrap {
249 .truncate-wrap {
250 white-space: nowrap !important;
250 white-space: nowrap !important;
251
251
252 //truncated text
252 //truncated text
253 .truncate {
253 .truncate {
254 max-width: 450px;
254 max-width: 450px;
255 width: 300px;
255 width: 300px;
256 overflow: hidden;
256 overflow: hidden;
257 text-overflow: ellipsis;
257 text-overflow: ellipsis;
258 -o-text-overflow: ellipsis;
258 -o-text-overflow: ellipsis;
259 -ms-text-overflow: ellipsis;
259 -ms-text-overflow: ellipsis;
260
260
261 &.autoexpand {
261 &.autoexpand {
262 width: 120px;
262 width: 120px;
263 margin-right: 200px;
263 margin-right: 200px;
264 }
264 }
265 }
265 }
266 &:hover .truncate.autoexpand {
266 &:hover .truncate.autoexpand {
267 overflow: visible;
267 overflow: visible;
268 }
268 }
269
269
270 .tags-truncate {
270 .tags-truncate {
271 width: 150px;
271 width: 150px;
272 height: 22px;
272 height: 22px;
273 overflow: hidden;
273 overflow: hidden;
274
274
275 .tag {
275 .tag {
276 display: inline-block;
276 display: inline-block;
277 }
277 }
278
278
279 &.truncate {
279 &.truncate {
280 height: 22px;
280 height: 22px;
281 max-height:2em;
281 max-height:2em;
282 width: 140px;
282 width: 140px;
283 }
283 }
284 }
284 }
285 }
285 }
286
286
287 .apikeys_wrap {
287 .apikeys_wrap {
288 margin-bottom: @padding;
288 margin-bottom: @padding;
289
289
290 table.rctable td:first-child {
290 table.rctable td:first-child {
291 width: 340px;
291 width: 340px;
292 }
292 }
293 }
293 }
294
294
295
295
296
296
297 // SPECIAL CASES
297 // SPECIAL CASES
298
298
299 // Repository Followers
299 // Repository Followers
300 table.rctable.followers_data {
300 table.rctable.followers_data {
301 width: 75%;
301 width: 75%;
302 margin: 0;
302 margin: 0;
303 }
303 }
304
304
305 // Repository List
305 // Repository List
306 // Group Members List
306 // Group Members List
307 table.rctable.group_members,
307 table.rctable.group_members,
308 table#repo_list_table {
308 table#repo_list_table {
309 min-width: 600px;
309 min-width: 600px;
310 }
310 }
311
311
312 // Keyboard mappings
312 // Keyboard mappings
313 table.keyboard-mappings {
313 table.keyboard-mappings {
314 th {
314 th {
315 text-align: left;
315 text-align: left;
316 font-family: @text-semibold;
316 font-family: @text-semibold;
317 }
317 }
318 }
318 }
319
319
320 // Branches, Tags, and Bookmarks
320 // Branches, Tags, and Bookmarks
321 #obj_list_table.dataTable {
321 #obj_list_table.dataTable {
322 td.td-time {
322 td.td-time {
323 padding-right: 1em;
323 padding-right: 1em;
324 }
324 }
325 }
325 }
326
326
327 // User Admin
327 // User Admin
328 .rctable.useremails,
328 .rctable.useremails,
329 .rctable.account_emails {
329 .rctable.account_emails {
330 .tag,
330 .tag,
331 .btn {
331 .btn {
332 float: right;
332 float: right;
333 }
333 }
334 .btn { //to line up with tags
334 .btn { //to line up with tags
335 margin-right: 1.65em;
335 margin-right: 1.65em;
336 }
336 }
337 }
337 }
338
338
339 // User List
339 // User List
340 #user_list_table {
340 #user_list_table {
341
341
342 td.td-user {
342 td.td-user {
343 min-width: 100px;
343 min-width: 100px;
344 }
344 }
345 }
345 }
346
346
347 // Pull Request List Table
347 // Pull Request List Table
348 #pull_request_list_table.dataTable {
348 #pull_request_list_table.dataTable {
349
349
350 //TODO: lisa: This needs to be removed once the description is adjusted
350 //TODO: lisa: This needs to be removed once the description is adjusted
351 // for using an expand_commit button (see issue 765)
351 // for using an expand_commit button (see issue 765)
352 td {
352 td {
353 vertical-align: middle;
353 vertical-align: middle;
354 }
354 }
355 }
355 }
356
356
357 // Settings (no border)
357 // Settings (no border)
358 table.rctable.dl-settings {
358 table.rctable.dl-settings {
359 td {
359 td {
360 border: none;
360 border: none;
361 }
361 }
362 }
362 }
363
363
364
364
365 // Statistics
365 // Statistics
366 table.trending_language_tbl {
366 table.trending_language_tbl {
367 width: 100%;
367 width: 100%;
368 line-height: 1em;
368 line-height: 1em;
369
369
370 td div {
370 td div {
371 overflow: visible;
371 overflow: visible;
372 }
372 }
373 }
373 }
374
374
375 .trending_language_tbl, .trending_language_tbl td {
375 .trending_language_tbl, .trending_language_tbl td {
376 border: 0;
376 border: 0;
377 margin: 0;
377 margin: 0;
378 padding: 0;
378 padding: 0;
379 background: transparent;
379 background: transparent;
380 }
380 }
381
381
382 .trending_language_tbl, .trending_language_tbl tr {
382 .trending_language_tbl, .trending_language_tbl tr {
383 border-spacing: 0 3px;
383 border-spacing: 0 3px;
384 }
384 }
385
385
386 .trending_language {
386 .trending_language {
387 position: relative;
387 position: relative;
388 width: 100%;
388 width: 100%;
389 height: 19px;
389 height: 19px;
390 overflow: hidden;
390 overflow: hidden;
391 background-color: @grey6;
391 background-color: @grey6;
392
392
393 span, b{
393 span, b{
394 position: absolute;
394 position: absolute;
395 display: block;
395 display: block;
396 height: 12px;
396 height: 12px;
397 margin-bottom: 0px;
397 margin-bottom: 0px;
398 white-space: pre;
398 white-space: pre;
399 padding: floor(@basefontsize/4);
399 padding: floor(@basefontsize/4);
400 top: 0;
400 top: 0;
401 left: 0;
401 left: 0;
402 }
402 }
403
403
404 span{
404 span{
405 color: @text-color;
405 color: @text-color;
406 z-index: 0;
406 z-index: 0;
407 min-width: 20px;
407 min-width: 20px;
408 }
408 }
409
409
410 b {
410 b {
411 z-index: 1;
411 z-index: 1;
412 overflow: hidden;
412 overflow: hidden;
413 background-color: @rcblue;
413 background-color: @rcblue;
414 color: #FFF;
414 color: #FFF;
415 text-decoration: none;
415 text-decoration: none;
416 }
416 }
417
417
418 }
418 }
419
419
420 // Changesets
420 // Changesets
421 #changesets.rctable {
421 #changesets.rctable {
422
422
423 // td must be fixed height for graph
423 // td must be fixed height for graph
424 td {
424 td {
425 height: 32px;
425 height: 32px;
426 padding: 0 1em 0 0;
426 padding: 0 1em 0 0;
427 vertical-align: middle;
427 vertical-align: middle;
428 white-space: nowrap;
428 white-space: nowrap;
429
429
430 &.td-description {
430 &.td-description {
431 white-space: normal;
431 white-space: normal;
432 }
432 }
433
433
434 &.expand_commit {
434 &.expand_commit {
435 padding-right: 0;
435 padding-right: 0;
436 }
436 }
437 }
437 }
438 }
438 }
439
439
440 // Compare
440 // Compare
441 table.compare_view_commits {
441 table.compare_view_commits {
442 margin-top: @space;
442 margin-top: @space;
443
443
444 td.td-time {
444 td.td-time {
445 padding-left: .5em;
445 padding-left: .5em;
446 }
446 }
447
447
448 // special case to not show hover actions on hidden indicator
449 tr.compare_select_hidden:hover {
450 cursor: inherit;
451
452 td {
453 background-color: inherit;
454 }
455 }
456
448 tr:hover {
457 tr:hover {
449 cursor: pointer;
458 cursor: pointer;
450
459
451 td {
460 td {
452 background-color: lighten(@alert4,25%);
461 background-color: lighten(@alert4,25%);
453 }
462 }
454 }
463 }
464
465
455 }
466 }
456
467
457 .file_history {
468 .file_history {
458 td.td-actions {
469 td.td-actions {
459 text-align: right;
470 text-align: right;
460 }
471 }
461 }
472 }
462
473
463 .compare_view_files {
474 .compare_view_files {
464
475
465 td.td-actions {
476 td.td-actions {
466 text-align: right;
477 text-align: right;
467 }
478 }
468
479
469 .flag_status {
480 .flag_status {
470 margin: 0 0 0 5px;
481 margin: 0 0 0 5px;
471 }
482 }
472
483
473 td.injected_diff {
484 td.injected_diff {
474
485
475 .code-difftable {
486 .code-difftable {
476 border:none;
487 border:none;
477 }
488 }
478
489
479 .diff-container {
490 .diff-container {
480 border: @border-thickness solid @border-default-color;
491 border: @border-thickness solid @border-default-color;
481 .border-radius(@border-radius);
492 .border-radius(@border-radius);
482 }
493 }
483
494
484 div.diffblock {
495 div.diffblock {
485 border:none;
496 border:none;
486 }
497 }
487
498
488 div.code-body {
499 div.code-body {
489 max-width: 1152px;
500 max-width: 1152px;
490 }
501 }
491 }
502 }
492
503
493 .rctable {
504 .rctable {
494
505
495 td {
506 td {
496 padding-top: @space;
507 padding-top: @space;
497 }
508 }
498
509
499 &:first-child td {
510 &:first-child td {
500 padding-top: 0;
511 padding-top: 0;
501 }
512 }
502 }
513 }
503
514
504 .comment-bubble,
515 .comment-bubble,
505 .show_comments {
516 .show_comments {
506 float: right;
517 float: right;
507 visibility: hidden;
518 visibility: hidden;
508 padding: 0 1em 0 0;
519 padding: 0 1em 0 0;
509 }
520 }
510
521
511 .injected_diff {
522 .injected_diff {
512 padding-bottom: @padding;
523 padding-bottom: @padding;
513 }
524 }
514 }
525 }
515
526
516 // Gist List
527 // Gist List
517 #gist_list_table {
528 #gist_list_table {
518 td {
529 td {
519 vertical-align: middle;
530 vertical-align: middle;
520
531
521 div{
532 div{
522 display: inline-block;
533 display: inline-block;
523 vertical-align: middle;
534 vertical-align: middle;
524 }
535 }
525
536
526 img{
537 img{
527 vertical-align: middle;
538 vertical-align: middle;
528 }
539 }
529 }
540 }
530 }
541 }
@@ -1,640 +1,651 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 var firefoxAnchorFix = function() {
19 var firefoxAnchorFix = function() {
20 // hack to make anchor links behave properly on firefox, in our inline
20 // hack to make anchor links behave properly on firefox, in our inline
21 // comments generation when comments are injected firefox is misbehaving
21 // comments generation when comments are injected firefox is misbehaving
22 // when jumping to anchor links
22 // when jumping to anchor links
23 if (location.href.indexOf('#') > -1) {
23 if (location.href.indexOf('#') > -1) {
24 location.href += '';
24 location.href += '';
25 }
25 }
26 };
26 };
27
27
28 // returns a node from given html;
28 // returns a node from given html;
29 var fromHTML = function(html){
29 var fromHTML = function(html){
30 var _html = document.createElement('element');
30 var _html = document.createElement('element');
31 _html.innerHTML = html;
31 _html.innerHTML = html;
32 return _html;
32 return _html;
33 };
33 };
34
34
35 var tableTr = function(cls, body){
35 var tableTr = function(cls, body){
36 var _el = document.createElement('div');
36 var _el = document.createElement('div');
37 var _body = $(body).attr('id');
37 var _body = $(body).attr('id');
38 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
38 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
39 var id = 'comment-tr-{0}'.format(comment_id);
39 var id = 'comment-tr-{0}'.format(comment_id);
40 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
40 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
41 '<td class="add-comment-line tooltip tooltip" title="Add Comment"><span class="add-comment-content"></span></td>'+
41 '<td class="add-comment-line tooltip tooltip" title="Add Comment"><span class="add-comment-content"></span></td>'+
42 '<td></td>'+
42 '<td></td>'+
43 '<td></td>'+
43 '<td></td>'+
44 '<td></td>'+
44 '<td></td>'+
45 '<td>{2}</td>'+
45 '<td>{2}</td>'+
46 '</tr></tbody></table>').format(id, cls, body);
46 '</tr></tbody></table>').format(id, cls, body);
47 $(_el).html(_html);
47 $(_el).html(_html);
48 return _el.children[0].children[0].children[0];
48 return _el.children[0].children[0].children[0];
49 };
49 };
50
50
51 function bindDeleteCommentButtons() {
51 function bindDeleteCommentButtons() {
52 $('.delete-comment').one('click', function() {
52 $('.delete-comment').one('click', function() {
53 var comment_id = $(this).data("comment-id");
53 var comment_id = $(this).data("comment-id");
54
54
55 if (comment_id){
55 if (comment_id){
56 deleteComment(comment_id);
56 deleteComment(comment_id);
57 }
57 }
58 });
58 });
59 }
59 }
60
60
61 var deleteComment = function(comment_id) {
61 var deleteComment = function(comment_id) {
62 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
62 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
63 var postData = {
63 var postData = {
64 '_method': 'delete',
64 '_method': 'delete',
65 'csrf_token': CSRF_TOKEN
65 'csrf_token': CSRF_TOKEN
66 };
66 };
67
67
68 var success = function(o) {
68 var success = function(o) {
69 window.location.reload();
69 window.location.reload();
70 };
70 };
71 ajaxPOST(url, postData, success);
71 ajaxPOST(url, postData, success);
72 };
72 };
73
73
74
74
75 var bindToggleButtons = function() {
75 var bindToggleButtons = function() {
76 $('.comment-toggle').on('click', function() {
76 $('.comment-toggle').on('click', function() {
77 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
77 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
78 });
78 });
79 };
79 };
80
80
81 var linkifyComments = function(comments) {
81 var linkifyComments = function(comments) {
82 /* TODO: dan: remove this - it should no longer needed */
82 /* TODO: dan: remove this - it should no longer needed */
83 for (var i = 0; i < comments.length; i++) {
83 for (var i = 0; i < comments.length; i++) {
84 var comment_id = $(comments[i]).data('comment-id');
84 var comment_id = $(comments[i]).data('comment-id');
85 var prev_comment_id = $(comments[i - 1]).data('comment-id');
85 var prev_comment_id = $(comments[i - 1]).data('comment-id');
86 var next_comment_id = $(comments[i + 1]).data('comment-id');
86 var next_comment_id = $(comments[i + 1]).data('comment-id');
87
87
88 // place next/prev links
88 // place next/prev links
89 if (prev_comment_id) {
89 if (prev_comment_id) {
90 $('#prev_c_' + comment_id).show();
90 $('#prev_c_' + comment_id).show();
91 $('#prev_c_' + comment_id + " a.arrow_comment_link").attr(
91 $('#prev_c_' + comment_id + " a.arrow_comment_link").attr(
92 'href', '#comment-' + prev_comment_id).removeClass('disabled');
92 'href', '#comment-' + prev_comment_id).removeClass('disabled');
93 }
93 }
94 if (next_comment_id) {
94 if (next_comment_id) {
95 $('#next_c_' + comment_id).show();
95 $('#next_c_' + comment_id).show();
96 $('#next_c_' + comment_id + " a.arrow_comment_link").attr(
96 $('#next_c_' + comment_id + " a.arrow_comment_link").attr(
97 'href', '#comment-' + next_comment_id).removeClass('disabled');
97 'href', '#comment-' + next_comment_id).removeClass('disabled');
98 }
98 }
99 // place a first link to the total counter
99 // place a first link to the total counter
100 if (i === 0) {
100 if (i === 0) {
101 $('#inline-comments-counter').attr('href', '#comment-' + comment_id);
101 $('#inline-comments-counter').attr('href', '#comment-' + comment_id);
102 }
102 }
103 }
103 }
104
104
105 };
105 };
106
106
107
107
108 /* Comment form for main and inline comments */
108 /* Comment form for main and inline comments */
109 var CommentForm = (function() {
109 var CommentForm = (function() {
110 "use strict";
110 "use strict";
111
111
112 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions) {
112 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions) {
113
113
114 this.withLineNo = function(selector) {
114 this.withLineNo = function(selector) {
115 var lineNo = this.lineNo;
115 var lineNo = this.lineNo;
116 if (lineNo === undefined) {
116 if (lineNo === undefined) {
117 return selector
117 return selector
118 } else {
118 } else {
119 return selector + '_' + lineNo;
119 return selector + '_' + lineNo;
120 }
120 }
121 };
121 };
122
122
123 this.commitId = commitId;
123 this.commitId = commitId;
124 this.pullRequestId = pullRequestId;
124 this.pullRequestId = pullRequestId;
125 this.lineNo = lineNo;
125 this.lineNo = lineNo;
126 this.initAutocompleteActions = initAutocompleteActions;
126 this.initAutocompleteActions = initAutocompleteActions;
127
127
128 this.previewButton = this.withLineNo('#preview-btn');
128 this.previewButton = this.withLineNo('#preview-btn');
129 this.previewContainer = this.withLineNo('#preview-container');
129 this.previewContainer = this.withLineNo('#preview-container');
130
130
131 this.previewBoxSelector = this.withLineNo('#preview-box');
131 this.previewBoxSelector = this.withLineNo('#preview-box');
132
132
133 this.editButton = this.withLineNo('#edit-btn');
133 this.editButton = this.withLineNo('#edit-btn');
134 this.editContainer = this.withLineNo('#edit-container');
134 this.editContainer = this.withLineNo('#edit-container');
135
135
136 this.cancelButton = this.withLineNo('#cancel-btn');
136 this.cancelButton = this.withLineNo('#cancel-btn');
137
137
138 this.statusChange = '#change_status';
138 this.statusChange = '#change_status';
139 this.cmBox = this.withLineNo('#text');
139 this.cmBox = this.withLineNo('#text');
140 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
140 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
141
141
142 this.submitForm = formElement;
142 this.submitForm = formElement;
143 this.submitButton = $(this.submitForm).find('input[type="submit"]');
143 this.submitButton = $(this.submitForm).find('input[type="submit"]');
144 this.submitButtonText = this.submitButton.val();
144 this.submitButtonText = this.submitButton.val();
145
145
146 this.previewUrl = pyroutes.url('changeset_comment_preview',
146 this.previewUrl = pyroutes.url('changeset_comment_preview',
147 {'repo_name': templateContext.repo_name});
147 {'repo_name': templateContext.repo_name});
148
148
149 // based on commitId, or pullReuqestId decide where do we submit
149 // based on commitId, or pullReuqestId decide where do we submit
150 // out data
150 // out data
151 if (this.commitId){
151 if (this.commitId){
152 this.submitUrl = pyroutes.url('changeset_comment',
152 this.submitUrl = pyroutes.url('changeset_comment',
153 {'repo_name': templateContext.repo_name,
153 {'repo_name': templateContext.repo_name,
154 'revision': this.commitId});
154 'revision': this.commitId});
155
155
156 } else if (this.pullRequestId) {
156 } else if (this.pullRequestId) {
157 this.submitUrl = pyroutes.url('pullrequest_comment',
157 this.submitUrl = pyroutes.url('pullrequest_comment',
158 {'repo_name': templateContext.repo_name,
158 {'repo_name': templateContext.repo_name,
159 'pull_request_id': this.pullRequestId});
159 'pull_request_id': this.pullRequestId});
160
160
161 } else {
161 } else {
162 throw new Error(
162 throw new Error(
163 'CommentForm requires pullRequestId, or commitId to be specified.')
163 'CommentForm requires pullRequestId, or commitId to be specified.')
164 }
164 }
165
165
166 this.getCmInstance = function(){
166 this.getCmInstance = function(){
167 return this.cm
167 return this.cm
168 };
168 };
169
169
170 var self = this;
170 var self = this;
171
171
172 this.getCommentStatus = function() {
172 this.getCommentStatus = function() {
173 return $(this.submitForm).find(this.statusChange).val();
173 return $(this.submitForm).find(this.statusChange).val();
174 };
174 };
175
175
176 this.isAllowedToSubmit = function() {
176 this.isAllowedToSubmit = function() {
177 return !$(this.submitButton).prop('disabled');
177 return !$(this.submitButton).prop('disabled');
178 };
178 };
179
179
180 this.initStatusChangeSelector = function(){
180 this.initStatusChangeSelector = function(){
181 var formatChangeStatus = function(state, escapeMarkup) {
181 var formatChangeStatus = function(state, escapeMarkup) {
182 var originalOption = state.element;
182 var originalOption = state.element;
183 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
183 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
184 '<span>' + escapeMarkup(state.text) + '</span>';
184 '<span>' + escapeMarkup(state.text) + '</span>';
185 };
185 };
186 var formatResult = function(result, container, query, escapeMarkup) {
186 var formatResult = function(result, container, query, escapeMarkup) {
187 return formatChangeStatus(result, escapeMarkup);
187 return formatChangeStatus(result, escapeMarkup);
188 };
188 };
189
189
190 var formatSelection = function(data, container, escapeMarkup) {
190 var formatSelection = function(data, container, escapeMarkup) {
191 return formatChangeStatus(data, escapeMarkup);
191 return formatChangeStatus(data, escapeMarkup);
192 };
192 };
193
193
194 $(this.submitForm).find(this.statusChange).select2({
194 $(this.submitForm).find(this.statusChange).select2({
195 placeholder: _gettext('Status Review'),
195 placeholder: _gettext('Status Review'),
196 formatResult: formatResult,
196 formatResult: formatResult,
197 formatSelection: formatSelection,
197 formatSelection: formatSelection,
198 containerCssClass: "drop-menu status_box_menu",
198 containerCssClass: "drop-menu status_box_menu",
199 dropdownCssClass: "drop-menu-dropdown",
199 dropdownCssClass: "drop-menu-dropdown",
200 dropdownAutoWidth: true,
200 dropdownAutoWidth: true,
201 minimumResultsForSearch: -1
201 minimumResultsForSearch: -1
202 });
202 });
203 $(this.submitForm).find(this.statusChange).on('change', function() {
203 $(this.submitForm).find(this.statusChange).on('change', function() {
204 var status = self.getCommentStatus();
204 var status = self.getCommentStatus();
205 if (status && !self.lineNo) {
205 if (status && !self.lineNo) {
206 $(self.submitButton).prop('disabled', false);
206 $(self.submitButton).prop('disabled', false);
207 }
207 }
208 //todo, fix this name
208 //todo, fix this name
209 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
209 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
210 self.cm.setOption('placeholder', placeholderText);
210 self.cm.setOption('placeholder', placeholderText);
211 })
211 })
212 };
212 };
213
213
214 // reset the comment form into it's original state
214 // reset the comment form into it's original state
215 this.resetCommentFormState = function(content) {
215 this.resetCommentFormState = function(content) {
216 content = content || '';
216 content = content || '';
217
217
218 $(this.editContainer).show();
218 $(this.editContainer).show();
219 $(this.editButton).hide();
219 $(this.editButton).hide();
220
220
221 $(this.previewContainer).hide();
221 $(this.previewContainer).hide();
222 $(this.previewButton).show();
222 $(this.previewButton).show();
223
223
224 this.setActionButtonsDisabled(true);
224 this.setActionButtonsDisabled(true);
225 self.cm.setValue(content);
225 self.cm.setValue(content);
226 self.cm.setOption("readOnly", false);
226 self.cm.setOption("readOnly", false);
227 };
227 };
228
228
229 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
229 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
230 failHandler = failHandler || function() {};
230 failHandler = failHandler || function() {};
231 var postData = toQueryString(postData);
231 var postData = toQueryString(postData);
232 var request = $.ajax({
232 var request = $.ajax({
233 url: url,
233 url: url,
234 type: 'POST',
234 type: 'POST',
235 data: postData,
235 data: postData,
236 headers: {'X-PARTIAL-XHR': true}
236 headers: {'X-PARTIAL-XHR': true}
237 })
237 })
238 .done(function(data) {
238 .done(function(data) {
239 successHandler(data);
239 successHandler(data);
240 })
240 })
241 .fail(function(data, textStatus, errorThrown){
241 .fail(function(data, textStatus, errorThrown){
242 alert(
242 alert(
243 "Error while submitting comment.\n" +
243 "Error while submitting comment.\n" +
244 "Error code {0} ({1}).".format(data.status, data.statusText));
244 "Error code {0} ({1}).".format(data.status, data.statusText));
245 failHandler()
245 failHandler()
246 });
246 });
247 return request;
247 return request;
248 };
248 };
249
249
250 // overwrite a submitHandler, we need to do it for inline comments
250 // overwrite a submitHandler, we need to do it for inline comments
251 this.setHandleFormSubmit = function(callback) {
251 this.setHandleFormSubmit = function(callback) {
252 this.handleFormSubmit = callback;
252 this.handleFormSubmit = callback;
253 };
253 };
254
254
255 // default handler for for submit for main comments
255 // default handler for for submit for main comments
256 this.handleFormSubmit = function() {
256 this.handleFormSubmit = function() {
257 var text = self.cm.getValue();
257 var text = self.cm.getValue();
258 var status = self.getCommentStatus();
258 var status = self.getCommentStatus();
259
259
260 if (text === "" && !status) {
260 if (text === "" && !status) {
261 return;
261 return;
262 }
262 }
263
263
264 var excludeCancelBtn = false;
264 var excludeCancelBtn = false;
265 var submitEvent = true;
265 var submitEvent = true;
266 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
266 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
267 self.cm.setOption("readOnly", true);
267 self.cm.setOption("readOnly", true);
268 var postData = {
268 var postData = {
269 'text': text,
269 'text': text,
270 'changeset_status': status,
270 'changeset_status': status,
271 'csrf_token': CSRF_TOKEN
271 'csrf_token': CSRF_TOKEN
272 };
272 };
273
273
274 var submitSuccessCallback = function(o) {
274 var submitSuccessCallback = function(o) {
275 if (status) {
275 if (status) {
276 location.reload(true);
276 location.reload(true);
277 } else {
277 } else {
278 $('#injected_page_comments').append(o.rendered_text);
278 $('#injected_page_comments').append(o.rendered_text);
279 self.resetCommentFormState();
279 self.resetCommentFormState();
280 bindDeleteCommentButtons();
280 bindDeleteCommentButtons();
281 timeagoActivate();
281 timeagoActivate();
282 }
282 }
283 };
283 };
284 var submitFailCallback = function(){
284 var submitFailCallback = function(){
285 self.resetCommentFormState(text)
285 self.resetCommentFormState(text)
286 };
286 };
287 self.submitAjaxPOST(
287 self.submitAjaxPOST(
288 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
288 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
289 };
289 };
290
290
291 this.previewSuccessCallback = function(o) {
291 this.previewSuccessCallback = function(o) {
292 $(self.previewBoxSelector).html(o);
292 $(self.previewBoxSelector).html(o);
293 $(self.previewBoxSelector).removeClass('unloaded');
293 $(self.previewBoxSelector).removeClass('unloaded');
294
294
295 // swap buttons
295 // swap buttons
296 $(self.previewButton).hide();
296 $(self.previewButton).hide();
297 $(self.editButton).show();
297 $(self.editButton).show();
298
298
299 // unlock buttons
299 // unlock buttons
300 self.setActionButtonsDisabled(false);
300 self.setActionButtonsDisabled(false);
301 };
301 };
302
302
303 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
303 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
304 excludeCancelBtn = excludeCancelBtn || false;
304 excludeCancelBtn = excludeCancelBtn || false;
305 submitEvent = submitEvent || false;
305 submitEvent = submitEvent || false;
306
306
307 $(this.editButton).prop('disabled', state);
307 $(this.editButton).prop('disabled', state);
308 $(this.previewButton).prop('disabled', state);
308 $(this.previewButton).prop('disabled', state);
309
309
310 if (!excludeCancelBtn) {
310 if (!excludeCancelBtn) {
311 $(this.cancelButton).prop('disabled', state);
311 $(this.cancelButton).prop('disabled', state);
312 }
312 }
313
313
314 var submitState = state;
314 var submitState = state;
315 if (!submitEvent && this.getCommentStatus() && !this.lineNo) {
315 if (!submitEvent && this.getCommentStatus() && !this.lineNo) {
316 // if the value of commit review status is set, we allow
316 // if the value of commit review status is set, we allow
317 // submit button, but only on Main form, lineNo means inline
317 // submit button, but only on Main form, lineNo means inline
318 submitState = false
318 submitState = false
319 }
319 }
320 $(this.submitButton).prop('disabled', submitState);
320 $(this.submitButton).prop('disabled', submitState);
321 if (submitEvent) {
321 if (submitEvent) {
322 $(this.submitButton).val(_gettext('Submitting...'));
322 $(this.submitButton).val(_gettext('Submitting...'));
323 } else {
323 } else {
324 $(this.submitButton).val(this.submitButtonText);
324 $(this.submitButton).val(this.submitButtonText);
325 }
325 }
326
326
327 };
327 };
328
328
329 // lock preview/edit/submit buttons on load, but exclude cancel button
329 // lock preview/edit/submit buttons on load, but exclude cancel button
330 var excludeCancelBtn = true;
330 var excludeCancelBtn = true;
331 this.setActionButtonsDisabled(true, excludeCancelBtn);
331 this.setActionButtonsDisabled(true, excludeCancelBtn);
332
332
333 // anonymous users don't have access to initialized CM instance
333 // anonymous users don't have access to initialized CM instance
334 if (this.cm !== undefined){
334 if (this.cm !== undefined){
335 this.cm.on('change', function(cMirror) {
335 this.cm.on('change', function(cMirror) {
336 if (cMirror.getValue() === "") {
336 if (cMirror.getValue() === "") {
337 self.setActionButtonsDisabled(true, excludeCancelBtn)
337 self.setActionButtonsDisabled(true, excludeCancelBtn)
338 } else {
338 } else {
339 self.setActionButtonsDisabled(false, excludeCancelBtn)
339 self.setActionButtonsDisabled(false, excludeCancelBtn)
340 }
340 }
341 });
341 });
342 }
342 }
343
343
344 $(this.editButton).on('click', function(e) {
344 $(this.editButton).on('click', function(e) {
345 e.preventDefault();
345 e.preventDefault();
346
346
347 $(self.previewButton).show();
347 $(self.previewButton).show();
348 $(self.previewContainer).hide();
348 $(self.previewContainer).hide();
349 $(self.editButton).hide();
349 $(self.editButton).hide();
350 $(self.editContainer).show();
350 $(self.editContainer).show();
351
351
352 });
352 });
353
353
354 $(this.previewButton).on('click', function(e) {
354 $(this.previewButton).on('click', function(e) {
355 e.preventDefault();
355 e.preventDefault();
356 var text = self.cm.getValue();
356 var text = self.cm.getValue();
357
357
358 if (text === "") {
358 if (text === "") {
359 return;
359 return;
360 }
360 }
361
361
362 var postData = {
362 var postData = {
363 'text': text,
363 'text': text,
364 'renderer': DEFAULT_RENDERER,
364 'renderer': DEFAULT_RENDERER,
365 'csrf_token': CSRF_TOKEN
365 'csrf_token': CSRF_TOKEN
366 };
366 };
367
367
368 // lock ALL buttons on preview
368 // lock ALL buttons on preview
369 self.setActionButtonsDisabled(true);
369 self.setActionButtonsDisabled(true);
370
370
371 $(self.previewBoxSelector).addClass('unloaded');
371 $(self.previewBoxSelector).addClass('unloaded');
372 $(self.previewBoxSelector).html(_gettext('Loading ...'));
372 $(self.previewBoxSelector).html(_gettext('Loading ...'));
373 $(self.editContainer).hide();
373 $(self.editContainer).hide();
374 $(self.previewContainer).show();
374 $(self.previewContainer).show();
375
375
376 // by default we reset state of comment preserving the text
376 // by default we reset state of comment preserving the text
377 var previewFailCallback = function(){
377 var previewFailCallback = function(){
378 self.resetCommentFormState(text)
378 self.resetCommentFormState(text)
379 };
379 };
380 self.submitAjaxPOST(
380 self.submitAjaxPOST(
381 self.previewUrl, postData, self.previewSuccessCallback, previewFailCallback);
381 self.previewUrl, postData, self.previewSuccessCallback, previewFailCallback);
382
382
383 });
383 });
384
384
385 $(this.submitForm).submit(function(e) {
385 $(this.submitForm).submit(function(e) {
386 e.preventDefault();
386 e.preventDefault();
387 var allowedToSubmit = self.isAllowedToSubmit();
387 var allowedToSubmit = self.isAllowedToSubmit();
388 if (!allowedToSubmit){
388 if (!allowedToSubmit){
389 return false;
389 return false;
390 }
390 }
391 self.handleFormSubmit();
391 self.handleFormSubmit();
392 });
392 });
393
393
394 }
394 }
395
395
396 return CommentForm;
396 return CommentForm;
397 })();
397 })();
398
398
399 var CommentsController = function() { /* comments controller */
399 var CommentsController = function() { /* comments controller */
400 var self = this;
400 var self = this;
401
401
402 this.cancelComment = function(node) {
402 this.cancelComment = function(node) {
403 var $node = $(node);
403 var $node = $(node);
404 var $td = $node.closest('td');
404 var $td = $node.closest('td');
405 $node.closest('.comment-inline-form').removeClass('comment-inline-form-open');
405 $node.closest('.comment-inline-form').removeClass('comment-inline-form-open');
406 return false;
406 return false;
407 };
407 };
408
408
409 this.getLineNumber = function(node) {
409 this.getLineNumber = function(node) {
410 var $node = $(node);
410 var $node = $(node);
411 return $node.closest('td').attr('data-line-number');
411 return $node.closest('td').attr('data-line-number');
412 };
412 };
413
413
414 this.scrollToComment = function(node, offset) {
414 this.scrollToComment = function(node, offset, outdated) {
415 var outdated = outdated || false;
416 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
417
415 if (!node) {
418 if (!node) {
416 node = $('.comment-selected');
419 node = $('.comment-selected');
417 if (!node.length) {
420 if (!node.length) {
418 node = $('comment-current')
421 node = $('comment-current')
419 }
422 }
420 }
423 }
421 $comment = $(node).closest('.comment-current');
424 $comment = $(node).closest(klass);
422 $comments = $('.comment-current');
425 $comments = $(klass);
423
426
424 $('.comment-selected').removeClass('comment-selected');
427 $('.comment-selected').removeClass('comment-selected');
425
428
426 var nextIdx = $('.comment-current').index($comment) + offset;
429 var nextIdx = $(klass).index($comment) + offset;
427 if (nextIdx >= $comments.length) {
430 if (nextIdx >= $comments.length) {
428 nextIdx = 0;
431 nextIdx = 0;
429 }
432 }
430 var $next = $('.comment-current').eq(nextIdx);
433 var $next = $(klass).eq(nextIdx);
431 var $cb = $next.closest('.cb');
434 var $cb = $next.closest('.cb');
432 $cb.removeClass('cb-collapsed');
435 $cb.removeClass('cb-collapsed');
433
436
434 var $filediffCollapseState = $cb.closest('.filediff').prev();
437 var $filediffCollapseState = $cb.closest('.filediff').prev();
435 $filediffCollapseState.prop('checked', false);
438 $filediffCollapseState.prop('checked', false);
436 $next.addClass('comment-selected');
439 $next.addClass('comment-selected');
437 scrollToElement($next);
440 scrollToElement($next);
438 return false;
441 return false;
439 };
442 };
440
443
441 this.nextComment = function(node) {
444 this.nextComment = function(node) {
442 return self.scrollToComment(node, 1);
445 return self.scrollToComment(node, 1);
443 };
446 };
444
447
445 this.prevComment = function(node) {
448 this.prevComment = function(node) {
446 return self.scrollToComment(node, -1);
449 return self.scrollToComment(node, -1);
447 };
450 };
448
451
452 this.nextOutdatedComment = function(node) {
453 return self.scrollToComment(node, 1, true);
454 };
455
456 this.prevOutdatedComment = function(node) {
457 return self.scrollToComment(node, -1, true);
458 };
459
449 this.deleteComment = function(node) {
460 this.deleteComment = function(node) {
450 if (!confirm(_gettext('Delete this comment?'))) {
461 if (!confirm(_gettext('Delete this comment?'))) {
451 return false;
462 return false;
452 }
463 }
453 var $node = $(node);
464 var $node = $(node);
454 var $td = $node.closest('td');
465 var $td = $node.closest('td');
455 var $comment = $node.closest('.comment');
466 var $comment = $node.closest('.comment');
456 var comment_id = $comment.attr('data-comment-id');
467 var comment_id = $comment.attr('data-comment-id');
457 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
468 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
458 var postData = {
469 var postData = {
459 '_method': 'delete',
470 '_method': 'delete',
460 'csrf_token': CSRF_TOKEN
471 'csrf_token': CSRF_TOKEN
461 };
472 };
462
473
463 $comment.addClass('comment-deleting');
474 $comment.addClass('comment-deleting');
464 $comment.hide('fast');
475 $comment.hide('fast');
465
476
466 var success = function(response) {
477 var success = function(response) {
467 $comment.remove();
478 $comment.remove();
468 return false;
479 return false;
469 };
480 };
470 var failure = function(data, textStatus, xhr) {
481 var failure = function(data, textStatus, xhr) {
471 alert("error processing request: " + textStatus);
482 alert("error processing request: " + textStatus);
472 $comment.show('fast');
483 $comment.show('fast');
473 $comment.removeClass('comment-deleting');
484 $comment.removeClass('comment-deleting');
474 return false;
485 return false;
475 };
486 };
476 ajaxPOST(url, postData, success, failure);
487 ajaxPOST(url, postData, success, failure);
477 };
488 };
478
489
479 this.toggleWideMode = function (node) {
490 this.toggleWideMode = function (node) {
480 if ($('#content').hasClass('wrapper')) {
491 if ($('#content').hasClass('wrapper')) {
481 $('#content').removeClass("wrapper");
492 $('#content').removeClass("wrapper");
482 $('#content').addClass("wide-mode-wrapper");
493 $('#content').addClass("wide-mode-wrapper");
483 $(node).addClass('btn-success');
494 $(node).addClass('btn-success');
484 } else {
495 } else {
485 $('#content').removeClass("wide-mode-wrapper");
496 $('#content').removeClass("wide-mode-wrapper");
486 $('#content').addClass("wrapper");
497 $('#content').addClass("wrapper");
487 $(node).removeClass('btn-success');
498 $(node).removeClass('btn-success');
488 }
499 }
489 return false;
500 return false;
490 };
501 };
491
502
492 this.toggleComments = function(node, show) {
503 this.toggleComments = function(node, show) {
493 var $filediff = $(node).closest('.filediff');
504 var $filediff = $(node).closest('.filediff');
494 if (show === true) {
505 if (show === true) {
495 $filediff.removeClass('hide-comments');
506 $filediff.removeClass('hide-comments');
496 } else if (show === false) {
507 } else if (show === false) {
497 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
508 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
498 $filediff.addClass('hide-comments');
509 $filediff.addClass('hide-comments');
499 } else {
510 } else {
500 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
511 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
501 $filediff.toggleClass('hide-comments');
512 $filediff.toggleClass('hide-comments');
502 }
513 }
503 return false;
514 return false;
504 };
515 };
505
516
506 this.toggleLineComments = function(node) {
517 this.toggleLineComments = function(node) {
507 self.toggleComments(node, true);
518 self.toggleComments(node, true);
508 var $node = $(node);
519 var $node = $(node);
509 $node.closest('tr').toggleClass('hide-line-comments');
520 $node.closest('tr').toggleClass('hide-line-comments');
510 };
521 };
511
522
512 this.createComment = function(node) {
523 this.createComment = function(node) {
513 var $node = $(node);
524 var $node = $(node);
514 var $td = $node.closest('td');
525 var $td = $node.closest('td');
515 var $form = $td.find('.comment-inline-form');
526 var $form = $td.find('.comment-inline-form');
516
527
517 if (!$form.length) {
528 if (!$form.length) {
518 var tmpl = $('#cb-comment-inline-form-template').html();
529 var tmpl = $('#cb-comment-inline-form-template').html();
519 var $filediff = $node.closest('.filediff');
530 var $filediff = $node.closest('.filediff');
520 $filediff.removeClass('hide-comments');
531 $filediff.removeClass('hide-comments');
521 var f_path = $filediff.attr('data-f-path');
532 var f_path = $filediff.attr('data-f-path');
522 var lineno = self.getLineNumber(node);
533 var lineno = self.getLineNumber(node);
523 tmpl = tmpl.format(f_path, lineno);
534 tmpl = tmpl.format(f_path, lineno);
524 $form = $(tmpl);
535 $form = $(tmpl);
525
536
526 var $comments = $td.find('.inline-comments');
537 var $comments = $td.find('.inline-comments');
527 if (!$comments.length) {
538 if (!$comments.length) {
528 $comments = $(
539 $comments = $(
529 $('#cb-comments-inline-container-template').html());
540 $('#cb-comments-inline-container-template').html());
530 $td.append($comments);
541 $td.append($comments);
531 }
542 }
532
543
533 $td.find('.cb-comment-add-button').before($form);
544 $td.find('.cb-comment-add-button').before($form);
534
545
535 var pullRequestId = templateContext.pull_request_data.pull_request_id;
546 var pullRequestId = templateContext.pull_request_data.pull_request_id;
536 var commitId = templateContext.commit_data.commit_id;
547 var commitId = templateContext.commit_data.commit_id;
537 var _form = $form[0];
548 var _form = $form[0];
538 var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
549 var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
539 var cm = commentForm.getCmInstance();
550 var cm = commentForm.getCmInstance();
540
551
541 // set a CUSTOM submit handler for inline comments.
552 // set a CUSTOM submit handler for inline comments.
542 commentForm.setHandleFormSubmit(function(o) {
553 commentForm.setHandleFormSubmit(function(o) {
543 var text = commentForm.cm.getValue();
554 var text = commentForm.cm.getValue();
544
555
545 if (text === "") {
556 if (text === "") {
546 return;
557 return;
547 }
558 }
548
559
549 if (lineno === undefined) {
560 if (lineno === undefined) {
550 alert('missing line !');
561 alert('missing line !');
551 return;
562 return;
552 }
563 }
553 if (f_path === undefined) {
564 if (f_path === undefined) {
554 alert('missing file path !');
565 alert('missing file path !');
555 return;
566 return;
556 }
567 }
557
568
558 var excludeCancelBtn = false;
569 var excludeCancelBtn = false;
559 var submitEvent = true;
570 var submitEvent = true;
560 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
571 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
561 commentForm.cm.setOption("readOnly", true);
572 commentForm.cm.setOption("readOnly", true);
562 var postData = {
573 var postData = {
563 'text': text,
574 'text': text,
564 'f_path': f_path,
575 'f_path': f_path,
565 'line': lineno,
576 'line': lineno,
566 'csrf_token': CSRF_TOKEN
577 'csrf_token': CSRF_TOKEN
567 };
578 };
568 var submitSuccessCallback = function(json_data) {
579 var submitSuccessCallback = function(json_data) {
569 $form.remove();
580 $form.remove();
570 try {
581 try {
571 var html = json_data.rendered_text;
582 var html = json_data.rendered_text;
572 var lineno = json_data.line_no;
583 var lineno = json_data.line_no;
573 var target_id = json_data.target_id;
584 var target_id = json_data.target_id;
574
585
575 $comments.find('.cb-comment-add-button').before(html);
586 $comments.find('.cb-comment-add-button').before(html);
576
587
577 } catch (e) {
588 } catch (e) {
578 console.error(e);
589 console.error(e);
579 }
590 }
580
591
581 // re trigger the linkification of next/prev navigation
592 // re trigger the linkification of next/prev navigation
582 linkifyComments($('.inline-comment-injected'));
593 linkifyComments($('.inline-comment-injected'));
583 timeagoActivate();
594 timeagoActivate();
584 bindDeleteCommentButtons();
595 bindDeleteCommentButtons();
585 commentForm.setActionButtonsDisabled(false);
596 commentForm.setActionButtonsDisabled(false);
586
597
587 };
598 };
588 var submitFailCallback = function(){
599 var submitFailCallback = function(){
589 commentForm.resetCommentFormState(text)
600 commentForm.resetCommentFormState(text)
590 };
601 };
591 commentForm.submitAjaxPOST(
602 commentForm.submitAjaxPOST(
592 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
603 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
593 });
604 });
594
605
595 setTimeout(function() {
606 setTimeout(function() {
596 // callbacks
607 // callbacks
597 if (cm !== undefined) {
608 if (cm !== undefined) {
598 cm.focus();
609 cm.focus();
599 }
610 }
600 }, 10);
611 }, 10);
601
612
602 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
613 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
603 form: _form,
614 form: _form,
604 parent: $td[0],
615 parent: $td[0],
605 lineno: lineno,
616 lineno: lineno,
606 f_path: f_path}
617 f_path: f_path}
607 );
618 );
608 }
619 }
609
620
610 $form.addClass('comment-inline-form-open');
621 $form.addClass('comment-inline-form-open');
611 };
622 };
612
623
613 this.renderInlineComments = function(file_comments) {
624 this.renderInlineComments = function(file_comments) {
614 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
625 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
615
626
616 for (var i = 0; i < file_comments.length; i++) {
627 for (var i = 0; i < file_comments.length; i++) {
617 var box = file_comments[i];
628 var box = file_comments[i];
618
629
619 var target_id = $(box).attr('target_id');
630 var target_id = $(box).attr('target_id');
620
631
621 // actually comments with line numbers
632 // actually comments with line numbers
622 var comments = box.children;
633 var comments = box.children;
623
634
624 for (var j = 0; j < comments.length; j++) {
635 for (var j = 0; j < comments.length; j++) {
625 var data = {
636 var data = {
626 'rendered_text': comments[j].outerHTML,
637 'rendered_text': comments[j].outerHTML,
627 'line_no': $(comments[j]).attr('line'),
638 'line_no': $(comments[j]).attr('line'),
628 'target_id': target_id
639 'target_id': target_id
629 };
640 };
630 }
641 }
631 }
642 }
632
643
633 // since order of injection is random, we're now re-iterating
644 // since order of injection is random, we're now re-iterating
634 // from correct order and filling in links
645 // from correct order and filling in links
635 linkifyComments($('.inline-comment-injected'));
646 linkifyComments($('.inline-comment-injected'));
636 bindDeleteCommentButtons();
647 bindDeleteCommentButtons();
637 firefoxAnchorFix();
648 firefoxAnchorFix();
638 };
649 };
639
650
640 };
651 };
@@ -1,418 +1,418 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name}
6 ${_('%s Changelog') % c.repo_name}
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path}
8 /${c.changelog_for_path}
9 %endif
9 %endif
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 %if c.changelog_for_path:
16 %if c.changelog_for_path:
17 /${c.changelog_for_path}
17 /${c.changelog_for_path}
18 %endif
18 %endif
19 ${ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
19 ${ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_nav()">
22 <%def name="menu_bar_nav()">
23 ${self.menu_items(active='repositories')}
23 ${self.menu_items(active='repositories')}
24 </%def>
24 </%def>
25
25
26 <%def name="menu_bar_subnav()">
26 <%def name="menu_bar_subnav()">
27 ${self.repo_menu(active='changelog')}
27 ${self.repo_menu(active='changelog')}
28 </%def>
28 </%def>
29
29
30 <%def name="main()">
30 <%def name="main()">
31
31
32 <div class="box">
32 <div class="box">
33 <div class="title">
33 <div class="title">
34 ${self.repo_page_title(c.rhodecode_db_repo)}
34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 <ul class="links">
35 <ul class="links">
36 <li>
36 <li>
37 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
37 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
38 %if c.rhodecode_db_repo.fork:
38 %if c.rhodecode_db_repo.fork:
39 <span>
39 <span>
40 <a id="compare_fork_button"
40 <a id="compare_fork_button"
41 title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}"
41 title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}"
42 class="btn btn-small"
42 class="btn btn-small"
43 href="${h.url('compare_url',
43 href="${h.url('compare_url',
44 repo_name=c.rhodecode_db_repo.fork.repo_name,
44 repo_name=c.rhodecode_db_repo.fork.repo_name,
45 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
45 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
46 source_ref=c.rhodecode_db_repo.landing_rev[1],
46 source_ref=c.rhodecode_db_repo.landing_rev[1],
47 target_repo=c.repo_name,
47 target_repo=c.repo_name,
48 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
48 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
49 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
49 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
50 merge=1)
50 merge=1)
51 }">
51 }">
52 <i class="icon-loop"></i>
52 <i class="icon-loop"></i>
53 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
53 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
54 </a>
54 </a>
55 </span>
55 </span>
56 %endif
56 %endif
57
57
58 ## pr open link
58 ## pr open link
59 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
59 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
60 <span>
60 <span>
61 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
61 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
62 ${_('Open new pull request')}
62 ${_('Open new pull request')}
63 </a>
63 </a>
64 </span>
64 </span>
65 %endif
65 %endif
66
66
67 ## clear selection
67 ## clear selection
68 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
68 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
69 ${_('Clear selection')}
69 ${_('Clear selection')}
70 </div>
70 </div>
71
71
72 </li>
72 </li>
73 </ul>
73 </ul>
74 </div>
74 </div>
75
75
76 % if c.pagination:
76 % if c.pagination:
77
77
78 <div class="graph-header">
78 <div class="graph-header">
79 <div id="filter_changelog">
79 <div id="filter_changelog">
80 ${h.hidden('branch_filter')}
80 ${h.hidden('branch_filter')}
81 %if c.selected_name:
81 %if c.selected_name:
82 <div class="btn btn-default" id="clear_filter" >
82 <div class="btn btn-default" id="clear_filter" >
83 ${_('Clear filter')}
83 ${_('Clear filter')}
84 </div>
84 </div>
85 %endif
85 %endif
86 </div>
86 </div>
87 ${self.breadcrumbs('breadcrumbs_light')}
87 ${self.breadcrumbs('breadcrumbs_light')}
88 </div>
88 </div>
89
89
90 <div id="graph">
90 <div id="graph">
91 <div class="graph-col-wrapper">
91 <div class="graph-col-wrapper">
92 <div id="graph_nodes">
92 <div id="graph_nodes">
93 <div id="graph_canvas" data-graph='${c.jsdata|n}'></div>
93 <div id="graph_canvas" data-graph='${c.jsdata|n}'></div>
94 </div>
94 </div>
95 <div id="graph_content" class="main-content graph_full_width">
95 <div id="graph_content" class="main-content graph_full_width">
96
96
97 <div class="table">
97 <div class="table">
98 <table id="changesets" class="rctable">
98 <table id="changesets" class="rctable">
99 <tr>
99 <tr>
100 ## checkbox
100 ## checkbox
101 <th></th>
101 <th></th>
102 <th colspan="2"></th>
102 <th colspan="2"></th>
103
103
104 <th>${_('Commit')}</th>
104 <th>${_('Commit')}</th>
105 ## commit message expand arrow
105 ## commit message expand arrow
106 <th></th>
106 <th></th>
107 <th>${_('Commit Message')}</th>
107 <th>${_('Commit Message')}</th>
108
108
109 <th>${_('Age')}</th>
109 <th>${_('Age')}</th>
110 <th>${_('Author')}</th>
110 <th>${_('Author')}</th>
111
111
112 <th>${_('Refs')}</th>
112 <th>${_('Refs')}</th>
113 </tr>
113 </tr>
114 <tbody>
114 <tbody>
115 %for cnt,commit in enumerate(c.pagination):
115 %for cnt,commit in enumerate(c.pagination):
116 <tr id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
116 <tr id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
117
117
118 <td class="td-checkbox">
118 <td class="td-checkbox">
119 ${h.checkbox(commit.raw_id,class_="commit-range")}
119 ${h.checkbox(commit.raw_id,class_="commit-range")}
120 </td>
120 </td>
121 <td class="td-status">
121 <td class="td-status">
122
122
123 %if c.statuses.get(commit.raw_id):
123 %if c.statuses.get(commit.raw_id):
124 <div class="changeset-status-ico">
124 <div class="changeset-status-ico">
125 %if c.statuses.get(commit.raw_id)[2]:
125 %if c.statuses.get(commit.raw_id)[2]:
126 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
126 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
127 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
127 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
128 </a>
128 </a>
129 %else:
129 %else:
130 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
130 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
131 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
131 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
132 </a>
132 </a>
133 %endif
133 %endif
134 </div>
134 </div>
135 %else:
135 %else:
136 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
136 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
137 %endif
137 %endif
138 </td>
138 </td>
139 <td class="td-comments comments-col">
139 <td class="td-comments comments-col">
140 %if c.comments.get(commit.raw_id):
140 %if c.comments.get(commit.raw_id):
141 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
141 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
142 <i class="icon-comment icon-comment-colored"></i> ${len(c.comments[commit.raw_id])}
142 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
143 </a>
143 </a>
144 %endif
144 %endif
145 </td>
145 </td>
146 <td class="td-hash">
146 <td class="td-hash">
147 <code>
147 <code>
148 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
148 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
149 <span class="commit_hash">${h.show_id(commit)}</span>
149 <span class="commit_hash">${h.show_id(commit)}</span>
150 </a>
150 </a>
151 </code>
151 </code>
152 </td>
152 </td>
153 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}">
153 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}">
154 <div class="show_more_col">
154 <div class="show_more_col">
155 <i class="show_more"></i>&nbsp;
155 <i class="show_more"></i>&nbsp;
156 </div>
156 </div>
157 </td>
157 </td>
158 <td class="td-description mid">
158 <td class="td-description mid">
159 <div class="log-container truncate-wrap">
159 <div class="log-container truncate-wrap">
160 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
160 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
161 </div>
161 </div>
162 </td>
162 </td>
163
163
164 <td class="td-time">
164 <td class="td-time">
165 ${h.age_component(commit.date)}
165 ${h.age_component(commit.date)}
166 </td>
166 </td>
167 <td class="td-user">
167 <td class="td-user">
168 ${self.gravatar_with_user(commit.author)}
168 ${self.gravatar_with_user(commit.author)}
169 </td>
169 </td>
170
170
171 <td class="td-tags tags-col">
171 <td class="td-tags tags-col">
172 <div id="t-${commit.raw_id}">
172 <div id="t-${commit.raw_id}">
173 ## branch
173 ## branch
174 %if commit.branch:
174 %if commit.branch:
175 <span class="branchtag tag" title="${_('Branch %s') % commit.branch}">
175 <span class="branchtag tag" title="${_('Branch %s') % commit.branch}">
176 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
176 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
177 </span>
177 </span>
178 %endif
178 %endif
179
179
180 ## bookmarks
180 ## bookmarks
181 %if h.is_hg(c.rhodecode_repo):
181 %if h.is_hg(c.rhodecode_repo):
182 %for book in commit.bookmarks:
182 %for book in commit.bookmarks:
183 <span class="tag booktag" title="${_('Bookmark %s') % book}">
183 <span class="tag booktag" title="${_('Bookmark %s') % book}">
184 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
184 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
185 </span>
185 </span>
186 %endfor
186 %endfor
187 %endif
187 %endif
188
188
189 ## tags
189 ## tags
190 %for tag in commit.tags:
190 %for tag in commit.tags:
191 <span class="tagtag tag" title="${_('Tag %s') % tag}">
191 <span class="tagtag tag" title="${_('Tag %s') % tag}">
192 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
192 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
193 </span>
193 </span>
194 %endfor
194 %endfor
195
195
196 </div>
196 </div>
197 </td>
197 </td>
198 </tr>
198 </tr>
199 %endfor
199 %endfor
200 </tbody>
200 </tbody>
201 </table>
201 </table>
202 </div>
202 </div>
203 </div>
203 </div>
204 </div>
204 </div>
205 <div class="pagination-wh pagination-left">
205 <div class="pagination-wh pagination-left">
206 ${c.pagination.pager('$link_previous ~2~ $link_next')}
206 ${c.pagination.pager('$link_previous ~2~ $link_next')}
207 </div>
207 </div>
208
208
209 <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script>
209 <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script>
210 <script type="text/javascript">
210 <script type="text/javascript">
211 var cache = {};
211 var cache = {};
212 $(function(){
212 $(function(){
213
213
214 // Create links to commit ranges when range checkboxes are selected
214 // Create links to commit ranges when range checkboxes are selected
215 var $commitCheckboxes = $('.commit-range');
215 var $commitCheckboxes = $('.commit-range');
216 // cache elements
216 // cache elements
217 var $commitRangeContainer = $('#rev_range_container');
217 var $commitRangeContainer = $('#rev_range_container');
218 var $commitRangeClear = $('#rev_range_clear');
218 var $commitRangeClear = $('#rev_range_clear');
219
219
220 var checkboxRangeSelector = function(e){
220 var checkboxRangeSelector = function(e){
221 var selectedCheckboxes = [];
221 var selectedCheckboxes = [];
222 for (pos in $commitCheckboxes){
222 for (pos in $commitCheckboxes){
223 if($commitCheckboxes[pos].checked){
223 if($commitCheckboxes[pos].checked){
224 selectedCheckboxes.push($commitCheckboxes[pos]);
224 selectedCheckboxes.push($commitCheckboxes[pos]);
225 }
225 }
226 }
226 }
227 var open_new_pull_request = $('#open_new_pull_request');
227 var open_new_pull_request = $('#open_new_pull_request');
228 if(open_new_pull_request){
228 if(open_new_pull_request){
229 var selected_changes = selectedCheckboxes.length;
229 var selected_changes = selectedCheckboxes.length;
230 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
230 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
231 open_new_pull_request.hide();
231 open_new_pull_request.hide();
232 } else {
232 } else {
233 if (selected_changes == 1) {
233 if (selected_changes == 1) {
234 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
234 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
235 } else if (selected_changes == 0) {
235 } else if (selected_changes == 0) {
236 open_new_pull_request.html(_gettext('Open new pull request'));
236 open_new_pull_request.html(_gettext('Open new pull request'));
237 }
237 }
238 open_new_pull_request.show();
238 open_new_pull_request.show();
239 }
239 }
240 }
240 }
241
241
242 if (selectedCheckboxes.length>0){
242 if (selectedCheckboxes.length>0){
243 var revEnd = selectedCheckboxes[0].name;
243 var revEnd = selectedCheckboxes[0].name;
244 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
244 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
245 var url = pyroutes.url('changeset_home',
245 var url = pyroutes.url('changeset_home',
246 {'repo_name': '${c.repo_name}',
246 {'repo_name': '${c.repo_name}',
247 'revision': revStart+'...'+revEnd});
247 'revision': revStart+'...'+revEnd});
248
248
249 var link = (revStart == revEnd)
249 var link = (revStart == revEnd)
250 ? _gettext('Show selected commit __S')
250 ? _gettext('Show selected commit __S')
251 : _gettext('Show selected commits __S ... __E');
251 : _gettext('Show selected commits __S ... __E');
252
252
253 link = link.replace('__S', revStart.substr(0,6));
253 link = link.replace('__S', revStart.substr(0,6));
254 link = link.replace('__E', revEnd.substr(0,6));
254 link = link.replace('__E', revEnd.substr(0,6));
255
255
256 $commitRangeContainer
256 $commitRangeContainer
257 .attr('href',url)
257 .attr('href',url)
258 .html(link)
258 .html(link)
259 .show();
259 .show();
260
260
261 $commitRangeClear.show();
261 $commitRangeClear.show();
262 var _url = pyroutes.url('pullrequest_home',
262 var _url = pyroutes.url('pullrequest_home',
263 {'repo_name': '${c.repo_name}',
263 {'repo_name': '${c.repo_name}',
264 'commit': revEnd});
264 'commit': revEnd});
265 open_new_pull_request.attr('href', _url);
265 open_new_pull_request.attr('href', _url);
266 $('#compare_fork_button').hide();
266 $('#compare_fork_button').hide();
267 } else {
267 } else {
268 $commitRangeContainer.hide();
268 $commitRangeContainer.hide();
269 $commitRangeClear.hide();
269 $commitRangeClear.hide();
270
270
271 %if c.branch_name:
271 %if c.branch_name:
272 var _url = pyroutes.url('pullrequest_home',
272 var _url = pyroutes.url('pullrequest_home',
273 {'repo_name': '${c.repo_name}',
273 {'repo_name': '${c.repo_name}',
274 'branch':'${c.branch_name}'});
274 'branch':'${c.branch_name}'});
275 open_new_pull_request.attr('href', _url);
275 open_new_pull_request.attr('href', _url);
276 %else:
276 %else:
277 var _url = pyroutes.url('pullrequest_home',
277 var _url = pyroutes.url('pullrequest_home',
278 {'repo_name': '${c.repo_name}'});
278 {'repo_name': '${c.repo_name}'});
279 open_new_pull_request.attr('href', _url);
279 open_new_pull_request.attr('href', _url);
280 %endif
280 %endif
281 $('#compare_fork_button').show();
281 $('#compare_fork_button').show();
282 }
282 }
283 };
283 };
284
284
285 $commitCheckboxes.on('click', checkboxRangeSelector);
285 $commitCheckboxes.on('click', checkboxRangeSelector);
286
286
287 $commitRangeClear.on('click',function(e) {
287 $commitRangeClear.on('click',function(e) {
288 $commitCheckboxes.attr('checked', false)
288 $commitCheckboxes.attr('checked', false)
289 checkboxRangeSelector();
289 checkboxRangeSelector();
290 e.preventDefault();
290 e.preventDefault();
291 });
291 });
292
292
293 // make sure the buttons are consistent when navigate back and forth
293 // make sure the buttons are consistent when navigate back and forth
294 checkboxRangeSelector();
294 checkboxRangeSelector();
295
295
296
296
297 var msgs = $('.message');
297 var msgs = $('.message');
298 // get first element height
298 // get first element height
299 var el = $('#graph_content .container')[0];
299 var el = $('#graph_content .container')[0];
300 var row_h = el.clientHeight;
300 var row_h = el.clientHeight;
301 for (var i=0; i < msgs.length; i++) {
301 for (var i=0; i < msgs.length; i++) {
302 var m = msgs[i];
302 var m = msgs[i];
303
303
304 var h = m.clientHeight;
304 var h = m.clientHeight;
305 var pad = $(m).css('padding');
305 var pad = $(m).css('padding');
306 if (h > row_h) {
306 if (h > row_h) {
307 var offset = row_h - (h+12);
307 var offset = row_h - (h+12);
308 $(m.nextElementSibling).css('display','block');
308 $(m.nextElementSibling).css('display','block');
309 $(m.nextElementSibling).css('margin-top',offset+'px');
309 $(m.nextElementSibling).css('margin-top',offset+'px');
310 }
310 }
311 }
311 }
312
312
313 $('.expand_commit').on('click',function(e){
313 $('.expand_commit').on('click',function(e){
314 var target_expand = $(this);
314 var target_expand = $(this);
315 var cid = target_expand.data('commitId');
315 var cid = target_expand.data('commitId');
316
316
317 if (target_expand.hasClass('open')){
317 if (target_expand.hasClass('open')){
318 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
318 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
319 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
319 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
320 target_expand.removeClass('open');
320 target_expand.removeClass('open');
321 }
321 }
322 else {
322 else {
323 $('#c-'+cid).css({'height': 'auto', 'white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'});
323 $('#c-'+cid).css({'height': 'auto', 'white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'});
324 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'});
324 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'});
325 target_expand.addClass('open');
325 target_expand.addClass('open');
326 }
326 }
327 // redraw the graph
327 // redraw the graph
328 graph_options.height = $("#changesets").height();
328 graph_options.height = $("#changesets").height();
329 $("canvas").remove();
329 $("canvas").remove();
330 $("[data-graph]").commits(graph_options);
330 $("[data-graph]").commits(graph_options);
331 });
331 });
332
332
333 $("#clear_filter").on("click", function() {
333 $("#clear_filter").on("click", function() {
334 var filter = {'repo_name': '${c.repo_name}'};
334 var filter = {'repo_name': '${c.repo_name}'};
335 window.location = pyroutes.url('changelog_home', filter);
335 window.location = pyroutes.url('changelog_home', filter);
336 });
336 });
337
337
338 $("#branch_filter").select2({
338 $("#branch_filter").select2({
339 'dropdownAutoWidth': true,
339 'dropdownAutoWidth': true,
340 'width': 'resolve',
340 'width': 'resolve',
341 'placeholder': "${c.selected_name or _('Filter changelog')}",
341 'placeholder': "${c.selected_name or _('Filter changelog')}",
342 containerCssClass: "drop-menu",
342 containerCssClass: "drop-menu",
343 dropdownCssClass: "drop-menu-dropdown",
343 dropdownCssClass: "drop-menu-dropdown",
344 query: function(query){
344 query: function(query){
345 var key = 'cache';
345 var key = 'cache';
346 var cached = cache[key] ;
346 var cached = cache[key] ;
347 if(cached) {
347 if(cached) {
348 var data = {results: []};
348 var data = {results: []};
349 //filter results
349 //filter results
350 $.each(cached.results, function(){
350 $.each(cached.results, function(){
351 var section = this.text;
351 var section = this.text;
352 var children = [];
352 var children = [];
353 $.each(this.children, function(){
353 $.each(this.children, function(){
354 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
354 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
355 children.push({'id': this.id, 'text': this.text, 'type': this.type})
355 children.push({'id': this.id, 'text': this.text, 'type': this.type})
356 }
356 }
357 });
357 });
358 data.results.push({'text': section, 'children': children});
358 data.results.push({'text': section, 'children': children});
359 query.callback({results: data.results});
359 query.callback({results: data.results});
360 });
360 });
361 }else{
361 }else{
362 $.ajax({
362 $.ajax({
363 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
363 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
364 data: {},
364 data: {},
365 dataType: 'json',
365 dataType: 'json',
366 type: 'GET',
366 type: 'GET',
367 success: function(data) {
367 success: function(data) {
368 cache[key] = data;
368 cache[key] = data;
369 query.callback({results: data.results});
369 query.callback({results: data.results});
370 }
370 }
371 })
371 })
372 }
372 }
373 }
373 }
374 });
374 });
375
375
376 $('#branch_filter').on('change', function(e){
376 $('#branch_filter').on('change', function(e){
377 var data = $('#branch_filter').select2('data');
377 var data = $('#branch_filter').select2('data');
378 var selected = data.text;
378 var selected = data.text;
379 var filter = {'repo_name': '${c.repo_name}'};
379 var filter = {'repo_name': '${c.repo_name}'};
380 if(data.type == 'branch' || data.type == 'branch_closed'){
380 if(data.type == 'branch' || data.type == 'branch_closed'){
381 filter["branch"] = selected;
381 filter["branch"] = selected;
382 }
382 }
383 else if (data.type == 'book'){
383 else if (data.type == 'book'){
384 filter["bookmark"] = selected;
384 filter["bookmark"] = selected;
385 }
385 }
386 window.location = pyroutes.url('changelog_home', filter);
386 window.location = pyroutes.url('changelog_home', filter);
387 });
387 });
388
388
389 // Determine max number of edges per row in graph
389 // Determine max number of edges per row in graph
390 var jsdata = $.parseJSON($("[data-graph]").attr('data-graph'));
390 var jsdata = $.parseJSON($("[data-graph]").attr('data-graph'));
391 var edgeCount = 1;
391 var edgeCount = 1;
392 $.each(jsdata, function(i, item){
392 $.each(jsdata, function(i, item){
393 $.each(item[2], function(key, value) {
393 $.each(item[2], function(key, value) {
394 if (value[1] > edgeCount){
394 if (value[1] > edgeCount){
395 edgeCount = value[1];
395 edgeCount = value[1];
396 }
396 }
397 });
397 });
398 });
398 });
399 var x_step = Math.min(18, Math.floor(86 / edgeCount));
399 var x_step = Math.min(18, Math.floor(86 / edgeCount));
400 var graph_options = {
400 var graph_options = {
401 width: 100,
401 width: 100,
402 height: $("#changesets").height(),
402 height: $("#changesets").height(),
403 x_step: x_step,
403 x_step: x_step,
404 y_step: 42,
404 y_step: 42,
405 dotRadius: 3.5,
405 dotRadius: 3.5,
406 lineWidth: 2.5
406 lineWidth: 2.5
407 };
407 };
408 $("[data-graph]").commits(graph_options);
408 $("[data-graph]").commits(graph_options);
409
409
410 });
410 });
411
411
412 </script>
412 </script>
413 %else:
413 %else:
414 ${_('There are no changes yet')}
414 ${_('There are no changes yet')}
415 %endif
415 %endif
416 </div>
416 </div>
417 </div>
417 </div>
418 </%def>
418 </%def>
@@ -1,136 +1,136 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.html"/>
2 <%namespace name="base" file="/base/base.html"/>
3 %if c.repo_commits:
3 %if c.repo_commits:
4 <table class="rctable repo_summary table_disp">
4 <table class="rctable repo_summary table_disp">
5 <tr>
5 <tr>
6
6
7 <th class="status" colspan="2"></th>
7 <th class="status" colspan="2"></th>
8 <th>${_('Commit')}</th>
8 <th>${_('Commit')}</th>
9 <th>${_('Commit message')}</th>
9 <th>${_('Commit message')}</th>
10 <th>${_('Age')}</th>
10 <th>${_('Age')}</th>
11 <th>${_('Author')}</th>
11 <th>${_('Author')}</th>
12 <th>${_('Refs')}</th>
12 <th>${_('Refs')}</th>
13 </tr>
13 </tr>
14 %for cnt,cs in enumerate(c.repo_commits):
14 %for cnt,cs in enumerate(c.repo_commits):
15 <tr class="parity${cnt%2}">
15 <tr class="parity${cnt%2}">
16
16
17 <td class="td-status">
17 <td class="td-status">
18 %if c.statuses.get(cs.raw_id):
18 %if c.statuses.get(cs.raw_id):
19 <div class="changeset-status-ico shortlog">
19 <div class="changeset-status-ico shortlog">
20 %if c.statuses.get(cs.raw_id)[2]:
20 %if c.statuses.get(cs.raw_id)[2]:
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
23 </a>
23 </a>
24 %else:
24 %else:
25 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
25 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
27 </a>
27 </a>
28 %endif
28 %endif
29 </div>
29 </div>
30 %else:
30 %else:
31 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
31 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
32 %endif
32 %endif
33 </td>
33 </td>
34 <td class="td-comments">
34 <td class="td-comments">
35 %if c.comments.get(cs.raw_id,[]):
35 %if c.comments.get(cs.raw_id,[]):
36 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
36 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
37 <i class="icon-comment icon-comment-colored"></i> ${len(c.comments[cs.raw_id])}
37 <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])}
38 </a>
38 </a>
39 %endif
39 %endif
40 </td>
40 </td>
41 <td class="td-commit">
41 <td class="td-commit">
42 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
42 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
43 </td>
43 </td>
44
44
45 <td class="td-description mid">
45 <td class="td-description mid">
46 <div class="log-container truncate-wrap">
46 <div class="log-container truncate-wrap">
47 <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div>
47 <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div>
48 </div>
48 </div>
49 </td>
49 </td>
50
50
51 <td class="td-time">
51 <td class="td-time">
52 ${h.age_component(cs.date)}
52 ${h.age_component(cs.date)}
53 </td>
53 </td>
54 <td class="td-user author">
54 <td class="td-user author">
55 ${base.gravatar_with_user(cs.author)}
55 ${base.gravatar_with_user(cs.author)}
56 </td>
56 </td>
57
57
58 <td class="td-tags">
58 <td class="td-tags">
59 <div class="autoexpand">
59 <div class="autoexpand">
60 %if h.is_hg(c.rhodecode_repo):
60 %if h.is_hg(c.rhodecode_repo):
61 %for book in cs.bookmarks:
61 %for book in cs.bookmarks:
62 <span class="booktag tag" title="${_('Bookmark %s') % book}">
62 <span class="booktag tag" title="${_('Bookmark %s') % book}">
63 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
63 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
64 </span>
64 </span>
65 %endfor
65 %endfor
66 %endif
66 %endif
67 ## tags
67 ## tags
68 %for tag in cs.tags:
68 %for tag in cs.tags:
69 <span class="tagtag tag" title="${_('Tag %s') % tag}">
69 <span class="tagtag tag" title="${_('Tag %s') % tag}">
70 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
70 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
71 </span>
71 </span>
72 %endfor
72 %endfor
73
73
74 ## branch
74 ## branch
75 %if cs.branch:
75 %if cs.branch:
76 <span class="branchtag tag" title="${_('Branch %s') % cs.branch}">
76 <span class="branchtag tag" title="${_('Branch %s') % cs.branch}">
77 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch)}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
77 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch)}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
78 </span>
78 </span>
79 %endif
79 %endif
80 </div>
80 </div>
81 </td>
81 </td>
82 </tr>
82 </tr>
83 %endfor
83 %endfor
84
84
85 </table>
85 </table>
86
86
87 <script type="text/javascript">
87 <script type="text/javascript">
88 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
88 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
89 $(document).on('pjax:success', function(){ timeagoActivate(); });
89 $(document).on('pjax:success', function(){ timeagoActivate(); });
90 </script>
90 </script>
91
91
92 <div class="pagination-wh pagination-left">
92 <div class="pagination-wh pagination-left">
93 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
93 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
94 </div>
94 </div>
95 %else:
95 %else:
96
96
97 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
97 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
98 <div class="quick_start">
98 <div class="quick_start">
99 <div class="fieldset">
99 <div class="fieldset">
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
101 <div class="right-content">
101 <div class="right-content">
102 <div id="add_node_id" class="add_node">
102 <div id="add_node_id" class="add_node">
103 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
103 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
104 </div>
104 </div>
105 </div>
105 </div>
106 %endif
106 %endif
107 </div>
107 </div>
108
108
109 %if not h.is_svn(c.rhodecode_repo):
109 %if not h.is_svn(c.rhodecode_repo):
110 <div class="fieldset">
110 <div class="fieldset">
111 <div class="left-label">${_('Push new repo:')}</div>
111 <div class="left-label">${_('Push new repo:')}</div>
112 <div class="right-content">
112 <div class="right-content">
113 <pre>
113 <pre>
114 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
114 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
115 ${c.rhodecode_repo.alias} add README # add first file
115 ${c.rhodecode_repo.alias} add README # add first file
116 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
116 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
117 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
117 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
118 </pre>
118 </pre>
119 </div>
119 </div>
120 </div>
120 </div>
121 <div class="fieldset">
121 <div class="fieldset">
122 <div class="left-label">${_('Existing repository?')}</div>
122 <div class="left-label">${_('Existing repository?')}</div>
123 <div class="right-content">
123 <div class="right-content">
124 <pre>
124 <pre>
125 %if h.is_git(c.rhodecode_repo):
125 %if h.is_git(c.rhodecode_repo):
126 git remote add origin ${c.clone_repo_url}
126 git remote add origin ${c.clone_repo_url}
127 git push -u origin master
127 git push -u origin master
128 %else:
128 %else:
129 hg push ${c.clone_repo_url}
129 hg push ${c.clone_repo_url}
130 %endif
130 %endif
131 </pre>
131 </pre>
132 </div>
132 </div>
133 </div>
133 </div>
134 %endif
134 %endif
135 </div>
135 </div>
136 %endif
136 %endif
@@ -1,208 +1,209 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6 <%namespace name="base" file="/base/base.html"/>
6 <%namespace name="base" file="/base/base.html"/>
7
7
8 <%def name="comment_block(comment, inline=False)">
8 <%def name="comment_block(comment, inline=False)">
9 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version', None)) %>
10
9 <div class="comment
11 <div class="comment
10 ${'comment-inline' if inline else ''}
12 ${'comment-inline' if inline else ''}
11 ${'comment-outdated' if comment.outdated_at_version(getattr(c, 'at_version', None)) else 'comment-current'}"
13 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
12 id="comment-${comment.comment_id}"
14 id="comment-${comment.comment_id}"
13 line="${comment.line_no}"
15 line="${comment.line_no}"
14 data-comment-id="${comment.comment_id}"
16 data-comment-id="${comment.comment_id}"
15 style="${'display: none;' if comment.outdated_at_version(getattr(c, 'at_version', None)) else ''}">
17 style="${'display: none;' if outdated_at_ver else ''}">
16
18
17 <div class="meta">
19 <div class="meta">
18 <div class="author">
20 <div class="author">
19 ${base.gravatar_with_user(comment.author.email, 16)}
21 ${base.gravatar_with_user(comment.author.email, 16)}
20 </div>
22 </div>
21 <div class="date">
23 <div class="date">
22 ${h.age_component(comment.modified_at, time_is_local=True)}
24 ${h.age_component(comment.modified_at, time_is_local=True)}
23 </div>
25 </div>
24 <div class="status-change">
26 <div class="status-change">
25 % if comment.pull_request:
27 % if comment.pull_request:
26 % if comment.outdated:
28 % if comment.outdated:
27 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
29 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
28 ${_('Outdated comment from pull request version {}').format(comment.pull_request_version_id)}
30 ${_('Outdated comment from pull request version {}').format(comment.pull_request_version_id)}
29 </a>
31 </a>
30 % else:
32 % else:
31 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
33 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
32 %if comment.status_change:
34 %if comment.status_change:
33 ${_('Vote on pull request #%s') % comment.pull_request.pull_request_id}:
35 ${_('Vote on pull request #%s') % comment.pull_request.pull_request_id}:
34 %else:
36 %else:
35 ${_('Comment on pull request #%s') % comment.pull_request.pull_request_id}
37 ${_('Comment on pull request #%s') % comment.pull_request.pull_request_id}
36 %endif
38 %endif
37 </a>
39 </a>
38 % endif
40 % endif
39 % else:
41 % else:
40 % if comment.status_change:
42 % if comment.status_change:
41 ${_('Status change on commit')}:
43 ${_('Status change on commit')}:
42 % else:
44 % else:
43 ${_('Comment on commit')}
45 ${_('Comment on commit')}
44 % endif
46 % endif
45 % endif
47 % endif
46 </div>
48 </div>
47 %if comment.status_change:
49 %if comment.status_change:
48 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
50 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
49 <div title="${_('Commit status')}" class="changeset-status-lbl">
51 <div title="${_('Commit status')}" class="changeset-status-lbl">
50 ${comment.status_change[0].status_lbl}
52 ${comment.status_change[0].status_lbl}
51 </div>
53 </div>
52 %endif
54 %endif
53 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
55 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
54
56
55
56 <div class="comment-links-block">
57 <div class="comment-links-block">
57
58 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
58 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
59 ## only super-admin, repo admin OR comment owner can delete
59 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
60 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
60 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
61 ## permissions to delete
61 ## permissions to delete
62 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
62 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
63 ## TODO: dan: add edit comment here
63 ## TODO: dan: add edit comment here
64 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
64 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
65 %else:
65 %else:
66 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
66 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
67 %endif
67 %endif
68 %else:
68 %else:
69 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
69 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
70 %endif
70 %endif
71 %if not comment.outdated_at_version(getattr(c, 'at_version', None)):
71
72 %if not outdated_at_ver:
72 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
73 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
73 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
74 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
74 %endif
75 %endif
75
76
76 </div>
77 </div>
77 </div>
78 </div>
78 <div class="text">
79 <div class="text">
79 ${comment.render(mentions=True)|n}
80 ${comment.render(mentions=True)|n}
80 </div>
81 </div>
81
82
82 </div>
83 </div>
83 </%def>
84 </%def>
84 ## generate main comments
85 ## generate main comments
85 <%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
86 <%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
86 <div id="comments">
87 <div id="comments">
87 %for comment in c.comments:
88 %for comment in c.comments:
88 <div id="comment-tr-${comment.comment_id}">
89 <div id="comment-tr-${comment.comment_id}">
89 ## only render comments that are not from pull request, or from
90 ## only render comments that are not from pull request, or from
90 ## pull request and a status change
91 ## pull request and a status change
91 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
92 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
92 ${comment_block(comment)}
93 ${comment_block(comment)}
93 %endif
94 %endif
94 </div>
95 </div>
95 %endfor
96 %endfor
96 ## to anchor ajax comments
97 ## to anchor ajax comments
97 <div id="injected_page_comments"></div>
98 <div id="injected_page_comments"></div>
98 </div>
99 </div>
99 </%def>
100 </%def>
100
101
101 ## MAIN COMMENT FORM
102 ## MAIN COMMENT FORM
102 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
103 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
103
104
104 %if is_compare:
105 %if is_compare:
105 <% form_id = "comments_form_compare" %>
106 <% form_id = "comments_form_compare" %>
106 %else:
107 %else:
107 <% form_id = "comments_form" %>
108 <% form_id = "comments_form" %>
108 %endif
109 %endif
109
110
110
111
111 %if is_pull_request:
112 %if is_pull_request:
112 <div class="pull-request-merge">
113 <div class="pull-request-merge">
113 %if c.allowed_to_merge:
114 %if c.allowed_to_merge:
114 <div class="pull-request-wrap">
115 <div class="pull-request-wrap">
115 <div class="pull-right">
116 <div class="pull-right">
116 ${h.secure_form(url('pullrequest_merge', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), id='merge_pull_request_form')}
117 ${h.secure_form(url('pullrequest_merge', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), id='merge_pull_request_form')}
117 <span data-role="merge-message">${c.pr_merge_msg} ${c.approval_msg if c.approval_msg else ''}</span>
118 <span data-role="merge-message">${c.pr_merge_msg} ${c.approval_msg if c.approval_msg else ''}</span>
118 <% merge_disabled = ' disabled' if c.pr_merge_status is False else '' %>
119 <% merge_disabled = ' disabled' if c.pr_merge_status is False else '' %>
119 <input type="submit" id="merge_pull_request" value="${_('Merge Pull Request')}" class="btn${merge_disabled}"${merge_disabled}>
120 <input type="submit" id="merge_pull_request" value="${_('Merge Pull Request')}" class="btn${merge_disabled}"${merge_disabled}>
120 ${h.end_form()}
121 ${h.end_form()}
121 </div>
122 </div>
122 </div>
123 </div>
123 %else:
124 %else:
124 <div class="pull-request-wrap">
125 <div class="pull-request-wrap">
125 <div class="pull-right">
126 <div class="pull-right">
126 <span>${c.pr_merge_msg} ${c.approval_msg if c.approval_msg else ''}</span>
127 <span>${c.pr_merge_msg} ${c.approval_msg if c.approval_msg else ''}</span>
127 </div>
128 </div>
128 </div>
129 </div>
129 %endif
130 %endif
130 </div>
131 </div>
131 %endif
132 %endif
132 <div class="comments">
133 <div class="comments">
133 %if c.rhodecode_user.username != h.DEFAULT_USER:
134 %if c.rhodecode_user.username != h.DEFAULT_USER:
134 <div class="comment-form ac">
135 <div class="comment-form ac">
135 ${h.secure_form(post_url, id_=form_id)}
136 ${h.secure_form(post_url, id_=form_id)}
136 <div id="edit-container" class="clearfix">
137 <div id="edit-container" class="clearfix">
137 <div class="comment-title pull-left">
138 <div class="comment-title pull-left">
138 %if is_pull_request:
139 %if is_pull_request:
139 ${(_('Create a comment on this Pull Request.'))}
140 ${(_('Create a comment on this Pull Request.'))}
140 %elif is_compare:
141 %elif is_compare:
141 ${(_('Create comments on this Commit range.'))}
142 ${(_('Create comments on this Commit range.'))}
142 %else:
143 %else:
143 ${(_('Create a comment on this Commit.'))}
144 ${(_('Create a comment on this Commit.'))}
144 %endif
145 %endif
145 </div>
146 </div>
146 <div class="comment-help pull-right">
147 <div class="comment-help pull-right">
147 ${(_('Comments parsed using %s syntax with %s support.') % (
148 ${(_('Comments parsed using %s syntax with %s support.') % (
148 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
149 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
149 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
150 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
150 )
151 )
151 )|n
152 )|n
152 }
153 }
153 </div>
154 </div>
154 <div style="clear: both"></div>
155 <div style="clear: both"></div>
155 ${h.textarea('text', class_="comment-block-ta")}
156 ${h.textarea('text', class_="comment-block-ta")}
156 </div>
157 </div>
157
158
158 <div id="preview-container" class="clearfix" style="display: none;">
159 <div id="preview-container" class="clearfix" style="display: none;">
159 <div class="comment-title">
160 <div class="comment-title">
160 ${_('Comment preview')}
161 ${_('Comment preview')}
161 </div>
162 </div>
162 <div id="preview-box" class="preview-box"></div>
163 <div id="preview-box" class="preview-box"></div>
163 </div>
164 </div>
164
165
165 <div id="comment_form_extras">
166 <div id="comment_form_extras">
166 %if form_extras and isinstance(form_extras, (list, tuple)):
167 %if form_extras and isinstance(form_extras, (list, tuple)):
167 % for form_ex_el in form_extras:
168 % for form_ex_el in form_extras:
168 ${form_ex_el|n}
169 ${form_ex_el|n}
169 % endfor
170 % endfor
170 %endif
171 %endif
171 </div>
172 </div>
172 <div class="comment-footer">
173 <div class="comment-footer">
173 %if change_status:
174 %if change_status:
174 <div class="status_box">
175 <div class="status_box">
175 <select id="change_status" name="changeset_status">
176 <select id="change_status" name="changeset_status">
176 <option></option> # Placeholder
177 <option></option> # Placeholder
177 %for status,lbl in c.commit_statuses:
178 %for status,lbl in c.commit_statuses:
178 <option value="${status}" data-status="${status}">${lbl}</option>
179 <option value="${status}" data-status="${status}">${lbl}</option>
179 %if is_pull_request and change_status and status in ('approved', 'rejected'):
180 %if is_pull_request and change_status and status in ('approved', 'rejected'):
180 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
181 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
181 %endif
182 %endif
182 %endfor
183 %endfor
183 </select>
184 </select>
184 </div>
185 </div>
185 %endif
186 %endif
186 <div class="action-buttons">
187 <div class="action-buttons">
187 <button id="preview-btn" class="btn btn-secondary">${_('Preview')}</button>
188 <button id="preview-btn" class="btn btn-secondary">${_('Preview')}</button>
188 <button id="edit-btn" class="btn btn-secondary" style="display:none;">${_('Edit')}</button>
189 <button id="edit-btn" class="btn btn-secondary" style="display:none;">${_('Edit')}</button>
189 <div class="comment-button">${h.submit('save', _('Comment'), class_="btn btn-success comment-button-input")}</div>
190 <div class="comment-button">${h.submit('save', _('Comment'), class_="btn btn-success comment-button-input")}</div>
190 </div>
191 </div>
191 </div>
192 </div>
192 ${h.end_form()}
193 ${h.end_form()}
193 </div>
194 </div>
194 %endif
195 %endif
195 </div>
196 </div>
196 <script>
197 <script>
197 // init active elements of commentForm
198 // init active elements of commentForm
198 var commitId = templateContext.commit_data.commit_id;
199 var commitId = templateContext.commit_data.commit_id;
199 var pullRequestId = templateContext.pull_request_data.pull_request_id;
200 var pullRequestId = templateContext.pull_request_data.pull_request_id;
200 var lineNo;
201 var lineNo;
201
202
202 var mainCommentForm = new CommentForm(
203 var mainCommentForm = new CommentForm(
203 "#${form_id}", commitId, pullRequestId, lineNo, true);
204 "#${form_id}", commitId, pullRequestId, lineNo, true);
204
205
205 mainCommentForm.initStatusChangeSelector();
206 mainCommentForm.initStatusChangeSelector();
206 bindToggleButtons();
207 bindToggleButtons();
207 </script>
208 </script>
208 </%def>
209 </%def>
@@ -1,609 +1,691 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 # show inline comments
37 # show inline comments
38 use_comments=False,
38 use_comments=False,
39
39
40 # disable new comments
40 # disable new comments
41 disable_new_comments=False,
41 disable_new_comments=False,
42
42
43 # special file-comments that were deleted in previous versions
44 # it's used for showing outdated comments for deleted files in a PR
45 deleted_files_comments=None
46
43 )">
47 )">
44
48
45 %if use_comments:
49 %if use_comments:
46 <div id="cb-comments-inline-container-template" class="js-template">
50 <div id="cb-comments-inline-container-template" class="js-template">
47 ${inline_comments_container([])}
51 ${inline_comments_container([])}
48 </div>
52 </div>
49 <div class="js-template" id="cb-comment-inline-form-template">
53 <div class="js-template" id="cb-comment-inline-form-template">
50 <div class="comment-inline-form ac">
54 <div class="comment-inline-form ac">
51 %if c.rhodecode_user.username != h.DEFAULT_USER:
55 %if c.rhodecode_user.username != h.DEFAULT_USER:
52 ${h.form('#', method='get')}
56 ${h.form('#', method='get')}
53 <div id="edit-container_{1}" class="clearfix">
57 <div id="edit-container_{1}" class="clearfix">
54 <div class="comment-title pull-left">
58 <div class="comment-title pull-left">
55 ${_('Create a comment on line {1}.')}
59 ${_('Create a comment on line {1}.')}
56 </div>
60 </div>
57 <div class="comment-help pull-right">
61 <div class="comment-help pull-right">
58 ${(_('Comments parsed using %s syntax with %s support.') % (
62 ${(_('Comments parsed using %s syntax with %s support.') % (
59 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
63 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
60 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
64 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
61 )
65 )
62 )|n
66 )|n
63 }
67 }
64 </div>
68 </div>
65 <div style="clear: both"></div>
69 <div style="clear: both"></div>
66 <textarea id="text_{1}" name="text" class="comment-block-ta ac-input"></textarea>
70 <textarea id="text_{1}" name="text" class="comment-block-ta ac-input"></textarea>
67 </div>
71 </div>
68 <div id="preview-container_{1}" class="clearfix" style="display: none;">
72 <div id="preview-container_{1}" class="clearfix" style="display: none;">
69 <div class="comment-help">
73 <div class="comment-help">
70 ${_('Comment preview')}
74 ${_('Comment preview')}
71 </div>
75 </div>
72 <div id="preview-box_{1}" class="preview-box"></div>
76 <div id="preview-box_{1}" class="preview-box"></div>
73 </div>
77 </div>
74 <div class="comment-footer">
78 <div class="comment-footer">
75 <div class="action-buttons">
79 <div class="action-buttons">
76 <input type="hidden" name="f_path" value="{0}">
80 <input type="hidden" name="f_path" value="{0}">
77 <input type="hidden" name="line" value="{1}">
81 <input type="hidden" name="line" value="{1}">
78 <button id="preview-btn_{1}" class="btn btn-secondary">${_('Preview')}</button>
82 <button id="preview-btn_{1}" class="btn btn-secondary">${_('Preview')}</button>
79 <button id="edit-btn_{1}" class="btn btn-secondary" style="display: none;">${_('Edit')}</button>
83 <button id="edit-btn_{1}" class="btn btn-secondary" style="display: none;">${_('Edit')}</button>
80 ${h.submit('save', _('Comment'), class_='btn btn-success save-inline-form')}
84 ${h.submit('save', _('Comment'), class_='btn btn-success save-inline-form')}
81 </div>
85 </div>
82 <div class="comment-button">
86 <div class="comment-button">
83 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
87 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
84 ${_('Cancel')}
88 ${_('Cancel')}
85 </button>
89 </button>
86 </div>
90 </div>
87 ${h.end_form()}
91 ${h.end_form()}
88 </div>
92 </div>
89 %else:
93 %else:
90 ${h.form('', class_='inline-form comment-form-login', method='get')}
94 ${h.form('', class_='inline-form comment-form-login', method='get')}
91 <div class="pull-left">
95 <div class="pull-left">
92 <div class="comment-help pull-right">
96 <div class="comment-help pull-right">
93 ${_('You need to be logged in to comment.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
97 ${_('You need to be logged in to comment.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
94 </div>
98 </div>
95 </div>
99 </div>
96 <div class="comment-button pull-right">
100 <div class="comment-button pull-right">
97 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
101 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
98 ${_('Cancel')}
102 ${_('Cancel')}
99 </button>
103 </button>
100 </div>
104 </div>
101 <div class="clearfix"></div>
105 <div class="clearfix"></div>
102 ${h.end_form()}
106 ${h.end_form()}
103 %endif
107 %endif
104 </div>
108 </div>
105 </div>
109 </div>
106
110
107 %endif
111 %endif
108 <%
112 <%
109 collapse_all = len(diffset.files) > collapse_when_files_over
113 collapse_all = len(diffset.files) > collapse_when_files_over
110 %>
114 %>
111
115
112 %if c.diffmode == 'sideside':
116 %if c.diffmode == 'sideside':
113 <style>
117 <style>
114 .wrapper {
118 .wrapper {
115 max-width: 1600px !important;
119 max-width: 1600px !important;
116 }
120 }
117 </style>
121 </style>
118 %endif
122 %endif
119 %if ruler_at_chars:
123 %if ruler_at_chars:
120 <style>
124 <style>
121 .diff table.cb .cb-content:after {
125 .diff table.cb .cb-content:after {
122 content: "";
126 content: "";
123 border-left: 1px solid blue;
127 border-left: 1px solid blue;
124 position: absolute;
128 position: absolute;
125 top: 0;
129 top: 0;
126 height: 18px;
130 height: 18px;
127 opacity: .2;
131 opacity: .2;
128 z-index: 10;
132 z-index: 10;
129 ## +5 to account for diff action (+/-)
133 //## +5 to account for diff action (+/-)
130 left: ${ruler_at_chars + 5}ch;
134 left: ${ruler_at_chars + 5}ch;
131 </style>
135 </style>
132 %endif
136 %endif
133 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
137 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
134 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
138 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
135 %if commit:
139 %if commit:
136 <div class="pull-right">
140 <div class="pull-right">
137 <a class="btn tooltip" title="${_('Browse Files at revision {}').format(commit.raw_id)}" href="${h.url('files_home',repo_name=diffset.repo_name, revision=commit.raw_id, f_path='')}">
141 <a class="btn tooltip" title="${_('Browse Files at revision {}').format(commit.raw_id)}" href="${h.url('files_home',repo_name=diffset.repo_name, revision=commit.raw_id, f_path='')}">
138 ${_('Browse Files')}
142 ${_('Browse Files')}
139 </a>
143 </a>
140 </div>
144 </div>
141 %endif
145 %endif
142 <h2 class="clearinner">
146 <h2 class="clearinner">
143 %if commit:
147 %if commit:
144 <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> -
148 <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> -
145 ${h.age_component(commit.date)} -
149 ${h.age_component(commit.date)} -
146 %endif
150 %endif
147 %if diffset.limited_diff:
151 %if diffset.limited_diff:
148 ${_('The requested commit is too big and content was truncated.')}
152 ${_('The requested commit is too big and content was truncated.')}
149
153
150 ${ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}}
154 ${ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}}
151 <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
155 <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
152 %else:
156 %else:
153 ${ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
157 ${ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
154 '%(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}}
158 '%(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}}
155 %endif
159 %endif
160
161 <% at_ver = getattr(c, 'at_version_num', None) %>
162 % if at_ver:
163 <div class="pull-right">
164 ${_('Changes at version %d') % at_ver}
165 </div>
166 % endif
167
156 </h2>
168 </h2>
157 </div>
169 </div>
158
170
159 %if not diffset.files:
171 %if not diffset.files:
160 <p class="empty_data">${_('No files')}</p>
172 <p class="empty_data">${_('No files')}</p>
161 %endif
173 %endif
162
174
163 <div class="filediffs">
175 <div class="filediffs">
164 %for i, filediff in enumerate(diffset.files):
176 %for i, filediff in enumerate(diffset.files):
165
177
166 <%
178 <%
167 lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted']
179 lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted']
168 over_lines_changed_limit = lines_changed > lines_changed_limit
180 over_lines_changed_limit = lines_changed > lines_changed_limit
169 %>
181 %>
170 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
182 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
171 <div
183 <div
172 class="filediff"
184 class="filediff"
173 data-f-path="${filediff['patch']['filename']}"
185 data-f-path="${filediff['patch']['filename']}"
174 id="a_${h.FID('', filediff['patch']['filename'])}">
186 id="a_${h.FID('', filediff['patch']['filename'])}">
175 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
187 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
176 <div class="filediff-collapse-indicator"></div>
188 <div class="filediff-collapse-indicator"></div>
177 ${diff_ops(filediff)}
189 ${diff_ops(filediff)}
178 </label>
190 </label>
179 ${diff_menu(filediff, use_comments=use_comments)}
191 ${diff_menu(filediff, use_comments=use_comments)}
180 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
192 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
181 %if not filediff.hunks:
193 %if not filediff.hunks:
182 %for op_id, op_text in filediff['patch']['stats']['ops'].items():
194 %for op_id, op_text in filediff['patch']['stats']['ops'].items():
183 <tr>
195 <tr>
184 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=3' or 'colspan=4'}>
196 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
185 %if op_id == DEL_FILENODE:
197 %if op_id == DEL_FILENODE:
186 ${_('File was deleted')}
198 ${_('File was deleted')}
187 %elif op_id == BIN_FILENODE:
199 %elif op_id == BIN_FILENODE:
188 ${_('Binary file hidden')}
200 ${_('Binary file hidden')}
189 %else:
201 %else:
190 ${op_text}
202 ${op_text}
191 %endif
203 %endif
192 </td>
204 </td>
193 </tr>
205 </tr>
194 %endfor
206 %endfor
195 %endif
207 %endif
196 %if over_lines_changed_limit:
197 <tr class="cb-warning cb-collapser">
198 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
199 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
200 <a href="#" class="cb-expand"
201 onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')}
202 </a>
203 <a href="#" class="cb-collapse"
204 onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')}
205 </a>
206 </td>
207 </tr>
208 %endif
209 %if filediff.patch['is_limited_diff']:
208 %if filediff.patch['is_limited_diff']:
210 <tr class="cb-warning cb-collapser">
209 <tr class="cb-warning cb-collapser">
211 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
210 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
212 ${_('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>
211 ${_('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>
213 </td>
212 </td>
214 </tr>
213 </tr>
214 %else:
215 %if over_lines_changed_limit:
216 <tr class="cb-warning cb-collapser">
217 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
218 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
219 <a href="#" class="cb-expand"
220 onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')}
221 </a>
222 <a href="#" class="cb-collapse"
223 onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')}
224 </a>
225 </td>
226 </tr>
227 %endif
215 %endif
228 %endif
229
216 %for hunk in filediff.hunks:
230 %for hunk in filediff.hunks:
217 <tr class="cb-hunk">
231 <tr class="cb-hunk">
218 <td ${c.diffmode == 'unified' and 'colspan=3' or ''}>
232 <td ${c.diffmode == 'unified' and 'colspan=3' or ''}>
219 ## TODO: dan: add ajax loading of more context here
233 ## TODO: dan: add ajax loading of more context here
220 ## <a href="#">
234 ## <a href="#">
221 <i class="icon-more"></i>
235 <i class="icon-more"></i>
222 ## </a>
236 ## </a>
223 </td>
237 </td>
224 <td ${c.diffmode == 'sideside' and 'colspan=5' or ''}>
238 <td ${c.diffmode == 'sideside' and 'colspan=5' or ''}>
225 @@
239 @@
226 -${hunk.source_start},${hunk.source_length}
240 -${hunk.source_start},${hunk.source_length}
227 +${hunk.target_start},${hunk.target_length}
241 +${hunk.target_start},${hunk.target_length}
228 ${hunk.section_header}
242 ${hunk.section_header}
229 </td>
243 </td>
230 </tr>
244 </tr>
231 %if c.diffmode == 'unified':
245 %if c.diffmode == 'unified':
232 ${render_hunk_lines_unified(hunk, use_comments=use_comments)}
246 ${render_hunk_lines_unified(hunk, use_comments=use_comments)}
233 %elif c.diffmode == 'sideside':
247 %elif c.diffmode == 'sideside':
234 ${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
248 ${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
235 %else:
249 %else:
236 <tr class="cb-line">
250 <tr class="cb-line">
237 <td>unknown diff mode</td>
251 <td>unknown diff mode</td>
238 </tr>
252 </tr>
239 %endif
253 %endif
240 %endfor
254 %endfor
241
255
242 % for lineno, comments in filediff.left_comments.items():
256 ## outdated comments that do not fit into currently displayed lines
257 % for lineno, comments in filediff.left_comments.items():
258
259 %if c.diffmode == 'unified':
260 <tr class="cb-line">
261 <td class="cb-data cb-context"></td>
262 <td class="cb-lineno cb-context"></td>
263 <td class="cb-lineno cb-context"></td>
264 <td class="cb-content cb-context">
265 ${inline_comments_container(comments)}
266 </td>
267 </tr>
268 %elif c.diffmode == 'sideside':
269 <tr class="cb-line">
270 <td class="cb-data cb-context"></td>
271 <td class="cb-lineno cb-context"></td>
272 <td class="cb-content cb-context"></td>
273
274 <td class="cb-data cb-context"></td>
275 <td class="cb-lineno cb-context"></td>
276 <td class="cb-content cb-context">
277 ${inline_comments_container(comments)}
278 </td>
279 </tr>
280 %endif
281
282 % endfor
283
284 </table>
285 </div>
286 %endfor
287
288 ## outdated comments that are made for a file that has been deleted
289 % for filename, comments_dict in (deleted_files_comments or {}).items():
243
290
244 %if c.diffmode == 'unified':
291 <div class="filediffs filediff-outdated" style="display: none">
245 <tr class="cb-line">
292 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filename)}" type="checkbox">
246 <td class="cb-data cb-context"></td>
293 <div class="filediff" data-f-path="${filename}" id="a_${h.FID('', filename)}">
247 <td class="cb-lineno cb-context"></td>
294 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
248 <td class="cb-lineno cb-context"></td>
295 <div class="filediff-collapse-indicator"></div>
249 <td class="cb-content cb-context">
296 <span class="pill">
250 ${inline_comments_container(comments)}
297 ## file was deleted
251 </td>
298 <strong>${filename}</strong>
252 </tr>
299 </span>
253 %elif c.diffmode == 'sideside':
300 <span class="pill-group" style="float: left">
254 <tr class="cb-line">
301 ## file op, doesn't need translation
302 <span class="pill" op="removed">removed in this version</span>
303 </span>
304 <a class="pill filediff-anchor" href="#a_${h.FID('', filename)}">ΒΆ</a>
305 <span class="pill-group" style="float: right">
306 <span class="pill" op="deleted">-${comments_dict['stats']}</span>
307 </span>
308 </label>
309
310 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
311 <tr>
312 % if c.diffmode == 'unified':
313 <td></td>
314 %endif
315
316 <td></td>
317 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=5'}>
318 ${_('File was deleted in this version, and outdated comments were made on it')}
319 </td>
320 </tr>
321 %if c.diffmode == 'unified':
322 <tr class="cb-line">
323 <td class="cb-data cb-context"></td>
324 <td class="cb-lineno cb-context"></td>
325 <td class="cb-lineno cb-context"></td>
326 <td class="cb-content cb-context">
327 ${inline_comments_container(comments_dict['comments'])}
328 </td>
329 </tr>
330 %elif c.diffmode == 'sideside':
331 <tr class="cb-line">
255 <td class="cb-data cb-context"></td>
332 <td class="cb-data cb-context"></td>
256 <td class="cb-lineno cb-context"></td>
333 <td class="cb-lineno cb-context"></td>
257 <td class="cb-content cb-context"></td>
334 <td class="cb-content cb-context"></td>
258
335
259 <td class="cb-data cb-context"></td>
336 <td class="cb-data cb-context"></td>
260 <td class="cb-lineno cb-context"></td>
337 <td class="cb-lineno cb-context"></td>
261 <td class="cb-content cb-context">
338 <td class="cb-content cb-context">
262 ${inline_comments_container(comments)}
339 ${inline_comments_container(comments_dict['comments'])}
263 </td>
340 </td>
264 </tr>
341 </tr>
265 %endif
342 %endif
343 </table>
344 </div>
345 </div>
346 % endfor
266
347
267 % endfor
268
269 </table>
270 </div>
271 %endfor
272 </div>
273 </div>
348 </div>
274 </%def>
349 </%def>
275
350
276 <%def name="diff_ops(filediff)">
351 <%def name="diff_ops(filediff)">
277 <%
352 <%
278 stats = filediff['patch']['stats']
353 stats = filediff['patch']['stats']
279 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
354 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
280 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
355 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
281 %>
356 %>
282 <span class="pill">
357 <span class="pill">
283 %if filediff.source_file_path and filediff.target_file_path:
358 %if filediff.source_file_path and filediff.target_file_path:
284 %if filediff.source_file_path != filediff.target_file_path: # file was renamed
359 %if filediff.source_file_path != filediff.target_file_path: # file was renamed
285 <strong>${filediff.target_file_path}</strong> β¬… <del>${filediff.source_file_path}</del>
360 <strong>${filediff.target_file_path}</strong> β¬… <del>${filediff.source_file_path}</del>
286 %else:
361 %else:
287 ## file was modified
362 ## file was modified
288 <strong>${filediff.source_file_path}</strong>
363 <strong>${filediff.source_file_path}</strong>
289 %endif
364 %endif
290 %else:
365 %else:
291 %if filediff.source_file_path:
366 %if filediff.source_file_path:
292 ## file was deleted
367 ## file was deleted
293 <strong>${filediff.source_file_path}</strong>
368 <strong>${filediff.source_file_path}</strong>
294 %else:
369 %else:
295 ## file was added
370 ## file was added
296 <strong>${filediff.target_file_path}</strong>
371 <strong>${filediff.target_file_path}</strong>
297 %endif
372 %endif
298 %endif
373 %endif
299 </span>
374 </span>
300 <span class="pill-group" style="float: left">
375 <span class="pill-group" style="float: left">
301 %if filediff.patch['is_limited_diff']:
376 %if filediff.patch['is_limited_diff']:
302 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
377 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
303 %endif
378 %endif
304 %if RENAMED_FILENODE in stats['ops']:
379 %if RENAMED_FILENODE in stats['ops']:
305 <span class="pill" op="renamed">renamed</span>
380 <span class="pill" op="renamed">renamed</span>
306 %endif
381 %endif
307
382
308 %if NEW_FILENODE in stats['ops']:
383 %if NEW_FILENODE in stats['ops']:
309 <span class="pill" op="created">created</span>
384 <span class="pill" op="created">created</span>
310 %if filediff['target_mode'].startswith('120'):
385 %if filediff['target_mode'].startswith('120'):
311 <span class="pill" op="symlink">symlink</span>
386 <span class="pill" op="symlink">symlink</span>
312 %else:
387 %else:
313 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
388 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
314 %endif
389 %endif
315 %endif
390 %endif
316
391
317 %if DEL_FILENODE in stats['ops']:
392 %if DEL_FILENODE in stats['ops']:
318 <span class="pill" op="removed">removed</span>
393 <span class="pill" op="removed">removed</span>
319 %endif
394 %endif
320
395
321 %if CHMOD_FILENODE in stats['ops']:
396 %if CHMOD_FILENODE in stats['ops']:
322 <span class="pill" op="mode">
397 <span class="pill" op="mode">
323 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
398 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
324 </span>
399 </span>
325 %endif
400 %endif
326 </span>
401 </span>
327
402
328 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}">ΒΆ</a>
403 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}">ΒΆ</a>
329
404
330 <span class="pill-group" style="float: right">
405 <span class="pill-group" style="float: right">
331 %if BIN_FILENODE in stats['ops']:
406 %if BIN_FILENODE in stats['ops']:
332 <span class="pill" op="binary">binary</span>
407 <span class="pill" op="binary">binary</span>
333 %if MOD_FILENODE in stats['ops']:
408 %if MOD_FILENODE in stats['ops']:
334 <span class="pill" op="modified">modified</span>
409 <span class="pill" op="modified">modified</span>
335 %endif
410 %endif
336 %endif
411 %endif
337 %if stats['added']:
412 %if stats['added']:
338 <span class="pill" op="added">+${stats['added']}</span>
413 <span class="pill" op="added">+${stats['added']}</span>
339 %endif
414 %endif
340 %if stats['deleted']:
415 %if stats['deleted']:
341 <span class="pill" op="deleted">-${stats['deleted']}</span>
416 <span class="pill" op="deleted">-${stats['deleted']}</span>
342 %endif
417 %endif
343 </span>
418 </span>
344
419
345 </%def>
420 </%def>
346
421
347 <%def name="nice_mode(filemode)">
422 <%def name="nice_mode(filemode)">
348 ${filemode.startswith('100') and filemode[3:] or filemode}
423 ${filemode.startswith('100') and filemode[3:] or filemode}
349 </%def>
424 </%def>
350
425
351 <%def name="diff_menu(filediff, use_comments=False)">
426 <%def name="diff_menu(filediff, use_comments=False)">
352 <div class="filediff-menu">
427 <div class="filediff-menu">
353 %if filediff.diffset.source_ref:
428 %if filediff.diffset.source_ref:
354 %if filediff.patch['operation'] in ['D', 'M']:
429 %if filediff.patch['operation'] in ['D', 'M']:
355 <a
430 <a
356 class="tooltip"
431 class="tooltip"
357 href="${h.url('files_home',repo_name=filediff.diffset.repo_name,f_path=filediff.source_file_path,revision=filediff.diffset.source_ref)}"
432 href="${h.url('files_home',repo_name=filediff.diffset.repo_name,f_path=filediff.source_file_path,revision=filediff.diffset.source_ref)}"
358 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
433 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
359 >
434 >
360 ${_('Show file before')}
435 ${_('Show file before')}
361 </a>
436 </a> |
362 %else:
437 %else:
363 <span
438 <span
364 class="tooltip"
439 class="tooltip"
365 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
440 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
366 >
441 >
367 ${_('Show file before')}
442 ${_('Show file before')}
368 </span>
443 </span> |
369 %endif
444 %endif
370 %if filediff.patch['operation'] in ['A', 'M']:
445 %if filediff.patch['operation'] in ['A', 'M']:
371 <a
446 <a
372 class="tooltip"
447 class="tooltip"
373 href="${h.url('files_home',repo_name=filediff.diffset.source_repo_name,f_path=filediff.target_file_path,revision=filediff.diffset.target_ref)}"
448 href="${h.url('files_home',repo_name=filediff.diffset.source_repo_name,f_path=filediff.target_file_path,revision=filediff.diffset.target_ref)}"
374 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
449 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
375 >
450 >
376 ${_('Show file after')}
451 ${_('Show file after')}
377 </a>
452 </a> |
378 %else:
453 %else:
379 <span
454 <span
380 class="tooltip"
455 class="tooltip"
381 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
456 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
382 >
457 >
383 ${_('Show file after')}
458 ${_('Show file after')}
384 </span>
459 </span> |
385 %endif
460 %endif
386 <a
461 <a
387 class="tooltip"
462 class="tooltip"
388 title="${h.tooltip(_('Raw diff'))}"
463 title="${h.tooltip(_('Raw diff'))}"
389 href="${h.url('files_diff_home',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw')}"
464 href="${h.url('files_diff_home',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw')}"
390 >
465 >
391 ${_('Raw diff')}
466 ${_('Raw diff')}
392 </a>
467 </a> |
393 <a
468 <a
394 class="tooltip"
469 class="tooltip"
395 title="${h.tooltip(_('Download diff'))}"
470 title="${h.tooltip(_('Download diff'))}"
396 href="${h.url('files_diff_home',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download')}"
471 href="${h.url('files_diff_home',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path,diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download')}"
397 >
472 >
398 ${_('Download diff')}
473 ${_('Download diff')}
399 </a>
474 </a>
475 % if use_comments:
476 |
477 % endif
400
478
401 ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks)
479 ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks)
402 %if hasattr(c, 'ignorews_url'):
480 %if hasattr(c, 'ignorews_url'):
403 ${c.ignorews_url(request.GET, h.FID('', filediff['patch']['filename']))}
481 ${c.ignorews_url(request.GET, h.FID('', filediff['patch']['filename']))}
404 %endif
482 %endif
405 %if hasattr(c, 'context_url'):
483 %if hasattr(c, 'context_url'):
406 ${c.context_url(request.GET, h.FID('', filediff['patch']['filename']))}
484 ${c.context_url(request.GET, h.FID('', filediff['patch']['filename']))}
407 %endif
485 %endif
408
486
409
410 %if use_comments:
487 %if use_comments:
411 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
488 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
412 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
489 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
413 </a>
490 </a>
414 %endif
491 %endif
415 %endif
492 %endif
416 </div>
493 </div>
417 </%def>
494 </%def>
418
495
419
496
420 <%namespace name="commentblock" file="/changeset/changeset_file_comment.html"/>
497 <%namespace name="commentblock" file="/changeset/changeset_file_comment.html"/>
421 <%def name="inline_comments_container(comments)">
498 <%def name="inline_comments_container(comments)">
422 <div class="inline-comments">
499 <div class="inline-comments">
423 %for comment in comments:
500 %for comment in comments:
424 ${commentblock.comment_block(comment, inline=True)}
501 ${commentblock.comment_block(comment, inline=True)}
425 %endfor
502 %endfor
426
503
427 <span onclick="return Rhodecode.comments.createComment(this)"
504 % if comments and comments[-1].outdated:
428 class="btn btn-secondary cb-comment-add-button ${'comment-outdated' if comments and comments[-1].outdated else ''}"
505 <span class="btn btn-secondary cb-comment-add-button comment-outdated}"
429 style="${'display: none;' if comments and comments[-1].outdated else ''}">
506 style="display: none;}">
430 ${_('Add another comment')}
507 ${_('Add another comment')}
431 </span>
508 </span>
509 % else:
510 <span onclick="return Rhodecode.comments.createComment(this)"
511 class="btn btn-secondary cb-comment-add-button">
512 ${_('Add another comment')}
513 </span>
514 % endif
432
515
433 </div>
516 </div>
434 </%def>
517 </%def>
435
518
436
519
437 <%def name="render_hunk_lines_sideside(hunk, use_comments=False)">
520 <%def name="render_hunk_lines_sideside(hunk, use_comments=False)">
438 %for i, line in enumerate(hunk.sideside):
521 %for i, line in enumerate(hunk.sideside):
439 <%
522 <%
440 old_line_anchor, new_line_anchor = None, None
523 old_line_anchor, new_line_anchor = None, None
441 if line.original.lineno:
524 if line.original.lineno:
442 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, line.original.lineno, 'o')
525 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, line.original.lineno, 'o')
443 if line.modified.lineno:
526 if line.modified.lineno:
444 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, line.modified.lineno, 'n')
527 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, line.modified.lineno, 'n')
445 %>
528 %>
446
529
447 <tr class="cb-line">
530 <tr class="cb-line">
448 <td class="cb-data ${action_class(line.original.action)}"
531 <td class="cb-data ${action_class(line.original.action)}"
449 data-line-number="${line.original.lineno}"
532 data-line-number="${line.original.lineno}"
450 >
533 >
451 <div>
534 <div>
452 %if line.original.comments:
535 %if line.original.comments:
453 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
536 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
454 %endif
537 %endif
455 </div>
538 </div>
456 </td>
539 </td>
457 <td class="cb-lineno ${action_class(line.original.action)}"
540 <td class="cb-lineno ${action_class(line.original.action)}"
458 data-line-number="${line.original.lineno}"
541 data-line-number="${line.original.lineno}"
459 %if old_line_anchor:
542 %if old_line_anchor:
460 id="${old_line_anchor}"
543 id="${old_line_anchor}"
461 %endif
544 %endif
462 >
545 >
463 %if line.original.lineno:
546 %if line.original.lineno:
464 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
547 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
465 %endif
548 %endif
466 </td>
549 </td>
467 <td class="cb-content ${action_class(line.original.action)}"
550 <td class="cb-content ${action_class(line.original.action)}"
468 data-line-number="o${line.original.lineno}"
551 data-line-number="o${line.original.lineno}"
469 >
552 >
470 %if use_comments and line.original.lineno:
553 %if use_comments and line.original.lineno:
471 ${render_add_comment_button()}
554 ${render_add_comment_button()}
472 %endif
555 %endif
473 <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
556 <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
474 %if use_comments and line.original.lineno and line.original.comments:
557 %if use_comments and line.original.lineno and line.original.comments:
475 ${inline_comments_container(line.original.comments)}
558 ${inline_comments_container(line.original.comments)}
476 %endif
559 %endif
477 </td>
560 </td>
478 <td class="cb-data ${action_class(line.modified.action)}"
561 <td class="cb-data ${action_class(line.modified.action)}"
479 data-line-number="${line.modified.lineno}"
562 data-line-number="${line.modified.lineno}"
480 >
563 >
481 <div>
564 <div>
482 %if line.modified.comments:
565 %if line.modified.comments:
483 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
566 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
484 %endif
567 %endif
485 </div>
568 </div>
486 </td>
569 </td>
487 <td class="cb-lineno ${action_class(line.modified.action)}"
570 <td class="cb-lineno ${action_class(line.modified.action)}"
488 data-line-number="${line.modified.lineno}"
571 data-line-number="${line.modified.lineno}"
489 %if new_line_anchor:
572 %if new_line_anchor:
490 id="${new_line_anchor}"
573 id="${new_line_anchor}"
491 %endif
574 %endif
492 >
575 >
493 %if line.modified.lineno:
576 %if line.modified.lineno:
494 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
577 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
495 %endif
578 %endif
496 </td>
579 </td>
497 <td class="cb-content ${action_class(line.modified.action)}"
580 <td class="cb-content ${action_class(line.modified.action)}"
498 data-line-number="n${line.modified.lineno}"
581 data-line-number="n${line.modified.lineno}"
499 >
582 >
500 %if use_comments and line.modified.lineno:
583 %if use_comments and line.modified.lineno:
501 ${render_add_comment_button()}
584 ${render_add_comment_button()}
502 %endif
585 %endif
503 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
586 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
504 %if use_comments and line.modified.lineno and line.modified.comments:
587 %if use_comments and line.modified.lineno and line.modified.comments:
505 ${inline_comments_container(line.modified.comments)}
588 ${inline_comments_container(line.modified.comments)}
506 %endif
589 %endif
507 </td>
590 </td>
508 </tr>
591 </tr>
509 %endfor
592 %endfor
510 </%def>
593 </%def>
511
594
512
595
513 <%def name="render_hunk_lines_unified(hunk, use_comments=False)">
596 <%def name="render_hunk_lines_unified(hunk, use_comments=False)">
514 %for old_line_no, new_line_no, action, content, comments in hunk.unified:
597 %for old_line_no, new_line_no, action, content, comments in hunk.unified:
515 <%
598 <%
516 old_line_anchor, new_line_anchor = None, None
599 old_line_anchor, new_line_anchor = None, None
517 if old_line_no:
600 if old_line_no:
518 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, old_line_no, 'o')
601 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, old_line_no, 'o')
519 if new_line_no:
602 if new_line_no:
520 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, new_line_no, 'n')
603 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, new_line_no, 'n')
521 %>
604 %>
522 <tr class="cb-line">
605 <tr class="cb-line">
523 <td class="cb-data ${action_class(action)}">
606 <td class="cb-data ${action_class(action)}">
524 <div>
607 <div>
525 %if comments:
608 %if comments:
526 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
609 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
527 %endif
610 %endif
528 </div>
611 </div>
529 </td>
612 </td>
530 <td class="cb-lineno ${action_class(action)}"
613 <td class="cb-lineno ${action_class(action)}"
531 data-line-number="${old_line_no}"
614 data-line-number="${old_line_no}"
532 %if old_line_anchor:
615 %if old_line_anchor:
533 id="${old_line_anchor}"
616 id="${old_line_anchor}"
534 %endif
617 %endif
535 >
618 >
536 %if old_line_anchor:
619 %if old_line_anchor:
537 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
620 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
538 %endif
621 %endif
539 </td>
622 </td>
540 <td class="cb-lineno ${action_class(action)}"
623 <td class="cb-lineno ${action_class(action)}"
541 data-line-number="${new_line_no}"
624 data-line-number="${new_line_no}"
542 %if new_line_anchor:
625 %if new_line_anchor:
543 id="${new_line_anchor}"
626 id="${new_line_anchor}"
544 %endif
627 %endif
545 >
628 >
546 %if new_line_anchor:
629 %if new_line_anchor:
547 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
630 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
548 %endif
631 %endif
549 </td>
632 </td>
550 <td class="cb-content ${action_class(action)}"
633 <td class="cb-content ${action_class(action)}"
551 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
634 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
552 >
635 >
553 %if use_comments:
636 %if use_comments:
554 ${render_add_comment_button()}
637 ${render_add_comment_button()}
555 %endif
638 %endif
556 <span class="cb-code">${action} ${content or '' | n}</span>
639 <span class="cb-code">${action} ${content or '' | n}</span>
557 %if use_comments and comments:
640 %if use_comments and comments:
558 ${inline_comments_container(comments)}
641 ${inline_comments_container(comments)}
559 %endif
642 %endif
560 </td>
643 </td>
561 </tr>
644 </tr>
562 %endfor
645 %endfor
563 </%def>
646 </%def>
564
647
565 <%def name="render_add_comment_button()">
648 <%def name="render_add_comment_button()">
566 <button
649 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
567 class="btn btn-small btn-primary cb-comment-box-opener"
650 <span><i class="icon-comment"></i></span>
568 onclick="return Rhodecode.comments.createComment(this)"
651 </button>
569 ><span>+</span></button>
570 </%def>
652 </%def>
571
653
572 <%def name="render_diffset_menu()">
654 <%def name="render_diffset_menu()">
573
655
574 <div class="diffset-menu clearinner">
656 <div class="diffset-menu clearinner">
575 <div class="pull-right">
657 <div class="pull-right">
576 <div class="btn-group">
658 <div class="btn-group">
577
659
578 <a
660 <a
579 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
661 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
580 title="${_('View side by side')}"
662 title="${_('View side by side')}"
581 href="${h.url_replace(diffmode='sideside')}">
663 href="${h.url_replace(diffmode='sideside')}">
582 <span>${_('Side by Side')}</span>
664 <span>${_('Side by Side')}</span>
583 </a>
665 </a>
584 <a
666 <a
585 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
667 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
586 title="${_('View unified')}" href="${h.url_replace(diffmode='unified')}">
668 title="${_('View unified')}" href="${h.url_replace(diffmode='unified')}">
587 <span>${_('Unified')}</span>
669 <span>${_('Unified')}</span>
588 </a>
670 </a>
589 </div>
671 </div>
590 </div>
672 </div>
591
673
592 <div class="pull-left">
674 <div class="pull-left">
593 <div class="btn-group">
675 <div class="btn-group">
594 <a
676 <a
595 class="btn"
677 class="btn"
596 href="#"
678 href="#"
597 onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a>
679 onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a>
598 <a
680 <a
599 class="btn"
681 class="btn"
600 href="#"
682 href="#"
601 onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a>
683 onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a>
602 <a
684 <a
603 class="btn"
685 class="btn"
604 href="#"
686 href="#"
605 onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a>
687 onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a>
606 </div>
688 </div>
607 </div>
689 </div>
608 </div>
690 </div>
609 </%def>
691 </%def>
@@ -1,113 +1,115 b''
1 ## Changesets table !
1 ## Changesets table !
2 <%namespace name="base" file="/base/base.html"/>
2 <%namespace name="base" file="/base/base.html"/>
3
3
4 %if c.ancestor:
4 %if c.ancestor:
5 <div class="ancestor">${_('Common Ancestor Commit')}:
5 <div class="ancestor">${_('Common Ancestor Commit')}:
6 <a href="${h.url('changeset_home',
6 <a href="${h.url('changeset_home',
7 repo_name=c.repo_name,
7 repo_name=c.repo_name,
8 revision=c.ancestor)}">
8 revision=c.ancestor)}">
9 ${h.short_id(c.ancestor)}
9 ${h.short_id(c.ancestor)}
10 </a>
10 </a>
11 </div>
11 </div>
12 %endif
12 %endif
13
13
14 <div class="container">
14 <div class="container">
15 <input type="hidden" name="__start__" value="revisions:sequence">
15 <input type="hidden" name="__start__" value="revisions:sequence">
16 <table class="rctable compare_view_commits">
16 <table class="rctable compare_view_commits">
17 <tr>
17 <tr>
18 <th>${_('Time')}</th>
18 <th>${_('Time')}</th>
19 <th>${_('Author')}</th>
19 <th>${_('Author')}</th>
20 <th>${_('Commit')}</th>
20 <th>${_('Commit')}</th>
21 <th></th>
21 <th></th>
22 <th>${_('Description')}</th>
22 <th>${_('Description')}</th>
23 </tr>
23 </tr>
24 %for commit in c.commit_ranges:
24 %for commit in c.commit_ranges:
25 <tr id="row-${commit.raw_id}"
25 <tr id="row-${commit.raw_id}"
26 commit_id="${commit.raw_id}"
26 commit_id="${commit.raw_id}"
27 class="compare_select"
27 class="compare_select"
28 style="${'display: none' if c.collapse_all_commits else ''}"
28 >
29 >
29 <td class="td-time">
30 <td class="td-time">
30 ${h.age_component(commit.date)}
31 ${h.age_component(commit.date)}
31 </td>
32 </td>
32 <td class="td-user">
33 <td class="td-user">
33 ${base.gravatar_with_user(commit.author, 16)}
34 ${base.gravatar_with_user(commit.author, 16)}
34 </td>
35 </td>
35 <td class="td-hash">
36 <td class="td-hash">
36 <code>
37 <code>
37 <a href="${h.url('changeset_home',
38 <a href="${h.url('changeset_home',
38 repo_name=c.target_repo.repo_name,
39 repo_name=c.target_repo.repo_name,
39 revision=commit.raw_id)}">
40 revision=commit.raw_id)}">
40 r${commit.revision}:${h.short_id(commit.raw_id)}
41 r${commit.revision}:${h.short_id(commit.raw_id)}
41 </a>
42 </a>
42 ${h.hidden('revisions',commit.raw_id)}
43 ${h.hidden('revisions',commit.raw_id)}
43 </code>
44 </code>
44 </td>
45 </td>
45 <td class="expand_commit"
46 <td class="expand_commit"
46 data-commit-id="${commit.raw_id}"
47 data-commit-id="${commit.raw_id}"
47 title="${_( 'Expand commit message')}"
48 title="${_( 'Expand commit message')}"
48 >
49 >
49 <div class="show_more_col">
50 <div class="show_more_col">
50 <i class="show_more"></i>
51 <i class="show_more"></i>
51 </div>
52 </div>
52 </td>
53 </td>
53 <td class="mid td-description">
54 <td class="mid td-description">
54 <div class="log-container truncate-wrap">
55 <div class="log-container truncate-wrap">
55 <div
56 <div
56 id="c-${commit.raw_id}"
57 id="c-${commit.raw_id}"
57 class="message truncate"
58 class="message truncate"
58 data-message-raw="${commit.message}"
59 data-message-raw="${commit.message}"
59 >
60 >
60 ${h.urlify_commit_message(commit.message, c.repo_name)}
61 ${h.urlify_commit_message(commit.message, c.repo_name)}
61 </div>
62 </div>
62 </div>
63 </div>
63 </td>
64 </td>
64 </tr>
65 </tr>
65 %endfor
66 %endfor
66 <tr class="compare_select_hidden" style="display: none">
67 <tr class="compare_select_hidden" style="${'' if c.collapse_all_commits else 'display: none'}">
67 <td colspan="5">
68 <td colspan="5">
68 ${ungettext('%s commit hidden','%s commits hidden', len(c.commit_ranges)) % len(c.commit_ranges)}
69 ${ungettext('%s commit hidden','%s commits hidden', len(c.commit_ranges)) % len(c.commit_ranges)},
70 <a href="#" onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">${ungettext('show it','show them', len(c.commit_ranges))}</a>
69 </td>
71 </td>
70 </tr>
72 </tr>
71 % if not c.commit_ranges:
73 % if not c.commit_ranges:
72 <tr class="compare_select">
74 <tr class="compare_select">
73 <td colspan="5">
75 <td colspan="5">
74 ${_('No commits in this compare')}
76 ${_('No commits in this compare')}
75 </td>
77 </td>
76 </tr>
78 </tr>
77 % endif
79 % endif
78 </table>
80 </table>
79 <input type="hidden" name="__end__" value="revisions:sequence">
81 <input type="hidden" name="__end__" value="revisions:sequence">
80
82
81 </div>
83 </div>
82
84
83 <script>
85 <script>
84 $('.expand_commit').on('click',function(e){
86 $('.expand_commit').on('click',function(e){
85 var target_expand = $(this);
87 var target_expand = $(this);
86 var cid = target_expand.data('commitId');
88 var cid = target_expand.data('commitId');
87
89
88 // ## TODO: dan: extract styles into css, and just toggleClass('open') here
90 // ## TODO: dan: extract styles into css, and just toggleClass('open') here
89 if (target_expand.hasClass('open')){
91 if (target_expand.hasClass('open')){
90 $('#c-'+cid).css({
92 $('#c-'+cid).css({
91 'height': '1.5em',
93 'height': '1.5em',
92 'white-space': 'nowrap',
94 'white-space': 'nowrap',
93 'text-overflow': 'ellipsis',
95 'text-overflow': 'ellipsis',
94 'overflow':'hidden'
96 'overflow':'hidden'
95 });
97 });
96 target_expand.removeClass('open');
98 target_expand.removeClass('open');
97 }
99 }
98 else {
100 else {
99 $('#c-'+cid).css({
101 $('#c-'+cid).css({
100 'height': 'auto',
102 'height': 'auto',
101 'white-space': 'pre-line',
103 'white-space': 'pre-line',
102 'text-overflow': 'initial',
104 'text-overflow': 'initial',
103 'overflow':'visible'
105 'overflow':'visible'
104 });
106 });
105 target_expand.addClass('open');
107 target_expand.addClass('open');
106 }
108 }
107 });
109 });
108
110
109 $('.compare_select').on('click',function(e){
111 $('.compare_select').on('click',function(e){
110 var cid = $(this).attr('commit_id');
112 var cid = $(this).attr('commit_id');
111 $('#row-'+cid).toggleClass('hl', !$('#row-'+cid).hasClass('hl'));
113 $('#row-'+cid).toggleClass('hl', !$('#row-'+cid).hasClass('hl'));
112 });
114 });
113 </script>
115 </script>
@@ -1,317 +1,317 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
4 <%namespace name="base" file="/base/base.html"/>
4 <%namespace name="base" file="/base/base.html"/>
5
5
6 ## REPOSITORY RENDERERS
6 ## REPOSITORY RENDERERS
7 <%def name="quick_menu(repo_name)">
7 <%def name="quick_menu(repo_name)">
8 <i class="pointer icon-more"></i>
8 <i class="pointer icon-more"></i>
9 <div class="menu_items_container hidden">
9 <div class="menu_items_container hidden">
10 <ul class="menu_items">
10 <ul class="menu_items">
11 <li>
11 <li>
12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
13 <span>${_('Summary')}</span>
13 <span>${_('Summary')}</span>
14 </a>
14 </a>
15 </li>
15 </li>
16 <li>
16 <li>
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 <span>${_('Changelog')}</span>
18 <span>${_('Changelog')}</span>
19 </a>
19 </a>
20 </li>
20 </li>
21 <li>
21 <li>
22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
23 <span>${_('Files')}</span>
23 <span>${_('Files')}</span>
24 </a>
24 </a>
25 </li>
25 </li>
26 <li>
26 <li>
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 <span>${_('Fork')}</span>
28 <span>${_('Fork')}</span>
29 </a>
29 </a>
30 </li>
30 </li>
31 </ul>
31 </ul>
32 </div>
32 </div>
33 </%def>
33 </%def>
34
34
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 <%
36 <%
37 def get_name(name,short_name=short_name):
37 def get_name(name,short_name=short_name):
38 if short_name:
38 if short_name:
39 return name.split('/')[-1]
39 return name.split('/')[-1]
40 else:
40 else:
41 return name
41 return name
42 %>
42 %>
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 ##NAME
44 ##NAME
45 <a href="${h.url('edit_repo' if admin else 'summary_home',repo_name=name)}">
45 <a href="${h.url('edit_repo' if admin else 'summary_home',repo_name=name)}">
46
46
47 ##TYPE OF REPO
47 ##TYPE OF REPO
48 %if h.is_hg(rtype):
48 %if h.is_hg(rtype):
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 %elif h.is_git(rtype):
50 %elif h.is_git(rtype):
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 %elif h.is_svn(rtype):
52 %elif h.is_svn(rtype):
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 %endif
54 %endif
55
55
56 ##PRIVATE/PUBLIC
56 ##PRIVATE/PUBLIC
57 %if private and c.visual.show_private_icon:
57 %if private and c.visual.show_private_icon:
58 <i class="icon-lock" title="${_('Private repository')}"></i>
58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 %elif not private and c.visual.show_public_icon:
59 %elif not private and c.visual.show_public_icon:
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 %else:
61 %else:
62 <span></span>
62 <span></span>
63 %endif
63 %endif
64 ${get_name(name)}
64 ${get_name(name)}
65 </a>
65 </a>
66 %if fork_of:
66 %if fork_of:
67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 %endif
68 %endif
69 %if rstate == 'repo_state_pending':
69 %if rstate == 'repo_state_pending':
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
71 %endif
71 %endif
72 </div>
72 </div>
73 </%def>
73 </%def>
74
74
75 <%def name="repo_desc(description)">
75 <%def name="repo_desc(description)">
76 <div class="truncate-wrap">${description}</div>
76 <div class="truncate-wrap">${description}</div>
77 </%def>
77 </%def>
78
78
79 <%def name="last_change(last_change)">
79 <%def name="last_change(last_change)">
80 ${h.age_component(last_change)}
80 ${h.age_component(last_change)}
81 </%def>
81 </%def>
82
82
83 <%def name="revision(name,rev,tip,author,last_msg)">
83 <%def name="revision(name,rev,tip,author,last_msg)">
84 <div>
84 <div>
85 %if rev >= 0:
85 %if rev >= 0:
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
87 %else:
87 %else:
88 ${_('No commits yet')}
88 ${_('No commits yet')}
89 %endif
89 %endif
90 </div>
90 </div>
91 </%def>
91 </%def>
92
92
93 <%def name="rss(name)">
93 <%def name="rss(name)">
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
95 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
96 %else:
96 %else:
97 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
97 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 %endif
98 %endif
99 </%def>
99 </%def>
100
100
101 <%def name="atom(name)">
101 <%def name="atom(name)">
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
103 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
104 %else:
104 %else:
105 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
105 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 %endif
106 %endif
107 </%def>
107 </%def>
108
108
109 <%def name="user_gravatar(email, size=16)">
109 <%def name="user_gravatar(email, size=16)">
110 <div class="rc-user tooltip" title="${h.author_string(email)}">
110 <div class="rc-user tooltip" title="${h.author_string(email)}">
111 ${base.gravatar(email, 16)}
111 ${base.gravatar(email, 16)}
112 </div>
112 </div>
113 </%def>
113 </%def>
114
114
115 <%def name="repo_actions(repo_name, super_user=True)">
115 <%def name="repo_actions(repo_name, super_user=True)">
116 <div>
116 <div>
117 <div class="grid_edit">
117 <div class="grid_edit">
118 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
118 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 <i class="icon-pencil"></i>Edit</a>
119 <i class="icon-pencil"></i>Edit</a>
120 </div>
120 </div>
121 <div class="grid_delete">
121 <div class="grid_delete">
122 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
122 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 ${h.end_form()}
125 ${h.end_form()}
126 </div>
126 </div>
127 </div>
127 </div>
128 </%def>
128 </%def>
129
129
130 <%def name="repo_state(repo_state)">
130 <%def name="repo_state(repo_state)">
131 <div>
131 <div>
132 %if repo_state == 'repo_state_pending':
132 %if repo_state == 'repo_state_pending':
133 <div class="tag tag4">${_('Creating')}</div>
133 <div class="tag tag4">${_('Creating')}</div>
134 %elif repo_state == 'repo_state_created':
134 %elif repo_state == 'repo_state_created':
135 <div class="tag tag1">${_('Created')}</div>
135 <div class="tag tag1">${_('Created')}</div>
136 %else:
136 %else:
137 <div class="tag alert2" title="${repo_state}">invalid</div>
137 <div class="tag alert2" title="${repo_state}">invalid</div>
138 %endif
138 %endif
139 </div>
139 </div>
140 </%def>
140 </%def>
141
141
142
142
143 ## REPO GROUP RENDERERS
143 ## REPO GROUP RENDERERS
144 <%def name="quick_repo_group_menu(repo_group_name)">
144 <%def name="quick_repo_group_menu(repo_group_name)">
145 <i class="pointer icon-more"></i>
145 <i class="pointer icon-more"></i>
146 <div class="menu_items_container hidden">
146 <div class="menu_items_container hidden">
147 <ul class="menu_items">
147 <ul class="menu_items">
148 <li>
148 <li>
149 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
149 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
150 <span class="icon">
150 <span class="icon">
151 <i class="icon-file-text"></i>
151 <i class="icon-file-text"></i>
152 </span>
152 </span>
153 <span>${_('Summary')}</span>
153 <span>${_('Summary')}</span>
154 </a>
154 </a>
155 </li>
155 </li>
156
156
157 </ul>
157 </ul>
158 </div>
158 </div>
159 </%def>
159 </%def>
160
160
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 <div>
162 <div>
163 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
163 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 %if children_groups:
165 %if children_groups:
166 ${h.literal(' &raquo; '.join(children_groups))}
166 ${h.literal(' &raquo; '.join(children_groups))}
167 %else:
167 %else:
168 ${repo_group_name}
168 ${repo_group_name}
169 %endif
169 %endif
170 </a>
170 </a>
171 </div>
171 </div>
172 </%def>
172 </%def>
173
173
174 <%def name="repo_group_desc(description)">
174 <%def name="repo_group_desc(description)">
175 <div class="truncate-wrap">${description}</div>
175 <div class="truncate-wrap">${description}</div>
176 </%def>
176 </%def>
177
177
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 <div class="grid_edit">
179 <div class="grid_edit">
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
181 </div>
181 </div>
182 <div class="grid_delete">
182 <div class="grid_delete">
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
186 ${h.end_form()}
186 ${h.end_form()}
187 </div>
187 </div>
188 </%def>
188 </%def>
189
189
190
190
191 <%def name="user_actions(user_id, username)">
191 <%def name="user_actions(user_id, username)">
192 <div class="grid_edit">
192 <div class="grid_edit">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
194 <i class="icon-pencil"></i>Edit</a>
194 <i class="icon-pencil"></i>Edit</a>
195 </div>
195 </div>
196 <div class="grid_delete">
196 <div class="grid_delete">
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
200 ${h.end_form()}
200 ${h.end_form()}
201 </div>
201 </div>
202 </%def>
202 </%def>
203
203
204 <%def name="user_group_actions(user_group_id, user_group_name)">
204 <%def name="user_group_actions(user_group_id, user_group_name)">
205 <div class="grid_edit">
205 <div class="grid_edit">
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
207 </div>
207 </div>
208 <div class="grid_delete">
208 <div class="grid_delete">
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
212 ${h.end_form()}
212 ${h.end_form()}
213 </div>
213 </div>
214 </%def>
214 </%def>
215
215
216
216
217 <%def name="user_name(user_id, username)">
217 <%def name="user_name(user_id, username)">
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
219 </%def>
219 </%def>
220
220
221 <%def name="user_profile(username)">
221 <%def name="user_profile(username)">
222 ${base.gravatar_with_user(username, 16)}
222 ${base.gravatar_with_user(username, 16)}
223 </%def>
223 </%def>
224
224
225 <%def name="user_group_name(user_group_id, user_group_name)">
225 <%def name="user_group_name(user_group_id, user_group_name)">
226 <div>
226 <div>
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
229 </div>
229 </div>
230 </%def>
230 </%def>
231
231
232
232
233 ## GISTS
233 ## GISTS
234
234
235 <%def name="gist_gravatar(full_contact)">
235 <%def name="gist_gravatar(full_contact)">
236 <div class="gist_gravatar">
236 <div class="gist_gravatar">
237 ${base.gravatar(full_contact, 30)}
237 ${base.gravatar(full_contact, 30)}
238 </div>
238 </div>
239 </%def>
239 </%def>
240
240
241 <%def name="gist_access_id(gist_access_id, full_contact)">
241 <%def name="gist_access_id(gist_access_id, full_contact)">
242 <div>
242 <div>
243 <b>
243 <b>
244 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
244 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 </b>
245 </b>
246 </div>
246 </div>
247 </%def>
247 </%def>
248
248
249 <%def name="gist_author(full_contact, created_on, expires)">
249 <%def name="gist_author(full_contact, created_on, expires)">
250 ${base.gravatar_with_user(full_contact, 16)}
250 ${base.gravatar_with_user(full_contact, 16)}
251 </%def>
251 </%def>
252
252
253
253
254 <%def name="gist_created(created_on)">
254 <%def name="gist_created(created_on)">
255 <div class="created">
255 <div class="created">
256 ${h.age_component(created_on, time_is_local=True)}
256 ${h.age_component(created_on, time_is_local=True)}
257 </div>
257 </div>
258 </%def>
258 </%def>
259
259
260 <%def name="gist_expires(expires)">
260 <%def name="gist_expires(expires)">
261 <div class="created">
261 <div class="created">
262 %if expires == -1:
262 %if expires == -1:
263 ${_('never')}
263 ${_('never')}
264 %else:
264 %else:
265 ${h.age_component(h.time_to_utcdatetime(expires))}
265 ${h.age_component(h.time_to_utcdatetime(expires))}
266 %endif
266 %endif
267 </div>
267 </div>
268 </%def>
268 </%def>
269
269
270 <%def name="gist_type(gist_type)">
270 <%def name="gist_type(gist_type)">
271 %if gist_type != 'public':
271 %if gist_type != 'public':
272 <div class="tag">${_('Private')}</div>
272 <div class="tag">${_('Private')}</div>
273 %endif
273 %endif
274 </%def>
274 </%def>
275
275
276 <%def name="gist_description(gist_description)">
276 <%def name="gist_description(gist_description)">
277 ${gist_description}
277 ${gist_description}
278 </%def>
278 </%def>
279
279
280
280
281 ## PULL REQUESTS GRID RENDERERS
281 ## PULL REQUESTS GRID RENDERERS
282
282
283 <%def name="pullrequest_target_repo(repo_name)">
283 <%def name="pullrequest_target_repo(repo_name)">
284 <div class="truncate">
284 <div class="truncate">
285 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
285 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
286 </div>
286 </div>
287 </%def>
287 </%def>
288 <%def name="pullrequest_status(status)">
288 <%def name="pullrequest_status(status)">
289 <div class="${'flag_status %s' % status} pull-left"></div>
289 <div class="${'flag_status %s' % status} pull-left"></div>
290 </%def>
290 </%def>
291
291
292 <%def name="pullrequest_title(title, description)">
292 <%def name="pullrequest_title(title, description)">
293 ${title} <br/>
293 ${title} <br/>
294 ${h.shorter(description, 40)}
294 ${h.shorter(description, 40)}
295 </%def>
295 </%def>
296
296
297 <%def name="pullrequest_comments(comments_nr)">
297 <%def name="pullrequest_comments(comments_nr)">
298 <i class="icon-comment icon-comment-colored"></i> ${comments_nr}
298 <i class="icon-comment"></i> ${comments_nr}
299 </%def>
299 </%def>
300
300
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 % if short:
303 % if short:
304 #${pull_request_id}
304 #${pull_request_id}
305 % else:
305 % else:
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
307 % endif
307 % endif
308 </a>
308 </a>
309 </%def>
309 </%def>
310
310
311 <%def name="pullrequest_updated_on(updated_on)">
311 <%def name="pullrequest_updated_on(updated_on)">
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
313 </%def>
313 </%def>
314
314
315 <%def name="pullrequest_author(full_contact)">
315 <%def name="pullrequest_author(full_contact)">
316 ${base.gravatar_with_user(full_contact, 16)}
316 ${base.gravatar_with_user(full_contact, 16)}
317 </%def>
317 </%def>
@@ -1,545 +1,545 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/debug_style/index.html"/>
2 <%inherit file="/debug_style/index.html"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 ${h.link_to(_('Style'), h.url('debug_style_home'))}
5 ${h.link_to(_('Style'), h.url('debug_style_home'))}
6 &raquo;
6 &raquo;
7 ${c.active}
7 ${c.active}
8 </%def>
8 </%def>
9
9
10
10
11 <%def name="real_main()">
11 <%def name="real_main()">
12 <div class="box">
12 <div class="box">
13 <div class="title">
13 <div class="title">
14 ${self.breadcrumbs()}
14 ${self.breadcrumbs()}
15 </div>
15 </div>
16
16
17 <div class='sidebar-col-wrapper'>
17 <div class='sidebar-col-wrapper'>
18 ##main
18 ##main
19 ${self.sidebar()}
19 ${self.sidebar()}
20
20
21 <div class="main-content">
21 <div class="main-content">
22
22
23 <div style="opacity:.5">
23 <div style="opacity:.5">
24
24
25 <h2>Simple tables</h2>
25 <h2>Simple tables</h2>
26
26
27 <p>These styles will be adjusted later to provide a baseline style
27 <p>These styles will be adjusted later to provide a baseline style
28 for all tables without classes added, whether part of the
28 for all tables without classes added, whether part of the
29 application or not. Currently, some of the
29 application or not. Currently, some of the
30 application-specific styles are applied to this table.</p>
30 application-specific styles are applied to this table.</p>
31 <p>This is a baseline style for all tables, whether part of the
31 <p>This is a baseline style for all tables, whether part of the
32 application or not. It has no class applied for styling. Use
32 application or not. It has no class applied for styling. Use
33 the "rctable" class as outlined before for tables which are
33 the "rctable" class as outlined before for tables which are
34 part of the RhodeCode application.</p>
34 part of the RhodeCode application.</p>
35 <table>
35 <table>
36 <tbody>
36 <tbody>
37 <tr>
37 <tr>
38 <th>Header A</th>
38 <th>Header A</th>
39 <th>Header B</th>
39 <th>Header B</th>
40 <th>Header C</th>
40 <th>Header C</th>
41 <th>Header D</th>
41 <th>Header D</th>
42 </tr>
42 </tr>
43 <tr>
43 <tr>
44 <td>Content of col A</td>
44 <td>Content of col A</td>
45 <td>Content of col B</td>
45 <td>Content of col B</td>
46 <td>Content of col C</td>
46 <td>Content of col C</td>
47 <td>Content of col D</td>
47 <td>Content of col D</td>
48 </tr>
48 </tr>
49 <tr>
49 <tr>
50 <td>Content of col A</td>
50 <td>Content of col A</td>
51 <td>Content of col B</td>
51 <td>Content of col B</td>
52 <td>Content of col C</td>
52 <td>Content of col C</td>
53 <td>Content of col D</td>
53 <td>Content of col D</td>
54 </tr>
54 </tr>
55 <tr>
55 <tr>
56 <td>Content of col A</td>
56 <td>Content of col A</td>
57 <td>Content of col B</td>
57 <td>Content of col B</td>
58 <td>Content of col C</td>
58 <td>Content of col C</td>
59 <td>Content of col D</td>
59 <td>Content of col D</td>
60 </tr>
60 </tr>
61 <tr>
61 <tr>
62 <td>Content of col A</td>
62 <td>Content of col A</td>
63 <td>Content of col B</td>
63 <td>Content of col B</td>
64 <td>Content of col C</td>
64 <td>Content of col C</td>
65 <td>Content of col D</td>
65 <td>Content of col D</td>
66 </tr>
66 </tr>
67 </tbody>
67 </tbody>
68 </table>
68 </table>
69 </div>
69 </div>
70
70
71
71
72
72
73
73
74 <h2>RC application table with examples</h2>
74 <h2>RC application table with examples</h2>
75
75
76 <p>This is a standard table which applies the rhodecode-specific styling to be used
76 <p>This is a standard table which applies the rhodecode-specific styling to be used
77 throughout the application; it has <code>&lt;table class="rctable"&gt;</code>.
77 throughout the application; it has <code>&lt;table class="rctable"&gt;</code>.
78 <br/>
78 <br/>
79 By default, table data is not truncated, and wraps inside of the <code>&lt;td&gt
79 By default, table data is not truncated, and wraps inside of the <code>&lt;td&gt
80 ;</code>. To prevent wrapping and contain data on one line, use the <code>&lt;
80 ;</code>. To prevent wrapping and contain data on one line, use the <code>&lt;
81 class="truncate-wrap"&gt;</code> on the <code>&lt;td&gt;</code>, and <code>span
81 class="truncate-wrap"&gt;</code> on the <code>&lt;td&gt;</code>, and <code>span
82 class="truncate"</code> around the specific data to be truncated.
82 class="truncate"</code> around the specific data to be truncated.
83 </p>
83 </p>
84 <p>
84 <p>
85 Ellipsis is added via CSS. Please always add a row of headers using <code>&lt;th
85 Ellipsis is added via CSS. Please always add a row of headers using <code>&lt;th
86 &gt;</code> to the top of a table.
86 &gt;</code> to the top of a table.
87 </p>
87 </p>
88
88
89 ## TODO: johbo: in case we have more tables with examples, we should
89 ## TODO: johbo: in case we have more tables with examples, we should
90 ## create a generic class here.
90 ## create a generic class here.
91 <table class="rctable issuetracker">
91 <table class="rctable issuetracker">
92 <thead>
92 <thead>
93 <tr>
93 <tr>
94 <th>Header A</th>
94 <th>Header A</th>
95 <th>Header B</th>
95 <th>Header B</th>
96 <th>Header C</th>
96 <th>Header C</th>
97 <th>Header D</th>
97 <th>Header D</th>
98 </tr>
98 </tr>
99 </thead>
99 </thead>
100 <tbody>
100 <tbody>
101 <tr>
101 <tr>
102 <td class="issue-tracker-example">
102 <td class="issue-tracker-example">
103 Example of col A
103 Example of col A
104 </td>
104 </td>
105 <td class="issue-tracker-example">
105 <td class="issue-tracker-example">
106 Example of col B
106 Example of col B
107 </td>
107 </td>
108 <td class="issue-tracker-example">
108 <td class="issue-tracker-example">
109 Example of col C
109 Example of col C
110 </td>
110 </td>
111 <td class="issue-tracker-example">
111 <td class="issue-tracker-example">
112 Example of col D
112 Example of col D
113 </td>
113 </td>
114 </tr>
114 </tr>
115 <tr>
115 <tr>
116 <td>Content of col A</td>
116 <td>Content of col A</td>
117 <td>Content of col B</td>
117 <td>Content of col B</td>
118 <td>Content of col C which is very long and will not be
118 <td>Content of col C which is very long and will not be
119 truncated because sometimes people just want to write
119 truncated because sometimes people just want to write
120 really, really long commit messages which explain what
120 really, really long commit messages which explain what
121 they did in excruciating detail and you really, really
121 they did in excruciating detail and you really, really
122 want to read them.</td>
122 want to read them.</td>
123 <td>Content of col D</td>
123 <td>Content of col D</td>
124 </tr>
124 </tr>
125 <tr>
125 <tr>
126 <td>Content of col A</td>
126 <td>Content of col A</td>
127 <td>Content of col B</td>
127 <td>Content of col B</td>
128 <td>Content of col C</td>
128 <td>Content of col C</td>
129 <td class="truncate-wrap"><span class="truncate">Truncated
129 <td class="truncate-wrap"><span class="truncate">Truncated
130 content of column D truncate truncate truncatetruncate
130 content of column D truncate truncate truncatetruncate
131 truncate truncate</span></td>
131 truncate truncate</span></td>
132 </tr>
132 </tr>
133 </tbody>
133 </tbody>
134 </table>
134 </table>
135
135
136 <h2>RC application table data classes</h2>
136 <h2>RC application table data classes</h2>
137
137
138 <p>The following tables contain documentation of all existing table data classes.
138 <p>The following tables contain documentation of all existing table data classes.
139 Please update when new classes are made.
139 Please update when new classes are made.
140 </p>
140 </p>
141 <table class="rctable examples">
141 <table class="rctable examples">
142 <thead>
142 <thead>
143 <tr>
143 <tr>
144 <th>Class</th>
144 <th>Class</th>
145 <th>Description</th>
145 <th>Description</th>
146 <th>Example</th>
146 <th>Example</th>
147 </tr>
147 </tr>
148 </thead>
148 </thead>
149 <tbody>
149 <tbody>
150 <td>td-user</td>
150 <td>td-user</td>
151 <td>Any username/gravatar combination (see also Icons style).</td>
151 <td>Any username/gravatar combination (see also Icons style).</td>
152 <td class="td-user author">
152 <td class="td-user author">
153 <img class="gravatar" alt="gravatar" src="https://secure.gravatar.com/avatar/0c9a7e6674b6f0b35d98dbe073e3f0ab?d=identicon&amp;s=32" height="16" width="16">
153 <img class="gravatar" alt="gravatar" src="https://secure.gravatar.com/avatar/0c9a7e6674b6f0b35d98dbe073e3f0ab?d=identicon&amp;s=32" height="16" width="16">
154 <span title="Oliver Strobel <oliver@rhodecode.com>" class="user">ostrobel (Oliver Strobel)</span>
154 <span title="Oliver Strobel <oliver@rhodecode.com>" class="user">ostrobel (Oliver Strobel)</span>
155 </td>
155 </td>
156 </tr>
156 </tr>
157 <tr>
157 <tr>
158 <td>td-hash</td>
158 <td>td-hash</td>
159 <td>Any hash; a commit, revision, etc. Use <code>&lt;pre&gt;</code> and header 'Commit'</td>
159 <td>Any hash; a commit, revision, etc. Use <code>&lt;pre&gt;</code> and header 'Commit'</td>
160 <td class="td-commit">
160 <td class="td-commit">
161 <pre><a href="/anothercpythonforkkkk/files/8d6b27837c6979983b037693fe975cdbb761b500/">r93699:8d6b27837c69</a></pre>
161 <pre><a href="/anothercpythonforkkkk/files/8d6b27837c6979983b037693fe975cdbb761b500/">r93699:8d6b27837c69</a></pre>
162 </td>
162 </td>
163 </tr>
163 </tr>
164 <tr>
164 <tr>
165 <td>td-rss</td>
165 <td>td-rss</td>
166 <td>RSS feed link icon</td>
166 <td>RSS feed link icon</td>
167 <td class="td-rss">
167 <td class="td-rss">
168 <a title="Subscribe to rss feed" href="/feed/rss"><i class="icon-rss-sign"></i></a>
168 <a title="Subscribe to rss feed" href="/feed/rss"><i class="icon-rss-sign"></i></a>
169 </td>
169 </td>
170 </tr>
170 </tr>
171 <tr>
171 <tr>
172 <td>td-componentname</td>
172 <td>td-componentname</td>
173 <td>Any group, file, gist, or directory name.</td>
173 <td>Any group, file, gist, or directory name.</td>
174 <td class="td-componentname">
174 <td class="td-componentname">
175 <a href="/cpythonfork">
175 <a href="/cpythonfork">
176 <span title="Mercurial repository"><i class="icon-hg"></i></span>
176 <span title="Mercurial repository"><i class="icon-hg"></i></span>
177 <i class="icon-unlock-alt" title="Public repository"></i>
177 <i class="icon-unlock-alt" title="Public repository"></i>
178 rhodecode-dev-restyle-fork
178 rhodecode-dev-restyle-fork
179 </a>
179 </a>
180 </td>
180 </td>
181 </tr>
181 </tr>
182 <tr>
182 <tr>
183 <td>td-tags</td>
183 <td>td-tags</td>
184 <td>Any cell containing tags, including branches and bookmarks.</td>
184 <td>Any cell containing tags, including branches and bookmarks.</td>
185 <td class="td-tags">
185 <td class="td-tags">
186 <span class="branchtag tag" title="Branch default">
186 <span class="branchtag tag" title="Branch default">
187 <a href="/rhodecode-dev-restyle- fork/changelog?branch=default"><i class="icon-code-fork"></i>default</a>
187 <a href="/rhodecode-dev-restyle- fork/changelog?branch=default"><i class="icon-code-fork"></i>default</a>
188 </span>
188 </span>
189 </td>
189 </td>
190 </tr>
190 </tr>
191 <tr>
191 <tr>
192 <td>tags-truncate</td>
192 <td>tags-truncate</td>
193 <td>Used to truncate a cell containing tags; avoid if possible.</td>
193 <td>Used to truncate a cell containing tags; avoid if possible.</td>
194 <td class="td-tags truncate-wrap">
194 <td class="td-tags truncate-wrap">
195 <div class="truncate tags-truncate">
195 <div class="truncate tags-truncate">
196 <div class="autoexpand">
196 <div class="autoexpand">
197 <span class="tagtag tag" title="Tag tip">
197 <span class="tagtag tag" title="Tag tip">
198 <a href="/rhodecode-dev-restyle-fork/files/e519d5a0e71466d27257ddff921c4a13c540408e/"><i class="icon-tag"></i>tip</a>
198 <a href="/rhodecode-dev-restyle-fork/files/e519d5a0e71466d27257ddff921c4a13c540408e/"><i class="icon-tag"></i>tip</a>
199 </span>
199 </span>
200 <span class="branchtag tag" title="Branch default">
200 <span class="branchtag tag" title="Branch default">
201 <a href="/rhodecode-dev-restyle-fork/changelog?branch=default"><i class="icon-code-fork"></i>default</a>
201 <a href="/rhodecode-dev-restyle-fork/changelog?branch=default"><i class="icon-code-fork"></i>default</a>
202 </span>
202 </span>
203 <span class="branchtag tag" title="Branch default">
203 <span class="branchtag tag" title="Branch default">
204 <a href="/rhodecode-dev-restyle-fork/changelog?branch=default"><i class="icon-code-fork"></i>default</a>
204 <a href="/rhodecode-dev-restyle-fork/changelog?branch=default"><i class="icon-code-fork"></i>default</a>
205 </span>
205 </span>
206 </div>
206 </div>
207 </div>
207 </div>
208 </td>
208 </td>
209 </tr>
209 </tr>
210 <tr>
210 <tr>
211 <td>td-ip</td>
211 <td>td-ip</td>
212 <td>Any ip address.</td>
212 <td>Any ip address.</td>
213 <td class="td-ip">
213 <td class="td-ip">
214 172.16.115.168
214 172.16.115.168
215 </td>
215 </td>
216 </tr>
216 </tr>
217 <tr>
217 <tr>
218 <td>td-type</td>
218 <td>td-type</td>
219 <td>A state or an auth type.</td>
219 <td>A state or an auth type.</td>
220 <td class="td-type">
220 <td class="td-type">
221 rhodecode
221 rhodecode
222 </td>
222 </td>
223 </tr>
223 </tr>
224 <tr>
224 <tr>
225 <td>td-authtoken</td>
225 <td>td-authtoken</td>
226 <td>For auth tokens. Use truncate classes for hover expand; see html.</td>
226 <td>For auth tokens. Use truncate classes for hover expand; see html.</td>
227 <td class="truncate-wrap td-authtoken">
227 <td class="truncate-wrap td-authtoken">
228 <div class="truncate autoexpand">
228 <div class="truncate autoexpand">
229 <code>688df65b87d3ad16ae9f8fc6338a551d40f41c7a</code>
229 <code>688df65b87d3ad16ae9f8fc6338a551d40f41c7a</code>
230 </div>
230 </div>
231 </td>
231 </td>
232 </tr>
232 </tr>
233 <tr>
233 <tr>
234 <td>td-action</td>
234 <td>td-action</td>
235 <td>Buttons which perform an action.</td>
235 <td>Buttons which perform an action.</td>
236 <td class="td-action">
236 <td class="td-action">
237 <div class="grid_edit">
237 <div class="grid_edit">
238 <a href="/_admin/users/2/edit" title="edit">
238 <a href="/_admin/users/2/edit" title="edit">
239 <i class="icon-pencil"></i>Edit</a>
239 <i class="icon-pencil"></i>Edit</a>
240 </div>
240 </div>
241 <div class="grid_delete">
241 <div class="grid_delete">
242 <form action="/_admin/users/2" method="post">
242 <form action="/_admin/users/2" method="post">
243 <i class="icon-remove-sign"></i>
243 <i class="icon-remove-sign"></i>
244 <input class="btn btn-danger btn-link" id="remove_user_2" name="remove_" type="submit" value="delete">
244 <input class="btn btn-danger btn-link" id="remove_user_2" name="remove_" type="submit" value="delete">
245 </form>
245 </form>
246 </div>
246 </div>
247 </td>
247 </td>
248 </tr>
248 </tr>
249 <tr>
249 <tr>
250 <td>td-radio</td>
250 <td>td-radio</td>
251 <td>Radio buttons for a form. Centers element.</td>
251 <td>Radio buttons for a form. Centers element.</td>
252 <td class="td-radio">
252 <td class="td-radio">
253 <input type="radio" checked="checked" value="" name="1" id="read"></td>
253 <input type="radio" checked="checked" value="" name="1" id="read"></td>
254 </tr>
254 </tr>
255 <tr>
255 <tr>
256 <td>td-checkbox</td>
256 <td>td-checkbox</td>
257 <td>Checkbox for a form. Centers element.</td>
257 <td>Checkbox for a form. Centers element.</td>
258 <td class="td-checkbox">
258 <td class="td-checkbox">
259 <input type="checkbox" checked="checked" value="" name="1" id="read"></td>
259 <input type="checkbox" checked="checked" value="" name="1" id="read"></td>
260 </tr>
260 </tr>
261 <tr>
261 <tr>
262 <tr>
262 <tr>
263 <td>td-buttons</td>
263 <td>td-buttons</td>
264 <td>Buttons.</td>
264 <td>Buttons.</td>
265 <td class="td-buttons">
265 <td class="td-buttons">
266 <span class="btn btn-mini btn-primary">feed access</span>
266 <span class="btn btn-mini btn-primary">feed access</span>
267 </td>
267 </td>
268 </tr>
268 </tr>
269 <tr>
269 <tr>
270 <td>td-compare</td>
270 <td>td-compare</td>
271 <td>Radio buttons to compare commits.</td>
271 <td>Radio buttons to compare commits.</td>
272 <td class=" td-compare">
272 <td class=" td-compare">
273 <input class="compare-radio-button" type="radio" name="compare_source" value="2.0">
273 <input class="compare-radio-button" type="radio" name="compare_source" value="2.0">
274 <input class="compare-radio-button" type="radio" name="compare_target" value="2.0">
274 <input class="compare-radio-button" type="radio" name="compare_target" value="2.0">
275 </td>
275 </td>
276 </tr>
276 </tr>
277 <tr>
277 <tr>
278 <td>td-comments</td>
278 <td>td-comments</td>
279 <td>Comments indicator icon.</td>
279 <td>Comments indicator icon.</td>
280 <td>
280 <td>
281 <i class="icon-comment icon-comment-colored"></i> 0
281 <i class="icon-comment"></i> 0
282 </td>
282 </td>
283 </tr>
283 </tr>
284 <tr>
284 <tr>
285 <td>td-status</td>
285 <td>td-status</td>
286 <td>Status indicator icon.</td>
286 <td>Status indicator icon.</td>
287 <td class="td-description">
287 <td class="td-description">
288 <div class="flag_status under_review pull-left"></div>
288 <div class="flag_status under_review pull-left"></div>
289 </td>
289 </td>
290 </tr>
290 </tr>
291 </tbody>
291 </tbody>
292 </table>
292 </table>
293 <table class="dataTable rctable examples">
293 <table class="dataTable rctable examples">
294 <tbody>
294 <tbody>
295 <tr>
295 <tr>
296 <td>quick_repo_menu</td>
296 <td>quick_repo_menu</td>
297 <td>Hidden menu generated by dataTable.</td>
297 <td>Hidden menu generated by dataTable.</td>
298 <td class="quick_repo_menu">
298 <td class="quick_repo_menu">
299 <i class="pointer icon-more"></i>
299 <i class="pointer icon-more"></i>
300 <div class="menu_items_container" style="display: none;">
300 <div class="menu_items_container" style="display: none;">
301 <ul class="menu_items">
301 <ul class="menu_items">
302 <li>
302 <li>
303 <a title="Summary" href="/anothercpythonforkkkk-fork">
303 <a title="Summary" href="/anothercpythonforkkkk-fork">
304 <span>Summary</span>
304 <span>Summary</span>
305 </a>
305 </a>
306 </li>
306 </li>
307 <li>
307 <li>
308 <a title="Changelog" href="/anothercpythonforkkkk-fork/changelog">
308 <a title="Changelog" href="/anothercpythonforkkkk-fork/changelog">
309 <span>Changelog</span>
309 <span>Changelog</span>
310 </a>
310 </a>
311 </li>
311 </li>
312 <li>
312 <li>
313 <a title="Files" href="/anothercpythonforkkkk-fork/files/tip/">
313 <a title="Files" href="/anothercpythonforkkkk-fork/files/tip/">
314 <span>Files</span>
314 <span>Files</span>
315 </a>
315 </a>
316 </li>
316 </li>
317 <li>
317 <li>
318 <a title="Fork" href="/anothercpythonforkkkk-fork/fork">
318 <a title="Fork" href="/anothercpythonforkkkk-fork/fork">
319 <span>Fork</span>
319 <span>Fork</span>
320 </a>
320 </a>
321 </li>
321 </li>
322 </ul>
322 </ul>
323 </div>
323 </div>
324 </td>
324 </td>
325 <td></td>
325 <td></td>
326 </tr>
326 </tr>
327 </tbody>
327 </tbody>
328 </table>
328 </table>
329 <script>quick_repo_menu();</script>
329 <script>quick_repo_menu();</script>
330 <table class="rctable examples">
330 <table class="rctable examples">
331 <tbody>
331 <tbody>
332 <tr>
332 <tr>
333 <td>td-description</td>
333 <td>td-description</td>
334 <td>Any description. They may be rather long, and using the expand_commit outlined below is recommended.</td>
334 <td>Any description. They may be rather long, and using the expand_commit outlined below is recommended.</td>
335 <td class="td-description">
335 <td class="td-description">
336 Ultrices mattis! Enim pellentesque lacus, sit magna natoque risus turpis ut, auctor ultrices facilisis dapibus odio? Parturient! Porta egestas nascetur, quis, elementum dolor, in magna ac dis sit etiam turpis, scelerisque! Integer tristique aliquam.
336 Ultrices mattis! Enim pellentesque lacus, sit magna natoque risus turpis ut, auctor ultrices facilisis dapibus odio? Parturient! Porta egestas nascetur, quis, elementum dolor, in magna ac dis sit etiam turpis, scelerisque! Integer tristique aliquam.
337 </td>
337 </td>
338 </tr>
338 </tr>
339 </tbody>
339 </tbody>
340 </table>
340 </table>
341 <table id="changesets" class="rctable examples end">
341 <table id="changesets" class="rctable examples end">
342 <tbody>
342 <tbody>
343 <tr>
343 <tr>
344 <td>expand_commit</td>
344 <td>expand_commit</td>
345 <td>Expands a long message; see html+js.</td>
345 <td>Expands a long message; see html+js.</td>
346 <td class="expand_commit" data-commit-id="2ffc6faabc7a9c790b1b452943a3f0c047b8b436" title="Expand commit message">
346 <td class="expand_commit" data-commit-id="2ffc6faabc7a9c790b1b452943a3f0c047b8b436" title="Expand commit message">
347 <div class="show_more_col">
347 <div class="show_more_col">
348 <i class="show_more"></i>
348 <i class="show_more"></i>
349 </div>
349 </div>
350 </td>
350 </td>
351 <td class="mid td-description">
351 <td class="mid td-description">
352 <div class="log-container truncate-wrap">
352 <div class="log-container truncate-wrap">
353 <div id="c-2ffc6faabc7a9c790b1b452943a3f0c047b8b436" class="message truncate" data-message-raw="tests: Test echo method on the server object
353 <div id="c-2ffc6faabc7a9c790b1b452943a3f0c047b8b436" class="message truncate" data-message-raw="tests: Test echo method on the server object
354
354
355 This only works for Pyro4 so far, have to extend it still for HTTP to work.">tests: Test echo method on the server object
355 This only works for Pyro4 so far, have to extend it still for HTTP to work.">tests: Test echo method on the server object
356
356
357 This only works for Pyro4 so far, have to extend it still for HTTP to work.</div>
357 This only works for Pyro4 so far, have to extend it still for HTTP to work.</div>
358 </div>
358 </div>
359 </td>
359 </td>
360 </tr>
360 </tr>
361 </tbody>
361 </tbody>
362 </table>
362 </table>
363 <script type="text/javascript">
363 <script type="text/javascript">
364 var cache = {}
364 var cache = {}
365 $('.expand_commit').on('click',function(e){
365 $('.expand_commit').on('click',function(e){
366 var target_expand = $(this);
366 var target_expand = $(this);
367 var cid = target_expand.data('commitId');
367 var cid = target_expand.data('commitId');
368
368
369 if (target_expand.hasClass('open')){
369 if (target_expand.hasClass('open')){
370 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
370 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
371 $('#t-'+cid).css({'height': '1.5em', 'max-height': '1.5em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
371 $('#t-'+cid).css({'height': '1.5em', 'max-height': '1.5em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
372 target_expand.removeClass('open');
372 target_expand.removeClass('open');
373 }
373 }
374 else {
374 else {
375 $('#c-'+cid).css({'height': 'auto', 'white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'});
375 $('#c-'+cid).css({'height': 'auto', 'white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'});
376 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'});
376 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'});
377 target_expand.addClass('open');
377 target_expand.addClass('open');
378 }
378 }
379 });
379 });
380
380
381 </script>
381 </script>
382 <p>The following classes currently do not have unique styles applied.</p>
382 <p>The following classes currently do not have unique styles applied.</p>
383 <table class="rctable examples end">
383 <table class="rctable examples end">
384 <tbody>
384 <tbody>
385 <tr>
385 <tr>
386 <td>td-regex</td>
386 <td>td-regex</td>
387 <td>Regex patterns</td>
387 <td>Regex patterns</td>
388 <td class="td-regex">(?:#)(?P<issue_id>\d+)</td>
388 <td class="td-regex">(?:#)(?P<issue_id>\d+)</td>
389 </tr>
389 </tr>
390 <tr>
390 <tr>
391 <td>td-url</td>
391 <td>td-url</td>
392 <td>Any URL.</td>
392 <td>Any URL.</td>
393 <td class="td-url">https://rhodecode.com</td>
393 <td class="td-url">https://rhodecode.com</td>
394 </tr>
394 </tr>
395 <tr>
395 <tr>
396 <td>td-journalaction</td>
396 <td>td-journalaction</td>
397 <td>Action listed in a journal</td>
397 <td>Action listed in a journal</td>
398 <td class="td-journalaction">started following repository supervisor-fork-4</td>
398 <td class="td-journalaction">started following repository supervisor-fork-4</td>
399 </tr>
399 </tr>
400 <tr>
400 <tr>
401 <td>td-iprange</td>
401 <td>td-iprange</td>
402 <td>Any ip address.</td>
402 <td>Any ip address.</td>
403 <td class="td-ip">127.0.0.1-127.0.0.10</td>
403 <td class="td-ip">127.0.0.1-127.0.0.10</td>
404 </tr>
404 </tr>
405 <tr>
405 <tr>
406 <td>td-exp</td>
406 <td>td-exp</td>
407 <td>Expiration time.</td>
407 <td>Expiration time.</td>
408 <td class="td-exp">never</td>
408 <td class="td-exp">never</td>
409 </tr>
409 </tr>
410 <tr>
410 <tr>
411 <td>td-prefix</td>
411 <td>td-prefix</td>
412 <td>Prefixes outlined in settings.</td>
412 <td>Prefixes outlined in settings.</td>
413 <td class="td-prefix">ubuntu-92539</td>
413 <td class="td-prefix">ubuntu-92539</td>
414 </tr>
414 </tr>
415 <tr>
415 <tr>
416 <td>td-cachekey</td>
416 <td>td-cachekey</td>
417 <td>Cache key value.</td>
417 <td>Cache key value.</td>
418 <td class="td-cachekey">ubuntu-92539supervisor</td>
418 <td class="td-cachekey">ubuntu-92539supervisor</td>
419 </tr>
419 </tr>
420 <tr>
420 <tr>
421 <td>td-email</td>
421 <td>td-email</td>
422 <td>Any email address.</td>
422 <td>Any email address.</td>
423 <td class="td-email">example@rhodecode.com</td>
423 <td class="td-email">example@rhodecode.com</td>
424 </tr>
424 </tr>
425 <tr>
425 <tr>
426 <td>td-active</td>
426 <td>td-active</td>
427 <td>Shows active state with icon-true/icon-false.</td>
427 <td>Shows active state with icon-true/icon-false.</td>
428 <td class="td-active"><i class="icon-false"></i></td>
428 <td class="td-active"><i class="icon-false"></i></td>
429 </tr>
429 </tr>
430 <tr>
430 <tr>
431 <td>td-size</td>
431 <td>td-size</td>
432 <td>File, repo, or directory size.</td>
432 <td>File, repo, or directory size.</td>
433 <td class="td-size">89 MB</td>
433 <td class="td-size">89 MB</td>
434 </tr>
434 </tr>
435 <tr>
435 <tr>
436 <td>td-number</td>
436 <td>td-number</td>
437 <td>Any numerical data.</td>
437 <td>Any numerical data.</td>
438 <td class="td-number">42</td>
438 <td class="td-number">42</td>
439 </tr>
439 </tr>
440 <tr>
440 <tr>
441 <td>td-message</td>
441 <td>td-message</td>
442 <td>Any commit message. Often treated with the truncate class used for descriptions as well.</td>
442 <td>Any commit message. Often treated with the truncate class used for descriptions as well.</td>
443 <td class="td-message">Updated the files</td>
443 <td class="td-message">Updated the files</td>
444 </tr>
444 </tr>
445 </tbody>
445 </tbody>
446 </table>
446 </table>
447
447
448
448
449 <h2>Permissions table</h2>
449 <h2>Permissions table</h2>
450
450
451 <p>
451 <p>
452 This is a special-case table; it has
452 This is a special-case table; it has
453 <code>table class="rctable permissions"</code>
453 <code>table class="rctable permissions"</code>
454 where "rctable" applies the rhodecode styling as above, and
454 where "rctable" applies the rhodecode styling as above, and
455 "permissions" adds an extra layer of customization specific to
455 "permissions" adds an extra layer of customization specific to
456 permissions tables. Other special-case tables may exist or be
456 permissions tables. Other special-case tables may exist or be
457 created if necessary.
457 created if necessary.
458 </p>
458 </p>
459
459
460 <table class="rctable permissions">
460 <table class="rctable permissions">
461 <tr>
461 <tr>
462 <th class="td-radio">none</th>
462 <th class="td-radio">none</th>
463 <th class="td-radio">read</th>
463 <th class="td-radio">read</th>
464 <th class="td-radio">write</th>
464 <th class="td-radio">write</th>
465 <th class="td-radio">admin</th>
465 <th class="td-radio">admin</th>
466 <th>user/user group</th>
466 <th>user/user group</th>
467 <th></th>
467 <th></th>
468 </tr>
468 </tr>
469 <tr class="perm_admin_row">
469 <tr class="perm_admin_row">
470 <td class="td-radio"><input type="radio" value="repository.none"
470 <td class="td-radio"><input type="radio" value="repository.none"
471 name="admin_perm_2" id="admin_perm_2_repositorynone"
471 name="admin_perm_2" id="admin_perm_2_repositorynone"
472 disabled="disabled"></td>
472 disabled="disabled"></td>
473 <td class="td-radio"><input type="radio" value="repository.read"
473 <td class="td-radio"><input type="radio" value="repository.read"
474 name="admin_perm_2" id="admin_perm_2_repositoryread"
474 name="admin_perm_2" id="admin_perm_2_repositoryread"
475 disabled="disabled"></td>
475 disabled="disabled"></td>
476 <td class="td-radio"><input type="radio" value="repository.write"
476 <td class="td-radio"><input type="radio" value="repository.write"
477 name="admin_perm_2" id="admin_perm_2_repositorywrite"
477 name="admin_perm_2" id="admin_perm_2_repositorywrite"
478 disabled="disabled"></td>
478 disabled="disabled"></td>
479 <td class="td-radio"><input type="radio" value="repository.admin"
479 <td class="td-radio"><input type="radio" value="repository.admin"
480 name="admin_perm_2" id="admin_perm_2_repositoryadmin"
480 name="admin_perm_2" id="admin_perm_2_repositoryadmin"
481 disabled="disabled" checked="checked"></td>
481 disabled="disabled" checked="checked"></td>
482 <td>
482 <td>
483 <img class="gravatar" src="https://secure.gravatar.com/avatar/be9d18f611892a738e54f2a3a171e2f9?d=identicon&amp;s=32" height="16" width="16">
483 <img class="gravatar" src="https://secure.gravatar.com/avatar/be9d18f611892a738e54f2a3a171e2f9?d=identicon&amp;s=32" height="16" width="16">
484 <span class="user">dev (super admin) (owner)</span>
484 <span class="user">dev (super admin) (owner)</span>
485 </td>
485 </td>
486 <td></td>
486 <td></td>
487 </tr>
487 </tr>
488 <tr>
488 <tr>
489 <td colspan="4">
489 <td colspan="4">
490 <span class="private_repo_msg">
490 <span class="private_repo_msg">
491 private repository
491 private repository
492 </span>
492 </span>
493 </td>
493 </td>
494 <td class="private_repo_msg">
494 <td class="private_repo_msg">
495 <i class="icon-user"></i>
495 <i class="icon-user"></i>
496 default - only people explicitly added here will have access</td>
496 default - only people explicitly added here will have access</td>
497 <td></td>
497 <td></td>
498 </tr>
498 </tr>
499 <tr>
499 <tr>
500 <td class="td-radio"><input type="radio" value="repository.none"
500 <td class="td-radio"><input type="radio" value="repository.none"
501 name="u_perm_1" id="u_perm_1_repositorynone"></td>
501 name="u_perm_1" id="u_perm_1_repositorynone"></td>
502 <td class="td-radio"><input type="radio" checked="checked"
502 <td class="td-radio"><input type="radio" checked="checked"
503 value="repository.read" name="u_perm_1"
503 value="repository.read" name="u_perm_1"
504 id="u_perm_1_repositoryread"></td>
504 id="u_perm_1_repositoryread"></td>
505 <td class="td-radio"><input type="radio" value="repository.write"
505 <td class="td-radio"><input type="radio" value="repository.write"
506 name="u_perm_1" id="u_perm_1_repositorywrite"></td>
506 name="u_perm_1" id="u_perm_1_repositorywrite"></td>
507 <td class="td-radio"><input type="radio" value="repository.admin"
507 <td class="td-radio"><input type="radio" value="repository.admin"
508 name="u_perm_1" id="u_perm_1_repositoryadmin"></td>
508 name="u_perm_1" id="u_perm_1_repositoryadmin"></td>
509 <td>
509 <td>
510 <img class="gravatar" src="/_static/rhodecode/images/user30.png" height="16" width="16">
510 <img class="gravatar" src="/_static/rhodecode/images/user30.png" height="16" width="16">
511 <span class="user">default</span>
511 <span class="user">default</span>
512 </td>
512 </td>
513 <td></td>
513 <td></td>
514 </tr>
514 </tr>
515 <tr>
515 <tr>
516 <td class="td-radio"><input type="radio" value="repository.none"
516 <td class="td-radio"><input type="radio" value="repository.none"
517 name="u_perm_2" id="u_perm_2_repositorynone"></td>
517 name="u_perm_2" id="u_perm_2_repositorynone"></td>
518 <td class="td-radio"><input type="radio" checked="checked"
518 <td class="td-radio"><input type="radio" checked="checked"
519 value="repository.read" name="u_perm_2"
519 value="repository.read" name="u_perm_2"
520 id="u_perm_2_repositoryread"></td>
520 id="u_perm_2_repositoryread"></td>
521 <td class="td-radio"><input type="radio" value="repository.write"
521 <td class="td-radio"><input type="radio" value="repository.write"
522 name="u_perm_2" id="u_perm_2_repositorywrite"></td>
522 name="u_perm_2" id="u_perm_2_repositorywrite"></td>
523 <td class="td-radio"><input type="radio" value="repository.admin"
523 <td class="td-radio"><input type="radio" value="repository.admin"
524 name="u_perm_2" id="u_perm_2_repositoryadmin"></td>
524 name="u_perm_2" id="u_perm_2_repositoryadmin"></td>
525 <td>
525 <td>
526 <img class="gravatar" src="https://secure.gravatar.com/avatar/be9d18f611892a738e54f2a3a171e2f9?d=identicon&amp;s=32" height="16" width="16">
526 <img class="gravatar" src="https://secure.gravatar.com/avatar/be9d18f611892a738e54f2a3a171e2f9?d=identicon&amp;s=32" height="16" width="16">
527 <a class="user" href="/_admin/users/2/edit">dev</a>
527 <a class="user" href="/_admin/users/2/edit">dev</a>
528 </td>
528 </td>
529 <td>
529 <td>
530 <span member_type="user" member="2"
530 <span member_type="user" member="2"
531 class="btn action_button btn-link btn-danger">revoke</span>
531 class="btn action_button btn-link btn-danger">revoke</span>
532 </td>
532 </td>
533 </tr>
533 </tr>
534 </tbody>
534 </tbody>
535 </table>
535 </table>
536 <div class="link" id="add_perm">
536 <div class="link" id="add_perm">
537 Add new
537 Add new
538 </div>
538 </div>
539
539
540
540
541
541
542 </div>
542 </div>
543 </div>
543 </div>
544 </div>
544 </div>
545 </%def>
545 </%def>
@@ -1,605 +1,637 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
4 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()">
10 <%def name="breadcrumbs_links()">
11 <span id="pr-title">
11 <span id="pr-title">
12 ${c.pull_request.title}
12 ${c.pull_request.title}
13 %if c.pull_request.is_closed():
13 %if c.pull_request.is_closed():
14 (${_('Closed')})
14 (${_('Closed')})
15 %endif
15 %endif
16 </span>
16 </span>
17 <div id="pr-title-edit" class="input" style="display: none;">
17 <div id="pr-title-edit" class="input" style="display: none;">
18 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
18 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
19 </div>
19 </div>
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_nav()">
22 <%def name="menu_bar_nav()">
23 ${self.menu_items(active='repositories')}
23 ${self.menu_items(active='repositories')}
24 </%def>
24 </%def>
25
25
26 <%def name="menu_bar_subnav()">
26 <%def name="menu_bar_subnav()">
27 ${self.repo_menu(active='showpullrequest')}
27 ${self.repo_menu(active='showpullrequest')}
28 </%def>
28 </%def>
29
29
30 <%def name="main()">
30 <%def name="main()">
31 <script type="text/javascript">
31 <script type="text/javascript">
32 // TODO: marcink switch this to pyroutes
32 // TODO: marcink switch this to pyroutes
33 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
33 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
34 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
34 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
35 </script>
35 </script>
36 <div class="box">
36 <div class="box">
37 <div class="title">
37 <div class="title">
38 ${self.repo_page_title(c.rhodecode_db_repo)}
38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 </div>
39 </div>
40
40
41 ${self.breadcrumbs()}
41 ${self.breadcrumbs()}
42
42
43
43
44 <div class="box pr-summary">
44 <div class="box pr-summary">
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <%summary = lambda n:{False:'summary-short'}.get(n)%>
46 <%summary = lambda n:{False:'summary-short'}.get(n)%>
47 <div class="pr-details-title">
47 <div class="pr-details-title">
48 <a href="${h.url('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
48 <a href="${h.url('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 %if c.allowed_to_update:
49 %if c.allowed_to_update:
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 % if c.allowed_to_delete:
51 % if c.allowed_to_delete:
52 ${h.secure_form(url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
52 ${h.secure_form(url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
54 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 % else:
56 % else:
57 ${_('Delete')}
57 ${_('Delete')}
58 % endif
58 % endif
59 </div>
59 </div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel edit')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 %endif
62 %endif
63 </div>
63 </div>
64
64
65 <div id="summary" class="fields pr-details-content">
65 <div id="summary" class="fields pr-details-content">
66 <div class="field">
66 <div class="field">
67 <div class="label-summary">
67 <div class="label-summary">
68 <label>${_('Origin')}:</label>
68 <label>${_('Origin')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 <div class="pr-origininfo">
71 <div class="pr-origininfo">
72 ## branch link is only valid if it is a branch
72 ## branch link is only valid if it is a branch
73 <span class="tag">
73 <span class="tag">
74 %if c.pull_request.source_ref_parts.type == 'branch':
74 %if c.pull_request.source_ref_parts.type == 'branch':
75 <a href="${h.url('changelog_home', repo_name=c.pull_request.source_repo.repo_name, branch=c.pull_request.source_ref_parts.name)}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
75 <a href="${h.url('changelog_home', repo_name=c.pull_request.source_repo.repo_name, branch=c.pull_request.source_ref_parts.name)}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 %else:
76 %else:
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 %endif
78 %endif
79 </span>
79 </span>
80 <span class="clone-url">
80 <span class="clone-url">
81 <a href="${h.url('summary_home', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
81 <a href="${h.url('summary_home', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 </span>
82 </span>
83 </div>
83 </div>
84 <div class="pr-pullinfo">
84 <div class="pr-pullinfo">
85 %if h.is_hg(c.pull_request.source_repo):
85 %if h.is_hg(c.pull_request.source_repo):
86 <input type="text" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
86 <input type="text" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
87 %elif h.is_git(c.pull_request.source_repo):
87 %elif h.is_git(c.pull_request.source_repo):
88 <input type="text" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
88 <input type="text" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
89 %endif
89 %endif
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93 <div class="field">
93 <div class="field">
94 <div class="label-summary">
94 <div class="label-summary">
95 <label>${_('Target')}:</label>
95 <label>${_('Target')}:</label>
96 </div>
96 </div>
97 <div class="input">
97 <div class="input">
98 <div class="pr-targetinfo">
98 <div class="pr-targetinfo">
99 ## branch link is only valid if it is a branch
99 ## branch link is only valid if it is a branch
100 <span class="tag">
100 <span class="tag">
101 %if c.pull_request.target_ref_parts.type == 'branch':
101 %if c.pull_request.target_ref_parts.type == 'branch':
102 <a href="${h.url('changelog_home', repo_name=c.pull_request.target_repo.repo_name, branch=c.pull_request.target_ref_parts.name)}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
102 <a href="${h.url('changelog_home', repo_name=c.pull_request.target_repo.repo_name, branch=c.pull_request.target_ref_parts.name)}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
103 %else:
103 %else:
104 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
104 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
105 %endif
105 %endif
106 </span>
106 </span>
107 <span class="clone-url">
107 <span class="clone-url">
108 <a href="${h.url('summary_home', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
108 <a href="${h.url('summary_home', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
109 </span>
109 </span>
110 </div>
110 </div>
111 </div>
111 </div>
112 </div>
112 </div>
113
113
114 ## Link to the shadow repository.
114 ## Link to the shadow repository.
115 <div class="field">
115 <div class="field">
116 <div class="label-summary">
116 <div class="label-summary">
117 <label>${_('Merge')}:</label>
117 <label>${_('Merge')}:</label>
118 </div>
118 </div>
119 <div class="input">
119 <div class="input">
120 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
120 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
121 <div class="pr-mergeinfo">
121 <div class="pr-mergeinfo">
122 %if h.is_hg(c.pull_request.target_repo):
122 %if h.is_hg(c.pull_request.target_repo):
123 <input type="text" value="hg clone -u ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
123 <input type="text" value="hg clone -u ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
124 %elif h.is_git(c.pull_request.target_repo):
124 %elif h.is_git(c.pull_request.target_repo):
125 <input type="text" value="git clone --branch ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
125 <input type="text" value="git clone --branch ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
126 %endif
126 %endif
127 </div>
127 </div>
128 % else:
128 % else:
129 <div class="">
129 <div class="">
130 ${_('Shadow repository data not available')}.
130 ${_('Shadow repository data not available')}.
131 </div>
131 </div>
132 % endif
132 % endif
133 </div>
133 </div>
134 </div>
134 </div>
135
135
136 <div class="field">
136 <div class="field">
137 <div class="label-summary">
137 <div class="label-summary">
138 <label>${_('Review')}:</label>
138 <label>${_('Review')}:</label>
139 </div>
139 </div>
140 <div class="input">
140 <div class="input">
141 %if c.pull_request_review_status:
141 %if c.pull_request_review_status:
142 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
142 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
143 <span class="changeset-status-lbl tooltip">
143 <span class="changeset-status-lbl tooltip">
144 %if c.pull_request.is_closed():
144 %if c.pull_request.is_closed():
145 ${_('Closed')},
145 ${_('Closed')},
146 %endif
146 %endif
147 ${h.commit_status_lbl(c.pull_request_review_status)}
147 ${h.commit_status_lbl(c.pull_request_review_status)}
148 </span>
148 </span>
149 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
149 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
150 %endif
150 %endif
151 </div>
151 </div>
152 </div>
152 </div>
153 <div class="field">
153 <div class="field">
154 <div class="pr-description-label label-summary">
154 <div class="pr-description-label label-summary">
155 <label>${_('Description')}:</label>
155 <label>${_('Description')}:</label>
156 </div>
156 </div>
157 <div id="pr-desc" class="input">
157 <div id="pr-desc" class="input">
158 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
158 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
159 </div>
159 </div>
160 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
160 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
161 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
161 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
162 </div>
162 </div>
163 </div>
163 </div>
164 <div class="field">
165 <div class="label-summary">
166 <label>${_('Comments')}:</label>
167 </div>
168 <div class="input">
169 <div>
170 <div class="comments-number">
171 %if c.comments:
172 <a href="#comments">${ungettext("%d General Comment", "%d General Comments", len(c.comments)) % len(c.comments)}</a>,
173 %else:
174 ${ungettext("%d General Comment", "%d General Comments", len(c.comments)) % len(c.comments)}
175 %endif
176
177 %if c.inline_cnt:
178 <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
179 %else:
180 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
181 %endif
182
183 %if c.outdated_cnt:
184 , ${ungettext("%d Outdated Comment", "%d Outdated Comments", c.outdated_cnt) % c.outdated_cnt} <span id="show-outdated-comments" class="btn btn-link">${_('(Show)')}</span>
185 %endif
186 </div>
187 </div>
188 </div>
189
190 </div>
191
192 <style>
193 .pr-versions {
194 position: relative;
195 top: 6px;
196 }
197 </style>
198
164
199 <div class="field">
165 <div class="field">
200 <div class="label-summary">
166 <div class="label-summary">
201 <label>${_('Versions')} (${len(c.versions)+1}):</label>
167 <label>${_('Versions')} (${len(c.versions)+1}):</label>
202 </div>
168 </div>
203
169
204 <div class="pr-versions">
170 <div class="pr-versions">
205 % if c.show_version_changes:
171 % if c.show_version_changes:
206 <table>
172 <table>
207 ## current visible version
173 ## CURRENTLY SELECT PR VERSION
208 <tr class="version-pr" style="display: ${'' if c.at_version in [None, 'latest'] else 'none'}">
174 <tr class="version-pr" style="display: ${'' if c.at_version_num is None else 'none'}">
209 <td>
175 <td>
210 % if c.at_version in [None, 'latest']:
176 % if c.at_version in [None, 'latest']:
211 <i class="icon-ok link"></i>
177 <i class="icon-ok link"></i>
178 % else:
179 <i class="icon-comment"></i> <code>${len(c.inline_versions[None])}</code>
212 % endif
180 % endif
213 </td>
181 </td>
214 <td>
182 <td>
215 <code>
183 <code>
216 % if c.versions:
184 % if c.versions:
217 <a href="${h.url.current(version='latest')}">${_('latest')}</a>
185 <a href="${h.url.current(version='latest')}">${_('latest')}</a>
218 % else:
186 % else:
219 ${_('initial')}
187 ${_('initial')}
220 % endif
188 % endif
221 </code>
189 </code>
222 </td>
190 </td>
223 <td>
191 <td>
224 <code>${c.pull_request_latest.source_ref_parts.commit_id[:6]}</code>
192 <code>${c.pull_request_latest.source_ref_parts.commit_id[:6]}</code>
225 </td>
193 </td>
226 <td>${_('created')} ${h.age_component(c.pull_request_latest.updated_on)}</td>
227 <td>
194 <td>
228 % if c.versions and c.at_version in [None, 'latest']:
195 ${_('created')} ${h.age_component(c.pull_request_latest.updated_on)}
229 <span id="show-pr-versions" class="btn btn-link" onclick="$('.version-pr').show(); $(this).hide(); return false">${_('(Show all)')}</span>
196 </td>
197 <td align="right">
198 % if c.versions and c.at_version_num in [None, 'latest']:
199 <span id="show-pr-versions" class="btn btn-link" onclick="$('.version-pr').show(); $(this).hide(); return false">${_('Show all versions')}</span>
230 % endif
200 % endif
231 </td>
201 </td>
232 </tr>
202 </tr>
233
203
204 ## SHOW ALL VERSIONS OF PR
205 <% ver_pr = None %>
234 % for ver in reversed(c.pull_request.versions()):
206 % for ver in reversed(c.pull_request.versions()):
235 <tr class="version-pr" style="display: ${'' if c.at_version == ver.pull_request_version_id else 'none'}">
207 <% ver_pr = ver.pull_request_version_id %>
208 <tr class="version-pr" style="display: ${'' if c.at_version == ver_pr else 'none'}">
236 <td>
209 <td>
237 % if c.at_version == ver.pull_request_version_id:
210 % if c.at_version == ver_pr:
238 <i class="icon-ok link"></i>
211 <i class="icon-ok link"></i>
212 % else:
213 <i class="icon-comment"></i> <code>${len(c.inline_versions[ver_pr])}</code>
239 % endif
214 % endif
240 </td>
215 </td>
241 <td><code><a href="${h.url.current(version=ver.pull_request_version_id)}">version ${ver.pull_request_version_id}</a></code></td>
216 <td>
217 <code><a href="${h.url.current(version=ver_pr)}">version ${ver_pr}</a></code>
218 </td>
242 <td>
219 <td>
243 <code>${ver.source_ref_parts.commit_id[:6]}</code>
220 <code>${ver.source_ref_parts.commit_id[:6]}</code>
244 </td>
221 </td>
245 <td>${_('created')} ${h.age_component(ver.updated_on)}</td>
246 <td>
222 <td>
247 % if c.at_version == ver.pull_request_version_id:
223 ${_('created')} ${h.age_component(ver.updated_on)}
248 <span id="show-pr-versions" class="btn btn-link" onclick="$('.version-pr').show(); $(this).hide(); return false">${_('(Show all)')}</span>
224 </td>
225 <td align="right">
226 % if c.at_version == ver_pr:
227 <span id="show-pr-versions" class="btn btn-link" onclick="$('.version-pr').show(); $(this).hide(); return false">${_('Show all versions')}</span>
249 % endif
228 % endif
250 </td>
229 </td>
251 </tr>
230 </tr>
252 % endfor
231 % endfor
253 </table>
232
233 ## show comment/inline comments summary
234 <tr>
235 <td>
236 </td>
254
237
255 % if c.at_version:
238 <% inline_comm_count_ver = len(c.inline_versions[ver_pr])%>
256 <pre>
239 <td colspan="4" style="border-top: 1px dashed #dbd9da">
257 Changed commits:
240 ${_('Comments for this version')}:
258 * added: ${len(c.changes.added)}
241 %if c.comments:
259 * removed: ${len(c.changes.removed)}
242 <a href="#comments">${_("%d General ") % len(c.comments)}</a>
243 %else:
244 ${_("%d General ") % len(c.comments)}
245 %endif
246
247 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num])%>
248 %if inline_comm_count_ver:
249 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
250 %else:
251 , ${_("%d Inline") % inline_comm_count_ver}
252 %endif
260
253
261 % if not (c.file_changes.added+c.file_changes.modified+c.file_changes.removed):
254 %if c.outdated_cnt:
262 No file changes found
255 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % c.outdated_cnt}</a>
263 % else:
256 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
264 Changed files:
257 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
265 %for file_name in c.file_changes.added:
258 %else:
266 * A <a href="#${'a_' + h.FID('', file_name)}">${file_name}</a>
259 , ${_("%d Outdated") % c.outdated_cnt}
267 %endfor
260 %endif
268 %for file_name in c.file_changes.modified:
261 </td>
269 * M <a href="#${'a_' + h.FID('', file_name)}">${file_name}</a>
262 </tr>
270 %endfor
263
271 %for file_name in c.file_changes.removed:
264 <tr>
272 * R ${file_name}
265 <td></td>
273 %endfor
266 <td colspan="4">
274 % endif
267 % if c.at_version:
268 <pre>
269 Changed commits:
270 * added: ${len(c.changes.added)}
271 * removed: ${len(c.changes.removed)}
272
273 % if not (c.file_changes.added+c.file_changes.modified+c.file_changes.removed):
274 No file changes found
275 % else:
276 Changed files:
277 %for file_name in c.file_changes.added:
278 * A <a href="#${'a_' + h.FID('', file_name)}">${file_name}</a>
279 %endfor
280 %for file_name in c.file_changes.modified:
281 * M <a href="#${'a_' + h.FID('', file_name)}">${file_name}</a>
282 %endfor
283 %for file_name in c.file_changes.removed:
284 * R ${file_name}
285 %endfor
286 % endif
275 </pre>
287 </pre>
276 % endif
288 % endif
289 </td>
290 </tr>
291 </table>
277 % else:
292 % else:
278 ${_('Pull request versions not available')}.
293 ${_('Pull request versions not available')}.
279 % endif
294 % endif
280 </div>
295 </div>
281 </div>
296 </div>
282
297
283 <div id="pr-save" class="field" style="display: none;">
298 <div id="pr-save" class="field" style="display: none;">
284 <div class="label-summary"></div>
299 <div class="label-summary"></div>
285 <div class="input">
300 <div class="input">
286 <span id="edit_pull_request" class="btn btn-small">${_('Save Changes')}</span>
301 <span id="edit_pull_request" class="btn btn-small">${_('Save Changes')}</span>
287 </div>
302 </div>
288 </div>
303 </div>
289 </div>
304 </div>
290 </div>
305 </div>
291 <div>
306 <div>
292 ## AUTHOR
307 ## AUTHOR
293 <div class="reviewers-title block-right">
308 <div class="reviewers-title block-right">
294 <div class="pr-details-title">
309 <div class="pr-details-title">
295 ${_('Author')}
310 ${_('Author')}
296 </div>
311 </div>
297 </div>
312 </div>
298 <div class="block-right pr-details-content reviewers">
313 <div class="block-right pr-details-content reviewers">
299 <ul class="group_members">
314 <ul class="group_members">
300 <li>
315 <li>
301 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
316 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
302 </li>
317 </li>
303 </ul>
318 </ul>
304 </div>
319 </div>
305 ## REVIEWERS
320 ## REVIEWERS
306 <div class="reviewers-title block-right">
321 <div class="reviewers-title block-right">
307 <div class="pr-details-title">
322 <div class="pr-details-title">
308 ${_('Pull request reviewers')}
323 ${_('Pull request reviewers')}
309 %if c.allowed_to_update:
324 %if c.allowed_to_update:
310 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
325 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
311 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
326 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
312 %endif
327 %endif
313 </div>
328 </div>
314 </div>
329 </div>
315 <div id="reviewers" class="block-right pr-details-content reviewers">
330 <div id="reviewers" class="block-right pr-details-content reviewers">
316 ## members goes here !
331 ## members goes here !
317 <input type="hidden" name="__start__" value="review_members:sequence">
332 <input type="hidden" name="__start__" value="review_members:sequence">
318 <ul id="review_members" class="group_members">
333 <ul id="review_members" class="group_members">
319 %for member,reasons,status in c.pull_request_reviewers:
334 %for member,reasons,status in c.pull_request_reviewers:
320 <li id="reviewer_${member.user_id}">
335 <li id="reviewer_${member.user_id}">
321 <div class="reviewers_member">
336 <div class="reviewers_member">
322 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
337 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
323 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
338 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
324 </div>
339 </div>
325 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
340 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
326 ${self.gravatar_with_user(member.email, 16)}
341 ${self.gravatar_with_user(member.email, 16)}
327 </div>
342 </div>
328 <input type="hidden" name="__start__" value="reviewer:mapping">
343 <input type="hidden" name="__start__" value="reviewer:mapping">
329 <input type="hidden" name="__start__" value="reasons:sequence">
344 <input type="hidden" name="__start__" value="reasons:sequence">
330 %for reason in reasons:
345 %for reason in reasons:
331 <div class="reviewer_reason">- ${reason}</div>
346 <div class="reviewer_reason">- ${reason}</div>
332 <input type="hidden" name="reason" value="${reason}">
347 <input type="hidden" name="reason" value="${reason}">
333
348
334 %endfor
349 %endfor
335 <input type="hidden" name="__end__" value="reasons:sequence">
350 <input type="hidden" name="__end__" value="reasons:sequence">
336 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
351 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
337 <input type="hidden" name="__end__" value="reviewer:mapping">
352 <input type="hidden" name="__end__" value="reviewer:mapping">
338 %if c.allowed_to_update:
353 %if c.allowed_to_update:
339 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
354 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
340 <i class="icon-remove-sign" ></i>
355 <i class="icon-remove-sign" ></i>
341 </div>
356 </div>
342 %endif
357 %endif
343 </div>
358 </div>
344 </li>
359 </li>
345 %endfor
360 %endfor
346 </ul>
361 </ul>
347 <input type="hidden" name="__end__" value="review_members:sequence">
362 <input type="hidden" name="__end__" value="review_members:sequence">
348 %if not c.pull_request.is_closed():
363 %if not c.pull_request.is_closed():
349 <div id="add_reviewer_input" class='ac' style="display: none;">
364 <div id="add_reviewer_input" class='ac' style="display: none;">
350 %if c.allowed_to_update:
365 %if c.allowed_to_update:
351 <div class="reviewer_ac">
366 <div class="reviewer_ac">
352 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
367 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
353 <div id="reviewers_container"></div>
368 <div id="reviewers_container"></div>
354 </div>
369 </div>
355 <div>
370 <div>
356 <span id="update_pull_request" class="btn btn-small">${_('Save Changes')}</span>
371 <span id="update_pull_request" class="btn btn-small">${_('Save Changes')}</span>
357 </div>
372 </div>
358 %endif
373 %endif
359 </div>
374 </div>
360 %endif
375 %endif
361 </div>
376 </div>
362 </div>
377 </div>
363 </div>
378 </div>
364 <div class="box">
379 <div class="box">
365 ##DIFF
380 ##DIFF
366 <div class="table" >
381 <div class="table" >
367 <div id="changeset_compare_view_content">
382 <div id="changeset_compare_view_content">
368 ##CS
383 ##CS
369 % if c.missing_requirements:
384 % if c.missing_requirements:
370 <div class="box">
385 <div class="box">
371 <div class="alert alert-warning">
386 <div class="alert alert-warning">
372 <div>
387 <div>
373 <strong>${_('Missing requirements:')}</strong>
388 <strong>${_('Missing requirements:')}</strong>
374 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
389 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
375 </div>
390 </div>
376 </div>
391 </div>
377 </div>
392 </div>
378 % elif c.missing_commits:
393 % elif c.missing_commits:
379 <div class="box">
394 <div class="box">
380 <div class="alert alert-warning">
395 <div class="alert alert-warning">
381 <div>
396 <div>
382 <strong>${_('Missing commits')}:</strong>
397 <strong>${_('Missing commits')}:</strong>
383 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
398 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
384 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
399 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
385 </div>
400 </div>
386 </div>
401 </div>
387 </div>
402 </div>
388 % endif
403 % endif
389 <div class="compare_view_commits_title">
404 <div class="compare_view_commits_title">
390
405
391 <div class="pull-left">
406 <div class="pull-left">
392 <div class="btn-group">
407 <div class="btn-group">
393 <a
408 <a
394 class="btn"
409 class="btn"
395 href="#"
410 href="#"
396 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
411 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
397 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
412 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
398 </a>
413 </a>
399 <a
414 <a
400 class="btn"
415 class="btn"
401 href="#"
416 href="#"
402 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
417 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
403 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
418 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
404 </a>
419 </a>
405 </div>
420 </div>
406 </div>
421 </div>
407
422
408 <div class="pull-right">
423 <div class="pull-right">
409 % if c.allowed_to_update and not c.pull_request.is_closed():
424 % if c.allowed_to_update and not c.pull_request.is_closed():
410 <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a>
425 <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a>
411 % else:
426 % else:
412 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
427 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
413 % endif
428 % endif
414
429
415 </div>
430 </div>
416
431
417 </div>
432 </div>
418 % if not c.missing_commits:
433 % if not c.missing_commits:
419 <%include file="/compare/compare_commits.html" />
434 <%include file="/compare/compare_commits.html" />
420 <div class="cs_files">
435 <div class="cs_files">
421 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
436 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
422 ${cbdiffs.render_diffset_menu()}
437 ${cbdiffs.render_diffset_menu()}
423 ${cbdiffs.render_diffset(
438 ${cbdiffs.render_diffset(
424 c.diffset, use_comments=True,
439 c.diffset, use_comments=True,
425 collapse_when_files_over=30,
440 collapse_when_files_over=30,
426 disable_new_comments=not c.allowed_to_comment)}
441 disable_new_comments=not c.allowed_to_comment,
442 deleted_files_comments=c.deleted_files_comments)}
427
443
428 </div>
444 </div>
429 % endif
445 % endif
430 </div>
446 </div>
431
447
432 ## template for inline comment form
448 ## template for inline comment form
433 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
449 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
434
450
435 ## render general comments
451 ## render general comments
436 ${comment.generate_comments(include_pull_request=True, is_pull_request=True)}
452 ${comment.generate_comments(include_pull_request=True, is_pull_request=True)}
437
453
438 % if not c.pull_request.is_closed():
454 % if not c.pull_request.is_closed():
439 ## main comment form and it status
455 ## main comment form and it status
440 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
456 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
441 pull_request_id=c.pull_request.pull_request_id),
457 pull_request_id=c.pull_request.pull_request_id),
442 c.pull_request_review_status,
458 c.pull_request_review_status,
443 is_pull_request=True, change_status=c.allowed_to_change_status)}
459 is_pull_request=True, change_status=c.allowed_to_change_status)}
444 %endif
460 %endif
445
461
446 <script type="text/javascript">
462 <script type="text/javascript">
447 if (location.hash) {
463 if (location.hash) {
448 var result = splitDelimitedHash(location.hash);
464 var result = splitDelimitedHash(location.hash);
449 var line = $('html').find(result.loc);
465 var line = $('html').find(result.loc);
450 if (line.length > 0){
466 if (line.length > 0){
451 offsetScroll(line, 70);
467 offsetScroll(line, 70);
452 }
468 }
453 }
469 }
454 $(function(){
470 $(function(){
455 ReviewerAutoComplete('user');
471 ReviewerAutoComplete('user');
456 // custom code mirror
472 // custom code mirror
457 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
473 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
458
474
459 var PRDetails = {
475 var PRDetails = {
460 editButton: $('#open_edit_pullrequest'),
476 editButton: $('#open_edit_pullrequest'),
461 closeButton: $('#close_edit_pullrequest'),
477 closeButton: $('#close_edit_pullrequest'),
462 deleteButton: $('#delete_pullrequest'),
478 deleteButton: $('#delete_pullrequest'),
463 viewFields: $('#pr-desc, #pr-title'),
479 viewFields: $('#pr-desc, #pr-title'),
464 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
480 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
465
481
466 init: function() {
482 init: function() {
467 var that = this;
483 var that = this;
468 this.editButton.on('click', function(e) { that.edit(); });
484 this.editButton.on('click', function(e) { that.edit(); });
469 this.closeButton.on('click', function(e) { that.view(); });
485 this.closeButton.on('click', function(e) { that.view(); });
470 },
486 },
471
487
472 edit: function(event) {
488 edit: function(event) {
473 this.viewFields.hide();
489 this.viewFields.hide();
474 this.editButton.hide();
490 this.editButton.hide();
475 this.deleteButton.hide();
491 this.deleteButton.hide();
476 this.closeButton.show();
492 this.closeButton.show();
477 this.editFields.show();
493 this.editFields.show();
478 codeMirrorInstance.refresh();
494 codeMirrorInstance.refresh();
479 },
495 },
480
496
481 view: function(event) {
497 view: function(event) {
482 this.editButton.show();
498 this.editButton.show();
483 this.deleteButton.show();
499 this.deleteButton.show();
484 this.editFields.hide();
500 this.editFields.hide();
485 this.closeButton.hide();
501 this.closeButton.hide();
486 this.viewFields.show();
502 this.viewFields.show();
487 }
503 }
488 };
504 };
489
505
490 var ReviewersPanel = {
506 var ReviewersPanel = {
491 editButton: $('#open_edit_reviewers'),
507 editButton: $('#open_edit_reviewers'),
492 closeButton: $('#close_edit_reviewers'),
508 closeButton: $('#close_edit_reviewers'),
493 addButton: $('#add_reviewer_input'),
509 addButton: $('#add_reviewer_input'),
494 removeButtons: $('.reviewer_member_remove'),
510 removeButtons: $('.reviewer_member_remove'),
495
511
496 init: function() {
512 init: function() {
497 var that = this;
513 var that = this;
498 this.editButton.on('click', function(e) { that.edit(); });
514 this.editButton.on('click', function(e) { that.edit(); });
499 this.closeButton.on('click', function(e) { that.close(); });
515 this.closeButton.on('click', function(e) { that.close(); });
500 },
516 },
501
517
502 edit: function(event) {
518 edit: function(event) {
503 this.editButton.hide();
519 this.editButton.hide();
504 this.closeButton.show();
520 this.closeButton.show();
505 this.addButton.show();
521 this.addButton.show();
506 this.removeButtons.css('visibility', 'visible');
522 this.removeButtons.css('visibility', 'visible');
507 },
523 },
508
524
509 close: function(event) {
525 close: function(event) {
510 this.editButton.show();
526 this.editButton.show();
511 this.closeButton.hide();
527 this.closeButton.hide();
512 this.addButton.hide();
528 this.addButton.hide();
513 this.removeButtons.css('visibility', 'hidden');
529 this.removeButtons.css('visibility', 'hidden');
514 }
530 }
515 };
531 };
516
532
517 PRDetails.init();
533 PRDetails.init();
518 ReviewersPanel.init();
534 ReviewersPanel.init();
519
535
536 showOutdated = function(self){
537 $('.comment-outdated').show();
538 $('.filediff-outdated').show();
539 $('.showOutdatedComments').hide();
540 $('.hideOutdatedComments').show();
541
542 };
543
544 hideOutdated = function(self){
545 $('.comment-outdated').hide();
546 $('.filediff-outdated').hide();
547 $('.hideOutdatedComments').hide();
548 $('.showOutdatedComments').show();
549 };
550
520 $('#show-outdated-comments').on('click', function(e){
551 $('#show-outdated-comments').on('click', function(e){
521 var button = $(this);
552 var button = $(this);
522 var outdated = $('.comment-outdated');
553 var outdated = $('.comment-outdated');
554
523 if (button.html() === "(Show)") {
555 if (button.html() === "(Show)") {
524 button.html("(Hide)");
556 button.html("(Hide)");
525 outdated.show();
557 outdated.show();
526 } else {
558 } else {
527 button.html("(Show)");
559 button.html("(Show)");
528 outdated.hide();
560 outdated.hide();
529 }
561 }
530 });
562 });
531
563
532 $('.show-inline-comments').on('change', function(e){
564 $('.show-inline-comments').on('change', function(e){
533 var show = 'none';
565 var show = 'none';
534 var target = e.currentTarget;
566 var target = e.currentTarget;
535 if(target.checked){
567 if(target.checked){
536 show = ''
568 show = ''
537 }
569 }
538 var boxid = $(target).attr('id_for');
570 var boxid = $(target).attr('id_for');
539 var comments = $('#{0} .inline-comments'.format(boxid));
571 var comments = $('#{0} .inline-comments'.format(boxid));
540 var fn_display = function(idx){
572 var fn_display = function(idx){
541 $(this).css('display', show);
573 $(this).css('display', show);
542 };
574 };
543 $(comments).each(fn_display);
575 $(comments).each(fn_display);
544 var btns = $('#{0} .inline-comments-button'.format(boxid));
576 var btns = $('#{0} .inline-comments-button'.format(boxid));
545 $(btns).each(fn_display);
577 $(btns).each(fn_display);
546 });
578 });
547
579
548 $('#merge_pull_request_form').submit(function() {
580 $('#merge_pull_request_form').submit(function() {
549 if (!$('#merge_pull_request').attr('disabled')) {
581 if (!$('#merge_pull_request').attr('disabled')) {
550 $('#merge_pull_request').attr('disabled', 'disabled');
582 $('#merge_pull_request').attr('disabled', 'disabled');
551 }
583 }
552 return true;
584 return true;
553 });
585 });
554
586
555 $('#edit_pull_request').on('click', function(e){
587 $('#edit_pull_request').on('click', function(e){
556 var title = $('#pr-title-input').val();
588 var title = $('#pr-title-input').val();
557 var description = codeMirrorInstance.getValue();
589 var description = codeMirrorInstance.getValue();
558 editPullRequest(
590 editPullRequest(
559 "${c.repo_name}", "${c.pull_request.pull_request_id}",
591 "${c.repo_name}", "${c.pull_request.pull_request_id}",
560 title, description);
592 title, description);
561 });
593 });
562
594
563 $('#update_pull_request').on('click', function(e){
595 $('#update_pull_request').on('click', function(e){
564 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
596 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
565 });
597 });
566
598
567 $('#update_commits').on('click', function(e){
599 $('#update_commits').on('click', function(e){
568 var isDisabled = !$(e.currentTarget).attr('disabled');
600 var isDisabled = !$(e.currentTarget).attr('disabled');
569 $(e.currentTarget).text(_gettext('Updating...'));
601 $(e.currentTarget).text(_gettext('Updating...'));
570 $(e.currentTarget).attr('disabled', 'disabled');
602 $(e.currentTarget).attr('disabled', 'disabled');
571 if(isDisabled){
603 if(isDisabled){
572 updateCommits("${c.repo_name}", "${c.pull_request.pull_request_id}");
604 updateCommits("${c.repo_name}", "${c.pull_request.pull_request_id}");
573 }
605 }
574
606
575 });
607 });
576 // fixing issue with caches on firefox
608 // fixing issue with caches on firefox
577 $('#update_commits').removeAttr("disabled");
609 $('#update_commits').removeAttr("disabled");
578
610
579 $('#close_pull_request').on('click', function(e){
611 $('#close_pull_request').on('click', function(e){
580 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
612 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
581 });
613 });
582
614
583 $('.show-inline-comments').on('click', function(e){
615 $('.show-inline-comments').on('click', function(e){
584 var boxid = $(this).attr('data-comment-id');
616 var boxid = $(this).attr('data-comment-id');
585 var button = $(this);
617 var button = $(this);
586
618
587 if(button.hasClass("comments-visible")) {
619 if(button.hasClass("comments-visible")) {
588 $('#{0} .inline-comments'.format(boxid)).each(function(index){
620 $('#{0} .inline-comments'.format(boxid)).each(function(index){
589 $(this).hide();
621 $(this).hide();
590 });
622 });
591 button.removeClass("comments-visible");
623 button.removeClass("comments-visible");
592 } else {
624 } else {
593 $('#{0} .inline-comments'.format(boxid)).each(function(index){
625 $('#{0} .inline-comments'.format(boxid)).each(function(index){
594 $(this).show();
626 $(this).show();
595 });
627 });
596 button.addClass("comments-visible");
628 button.addClass("comments-visible");
597 }
629 }
598 });
630 });
599 })
631 })
600 </script>
632 </script>
601
633
602 </div>
634 </div>
603 </div>
635 </div>
604
636
605 </%def>
637 </%def>
General Comments 0
You need to be logged in to leave comments. Login now