##// END OF EJS Templates
translation: unified usage of pluralize function ungettext....
marcink -
r1945:8e51a936 default
parent child Browse files
Show More
@@ -1,634 +1,660 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import ipaddress
30 import ipaddress
31 import pyramid.threadlocal
31 import pyramid.threadlocal
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from pylons import config, tmpl_context as c, request, url
36 from pylons import config, tmpl_context as c, request, url
37 from pylons.controllers import WSGIController
37 from pylons.controllers import WSGIController
38 from pylons.controllers.util import redirect
38 from pylons.controllers.util import redirect
39 from pylons.i18n import translation
39 from pylons.i18n import translation
40 # marcink: don't remove this import
40 # marcink: don't remove this import
41 from pylons.templating import render_mako as render # noqa
41 from pylons.templating import render_mako, pylons_globals, literal, cached_template
42 from pylons.i18n.translation import _
42 from pylons.i18n.translation import _
43 from webob.exc import HTTPFound
43 from webob.exc import HTTPFound
44
44
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import helpers as h
49 from rhodecode.lib import helpers as h
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.utils import (
52 from rhodecode.lib.utils import (
53 get_repo_slug, set_rhodecode_config, password_changed,
53 get_repo_slug, set_rhodecode_config, password_changed,
54 get_enabled_hook_classes)
54 get_enabled_hook_classes)
55 from rhodecode.lib.utils2 import (
55 from rhodecode.lib.utils2 import (
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.model import meta
58 from rhodecode.model import meta
59 from rhodecode.model.db import Repository, User, ChangesetComment
59 from rhodecode.model.db import Repository, User, ChangesetComment
60 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63
63
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 # hack to make the migration to pyramid easier
69 def render(template_name, extra_vars=None, cache_key=None,
70 cache_type=None, cache_expire=None):
71 """Render a template with Mako
72
73 Accepts the cache options ``cache_key``, ``cache_type``, and
74 ``cache_expire``.
75
76 """
77 # Create a render callable for the cache function
78 def render_template():
79 # Pull in extra vars if needed
80 globs = extra_vars or {}
81
82 # Second, get the globals
83 globs.update(pylons_globals())
84
85 globs['_ungettext'] = globs['ungettext']
86 # Grab a template reference
87 template = globs['app_globals'].mako_lookup.get_template(template_name)
88
89 return literal(template.render_unicode(**globs))
90
91 return cached_template(template_name, render_template, cache_key=cache_key,
92 cache_type=cache_type, cache_expire=cache_expire)
93
68 def _filter_proxy(ip):
94 def _filter_proxy(ip):
69 """
95 """
70 Passed in IP addresses in HEADERS can be in a special format of multiple
96 Passed in IP addresses in HEADERS can be in a special format of multiple
71 ips. Those comma separated IPs are passed from various proxies in the
97 ips. Those comma separated IPs are passed from various proxies in the
72 chain of request processing. The left-most being the original client.
98 chain of request processing. The left-most being the original client.
73 We only care about the first IP which came from the org. client.
99 We only care about the first IP which came from the org. client.
74
100
75 :param ip: ip string from headers
101 :param ip: ip string from headers
76 """
102 """
77 if ',' in ip:
103 if ',' in ip:
78 _ips = ip.split(',')
104 _ips = ip.split(',')
79 _first_ip = _ips[0].strip()
105 _first_ip = _ips[0].strip()
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
106 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
81 return _first_ip
107 return _first_ip
82 return ip
108 return ip
83
109
84
110
85 def _filter_port(ip):
111 def _filter_port(ip):
86 """
112 """
87 Removes a port from ip, there are 4 main cases to handle here.
113 Removes a port from ip, there are 4 main cases to handle here.
88 - ipv4 eg. 127.0.0.1
114 - ipv4 eg. 127.0.0.1
89 - ipv6 eg. ::1
115 - ipv6 eg. ::1
90 - ipv4+port eg. 127.0.0.1:8080
116 - ipv4+port eg. 127.0.0.1:8080
91 - ipv6+port eg. [::1]:8080
117 - ipv6+port eg. [::1]:8080
92
118
93 :param ip:
119 :param ip:
94 """
120 """
95 def is_ipv6(ip_addr):
121 def is_ipv6(ip_addr):
96 if hasattr(socket, 'inet_pton'):
122 if hasattr(socket, 'inet_pton'):
97 try:
123 try:
98 socket.inet_pton(socket.AF_INET6, ip_addr)
124 socket.inet_pton(socket.AF_INET6, ip_addr)
99 except socket.error:
125 except socket.error:
100 return False
126 return False
101 else:
127 else:
102 # fallback to ipaddress
128 # fallback to ipaddress
103 try:
129 try:
104 ipaddress.IPv6Address(safe_unicode(ip_addr))
130 ipaddress.IPv6Address(safe_unicode(ip_addr))
105 except Exception:
131 except Exception:
106 return False
132 return False
107 return True
133 return True
108
134
109 if ':' not in ip: # must be ipv4 pure ip
135 if ':' not in ip: # must be ipv4 pure ip
110 return ip
136 return ip
111
137
112 if '[' in ip and ']' in ip: # ipv6 with port
138 if '[' in ip and ']' in ip: # ipv6 with port
113 return ip.split(']')[0][1:].lower()
139 return ip.split(']')[0][1:].lower()
114
140
115 # must be ipv6 or ipv4 with port
141 # must be ipv6 or ipv4 with port
116 if is_ipv6(ip):
142 if is_ipv6(ip):
117 return ip
143 return ip
118 else:
144 else:
119 ip, _port = ip.split(':')[:2] # means ipv4+port
145 ip, _port = ip.split(':')[:2] # means ipv4+port
120 return ip
146 return ip
121
147
122
148
123 def get_ip_addr(environ):
149 def get_ip_addr(environ):
124 proxy_key = 'HTTP_X_REAL_IP'
150 proxy_key = 'HTTP_X_REAL_IP'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
151 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
126 def_key = 'REMOTE_ADDR'
152 def_key = 'REMOTE_ADDR'
127 _filters = lambda x: _filter_port(_filter_proxy(x))
153 _filters = lambda x: _filter_port(_filter_proxy(x))
128
154
129 ip = environ.get(proxy_key)
155 ip = environ.get(proxy_key)
130 if ip:
156 if ip:
131 return _filters(ip)
157 return _filters(ip)
132
158
133 ip = environ.get(proxy_key2)
159 ip = environ.get(proxy_key2)
134 if ip:
160 if ip:
135 return _filters(ip)
161 return _filters(ip)
136
162
137 ip = environ.get(def_key, '0.0.0.0')
163 ip = environ.get(def_key, '0.0.0.0')
138 return _filters(ip)
164 return _filters(ip)
139
165
140
166
141 def get_server_ip_addr(environ, log_errors=True):
167 def get_server_ip_addr(environ, log_errors=True):
142 hostname = environ.get('SERVER_NAME')
168 hostname = environ.get('SERVER_NAME')
143 try:
169 try:
144 return socket.gethostbyname(hostname)
170 return socket.gethostbyname(hostname)
145 except Exception as e:
171 except Exception as e:
146 if log_errors:
172 if log_errors:
147 # in some cases this lookup is not possible, and we don't want to
173 # in some cases this lookup is not possible, and we don't want to
148 # make it an exception in logs
174 # make it an exception in logs
149 log.exception('Could not retrieve server ip address: %s', e)
175 log.exception('Could not retrieve server ip address: %s', e)
150 return hostname
176 return hostname
151
177
152
178
153 def get_server_port(environ):
179 def get_server_port(environ):
154 return environ.get('SERVER_PORT')
180 return environ.get('SERVER_PORT')
155
181
156
182
157 def get_access_path(environ):
183 def get_access_path(environ):
158 path = environ.get('PATH_INFO')
184 path = environ.get('PATH_INFO')
159 org_req = environ.get('pylons.original_request')
185 org_req = environ.get('pylons.original_request')
160 if org_req:
186 if org_req:
161 path = org_req.environ.get('PATH_INFO')
187 path = org_req.environ.get('PATH_INFO')
162 return path
188 return path
163
189
164
190
165 def get_user_agent(environ):
191 def get_user_agent(environ):
166 return environ.get('HTTP_USER_AGENT')
192 return environ.get('HTTP_USER_AGENT')
167
193
168
194
169 def vcs_operation_context(
195 def vcs_operation_context(
170 environ, repo_name, username, action, scm, check_locking=True,
196 environ, repo_name, username, action, scm, check_locking=True,
171 is_shadow_repo=False):
197 is_shadow_repo=False):
172 """
198 """
173 Generate the context for a vcs operation, e.g. push or pull.
199 Generate the context for a vcs operation, e.g. push or pull.
174
200
175 This context is passed over the layers so that hooks triggered by the
201 This context is passed over the layers so that hooks triggered by the
176 vcs operation know details like the user, the user's IP address etc.
202 vcs operation know details like the user, the user's IP address etc.
177
203
178 :param check_locking: Allows to switch of the computation of the locking
204 :param check_locking: Allows to switch of the computation of the locking
179 data. This serves mainly the need of the simplevcs middleware to be
205 data. This serves mainly the need of the simplevcs middleware to be
180 able to disable this for certain operations.
206 able to disable this for certain operations.
181
207
182 """
208 """
183 # Tri-state value: False: unlock, None: nothing, True: lock
209 # Tri-state value: False: unlock, None: nothing, True: lock
184 make_lock = None
210 make_lock = None
185 locked_by = [None, None, None]
211 locked_by = [None, None, None]
186 is_anonymous = username == User.DEFAULT_USER
212 is_anonymous = username == User.DEFAULT_USER
187 if not is_anonymous and check_locking:
213 if not is_anonymous and check_locking:
188 log.debug('Checking locking on repository "%s"', repo_name)
214 log.debug('Checking locking on repository "%s"', repo_name)
189 user = User.get_by_username(username)
215 user = User.get_by_username(username)
190 repo = Repository.get_by_repo_name(repo_name)
216 repo = Repository.get_by_repo_name(repo_name)
191 make_lock, __, locked_by = repo.get_locking_state(
217 make_lock, __, locked_by = repo.get_locking_state(
192 action, user.user_id)
218 action, user.user_id)
193
219
194 settings_model = VcsSettingsModel(repo=repo_name)
220 settings_model = VcsSettingsModel(repo=repo_name)
195 ui_settings = settings_model.get_ui_settings()
221 ui_settings = settings_model.get_ui_settings()
196
222
197 extras = {
223 extras = {
198 'ip': get_ip_addr(environ),
224 'ip': get_ip_addr(environ),
199 'username': username,
225 'username': username,
200 'action': action,
226 'action': action,
201 'repository': repo_name,
227 'repository': repo_name,
202 'scm': scm,
228 'scm': scm,
203 'config': rhodecode.CONFIG['__file__'],
229 'config': rhodecode.CONFIG['__file__'],
204 'make_lock': make_lock,
230 'make_lock': make_lock,
205 'locked_by': locked_by,
231 'locked_by': locked_by,
206 'server_url': utils2.get_server_url(environ),
232 'server_url': utils2.get_server_url(environ),
207 'user_agent': get_user_agent(environ),
233 'user_agent': get_user_agent(environ),
208 'hooks': get_enabled_hook_classes(ui_settings),
234 'hooks': get_enabled_hook_classes(ui_settings),
209 'is_shadow_repo': is_shadow_repo,
235 'is_shadow_repo': is_shadow_repo,
210 }
236 }
211 return extras
237 return extras
212
238
213
239
214 class BasicAuth(AuthBasicAuthenticator):
240 class BasicAuth(AuthBasicAuthenticator):
215
241
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
242 def __init__(self, realm, authfunc, registry, auth_http_code=None,
217 initial_call_detection=False, acl_repo_name=None):
243 initial_call_detection=False, acl_repo_name=None):
218 self.realm = realm
244 self.realm = realm
219 self.initial_call = initial_call_detection
245 self.initial_call = initial_call_detection
220 self.authfunc = authfunc
246 self.authfunc = authfunc
221 self.registry = registry
247 self.registry = registry
222 self.acl_repo_name = acl_repo_name
248 self.acl_repo_name = acl_repo_name
223 self._rc_auth_http_code = auth_http_code
249 self._rc_auth_http_code = auth_http_code
224
250
225 def _get_response_from_code(self, http_code):
251 def _get_response_from_code(self, http_code):
226 try:
252 try:
227 return get_exception(safe_int(http_code))
253 return get_exception(safe_int(http_code))
228 except Exception:
254 except Exception:
229 log.exception('Failed to fetch response for code %s' % http_code)
255 log.exception('Failed to fetch response for code %s' % http_code)
230 return HTTPForbidden
256 return HTTPForbidden
231
257
232 def build_authentication(self):
258 def build_authentication(self):
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
259 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 if self._rc_auth_http_code and not self.initial_call:
260 if self._rc_auth_http_code and not self.initial_call:
235 # return alternative HTTP code if alternative http return code
261 # return alternative HTTP code if alternative http return code
236 # is specified in RhodeCode config, but ONLY if it's not the
262 # is specified in RhodeCode config, but ONLY if it's not the
237 # FIRST call
263 # FIRST call
238 custom_response_klass = self._get_response_from_code(
264 custom_response_klass = self._get_response_from_code(
239 self._rc_auth_http_code)
265 self._rc_auth_http_code)
240 return custom_response_klass(headers=head)
266 return custom_response_klass(headers=head)
241 return HTTPUnauthorized(headers=head)
267 return HTTPUnauthorized(headers=head)
242
268
243 def authenticate(self, environ):
269 def authenticate(self, environ):
244 authorization = AUTHORIZATION(environ)
270 authorization = AUTHORIZATION(environ)
245 if not authorization:
271 if not authorization:
246 return self.build_authentication()
272 return self.build_authentication()
247 (authmeth, auth) = authorization.split(' ', 1)
273 (authmeth, auth) = authorization.split(' ', 1)
248 if 'basic' != authmeth.lower():
274 if 'basic' != authmeth.lower():
249 return self.build_authentication()
275 return self.build_authentication()
250 auth = auth.strip().decode('base64')
276 auth = auth.strip().decode('base64')
251 _parts = auth.split(':', 1)
277 _parts = auth.split(':', 1)
252 if len(_parts) == 2:
278 if len(_parts) == 2:
253 username, password = _parts
279 username, password = _parts
254 if self.authfunc(
280 if self.authfunc(
255 username, password, environ, VCS_TYPE,
281 username, password, environ, VCS_TYPE,
256 registry=self.registry, acl_repo_name=self.acl_repo_name):
282 registry=self.registry, acl_repo_name=self.acl_repo_name):
257 return username
283 return username
258 if username and password:
284 if username and password:
259 # we mark that we actually executed authentication once, at
285 # we mark that we actually executed authentication once, at
260 # that point we can use the alternative auth code
286 # that point we can use the alternative auth code
261 self.initial_call = False
287 self.initial_call = False
262
288
263 return self.build_authentication()
289 return self.build_authentication()
264
290
265 __call__ = authenticate
291 __call__ = authenticate
266
292
267
293
268 def calculate_version_hash():
294 def calculate_version_hash():
269 return md5(
295 return md5(
270 config.get('beaker.session.secret', '') +
296 config.get('beaker.session.secret', '') +
271 rhodecode.__version__)[:8]
297 rhodecode.__version__)[:8]
272
298
273
299
274 def get_current_lang(request):
300 def get_current_lang(request):
275 # NOTE(marcink): remove after pyramid move
301 # NOTE(marcink): remove after pyramid move
276 try:
302 try:
277 return translation.get_lang()[0]
303 return translation.get_lang()[0]
278 except:
304 except:
279 pass
305 pass
280
306
281 return getattr(request, '_LOCALE_', request.locale_name)
307 return getattr(request, '_LOCALE_', request.locale_name)
282
308
283
309
284 def attach_context_attributes(context, request, user_id):
310 def attach_context_attributes(context, request, user_id):
285 """
311 """
286 Attach variables into template context called `c`, please note that
312 Attach variables into template context called `c`, please note that
287 request could be pylons or pyramid request in here.
313 request could be pylons or pyramid request in here.
288 """
314 """
289
315
290 rc_config = SettingsModel().get_all_settings(cache=True)
316 rc_config = SettingsModel().get_all_settings(cache=True)
291
317
292 context.rhodecode_version = rhodecode.__version__
318 context.rhodecode_version = rhodecode.__version__
293 context.rhodecode_edition = config.get('rhodecode.edition')
319 context.rhodecode_edition = config.get('rhodecode.edition')
294 # unique secret + version does not leak the version but keep consistency
320 # unique secret + version does not leak the version but keep consistency
295 context.rhodecode_version_hash = calculate_version_hash()
321 context.rhodecode_version_hash = calculate_version_hash()
296
322
297 # Default language set for the incoming request
323 # Default language set for the incoming request
298 context.language = get_current_lang(request)
324 context.language = get_current_lang(request)
299
325
300 # Visual options
326 # Visual options
301 context.visual = AttributeDict({})
327 context.visual = AttributeDict({})
302
328
303 # DB stored Visual Items
329 # DB stored Visual Items
304 context.visual.show_public_icon = str2bool(
330 context.visual.show_public_icon = str2bool(
305 rc_config.get('rhodecode_show_public_icon'))
331 rc_config.get('rhodecode_show_public_icon'))
306 context.visual.show_private_icon = str2bool(
332 context.visual.show_private_icon = str2bool(
307 rc_config.get('rhodecode_show_private_icon'))
333 rc_config.get('rhodecode_show_private_icon'))
308 context.visual.stylify_metatags = str2bool(
334 context.visual.stylify_metatags = str2bool(
309 rc_config.get('rhodecode_stylify_metatags'))
335 rc_config.get('rhodecode_stylify_metatags'))
310 context.visual.dashboard_items = safe_int(
336 context.visual.dashboard_items = safe_int(
311 rc_config.get('rhodecode_dashboard_items', 100))
337 rc_config.get('rhodecode_dashboard_items', 100))
312 context.visual.admin_grid_items = safe_int(
338 context.visual.admin_grid_items = safe_int(
313 rc_config.get('rhodecode_admin_grid_items', 100))
339 rc_config.get('rhodecode_admin_grid_items', 100))
314 context.visual.repository_fields = str2bool(
340 context.visual.repository_fields = str2bool(
315 rc_config.get('rhodecode_repository_fields'))
341 rc_config.get('rhodecode_repository_fields'))
316 context.visual.show_version = str2bool(
342 context.visual.show_version = str2bool(
317 rc_config.get('rhodecode_show_version'))
343 rc_config.get('rhodecode_show_version'))
318 context.visual.use_gravatar = str2bool(
344 context.visual.use_gravatar = str2bool(
319 rc_config.get('rhodecode_use_gravatar'))
345 rc_config.get('rhodecode_use_gravatar'))
320 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
346 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
321 context.visual.default_renderer = rc_config.get(
347 context.visual.default_renderer = rc_config.get(
322 'rhodecode_markup_renderer', 'rst')
348 'rhodecode_markup_renderer', 'rst')
323 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
349 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
324 context.visual.rhodecode_support_url = \
350 context.visual.rhodecode_support_url = \
325 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
351 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
326
352
327 context.visual.affected_files_cut_off = 60
353 context.visual.affected_files_cut_off = 60
328
354
329 context.pre_code = rc_config.get('rhodecode_pre_code')
355 context.pre_code = rc_config.get('rhodecode_pre_code')
330 context.post_code = rc_config.get('rhodecode_post_code')
356 context.post_code = rc_config.get('rhodecode_post_code')
331 context.rhodecode_name = rc_config.get('rhodecode_title')
357 context.rhodecode_name = rc_config.get('rhodecode_title')
332 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
358 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
333 # if we have specified default_encoding in the request, it has more
359 # if we have specified default_encoding in the request, it has more
334 # priority
360 # priority
335 if request.GET.get('default_encoding'):
361 if request.GET.get('default_encoding'):
336 context.default_encodings.insert(0, request.GET.get('default_encoding'))
362 context.default_encodings.insert(0, request.GET.get('default_encoding'))
337 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
363 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
338
364
339 # INI stored
365 # INI stored
340 context.labs_active = str2bool(
366 context.labs_active = str2bool(
341 config.get('labs_settings_active', 'false'))
367 config.get('labs_settings_active', 'false'))
342 context.visual.allow_repo_location_change = str2bool(
368 context.visual.allow_repo_location_change = str2bool(
343 config.get('allow_repo_location_change', True))
369 config.get('allow_repo_location_change', True))
344 context.visual.allow_custom_hooks_settings = str2bool(
370 context.visual.allow_custom_hooks_settings = str2bool(
345 config.get('allow_custom_hooks_settings', True))
371 config.get('allow_custom_hooks_settings', True))
346 context.debug_style = str2bool(config.get('debug_style', False))
372 context.debug_style = str2bool(config.get('debug_style', False))
347
373
348 context.rhodecode_instanceid = config.get('instance_id')
374 context.rhodecode_instanceid = config.get('instance_id')
349
375
350 context.visual.cut_off_limit_diff = safe_int(
376 context.visual.cut_off_limit_diff = safe_int(
351 config.get('cut_off_limit_diff'))
377 config.get('cut_off_limit_diff'))
352 context.visual.cut_off_limit_file = safe_int(
378 context.visual.cut_off_limit_file = safe_int(
353 config.get('cut_off_limit_file'))
379 config.get('cut_off_limit_file'))
354
380
355 # AppEnlight
381 # AppEnlight
356 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
382 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
357 context.appenlight_api_public_key = config.get(
383 context.appenlight_api_public_key = config.get(
358 'appenlight.api_public_key', '')
384 'appenlight.api_public_key', '')
359 context.appenlight_server_url = config.get('appenlight.server_url', '')
385 context.appenlight_server_url = config.get('appenlight.server_url', '')
360
386
361 # JS template context
387 # JS template context
362 context.template_context = {
388 context.template_context = {
363 'repo_name': None,
389 'repo_name': None,
364 'repo_type': None,
390 'repo_type': None,
365 'repo_landing_commit': None,
391 'repo_landing_commit': None,
366 'rhodecode_user': {
392 'rhodecode_user': {
367 'username': None,
393 'username': None,
368 'email': None,
394 'email': None,
369 'notification_status': False
395 'notification_status': False
370 },
396 },
371 'visual': {
397 'visual': {
372 'default_renderer': None
398 'default_renderer': None
373 },
399 },
374 'commit_data': {
400 'commit_data': {
375 'commit_id': None
401 'commit_id': None
376 },
402 },
377 'pull_request_data': {'pull_request_id': None},
403 'pull_request_data': {'pull_request_id': None},
378 'timeago': {
404 'timeago': {
379 'refresh_time': 120 * 1000,
405 'refresh_time': 120 * 1000,
380 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
406 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
381 },
407 },
382 'pylons_dispatch': {
408 'pylons_dispatch': {
383 # 'controller': request.environ['pylons.routes_dict']['controller'],
409 # 'controller': request.environ['pylons.routes_dict']['controller'],
384 # 'action': request.environ['pylons.routes_dict']['action'],
410 # 'action': request.environ['pylons.routes_dict']['action'],
385 },
411 },
386 'pyramid_dispatch': {
412 'pyramid_dispatch': {
387
413
388 },
414 },
389 'extra': {'plugins': {}}
415 'extra': {'plugins': {}}
390 }
416 }
391 # END CONFIG VARS
417 # END CONFIG VARS
392
418
393 # TODO: This dosn't work when called from pylons compatibility tween.
419 # TODO: This dosn't work when called from pylons compatibility tween.
394 # Fix this and remove it from base controller.
420 # Fix this and remove it from base controller.
395 # context.repo_name = get_repo_slug(request) # can be empty
421 # context.repo_name = get_repo_slug(request) # can be empty
396
422
397 diffmode = 'sideside'
423 diffmode = 'sideside'
398 if request.GET.get('diffmode'):
424 if request.GET.get('diffmode'):
399 if request.GET['diffmode'] == 'unified':
425 if request.GET['diffmode'] == 'unified':
400 diffmode = 'unified'
426 diffmode = 'unified'
401 elif request.session.get('diffmode'):
427 elif request.session.get('diffmode'):
402 diffmode = request.session['diffmode']
428 diffmode = request.session['diffmode']
403
429
404 context.diffmode = diffmode
430 context.diffmode = diffmode
405
431
406 if request.session.get('diffmode') != diffmode:
432 if request.session.get('diffmode') != diffmode:
407 request.session['diffmode'] = diffmode
433 request.session['diffmode'] = diffmode
408
434
409 context.csrf_token = auth.get_csrf_token(session=request.session)
435 context.csrf_token = auth.get_csrf_token(session=request.session)
410 context.backends = rhodecode.BACKENDS.keys()
436 context.backends = rhodecode.BACKENDS.keys()
411 context.backends.sort()
437 context.backends.sort()
412 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
438 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
413
439
414 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
440 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
415 # given request will ALWAYS be pyramid one
441 # given request will ALWAYS be pyramid one
416 pyramid_request = pyramid.threadlocal.get_current_request()
442 pyramid_request = pyramid.threadlocal.get_current_request()
417 context.pyramid_request = pyramid_request
443 context.pyramid_request = pyramid_request
418
444
419 # web case
445 # web case
420 if hasattr(pyramid_request, 'user'):
446 if hasattr(pyramid_request, 'user'):
421 context.auth_user = pyramid_request.user
447 context.auth_user = pyramid_request.user
422 context.rhodecode_user = pyramid_request.user
448 context.rhodecode_user = pyramid_request.user
423
449
424 # api case
450 # api case
425 if hasattr(pyramid_request, 'rpc_user'):
451 if hasattr(pyramid_request, 'rpc_user'):
426 context.auth_user = pyramid_request.rpc_user
452 context.auth_user = pyramid_request.rpc_user
427 context.rhodecode_user = pyramid_request.rpc_user
453 context.rhodecode_user = pyramid_request.rpc_user
428
454
429 # attach the whole call context to the request
455 # attach the whole call context to the request
430 request.call_context = context
456 request.call_context = context
431
457
432
458
433 def get_auth_user(request):
459 def get_auth_user(request):
434 environ = request.environ
460 environ = request.environ
435 session = request.session
461 session = request.session
436
462
437 ip_addr = get_ip_addr(environ)
463 ip_addr = get_ip_addr(environ)
438 # make sure that we update permissions each time we call controller
464 # make sure that we update permissions each time we call controller
439 _auth_token = (request.GET.get('auth_token', '') or
465 _auth_token = (request.GET.get('auth_token', '') or
440 request.GET.get('api_key', ''))
466 request.GET.get('api_key', ''))
441
467
442 if _auth_token:
468 if _auth_token:
443 # when using API_KEY we assume user exists, and
469 # when using API_KEY we assume user exists, and
444 # doesn't need auth based on cookies.
470 # doesn't need auth based on cookies.
445 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
471 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
446 authenticated = False
472 authenticated = False
447 else:
473 else:
448 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
474 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
449 try:
475 try:
450 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
476 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
451 ip_addr=ip_addr)
477 ip_addr=ip_addr)
452 except UserCreationError as e:
478 except UserCreationError as e:
453 h.flash(e, 'error')
479 h.flash(e, 'error')
454 # container auth or other auth functions that create users
480 # container auth or other auth functions that create users
455 # on the fly can throw this exception signaling that there's
481 # on the fly can throw this exception signaling that there's
456 # issue with user creation, explanation should be provided
482 # issue with user creation, explanation should be provided
457 # in Exception itself. We then create a simple blank
483 # in Exception itself. We then create a simple blank
458 # AuthUser
484 # AuthUser
459 auth_user = AuthUser(ip_addr=ip_addr)
485 auth_user = AuthUser(ip_addr=ip_addr)
460
486
461 if password_changed(auth_user, session):
487 if password_changed(auth_user, session):
462 session.invalidate()
488 session.invalidate()
463 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
489 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
464 auth_user = AuthUser(ip_addr=ip_addr)
490 auth_user = AuthUser(ip_addr=ip_addr)
465
491
466 authenticated = cookie_store.get('is_authenticated')
492 authenticated = cookie_store.get('is_authenticated')
467
493
468 if not auth_user.is_authenticated and auth_user.is_user_object:
494 if not auth_user.is_authenticated and auth_user.is_user_object:
469 # user is not authenticated and not empty
495 # user is not authenticated and not empty
470 auth_user.set_authenticated(authenticated)
496 auth_user.set_authenticated(authenticated)
471
497
472 return auth_user
498 return auth_user
473
499
474
500
475 class BaseController(WSGIController):
501 class BaseController(WSGIController):
476
502
477 def __before__(self):
503 def __before__(self):
478 """
504 """
479 __before__ is called before controller methods and after __call__
505 __before__ is called before controller methods and after __call__
480 """
506 """
481 # on each call propagate settings calls into global settings.
507 # on each call propagate settings calls into global settings.
482 set_rhodecode_config(config)
508 set_rhodecode_config(config)
483 attach_context_attributes(c, request, self._rhodecode_user.user_id)
509 attach_context_attributes(c, request, self._rhodecode_user.user_id)
484
510
485 # TODO: Remove this when fixed in attach_context_attributes()
511 # TODO: Remove this when fixed in attach_context_attributes()
486 c.repo_name = get_repo_slug(request) # can be empty
512 c.repo_name = get_repo_slug(request) # can be empty
487
513
488 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
514 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
489 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
515 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
490 self.sa = meta.Session
516 self.sa = meta.Session
491 self.scm_model = ScmModel(self.sa)
517 self.scm_model = ScmModel(self.sa)
492
518
493 # set user language
519 # set user language
494 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
520 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
495 if user_lang:
521 if user_lang:
496 translation.set_lang(user_lang)
522 translation.set_lang(user_lang)
497 log.debug('set language to %s for user %s',
523 log.debug('set language to %s for user %s',
498 user_lang, self._rhodecode_user)
524 user_lang, self._rhodecode_user)
499
525
500 def _dispatch_redirect(self, with_url, environ, start_response):
526 def _dispatch_redirect(self, with_url, environ, start_response):
501 resp = HTTPFound(with_url)
527 resp = HTTPFound(with_url)
502 environ['SCRIPT_NAME'] = '' # handle prefix middleware
528 environ['SCRIPT_NAME'] = '' # handle prefix middleware
503 environ['PATH_INFO'] = with_url
529 environ['PATH_INFO'] = with_url
504 return resp(environ, start_response)
530 return resp(environ, start_response)
505
531
506 def __call__(self, environ, start_response):
532 def __call__(self, environ, start_response):
507 """Invoke the Controller"""
533 """Invoke the Controller"""
508 # WSGIController.__call__ dispatches to the Controller method
534 # WSGIController.__call__ dispatches to the Controller method
509 # the request is routed to. This routing information is
535 # the request is routed to. This routing information is
510 # available in environ['pylons.routes_dict']
536 # available in environ['pylons.routes_dict']
511 from rhodecode.lib import helpers as h
537 from rhodecode.lib import helpers as h
512
538
513 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
539 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
514 if environ.get('debugtoolbar.wants_pylons_context', False):
540 if environ.get('debugtoolbar.wants_pylons_context', False):
515 environ['debugtoolbar.pylons_context'] = c._current_obj()
541 environ['debugtoolbar.pylons_context'] = c._current_obj()
516
542
517 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
543 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
518 environ['pylons.routes_dict']['action']])
544 environ['pylons.routes_dict']['action']])
519
545
520 self.rc_config = SettingsModel().get_all_settings(cache=True)
546 self.rc_config = SettingsModel().get_all_settings(cache=True)
521 self.ip_addr = get_ip_addr(environ)
547 self.ip_addr = get_ip_addr(environ)
522
548
523 # The rhodecode auth user is looked up and passed through the
549 # The rhodecode auth user is looked up and passed through the
524 # environ by the pylons compatibility tween in pyramid.
550 # environ by the pylons compatibility tween in pyramid.
525 # So we can just grab it from there.
551 # So we can just grab it from there.
526 auth_user = environ['rc_auth_user']
552 auth_user = environ['rc_auth_user']
527
553
528 # set globals for auth user
554 # set globals for auth user
529 request.user = auth_user
555 request.user = auth_user
530 self._rhodecode_user = auth_user
556 self._rhodecode_user = auth_user
531
557
532 log.info('IP: %s User: %s accessed %s [%s]' % (
558 log.info('IP: %s User: %s accessed %s [%s]' % (
533 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
559 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
534 _route_name)
560 _route_name)
535 )
561 )
536
562
537 user_obj = auth_user.get_instance()
563 user_obj = auth_user.get_instance()
538 if user_obj and user_obj.user_data.get('force_password_change'):
564 if user_obj and user_obj.user_data.get('force_password_change'):
539 h.flash('You are required to change your password', 'warning',
565 h.flash('You are required to change your password', 'warning',
540 ignore_duplicate=True)
566 ignore_duplicate=True)
541 return self._dispatch_redirect(
567 return self._dispatch_redirect(
542 url('my_account_password'), environ, start_response)
568 url('my_account_password'), environ, start_response)
543
569
544 return WSGIController.__call__(self, environ, start_response)
570 return WSGIController.__call__(self, environ, start_response)
545
571
546
572
547 class BaseRepoController(BaseController):
573 class BaseRepoController(BaseController):
548 """
574 """
549 Base class for controllers responsible for loading all needed data for
575 Base class for controllers responsible for loading all needed data for
550 repository loaded items are
576 repository loaded items are
551
577
552 c.rhodecode_repo: instance of scm repository
578 c.rhodecode_repo: instance of scm repository
553 c.rhodecode_db_repo: instance of db
579 c.rhodecode_db_repo: instance of db
554 c.repository_requirements_missing: shows that repository specific data
580 c.repository_requirements_missing: shows that repository specific data
555 could not be displayed due to the missing requirements
581 could not be displayed due to the missing requirements
556 c.repository_pull_requests: show number of open pull requests
582 c.repository_pull_requests: show number of open pull requests
557 """
583 """
558
584
559 def __before__(self):
585 def __before__(self):
560 super(BaseRepoController, self).__before__()
586 super(BaseRepoController, self).__before__()
561 if c.repo_name: # extracted from routes
587 if c.repo_name: # extracted from routes
562 db_repo = Repository.get_by_repo_name(c.repo_name)
588 db_repo = Repository.get_by_repo_name(c.repo_name)
563 if not db_repo:
589 if not db_repo:
564 return
590 return
565
591
566 log.debug(
592 log.debug(
567 'Found repository in database %s with state `%s`',
593 'Found repository in database %s with state `%s`',
568 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
594 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
569 route = getattr(request.environ.get('routes.route'), 'name', '')
595 route = getattr(request.environ.get('routes.route'), 'name', '')
570
596
571 # allow to delete repos that are somehow damages in filesystem
597 # allow to delete repos that are somehow damages in filesystem
572 if route in ['delete_repo']:
598 if route in ['delete_repo']:
573 return
599 return
574
600
575 if db_repo.repo_state in [Repository.STATE_PENDING]:
601 if db_repo.repo_state in [Repository.STATE_PENDING]:
576 if route in ['repo_creating_home']:
602 if route in ['repo_creating_home']:
577 return
603 return
578 check_url = url('repo_creating_home', repo_name=c.repo_name)
604 check_url = url('repo_creating_home', repo_name=c.repo_name)
579 return redirect(check_url)
605 return redirect(check_url)
580
606
581 self.rhodecode_db_repo = db_repo
607 self.rhodecode_db_repo = db_repo
582
608
583 missing_requirements = False
609 missing_requirements = False
584 try:
610 try:
585 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
611 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
586 except RepositoryRequirementError as e:
612 except RepositoryRequirementError as e:
587 missing_requirements = True
613 missing_requirements = True
588 self._handle_missing_requirements(e)
614 self._handle_missing_requirements(e)
589
615
590 if self.rhodecode_repo is None and not missing_requirements:
616 if self.rhodecode_repo is None and not missing_requirements:
591 log.error('%s this repository is present in database but it '
617 log.error('%s this repository is present in database but it '
592 'cannot be created as an scm instance', c.repo_name)
618 'cannot be created as an scm instance', c.repo_name)
593
619
594 h.flash(_(
620 h.flash(_(
595 "The repository at %(repo_name)s cannot be located.") %
621 "The repository at %(repo_name)s cannot be located.") %
596 {'repo_name': c.repo_name},
622 {'repo_name': c.repo_name},
597 category='error', ignore_duplicate=True)
623 category='error', ignore_duplicate=True)
598 redirect(h.route_path('home'))
624 redirect(h.route_path('home'))
599
625
600 # update last change according to VCS data
626 # update last change according to VCS data
601 if not missing_requirements:
627 if not missing_requirements:
602 commit = db_repo.get_commit(
628 commit = db_repo.get_commit(
603 pre_load=["author", "date", "message", "parents"])
629 pre_load=["author", "date", "message", "parents"])
604 db_repo.update_commit_cache(commit)
630 db_repo.update_commit_cache(commit)
605
631
606 # Prepare context
632 # Prepare context
607 c.rhodecode_db_repo = db_repo
633 c.rhodecode_db_repo = db_repo
608 c.rhodecode_repo = self.rhodecode_repo
634 c.rhodecode_repo = self.rhodecode_repo
609 c.repository_requirements_missing = missing_requirements
635 c.repository_requirements_missing = missing_requirements
610
636
611 self._update_global_counters(self.scm_model, db_repo)
637 self._update_global_counters(self.scm_model, db_repo)
612
638
613 def _update_global_counters(self, scm_model, db_repo):
639 def _update_global_counters(self, scm_model, db_repo):
614 """
640 """
615 Base variables that are exposed to every page of repository
641 Base variables that are exposed to every page of repository
616 """
642 """
617 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
643 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
618
644
619 def _handle_missing_requirements(self, error):
645 def _handle_missing_requirements(self, error):
620 self.rhodecode_repo = None
646 self.rhodecode_repo = None
621 log.error(
647 log.error(
622 'Requirements are missing for repository %s: %s',
648 'Requirements are missing for repository %s: %s',
623 c.repo_name, error.message)
649 c.repo_name, error.message)
624
650
625 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
651 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
626 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
652 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
627 settings_update_url = url('repo', repo_name=c.repo_name)
653 settings_update_url = url('repo', repo_name=c.repo_name)
628 path = request.path
654 path = request.path
629 should_redirect = (
655 should_redirect = (
630 path not in (summary_url, settings_update_url)
656 path not in (summary_url, settings_update_url)
631 and '/settings' not in path or path == statistics_url
657 and '/settings' not in path or path == statistics_url
632 )
658 )
633 if should_redirect:
659 if should_redirect:
634 redirect(summary_url)
660 redirect(summary_url)
@@ -1,97 +1,97 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 import logging
22 import logging
23 from mako import exceptions
23 from mako import exceptions
24 from pyramid.renderers import get_renderer
24 from pyramid.renderers import get_renderer
25
25
26 log = logging.getLogger(__name__)
26 log = logging.getLogger(__name__)
27
27
28
28
29 def get_partial_renderer(request, tmpl_name):
29 def get_partial_renderer(request, tmpl_name):
30 return PyramidPartialRenderer(request, tmpl_name=tmpl_name)
30 return PyramidPartialRenderer(request, tmpl_name=tmpl_name)
31
31
32
32
33 class PyramidPartialRenderer(object):
33 class PyramidPartialRenderer(object):
34
34
35 """
35 """
36 Partial renderer used to render chunks of html used in datagrids
36 Partial renderer used to render chunks of html used in datagrids
37 use like::
37 use like::
38
38
39 _renderer = request.get_partial_renderer('_dt/template_base.mako')
39 _renderer = request.get_partial_renderer('_dt/template_base.mako')
40 _render('quick_menu', args, kwargs)
40 _render('quick_menu', args, kwargs)
41
41
42 :param tmpl_name: template path relate to /templates/ dir
42 :param tmpl_name: template path relate to /templates/ dir
43 """
43 """
44
44
45 def __init__(self, request, tmpl_name):
45 def __init__(self, request, tmpl_name):
46 self.tmpl_name = tmpl_name
46 self.tmpl_name = tmpl_name
47 self.request = request
47 self.request = request
48
48
49 def _mako_lookup(self):
49 def _mako_lookup(self):
50 _tmpl_lookup = get_renderer('root.mako').lookup
50 _tmpl_lookup = get_renderer('root.mako').lookup
51 return _tmpl_lookup.get_template(self.tmpl_name)
51 return _tmpl_lookup.get_template(self.tmpl_name)
52
52
53 def get_call_context(self):
53 def get_call_context(self):
54 return self.request.call_context
54 return self.request.call_context
55
55
56 def get_helpers(self):
56 def get_helpers(self):
57 from rhodecode.lib import helpers
57 from rhodecode.lib import helpers
58 return helpers
58 return helpers
59
59
60 def _update_kwargs_for_render(self, kwargs):
60 def _update_kwargs_for_render(self, kwargs):
61 """
61 """
62 Inject params required for Mako rendering
62 Inject params required for Mako rendering
63 """
63 """
64
64
65 _kwargs = {
65 _kwargs = {
66 '_': self.request.translate,
66 '_': self.request.translate,
67 'ungettext': self.request.plularize,
67 '_ungettext': self.request.plularize,
68 'h': self.get_helpers(),
68 'h': self.get_helpers(),
69 'c': self.get_call_context(),
69 'c': self.get_call_context(),
70
70
71 'request': self.request,
71 'request': self.request,
72 }
72 }
73 _kwargs.update(kwargs)
73 _kwargs.update(kwargs)
74 return _kwargs
74 return _kwargs
75
75
76 def _render_with_exc(self, render_func, args, kwargs):
76 def _render_with_exc(self, render_func, args, kwargs):
77 try:
77 try:
78 return render_func.render(*args, **kwargs)
78 return render_func.render(*args, **kwargs)
79 except:
79 except:
80 log.error(exceptions.text_error_template().render())
80 log.error(exceptions.text_error_template().render())
81 raise
81 raise
82
82
83 def _get_template(self, template_obj, def_name):
83 def _get_template(self, template_obj, def_name):
84 if def_name:
84 if def_name:
85 tmpl = template_obj.get_def(def_name)
85 tmpl = template_obj.get_def(def_name)
86 else:
86 else:
87 tmpl = template_obj
87 tmpl = template_obj
88 return tmpl
88 return tmpl
89
89
90 def render(self, def_name, *args, **kwargs):
90 def render(self, def_name, *args, **kwargs):
91 lookup_obj = self._mako_lookup()
91 lookup_obj = self._mako_lookup()
92 tmpl = self._get_template(lookup_obj, def_name=def_name)
92 tmpl = self._get_template(lookup_obj, def_name=def_name)
93 kwargs = self._update_kwargs_for_render(kwargs)
93 kwargs = self._update_kwargs_for_render(kwargs)
94 return self._render_with_exc(tmpl, args, kwargs)
94 return self._render_with_exc(tmpl, args, kwargs)
95
95
96 def __call__(self, tmpl, *args, **kwargs):
96 def __call__(self, tmpl, *args, **kwargs):
97 return self.render(tmpl, *args, **kwargs)
97 return self.render(tmpl, *args, **kwargs)
@@ -1,982 +1,982 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
45 from pyramid.request import Request
45 from pyramid.request import Request
46
46
47 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.fakemod import create_module
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.utils2 import (
51 from rhodecode.lib.utils2 import (
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def repo_name_slug(value):
80 def repo_name_slug(value):
81 """
81 """
82 Return slug of name of repository
82 Return slug of name of repository
83 This function is called on each creation/modification
83 This function is called on each creation/modification
84 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
85 """
85 """
86 replacement_char = '-'
86 replacement_char = '-'
87
87
88 slug = remove_formatting(value)
88 slug = remove_formatting(value)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 slug = re.sub('[\s]+', '-', slug)
90 slug = re.sub('[\s]+', '-', slug)
91 slug = collapse(slug, replacement_char)
91 slug = collapse(slug, replacement_char)
92 return slug
92 return slug
93
93
94
94
95 #==============================================================================
95 #==============================================================================
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 if isinstance(request, Request) and getattr(request, 'db_repo', None):
99 if isinstance(request, Request) and getattr(request, 'db_repo', None):
100 # pyramid
100 # pyramid
101 _repo = request.db_repo.repo_name
101 _repo = request.db_repo.repo_name
102 else:
102 else:
103 # TODO(marcink): remove after pylons migration...
103 # TODO(marcink): remove after pylons migration...
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105
105
106 if _repo:
106 if _repo:
107 _repo = _repo.rstrip('/')
107 _repo = _repo.rstrip('/')
108 return _repo
108 return _repo
109
109
110
110
111 def get_repo_group_slug(request):
111 def get_repo_group_slug(request):
112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
113 # pyramid
113 # pyramid
114 _group = request.matchdict.get('repo_group_name')
114 _group = request.matchdict.get('repo_group_name')
115 else:
115 else:
116 _group = request.environ['pylons.routes_dict'].get('group_name')
116 _group = request.environ['pylons.routes_dict'].get('group_name')
117
117
118 if _group:
118 if _group:
119 _group = _group.rstrip('/')
119 _group = _group.rstrip('/')
120 return _group
120 return _group
121
121
122
122
123 def get_user_group_slug(request):
123 def get_user_group_slug(request):
124 if isinstance(request, Request) and getattr(request, 'matchdict', None):
124 if isinstance(request, Request) and getattr(request, 'matchdict', None):
125 # pyramid
125 # pyramid
126 _group = request.matchdict.get('user_group_id')
126 _group = request.matchdict.get('user_group_id')
127 else:
127 else:
128 _group = request.environ['pylons.routes_dict'].get('user_group_id')
128 _group = request.environ['pylons.routes_dict'].get('user_group_id')
129
129
130 try:
130 try:
131 _group = UserGroup.get(_group)
131 _group = UserGroup.get(_group)
132 if _group:
132 if _group:
133 _group = _group.users_group_name
133 _group = _group.users_group_name
134 except Exception:
134 except Exception:
135 log.debug(traceback.format_exc())
135 log.debug(traceback.format_exc())
136 # catch all failures here
136 # catch all failures here
137 pass
137 pass
138
138
139 return _group
139 return _group
140
140
141
141
142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
143 """
143 """
144 Scans given path for repos and return (name,(type,path)) tuple
144 Scans given path for repos and return (name,(type,path)) tuple
145
145
146 :param path: path to scan for repositories
146 :param path: path to scan for repositories
147 :param recursive: recursive search and return names with subdirs in front
147 :param recursive: recursive search and return names with subdirs in front
148 """
148 """
149
149
150 # remove ending slash for better results
150 # remove ending slash for better results
151 path = path.rstrip(os.sep)
151 path = path.rstrip(os.sep)
152 log.debug('now scanning in %s location recursive:%s...', path, recursive)
152 log.debug('now scanning in %s location recursive:%s...', path, recursive)
153
153
154 def _get_repos(p):
154 def _get_repos(p):
155 dirpaths = _get_dirpaths(p)
155 dirpaths = _get_dirpaths(p)
156 if not _is_dir_writable(p):
156 if not _is_dir_writable(p):
157 log.warning('repo path without write access: %s', p)
157 log.warning('repo path without write access: %s', p)
158
158
159 for dirpath in dirpaths:
159 for dirpath in dirpaths:
160 if os.path.isfile(os.path.join(p, dirpath)):
160 if os.path.isfile(os.path.join(p, dirpath)):
161 continue
161 continue
162 cur_path = os.path.join(p, dirpath)
162 cur_path = os.path.join(p, dirpath)
163
163
164 # skip removed repos
164 # skip removed repos
165 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
165 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
166 continue
166 continue
167
167
168 #skip .<somethin> dirs
168 #skip .<somethin> dirs
169 if dirpath.startswith('.'):
169 if dirpath.startswith('.'):
170 continue
170 continue
171
171
172 try:
172 try:
173 scm_info = get_scm(cur_path)
173 scm_info = get_scm(cur_path)
174 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
174 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
175 except VCSError:
175 except VCSError:
176 if not recursive:
176 if not recursive:
177 continue
177 continue
178 #check if this dir containts other repos for recursive scan
178 #check if this dir containts other repos for recursive scan
179 rec_path = os.path.join(p, dirpath)
179 rec_path = os.path.join(p, dirpath)
180 if os.path.isdir(rec_path):
180 if os.path.isdir(rec_path):
181 for inner_scm in _get_repos(rec_path):
181 for inner_scm in _get_repos(rec_path):
182 yield inner_scm
182 yield inner_scm
183
183
184 return _get_repos(path)
184 return _get_repos(path)
185
185
186
186
187 def _get_dirpaths(p):
187 def _get_dirpaths(p):
188 try:
188 try:
189 # OS-independable way of checking if we have at least read-only
189 # OS-independable way of checking if we have at least read-only
190 # access or not.
190 # access or not.
191 dirpaths = os.listdir(p)
191 dirpaths = os.listdir(p)
192 except OSError:
192 except OSError:
193 log.warning('ignoring repo path without read access: %s', p)
193 log.warning('ignoring repo path without read access: %s', p)
194 return []
194 return []
195
195
196 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
196 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
197 # decode paths and suddenly returns unicode objects itself. The items it
197 # decode paths and suddenly returns unicode objects itself. The items it
198 # cannot decode are returned as strings and cause issues.
198 # cannot decode are returned as strings and cause issues.
199 #
199 #
200 # Those paths are ignored here until a solid solution for path handling has
200 # Those paths are ignored here until a solid solution for path handling has
201 # been built.
201 # been built.
202 expected_type = type(p)
202 expected_type = type(p)
203
203
204 def _has_correct_type(item):
204 def _has_correct_type(item):
205 if type(item) is not expected_type:
205 if type(item) is not expected_type:
206 log.error(
206 log.error(
207 u"Ignoring path %s since it cannot be decoded into unicode.",
207 u"Ignoring path %s since it cannot be decoded into unicode.",
208 # Using "repr" to make sure that we see the byte value in case
208 # Using "repr" to make sure that we see the byte value in case
209 # of support.
209 # of support.
210 repr(item))
210 repr(item))
211 return False
211 return False
212 return True
212 return True
213
213
214 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
214 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
215
215
216 return dirpaths
216 return dirpaths
217
217
218
218
219 def _is_dir_writable(path):
219 def _is_dir_writable(path):
220 """
220 """
221 Probe if `path` is writable.
221 Probe if `path` is writable.
222
222
223 Due to trouble on Cygwin / Windows, this is actually probing if it is
223 Due to trouble on Cygwin / Windows, this is actually probing if it is
224 possible to create a file inside of `path`, stat does not produce reliable
224 possible to create a file inside of `path`, stat does not produce reliable
225 results in this case.
225 results in this case.
226 """
226 """
227 try:
227 try:
228 with tempfile.TemporaryFile(dir=path):
228 with tempfile.TemporaryFile(dir=path):
229 pass
229 pass
230 except OSError:
230 except OSError:
231 return False
231 return False
232 return True
232 return True
233
233
234
234
235 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
235 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
236 """
236 """
237 Returns True if given path is a valid repository False otherwise.
237 Returns True if given path is a valid repository False otherwise.
238 If expect_scm param is given also, compare if given scm is the same
238 If expect_scm param is given also, compare if given scm is the same
239 as expected from scm parameter. If explicit_scm is given don't try to
239 as expected from scm parameter. If explicit_scm is given don't try to
240 detect the scm, just use the given one to check if repo is valid
240 detect the scm, just use the given one to check if repo is valid
241
241
242 :param repo_name:
242 :param repo_name:
243 :param base_path:
243 :param base_path:
244 :param expect_scm:
244 :param expect_scm:
245 :param explicit_scm:
245 :param explicit_scm:
246
246
247 :return True: if given path is a valid repository
247 :return True: if given path is a valid repository
248 """
248 """
249 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
249 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
250 log.debug('Checking if `%s` is a valid path for repository. '
250 log.debug('Checking if `%s` is a valid path for repository. '
251 'Explicit type: %s', repo_name, explicit_scm)
251 'Explicit type: %s', repo_name, explicit_scm)
252
252
253 try:
253 try:
254 if explicit_scm:
254 if explicit_scm:
255 detected_scms = [get_scm_backend(explicit_scm)]
255 detected_scms = [get_scm_backend(explicit_scm)]
256 else:
256 else:
257 detected_scms = get_scm(full_path)
257 detected_scms = get_scm(full_path)
258
258
259 if expect_scm:
259 if expect_scm:
260 return detected_scms[0] == expect_scm
260 return detected_scms[0] == expect_scm
261 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
261 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
262 return True
262 return True
263 except VCSError:
263 except VCSError:
264 log.debug('path: %s is not a valid repo !', full_path)
264 log.debug('path: %s is not a valid repo !', full_path)
265 return False
265 return False
266
266
267
267
268 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
268 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
269 """
269 """
270 Returns True if given path is a repository group, False otherwise
270 Returns True if given path is a repository group, False otherwise
271
271
272 :param repo_name:
272 :param repo_name:
273 :param base_path:
273 :param base_path:
274 """
274 """
275 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
275 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
276 log.debug('Checking if `%s` is a valid path for repository group',
276 log.debug('Checking if `%s` is a valid path for repository group',
277 repo_group_name)
277 repo_group_name)
278
278
279 # check if it's not a repo
279 # check if it's not a repo
280 if is_valid_repo(repo_group_name, base_path):
280 if is_valid_repo(repo_group_name, base_path):
281 log.debug('Repo called %s exist, it is not a valid '
281 log.debug('Repo called %s exist, it is not a valid '
282 'repo group' % repo_group_name)
282 'repo group' % repo_group_name)
283 return False
283 return False
284
284
285 try:
285 try:
286 # we need to check bare git repos at higher level
286 # we need to check bare git repos at higher level
287 # since we might match branches/hooks/info/objects or possible
287 # since we might match branches/hooks/info/objects or possible
288 # other things inside bare git repo
288 # other things inside bare git repo
289 scm_ = get_scm(os.path.dirname(full_path))
289 scm_ = get_scm(os.path.dirname(full_path))
290 log.debug('path: %s is a vcs object:%s, not valid '
290 log.debug('path: %s is a vcs object:%s, not valid '
291 'repo group' % (full_path, scm_))
291 'repo group' % (full_path, scm_))
292 return False
292 return False
293 except VCSError:
293 except VCSError:
294 pass
294 pass
295
295
296 # check if it's a valid path
296 # check if it's a valid path
297 if skip_path_check or os.path.isdir(full_path):
297 if skip_path_check or os.path.isdir(full_path):
298 log.debug('path: %s is a valid repo group !', full_path)
298 log.debug('path: %s is a valid repo group !', full_path)
299 return True
299 return True
300
300
301 log.debug('path: %s is not a valid repo group !', full_path)
301 log.debug('path: %s is not a valid repo group !', full_path)
302 return False
302 return False
303
303
304
304
305 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
305 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
306 while True:
306 while True:
307 ok = raw_input(prompt)
307 ok = raw_input(prompt)
308 if ok.lower() in ('y', 'ye', 'yes'):
308 if ok.lower() in ('y', 'ye', 'yes'):
309 return True
309 return True
310 if ok.lower() in ('n', 'no', 'nop', 'nope'):
310 if ok.lower() in ('n', 'no', 'nop', 'nope'):
311 return False
311 return False
312 retries = retries - 1
312 retries = retries - 1
313 if retries < 0:
313 if retries < 0:
314 raise IOError
314 raise IOError
315 print(complaint)
315 print(complaint)
316
316
317 # propagated from mercurial documentation
317 # propagated from mercurial documentation
318 ui_sections = [
318 ui_sections = [
319 'alias', 'auth',
319 'alias', 'auth',
320 'decode/encode', 'defaults',
320 'decode/encode', 'defaults',
321 'diff', 'email',
321 'diff', 'email',
322 'extensions', 'format',
322 'extensions', 'format',
323 'merge-patterns', 'merge-tools',
323 'merge-patterns', 'merge-tools',
324 'hooks', 'http_proxy',
324 'hooks', 'http_proxy',
325 'smtp', 'patch',
325 'smtp', 'patch',
326 'paths', 'profiling',
326 'paths', 'profiling',
327 'server', 'trusted',
327 'server', 'trusted',
328 'ui', 'web', ]
328 'ui', 'web', ]
329
329
330
330
331 def config_data_from_db(clear_session=True, repo=None):
331 def config_data_from_db(clear_session=True, repo=None):
332 """
332 """
333 Read the configuration data from the database and return configuration
333 Read the configuration data from the database and return configuration
334 tuples.
334 tuples.
335 """
335 """
336 from rhodecode.model.settings import VcsSettingsModel
336 from rhodecode.model.settings import VcsSettingsModel
337
337
338 config = []
338 config = []
339
339
340 sa = meta.Session()
340 sa = meta.Session()
341 settings_model = VcsSettingsModel(repo=repo, sa=sa)
341 settings_model = VcsSettingsModel(repo=repo, sa=sa)
342
342
343 ui_settings = settings_model.get_ui_settings()
343 ui_settings = settings_model.get_ui_settings()
344
344
345 for setting in ui_settings:
345 for setting in ui_settings:
346 if setting.active:
346 if setting.active:
347 log.debug(
347 log.debug(
348 'settings ui from db: [%s] %s=%s',
348 'settings ui from db: [%s] %s=%s',
349 setting.section, setting.key, setting.value)
349 setting.section, setting.key, setting.value)
350 config.append((
350 config.append((
351 safe_str(setting.section), safe_str(setting.key),
351 safe_str(setting.section), safe_str(setting.key),
352 safe_str(setting.value)))
352 safe_str(setting.value)))
353 if setting.key == 'push_ssl':
353 if setting.key == 'push_ssl':
354 # force set push_ssl requirement to False, rhodecode
354 # force set push_ssl requirement to False, rhodecode
355 # handles that
355 # handles that
356 config.append((
356 config.append((
357 safe_str(setting.section), safe_str(setting.key), False))
357 safe_str(setting.section), safe_str(setting.key), False))
358 if clear_session:
358 if clear_session:
359 meta.Session.remove()
359 meta.Session.remove()
360
360
361 # TODO: mikhail: probably it makes no sense to re-read hooks information.
361 # TODO: mikhail: probably it makes no sense to re-read hooks information.
362 # It's already there and activated/deactivated
362 # It's already there and activated/deactivated
363 skip_entries = []
363 skip_entries = []
364 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
364 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
365 if 'pull' not in enabled_hook_classes:
365 if 'pull' not in enabled_hook_classes:
366 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
366 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
367 if 'push' not in enabled_hook_classes:
367 if 'push' not in enabled_hook_classes:
368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
371
371
372 config = [entry for entry in config if entry[:2] not in skip_entries]
372 config = [entry for entry in config if entry[:2] not in skip_entries]
373
373
374 return config
374 return config
375
375
376
376
377 def make_db_config(clear_session=True, repo=None):
377 def make_db_config(clear_session=True, repo=None):
378 """
378 """
379 Create a :class:`Config` instance based on the values in the database.
379 Create a :class:`Config` instance based on the values in the database.
380 """
380 """
381 config = Config()
381 config = Config()
382 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
382 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
383 for section, option, value in config_data:
383 for section, option, value in config_data:
384 config.set(section, option, value)
384 config.set(section, option, value)
385 return config
385 return config
386
386
387
387
388 def get_enabled_hook_classes(ui_settings):
388 def get_enabled_hook_classes(ui_settings):
389 """
389 """
390 Return the enabled hook classes.
390 Return the enabled hook classes.
391
391
392 :param ui_settings: List of ui_settings as returned
392 :param ui_settings: List of ui_settings as returned
393 by :meth:`VcsSettingsModel.get_ui_settings`
393 by :meth:`VcsSettingsModel.get_ui_settings`
394
394
395 :return: a list with the enabled hook classes. The order is not guaranteed.
395 :return: a list with the enabled hook classes. The order is not guaranteed.
396 :rtype: list
396 :rtype: list
397 """
397 """
398 enabled_hooks = []
398 enabled_hooks = []
399 active_hook_keys = [
399 active_hook_keys = [
400 key for section, key, value, active in ui_settings
400 key for section, key, value, active in ui_settings
401 if section == 'hooks' and active]
401 if section == 'hooks' and active]
402
402
403 hook_names = {
403 hook_names = {
404 RhodeCodeUi.HOOK_PUSH: 'push',
404 RhodeCodeUi.HOOK_PUSH: 'push',
405 RhodeCodeUi.HOOK_PULL: 'pull',
405 RhodeCodeUi.HOOK_PULL: 'pull',
406 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
406 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
407 }
407 }
408
408
409 for key in active_hook_keys:
409 for key in active_hook_keys:
410 hook = hook_names.get(key)
410 hook = hook_names.get(key)
411 if hook:
411 if hook:
412 enabled_hooks.append(hook)
412 enabled_hooks.append(hook)
413
413
414 return enabled_hooks
414 return enabled_hooks
415
415
416
416
417 def set_rhodecode_config(config):
417 def set_rhodecode_config(config):
418 """
418 """
419 Updates pylons config with new settings from database
419 Updates pylons config with new settings from database
420
420
421 :param config:
421 :param config:
422 """
422 """
423 from rhodecode.model.settings import SettingsModel
423 from rhodecode.model.settings import SettingsModel
424 app_settings = SettingsModel().get_all_settings()
424 app_settings = SettingsModel().get_all_settings()
425
425
426 for k, v in app_settings.items():
426 for k, v in app_settings.items():
427 config[k] = v
427 config[k] = v
428
428
429
429
430 def get_rhodecode_realm():
430 def get_rhodecode_realm():
431 """
431 """
432 Return the rhodecode realm from database.
432 Return the rhodecode realm from database.
433 """
433 """
434 from rhodecode.model.settings import SettingsModel
434 from rhodecode.model.settings import SettingsModel
435 realm = SettingsModel().get_setting_by_name('realm')
435 realm = SettingsModel().get_setting_by_name('realm')
436 return safe_str(realm.app_settings_value)
436 return safe_str(realm.app_settings_value)
437
437
438
438
439 def get_rhodecode_base_path():
439 def get_rhodecode_base_path():
440 """
440 """
441 Returns the base path. The base path is the filesystem path which points
441 Returns the base path. The base path is the filesystem path which points
442 to the repository store.
442 to the repository store.
443 """
443 """
444 from rhodecode.model.settings import SettingsModel
444 from rhodecode.model.settings import SettingsModel
445 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
445 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
446 return safe_str(paths_ui.ui_value)
446 return safe_str(paths_ui.ui_value)
447
447
448
448
449 def map_groups(path):
449 def map_groups(path):
450 """
450 """
451 Given a full path to a repository, create all nested groups that this
451 Given a full path to a repository, create all nested groups that this
452 repo is inside. This function creates parent-child relationships between
452 repo is inside. This function creates parent-child relationships between
453 groups and creates default perms for all new groups.
453 groups and creates default perms for all new groups.
454
454
455 :param paths: full path to repository
455 :param paths: full path to repository
456 """
456 """
457 from rhodecode.model.repo_group import RepoGroupModel
457 from rhodecode.model.repo_group import RepoGroupModel
458 sa = meta.Session()
458 sa = meta.Session()
459 groups = path.split(Repository.NAME_SEP)
459 groups = path.split(Repository.NAME_SEP)
460 parent = None
460 parent = None
461 group = None
461 group = None
462
462
463 # last element is repo in nested groups structure
463 # last element is repo in nested groups structure
464 groups = groups[:-1]
464 groups = groups[:-1]
465 rgm = RepoGroupModel(sa)
465 rgm = RepoGroupModel(sa)
466 owner = User.get_first_super_admin()
466 owner = User.get_first_super_admin()
467 for lvl, group_name in enumerate(groups):
467 for lvl, group_name in enumerate(groups):
468 group_name = '/'.join(groups[:lvl] + [group_name])
468 group_name = '/'.join(groups[:lvl] + [group_name])
469 group = RepoGroup.get_by_group_name(group_name)
469 group = RepoGroup.get_by_group_name(group_name)
470 desc = '%s group' % group_name
470 desc = '%s group' % group_name
471
471
472 # skip folders that are now removed repos
472 # skip folders that are now removed repos
473 if REMOVED_REPO_PAT.match(group_name):
473 if REMOVED_REPO_PAT.match(group_name):
474 break
474 break
475
475
476 if group is None:
476 if group is None:
477 log.debug('creating group level: %s group_name: %s',
477 log.debug('creating group level: %s group_name: %s',
478 lvl, group_name)
478 lvl, group_name)
479 group = RepoGroup(group_name, parent)
479 group = RepoGroup(group_name, parent)
480 group.group_description = desc
480 group.group_description = desc
481 group.user = owner
481 group.user = owner
482 sa.add(group)
482 sa.add(group)
483 perm_obj = rgm._create_default_perms(group)
483 perm_obj = rgm._create_default_perms(group)
484 sa.add(perm_obj)
484 sa.add(perm_obj)
485 sa.flush()
485 sa.flush()
486
486
487 parent = group
487 parent = group
488 return group
488 return group
489
489
490
490
491 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
491 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
492 """
492 """
493 maps all repos given in initial_repo_list, non existing repositories
493 maps all repos given in initial_repo_list, non existing repositories
494 are created, if remove_obsolete is True it also checks for db entries
494 are created, if remove_obsolete is True it also checks for db entries
495 that are not in initial_repo_list and removes them.
495 that are not in initial_repo_list and removes them.
496
496
497 :param initial_repo_list: list of repositories found by scanning methods
497 :param initial_repo_list: list of repositories found by scanning methods
498 :param remove_obsolete: check for obsolete entries in database
498 :param remove_obsolete: check for obsolete entries in database
499 """
499 """
500 from rhodecode.model.repo import RepoModel
500 from rhodecode.model.repo import RepoModel
501 from rhodecode.model.scm import ScmModel
501 from rhodecode.model.scm import ScmModel
502 from rhodecode.model.repo_group import RepoGroupModel
502 from rhodecode.model.repo_group import RepoGroupModel
503 from rhodecode.model.settings import SettingsModel
503 from rhodecode.model.settings import SettingsModel
504
504
505 sa = meta.Session()
505 sa = meta.Session()
506 repo_model = RepoModel()
506 repo_model = RepoModel()
507 user = User.get_first_super_admin()
507 user = User.get_first_super_admin()
508 added = []
508 added = []
509
509
510 # creation defaults
510 # creation defaults
511 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
511 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
512 enable_statistics = defs.get('repo_enable_statistics')
512 enable_statistics = defs.get('repo_enable_statistics')
513 enable_locking = defs.get('repo_enable_locking')
513 enable_locking = defs.get('repo_enable_locking')
514 enable_downloads = defs.get('repo_enable_downloads')
514 enable_downloads = defs.get('repo_enable_downloads')
515 private = defs.get('repo_private')
515 private = defs.get('repo_private')
516
516
517 for name, repo in initial_repo_list.items():
517 for name, repo in initial_repo_list.items():
518 group = map_groups(name)
518 group = map_groups(name)
519 unicode_name = safe_unicode(name)
519 unicode_name = safe_unicode(name)
520 db_repo = repo_model.get_by_repo_name(unicode_name)
520 db_repo = repo_model.get_by_repo_name(unicode_name)
521 # found repo that is on filesystem not in RhodeCode database
521 # found repo that is on filesystem not in RhodeCode database
522 if not db_repo:
522 if not db_repo:
523 log.info('repository %s not found, creating now', name)
523 log.info('repository %s not found, creating now', name)
524 added.append(name)
524 added.append(name)
525 desc = (repo.description
525 desc = (repo.description
526 if repo.description != 'unknown'
526 if repo.description != 'unknown'
527 else '%s repository' % name)
527 else '%s repository' % name)
528
528
529 db_repo = repo_model._create_repo(
529 db_repo = repo_model._create_repo(
530 repo_name=name,
530 repo_name=name,
531 repo_type=repo.alias,
531 repo_type=repo.alias,
532 description=desc,
532 description=desc,
533 repo_group=getattr(group, 'group_id', None),
533 repo_group=getattr(group, 'group_id', None),
534 owner=user,
534 owner=user,
535 enable_locking=enable_locking,
535 enable_locking=enable_locking,
536 enable_downloads=enable_downloads,
536 enable_downloads=enable_downloads,
537 enable_statistics=enable_statistics,
537 enable_statistics=enable_statistics,
538 private=private,
538 private=private,
539 state=Repository.STATE_CREATED
539 state=Repository.STATE_CREATED
540 )
540 )
541 sa.commit()
541 sa.commit()
542 # we added that repo just now, and make sure we updated server info
542 # we added that repo just now, and make sure we updated server info
543 if db_repo.repo_type == 'git':
543 if db_repo.repo_type == 'git':
544 git_repo = db_repo.scm_instance()
544 git_repo = db_repo.scm_instance()
545 # update repository server-info
545 # update repository server-info
546 log.debug('Running update server info')
546 log.debug('Running update server info')
547 git_repo._update_server_info()
547 git_repo._update_server_info()
548
548
549 db_repo.update_commit_cache()
549 db_repo.update_commit_cache()
550
550
551 config = db_repo._config
551 config = db_repo._config
552 config.set('extensions', 'largefiles', '')
552 config.set('extensions', 'largefiles', '')
553 ScmModel().install_hooks(
553 ScmModel().install_hooks(
554 db_repo.scm_instance(config=config),
554 db_repo.scm_instance(config=config),
555 repo_type=db_repo.repo_type)
555 repo_type=db_repo.repo_type)
556
556
557 removed = []
557 removed = []
558 if remove_obsolete:
558 if remove_obsolete:
559 # remove from database those repositories that are not in the filesystem
559 # remove from database those repositories that are not in the filesystem
560 for repo in sa.query(Repository).all():
560 for repo in sa.query(Repository).all():
561 if repo.repo_name not in initial_repo_list.keys():
561 if repo.repo_name not in initial_repo_list.keys():
562 log.debug("Removing non-existing repository found in db `%s`",
562 log.debug("Removing non-existing repository found in db `%s`",
563 repo.repo_name)
563 repo.repo_name)
564 try:
564 try:
565 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
565 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
566 sa.commit()
566 sa.commit()
567 removed.append(repo.repo_name)
567 removed.append(repo.repo_name)
568 except Exception:
568 except Exception:
569 # don't hold further removals on error
569 # don't hold further removals on error
570 log.error(traceback.format_exc())
570 log.error(traceback.format_exc())
571 sa.rollback()
571 sa.rollback()
572
572
573 def splitter(full_repo_name):
573 def splitter(full_repo_name):
574 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
574 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
575 gr_name = None
575 gr_name = None
576 if len(_parts) == 2:
576 if len(_parts) == 2:
577 gr_name = _parts[0]
577 gr_name = _parts[0]
578 return gr_name
578 return gr_name
579
579
580 initial_repo_group_list = [splitter(x) for x in
580 initial_repo_group_list = [splitter(x) for x in
581 initial_repo_list.keys() if splitter(x)]
581 initial_repo_list.keys() if splitter(x)]
582
582
583 # remove from database those repository groups that are not in the
583 # remove from database those repository groups that are not in the
584 # filesystem due to parent child relationships we need to delete them
584 # filesystem due to parent child relationships we need to delete them
585 # in a specific order of most nested first
585 # in a specific order of most nested first
586 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
586 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
587 nested_sort = lambda gr: len(gr.split('/'))
587 nested_sort = lambda gr: len(gr.split('/'))
588 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
588 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
589 if group_name not in initial_repo_group_list:
589 if group_name not in initial_repo_group_list:
590 repo_group = RepoGroup.get_by_group_name(group_name)
590 repo_group = RepoGroup.get_by_group_name(group_name)
591 if (repo_group.children.all() or
591 if (repo_group.children.all() or
592 not RepoGroupModel().check_exist_filesystem(
592 not RepoGroupModel().check_exist_filesystem(
593 group_name=group_name, exc_on_failure=False)):
593 group_name=group_name, exc_on_failure=False)):
594 continue
594 continue
595
595
596 log.info(
596 log.info(
597 'Removing non-existing repository group found in db `%s`',
597 'Removing non-existing repository group found in db `%s`',
598 group_name)
598 group_name)
599 try:
599 try:
600 RepoGroupModel(sa).delete(group_name, fs_remove=False)
600 RepoGroupModel(sa).delete(group_name, fs_remove=False)
601 sa.commit()
601 sa.commit()
602 removed.append(group_name)
602 removed.append(group_name)
603 except Exception:
603 except Exception:
604 # don't hold further removals on error
604 # don't hold further removals on error
605 log.exception(
605 log.exception(
606 'Unable to remove repository group `%s`',
606 'Unable to remove repository group `%s`',
607 group_name)
607 group_name)
608 sa.rollback()
608 sa.rollback()
609 raise
609 raise
610
610
611 return added, removed
611 return added, removed
612
612
613
613
614 def get_default_cache_settings(settings):
614 def get_default_cache_settings(settings):
615 cache_settings = {}
615 cache_settings = {}
616 for key in settings.keys():
616 for key in settings.keys():
617 for prefix in ['beaker.cache.', 'cache.']:
617 for prefix in ['beaker.cache.', 'cache.']:
618 if key.startswith(prefix):
618 if key.startswith(prefix):
619 name = key.split(prefix)[1].strip()
619 name = key.split(prefix)[1].strip()
620 cache_settings[name] = settings[key].strip()
620 cache_settings[name] = settings[key].strip()
621 return cache_settings
621 return cache_settings
622
622
623
623
624 # set cache regions for beaker so celery can utilise it
624 # set cache regions for beaker so celery can utilise it
625 def add_cache(settings):
625 def add_cache(settings):
626 from rhodecode.lib import caches
626 from rhodecode.lib import caches
627 cache_settings = {'regions': None}
627 cache_settings = {'regions': None}
628 # main cache settings used as default ...
628 # main cache settings used as default ...
629 cache_settings.update(get_default_cache_settings(settings))
629 cache_settings.update(get_default_cache_settings(settings))
630
630
631 if cache_settings['regions']:
631 if cache_settings['regions']:
632 for region in cache_settings['regions'].split(','):
632 for region in cache_settings['regions'].split(','):
633 region = region.strip()
633 region = region.strip()
634 region_settings = {}
634 region_settings = {}
635 for key, value in cache_settings.items():
635 for key, value in cache_settings.items():
636 if key.startswith(region):
636 if key.startswith(region):
637 region_settings[key.split('.')[1]] = value
637 region_settings[key.split('.')[1]] = value
638
638
639 caches.configure_cache_region(
639 caches.configure_cache_region(
640 region, region_settings, cache_settings)
640 region, region_settings, cache_settings)
641
641
642
642
643 def load_rcextensions(root_path):
643 def load_rcextensions(root_path):
644 import rhodecode
644 import rhodecode
645 from rhodecode.config import conf
645 from rhodecode.config import conf
646
646
647 path = os.path.join(root_path, 'rcextensions', '__init__.py')
647 path = os.path.join(root_path, 'rcextensions', '__init__.py')
648 if os.path.isfile(path):
648 if os.path.isfile(path):
649 rcext = create_module('rc', path)
649 rcext = create_module('rc', path)
650 EXT = rhodecode.EXTENSIONS = rcext
650 EXT = rhodecode.EXTENSIONS = rcext
651 log.debug('Found rcextensions now loading %s...', rcext)
651 log.debug('Found rcextensions now loading %s...', rcext)
652
652
653 # Additional mappings that are not present in the pygments lexers
653 # Additional mappings that are not present in the pygments lexers
654 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
654 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
655
655
656 # auto check if the module is not missing any data, set to default if is
656 # auto check if the module is not missing any data, set to default if is
657 # this will help autoupdate new feature of rcext module
657 # this will help autoupdate new feature of rcext module
658 #from rhodecode.config import rcextensions
658 #from rhodecode.config import rcextensions
659 #for k in dir(rcextensions):
659 #for k in dir(rcextensions):
660 # if not k.startswith('_') and not hasattr(EXT, k):
660 # if not k.startswith('_') and not hasattr(EXT, k):
661 # setattr(EXT, k, getattr(rcextensions, k))
661 # setattr(EXT, k, getattr(rcextensions, k))
662
662
663
663
664 def get_custom_lexer(extension):
664 def get_custom_lexer(extension):
665 """
665 """
666 returns a custom lexer if it is defined in rcextensions module, or None
666 returns a custom lexer if it is defined in rcextensions module, or None
667 if there's no custom lexer defined
667 if there's no custom lexer defined
668 """
668 """
669 import rhodecode
669 import rhodecode
670 from pygments import lexers
670 from pygments import lexers
671
671
672 # custom override made by RhodeCode
672 # custom override made by RhodeCode
673 if extension in ['mako']:
673 if extension in ['mako']:
674 return lexers.get_lexer_by_name('html+mako')
674 return lexers.get_lexer_by_name('html+mako')
675
675
676 # check if we didn't define this extension as other lexer
676 # check if we didn't define this extension as other lexer
677 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
677 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
678 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
678 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
679 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
679 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
680 return lexers.get_lexer_by_name(_lexer_name)
680 return lexers.get_lexer_by_name(_lexer_name)
681
681
682
682
683 #==============================================================================
683 #==============================================================================
684 # TEST FUNCTIONS AND CREATORS
684 # TEST FUNCTIONS AND CREATORS
685 #==============================================================================
685 #==============================================================================
686 def create_test_index(repo_location, config):
686 def create_test_index(repo_location, config):
687 """
687 """
688 Makes default test index.
688 Makes default test index.
689 """
689 """
690 import rc_testdata
690 import rc_testdata
691
691
692 rc_testdata.extract_search_index(
692 rc_testdata.extract_search_index(
693 'vcs_search_index', os.path.dirname(config['search.location']))
693 'vcs_search_index', os.path.dirname(config['search.location']))
694
694
695
695
696 def create_test_directory(test_path):
696 def create_test_directory(test_path):
697 """
697 """
698 Create test directory if it doesn't exist.
698 Create test directory if it doesn't exist.
699 """
699 """
700 if not os.path.isdir(test_path):
700 if not os.path.isdir(test_path):
701 log.debug('Creating testdir %s', test_path)
701 log.debug('Creating testdir %s', test_path)
702 os.makedirs(test_path)
702 os.makedirs(test_path)
703
703
704
704
705 def create_test_database(test_path, config):
705 def create_test_database(test_path, config):
706 """
706 """
707 Makes a fresh database.
707 Makes a fresh database.
708 """
708 """
709 from rhodecode.lib.db_manage import DbManage
709 from rhodecode.lib.db_manage import DbManage
710
710
711 # PART ONE create db
711 # PART ONE create db
712 dbconf = config['sqlalchemy.db1.url']
712 dbconf = config['sqlalchemy.db1.url']
713 log.debug('making test db %s', dbconf)
713 log.debug('making test db %s', dbconf)
714
714
715 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
715 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
716 tests=True, cli_args={'force_ask': True})
716 tests=True, cli_args={'force_ask': True})
717 dbmanage.create_tables(override=True)
717 dbmanage.create_tables(override=True)
718 dbmanage.set_db_version()
718 dbmanage.set_db_version()
719 # for tests dynamically set new root paths based on generated content
719 # for tests dynamically set new root paths based on generated content
720 dbmanage.create_settings(dbmanage.config_prompt(test_path))
720 dbmanage.create_settings(dbmanage.config_prompt(test_path))
721 dbmanage.create_default_user()
721 dbmanage.create_default_user()
722 dbmanage.create_test_admin_and_users()
722 dbmanage.create_test_admin_and_users()
723 dbmanage.create_permissions()
723 dbmanage.create_permissions()
724 dbmanage.populate_default_permissions()
724 dbmanage.populate_default_permissions()
725 Session().commit()
725 Session().commit()
726
726
727
727
728 def create_test_repositories(test_path, config):
728 def create_test_repositories(test_path, config):
729 """
729 """
730 Creates test repositories in the temporary directory. Repositories are
730 Creates test repositories in the temporary directory. Repositories are
731 extracted from archives within the rc_testdata package.
731 extracted from archives within the rc_testdata package.
732 """
732 """
733 import rc_testdata
733 import rc_testdata
734 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
734 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
735
735
736 log.debug('making test vcs repositories')
736 log.debug('making test vcs repositories')
737
737
738 idx_path = config['search.location']
738 idx_path = config['search.location']
739 data_path = config['cache_dir']
739 data_path = config['cache_dir']
740
740
741 # clean index and data
741 # clean index and data
742 if idx_path and os.path.exists(idx_path):
742 if idx_path and os.path.exists(idx_path):
743 log.debug('remove %s', idx_path)
743 log.debug('remove %s', idx_path)
744 shutil.rmtree(idx_path)
744 shutil.rmtree(idx_path)
745
745
746 if data_path and os.path.exists(data_path):
746 if data_path and os.path.exists(data_path):
747 log.debug('remove %s', data_path)
747 log.debug('remove %s', data_path)
748 shutil.rmtree(data_path)
748 shutil.rmtree(data_path)
749
749
750 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
750 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
751 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
751 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
752
752
753 # Note: Subversion is in the process of being integrated with the system,
753 # Note: Subversion is in the process of being integrated with the system,
754 # until we have a properly packed version of the test svn repository, this
754 # until we have a properly packed version of the test svn repository, this
755 # tries to copy over the repo from a package "rc_testdata"
755 # tries to copy over the repo from a package "rc_testdata"
756 svn_repo_path = rc_testdata.get_svn_repo_archive()
756 svn_repo_path = rc_testdata.get_svn_repo_archive()
757 with tarfile.open(svn_repo_path) as tar:
757 with tarfile.open(svn_repo_path) as tar:
758 tar.extractall(jn(test_path, SVN_REPO))
758 tar.extractall(jn(test_path, SVN_REPO))
759
759
760
760
761 #==============================================================================
761 #==============================================================================
762 # PASTER COMMANDS
762 # PASTER COMMANDS
763 #==============================================================================
763 #==============================================================================
764 class BasePasterCommand(Command):
764 class BasePasterCommand(Command):
765 """
765 """
766 Abstract Base Class for paster commands.
766 Abstract Base Class for paster commands.
767
767
768 The celery commands are somewhat aggressive about loading
768 The celery commands are somewhat aggressive about loading
769 celery.conf, and since our module sets the `CELERY_LOADER`
769 celery.conf, and since our module sets the `CELERY_LOADER`
770 environment variable to our loader, we have to bootstrap a bit and
770 environment variable to our loader, we have to bootstrap a bit and
771 make sure we've had a chance to load the pylons config off of the
771 make sure we've had a chance to load the pylons config off of the
772 command line, otherwise everything fails.
772 command line, otherwise everything fails.
773 """
773 """
774 min_args = 1
774 min_args = 1
775 min_args_error = "Please provide a paster config file as an argument."
775 min_args_error = "Please provide a paster config file as an argument."
776 takes_config_file = 1
776 takes_config_file = 1
777 requires_config_file = True
777 requires_config_file = True
778
778
779 def notify_msg(self, msg, log=False):
779 def notify_msg(self, msg, log=False):
780 """Make a notification to user, additionally if logger is passed
780 """Make a notification to user, additionally if logger is passed
781 it logs this action using given logger
781 it logs this action using given logger
782
782
783 :param msg: message that will be printed to user
783 :param msg: message that will be printed to user
784 :param log: logging instance, to use to additionally log this message
784 :param log: logging instance, to use to additionally log this message
785
785
786 """
786 """
787 if log and isinstance(log, logging):
787 if log and isinstance(log, logging):
788 log(msg)
788 log(msg)
789
789
790 def run(self, args):
790 def run(self, args):
791 """
791 """
792 Overrides Command.run
792 Overrides Command.run
793
793
794 Checks for a config file argument and loads it.
794 Checks for a config file argument and loads it.
795 """
795 """
796 if len(args) < self.min_args:
796 if len(args) < self.min_args:
797 raise BadCommand(
797 raise BadCommand(
798 self.min_args_error % {'min_args': self.min_args,
798 self.min_args_error % {'min_args': self.min_args,
799 'actual_args': len(args)})
799 'actual_args': len(args)})
800
800
801 # Decrement because we're going to lob off the first argument.
801 # Decrement because we're going to lob off the first argument.
802 # @@ This is hacky
802 # @@ This is hacky
803 self.min_args -= 1
803 self.min_args -= 1
804 self.bootstrap_config(args[0])
804 self.bootstrap_config(args[0])
805 self.update_parser()
805 self.update_parser()
806 return super(BasePasterCommand, self).run(args[1:])
806 return super(BasePasterCommand, self).run(args[1:])
807
807
808 def update_parser(self):
808 def update_parser(self):
809 """
809 """
810 Abstract method. Allows for the class' parser to be updated
810 Abstract method. Allows for the class' parser to be updated
811 before the superclass' `run` method is called. Necessary to
811 before the superclass' `run` method is called. Necessary to
812 allow options/arguments to be passed through to the underlying
812 allow options/arguments to be passed through to the underlying
813 celery command.
813 celery command.
814 """
814 """
815 raise NotImplementedError("Abstract Method.")
815 raise NotImplementedError("Abstract Method.")
816
816
817 def bootstrap_config(self, conf):
817 def bootstrap_config(self, conf):
818 """
818 """
819 Loads the pylons configuration.
819 Loads the pylons configuration.
820 """
820 """
821 from pylons import config as pylonsconfig
821 from pylons import config as pylonsconfig
822
822
823 self.path_to_ini_file = os.path.realpath(conf)
823 self.path_to_ini_file = os.path.realpath(conf)
824 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
824 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
825 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
825 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
826
826
827 def _init_session(self):
827 def _init_session(self):
828 """
828 """
829 Inits SqlAlchemy Session
829 Inits SqlAlchemy Session
830 """
830 """
831 logging.config.fileConfig(self.path_to_ini_file)
831 logging.config.fileConfig(self.path_to_ini_file)
832 from pylons import config
832 from pylons import config
833 from rhodecode.config.utils import initialize_database
833 from rhodecode.config.utils import initialize_database
834
834
835 # get to remove repos !!
835 # get to remove repos !!
836 add_cache(config)
836 add_cache(config)
837 initialize_database(config)
837 initialize_database(config)
838
838
839
839
840 @decorator.decorator
840 @decorator.decorator
841 def jsonify(func, *args, **kwargs):
841 def jsonify(func, *args, **kwargs):
842 """Action decorator that formats output for JSON
842 """Action decorator that formats output for JSON
843
843
844 Given a function that will return content, this decorator will turn
844 Given a function that will return content, this decorator will turn
845 the result into JSON, with a content-type of 'application/json' and
845 the result into JSON, with a content-type of 'application/json' and
846 output it.
846 output it.
847
847
848 """
848 """
849 from pylons.decorators.util import get_pylons
849 from pylons.decorators.util import get_pylons
850 from rhodecode.lib.ext_json import json
850 from rhodecode.lib.ext_json import json
851 pylons = get_pylons(args)
851 pylons = get_pylons(args)
852 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
852 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
853 data = func(*args, **kwargs)
853 data = func(*args, **kwargs)
854 if isinstance(data, (list, tuple)):
854 if isinstance(data, (list, tuple)):
855 msg = "JSON responses with Array envelopes are susceptible to " \
855 msg = "JSON responses with Array envelopes are susceptible to " \
856 "cross-site data leak attacks, see " \
856 "cross-site data leak attacks, see " \
857 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
857 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
858 warnings.warn(msg, Warning, 2)
858 warnings.warn(msg, Warning, 2)
859 log.warning(msg)
859 log.warning(msg)
860 log.debug("Returning JSON wrapped action output")
860 log.debug("Returning JSON wrapped action output")
861 return json.dumps(data, encoding='utf-8')
861 return json.dumps(data, encoding='utf-8')
862
862
863
863
864 class PartialRenderer(object):
864 class PartialRenderer(object):
865 """
865 """
866 Partial renderer used to render chunks of html used in datagrids
866 Partial renderer used to render chunks of html used in datagrids
867 use like::
867 use like::
868
868
869 _render = PartialRenderer('data_table/_dt_elements.mako')
869 _render = PartialRenderer('data_table/_dt_elements.mako')
870 _render('quick_menu', args, kwargs)
870 _render('quick_menu', args, kwargs)
871 PartialRenderer.h,
871 PartialRenderer.h,
872 c,
872 c,
873 _,
873 _,
874 ungettext
874 ungettext
875 are the template stuff initialized inside and can be re-used later
875 are the template stuff initialized inside and can be re-used later
876
876
877 :param tmpl_name: template path relate to /templates/ dir
877 :param tmpl_name: template path relate to /templates/ dir
878 """
878 """
879
879
880 def __init__(self, tmpl_name):
880 def __init__(self, tmpl_name):
881 import rhodecode
881 import rhodecode
882 from pylons import request, tmpl_context as c
882 from pylons import request, tmpl_context as c
883 from pylons.i18n.translation import _, ungettext
883 from pylons.i18n.translation import _, ungettext
884 from rhodecode.lib import helpers as h
884 from rhodecode.lib import helpers as h
885
885
886 self.tmpl_name = tmpl_name
886 self.tmpl_name = tmpl_name
887 self.rhodecode = rhodecode
887 self.rhodecode = rhodecode
888 self.c = c
888 self.c = c
889 self._ = _
889 self._ = _
890 self.ungettext = ungettext
890 self.ungettext = ungettext
891 self.h = h
891 self.h = h
892 self.request = request
892 self.request = request
893
893
894 def _mako_lookup(self):
894 def _mako_lookup(self):
895 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
895 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
896 return _tmpl_lookup.get_template(self.tmpl_name)
896 return _tmpl_lookup.get_template(self.tmpl_name)
897
897
898 def _update_kwargs_for_render(self, kwargs):
898 def _update_kwargs_for_render(self, kwargs):
899 """
899 """
900 Inject params required for Mako rendering
900 Inject params required for Mako rendering
901 """
901 """
902 _kwargs = {
902 _kwargs = {
903 '_': self._,
903 '_': self._,
904 'h': self.h,
904 'h': self.h,
905 'c': self.c,
905 'c': self.c,
906 'request': self.request,
906 'request': self.request,
907 'ungettext': self.ungettext,
907 '_ungettext': self.ungettext,
908 }
908 }
909 _kwargs.update(kwargs)
909 _kwargs.update(kwargs)
910 return _kwargs
910 return _kwargs
911
911
912 def _render_with_exc(self, render_func, args, kwargs):
912 def _render_with_exc(self, render_func, args, kwargs):
913 try:
913 try:
914 return render_func.render(*args, **kwargs)
914 return render_func.render(*args, **kwargs)
915 except:
915 except:
916 log.error(exceptions.text_error_template().render())
916 log.error(exceptions.text_error_template().render())
917 raise
917 raise
918
918
919 def _get_template(self, template_obj, def_name):
919 def _get_template(self, template_obj, def_name):
920 if def_name:
920 if def_name:
921 tmpl = template_obj.get_def(def_name)
921 tmpl = template_obj.get_def(def_name)
922 else:
922 else:
923 tmpl = template_obj
923 tmpl = template_obj
924 return tmpl
924 return tmpl
925
925
926 def render(self, def_name, *args, **kwargs):
926 def render(self, def_name, *args, **kwargs):
927 lookup_obj = self._mako_lookup()
927 lookup_obj = self._mako_lookup()
928 tmpl = self._get_template(lookup_obj, def_name=def_name)
928 tmpl = self._get_template(lookup_obj, def_name=def_name)
929 kwargs = self._update_kwargs_for_render(kwargs)
929 kwargs = self._update_kwargs_for_render(kwargs)
930 return self._render_with_exc(tmpl, args, kwargs)
930 return self._render_with_exc(tmpl, args, kwargs)
931
931
932 def __call__(self, tmpl, *args, **kwargs):
932 def __call__(self, tmpl, *args, **kwargs):
933 return self.render(tmpl, *args, **kwargs)
933 return self.render(tmpl, *args, **kwargs)
934
934
935
935
936 def password_changed(auth_user, session):
936 def password_changed(auth_user, session):
937 # Never report password change in case of default user or anonymous user.
937 # Never report password change in case of default user or anonymous user.
938 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
938 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
939 return False
939 return False
940
940
941 password_hash = md5(auth_user.password) if auth_user.password else None
941 password_hash = md5(auth_user.password) if auth_user.password else None
942 rhodecode_user = session.get('rhodecode_user', {})
942 rhodecode_user = session.get('rhodecode_user', {})
943 session_password_hash = rhodecode_user.get('password', '')
943 session_password_hash = rhodecode_user.get('password', '')
944 return password_hash != session_password_hash
944 return password_hash != session_password_hash
945
945
946
946
947 def read_opensource_licenses():
947 def read_opensource_licenses():
948 global _license_cache
948 global _license_cache
949
949
950 if not _license_cache:
950 if not _license_cache:
951 licenses = pkg_resources.resource_string(
951 licenses = pkg_resources.resource_string(
952 'rhodecode', 'config/licenses.json')
952 'rhodecode', 'config/licenses.json')
953 _license_cache = json.loads(licenses)
953 _license_cache = json.loads(licenses)
954
954
955 return _license_cache
955 return _license_cache
956
956
957
957
958 def get_registry(request):
958 def get_registry(request):
959 """
959 """
960 Utility to get the pyramid registry from a request. During migration to
960 Utility to get the pyramid registry from a request. During migration to
961 pyramid we sometimes want to use the pyramid registry from pylons context.
961 pyramid we sometimes want to use the pyramid registry from pylons context.
962 Therefore this utility returns `request.registry` for pyramid requests and
962 Therefore this utility returns `request.registry` for pyramid requests and
963 uses `get_current_registry()` for pylons requests.
963 uses `get_current_registry()` for pylons requests.
964 """
964 """
965 try:
965 try:
966 return request.registry
966 return request.registry
967 except AttributeError:
967 except AttributeError:
968 return get_current_registry()
968 return get_current_registry()
969
969
970
970
971 def generate_platform_uuid():
971 def generate_platform_uuid():
972 """
972 """
973 Generates platform UUID based on it's name
973 Generates platform UUID based on it's name
974 """
974 """
975 import platform
975 import platform
976
976
977 try:
977 try:
978 uuid_list = [platform.platform()]
978 uuid_list = [platform.platform()]
979 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
979 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
980 except Exception as e:
980 except Exception as e:
981 log.error('Failed to generate host uuid: %s' % e)
981 log.error('Failed to generate host uuid: %s' % e)
982 return 'UNDEFINED'
982 return 'UNDEFINED'
@@ -1,64 +1,64 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_group.user.email), '', ''),
5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_group.user.email), '', ''),
6 (_('Created on'), h.format_date(c.repo_group.created_on), '', ''),
6 (_('Created on'), h.format_date(c.repo_group.created_on), '', ''),
7 (_('Is Personal Group'), c.repo_group.personal or False, '', ''),
7 (_('Is Personal Group'), c.repo_group.personal or False, '', ''),
8
8
9 (_('Total repositories'), c.repo_group.repositories_recursive_count, '', ''),
9 (_('Total repositories'), c.repo_group.repositories_recursive_count, '', ''),
10 (_('Top level repositories'), c.repo_group.repositories.count(), '', c.repo_group.repositories.all()),
10 (_('Top level repositories'), c.repo_group.repositories.count(), '', c.repo_group.repositories.all()),
11
11
12 (_('Children groups'), c.repo_group.children.count(), '', c.repo_group.children.all()),
12 (_('Children groups'), c.repo_group.children.count(), '', c.repo_group.children.all()),
13 ]
13 ]
14 %>
14 %>
15
15
16 <div class="panel panel-default">
16 <div class="panel panel-default">
17 <div class="panel-heading">
17 <div class="panel-heading">
18 <h3 class="panel-title">${_('Repository Group: %s') % c.repo_group.group_name}</h3>
18 <h3 class="panel-title">${_('Repository Group: %s') % c.repo_group.group_name}</h3>
19 </div>
19 </div>
20 <div class="panel-body">
20 <div class="panel-body">
21 ${base.dt_info_panel(elems)}
21 ${base.dt_info_panel(elems)}
22 </div>
22 </div>
23
23
24 </div>
24 </div>
25
25
26 <div class="panel panel-danger">
26 <div class="panel panel-danger">
27 <div class="panel-heading">
27 <div class="panel-heading">
28 <h3 class="panel-title">${_('Delete repository group')}</h3>
28 <h3 class="panel-title">${_('Delete repository group')}</h3>
29 </div>
29 </div>
30 <div class="panel-body">
30 <div class="panel-body">
31 ${h.secure_form(h.url('delete_repo_group', group_name=c.repo_group.group_name),method='delete')}
31 ${h.secure_form(h.url('delete_repo_group', group_name=c.repo_group.group_name),method='delete')}
32 <table class="display">
32 <table class="display">
33
33
34 <tr>
34 <tr>
35 <td>
35 <td>
36 ${ungettext('This repository group includes %s children repository group.', 'This repository group includes %s children repository groups.', c.repo_group.children.count()) % c.repo_group.children.count()}
36 ${_ungettext('This repository group includes %s children repository group.', 'This repository group includes %s children repository groups.', c.repo_group.children.count()) % c.repo_group.children.count()}
37 </td>
37 </td>
38 <td>
38 <td>
39 </td>
39 </td>
40 <td>
40 <td>
41 </td>
41 </td>
42 </tr>
42 </tr>
43 <tr>
43 <tr>
44 <td>
44 <td>
45 ${ungettext('This repository group includes %s repository.', 'This repository group includes %s repositories.', c.repo_group.repositories_recursive_count) % c.repo_group.repositories_recursive_count}
45 ${_ungettext('This repository group includes %s repository.', 'This repository group includes %s repositories.', c.repo_group.repositories_recursive_count) % c.repo_group.repositories_recursive_count}
46 </td>
46 </td>
47 <td>
47 <td>
48 </td>
48 </td>
49 <td>
49 <td>
50 </td>
50 </td>
51 </tr>
51 </tr>
52
52
53 </table>
53 </table>
54 <div style="margin: 0 0 20px 0" class="fake-space"></div>
54 <div style="margin: 0 0 20px 0" class="fake-space"></div>
55
55
56 <button class="btn btn-small btn-danger" type="submit"
56 <button class="btn btn-small btn-danger" type="submit"
57 onclick="return confirm('${_('Confirm to delete this group: %s') % (c.repo_group.group_name)}');">
57 onclick="return confirm('${_('Confirm to delete this group: %s') % (c.repo_group.group_name)}');">
58 ${_('Delete this repository group')}
58 ${_('Delete this repository group')}
59 </button>
59 </button>
60 ${h.end_form()}
60 ${h.end_form()}
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64
64
@@ -1,159 +1,159 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Created on'), h.format_date(c.user.created_on), '', ''),
5 (_('Created on'), h.format_date(c.user.created_on), '', ''),
6 (_('Source of Record'), c.user.extern_type, '', ''),
6 (_('Source of Record'), c.user.extern_type, '', ''),
7
7
8 (_('Last login'), c.user.last_login or '-', '', ''),
8 (_('Last login'), c.user.last_login or '-', '', ''),
9 (_('Last activity'), c.user.last_activity, '', ''),
9 (_('Last activity'), c.user.last_activity, '', ''),
10
10
11 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
11 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
12 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
12 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
13 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
13 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
14
14
15 (_('Reviewer of pull requests'), len(c.user.reviewer_pull_requests), '', ['Pull Request #{}'.format(x.pull_request.pull_request_id) for x in c.user.reviewer_pull_requests]),
15 (_('Reviewer of pull requests'), len(c.user.reviewer_pull_requests), '', ['Pull Request #{}'.format(x.pull_request.pull_request_id) for x in c.user.reviewer_pull_requests]),
16 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
16 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
17 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
17 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
18 ]
18 ]
19 %>
19 %>
20
20
21 <div class="panel panel-default">
21 <div class="panel panel-default">
22 <div class="panel-heading">
22 <div class="panel-heading">
23 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
23 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
24 </div>
24 </div>
25 <div class="panel-body">
25 <div class="panel-body">
26 ${base.dt_info_panel(elems)}
26 ${base.dt_info_panel(elems)}
27 </div>
27 </div>
28 </div>
28 </div>
29
29
30 <div class="panel panel-default">
30 <div class="panel panel-default">
31 <div class="panel-heading">
31 <div class="panel-heading">
32 <h3 class="panel-title">${_('Force Password Reset')}</h3>
32 <h3 class="panel-title">${_('Force Password Reset')}</h3>
33 </div>
33 </div>
34 <div class="panel-body">
34 <div class="panel-body">
35 ${h.secure_form(h.url('force_password_reset_user', user_id=c.user.user_id), method='post')}
35 ${h.secure_form(h.url('force_password_reset_user', user_id=c.user.user_id), method='post')}
36 <div class="field">
36 <div class="field">
37 <button class="btn btn-default" type="submit">
37 <button class="btn btn-default" type="submit">
38 <i class="icon-lock"></i>
38 <i class="icon-lock"></i>
39 %if c.user.user_data.get('force_password_change'):
39 %if c.user.user_data.get('force_password_change'):
40 ${_('Disable forced password reset')}
40 ${_('Disable forced password reset')}
41 %else:
41 %else:
42 ${_('Enable forced password reset')}
42 ${_('Enable forced password reset')}
43 %endif
43 %endif
44 </button>
44 </button>
45 </div>
45 </div>
46 <div class="field">
46 <div class="field">
47 <span class="help-block">
47 <span class="help-block">
48 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
48 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
49 </span>
49 </span>
50 </div>
50 </div>
51 ${h.end_form()}
51 ${h.end_form()}
52 </div>
52 </div>
53 </div>
53 </div>
54
54
55 <div class="panel panel-default">
55 <div class="panel panel-default">
56 <div class="panel-heading">
56 <div class="panel-heading">
57 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
57 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
58 </div>
58 </div>
59 <div class="panel-body">
59 <div class="panel-body">
60 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
60 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
61
61
62 %if c.personal_repo_group:
62 %if c.personal_repo_group:
63 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
63 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
64 %else:
64 %else:
65 <div class="panel-body-title-text">
65 <div class="panel-body-title-text">
66 ${_('This user currently does not have a personal repository group')}
66 ${_('This user currently does not have a personal repository group')}
67 <br/>
67 <br/>
68 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
68 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
69 </div>
69 </div>
70 %endif
70 %endif
71 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
71 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
72 <i class="icon-folder-close"></i>
72 <i class="icon-folder-close"></i>
73 ${_('Create personal repository group')}
73 ${_('Create personal repository group')}
74 </button>
74 </button>
75 ${h.end_form()}
75 ${h.end_form()}
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79
79
80 <div class="panel panel-danger">
80 <div class="panel panel-danger">
81 <div class="panel-heading">
81 <div class="panel-heading">
82 <h3 class="panel-title">${_('Delete User')}</h3>
82 <h3 class="panel-title">${_('Delete User')}</h3>
83 </div>
83 </div>
84 <div class="panel-body">
84 <div class="panel-body">
85 ${h.secure_form(h.url('delete_user', user_id=c.user.user_id), method='delete')}
85 ${h.secure_form(h.url('delete_user', user_id=c.user.user_id), method='delete')}
86
86
87 <table class="display">
87 <table class="display">
88 <tr>
88 <tr>
89 <td>
89 <td>
90 ${ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
90 ${_ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
91 </td>
91 </td>
92 <td>
92 <td>
93 %if len(c.user.repositories) > 0:
93 %if len(c.user.repositories) > 0:
94 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked"/> <label for="user_repos_1">${_('Detach repositories')}</label>
94 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked"/> <label for="user_repos_1">${_('Detach repositories')}</label>
95 %endif
95 %endif
96 </td>
96 </td>
97 <td>
97 <td>
98 %if len(c.user.repositories) > 0:
98 %if len(c.user.repositories) > 0:
99 <input type="radio" id="user_repos_2" name="user_repos" value="delete" /> <label for="user_repos_2">${_('Delete repositories')}</label>
99 <input type="radio" id="user_repos_2" name="user_repos" value="delete" /> <label for="user_repos_2">${_('Delete repositories')}</label>
100 %endif
100 %endif
101 </td>
101 </td>
102 </tr>
102 </tr>
103
103
104 <tr>
104 <tr>
105 <td>
105 <td>
106 ${ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
106 ${_ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
107 </td>
107 </td>
108 <td>
108 <td>
109 %if len(c.user.repository_groups) > 0:
109 %if len(c.user.repository_groups) > 0:
110 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked"/> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
110 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked"/> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
111 %endif
111 %endif
112 </td>
112 </td>
113 <td>
113 <td>
114 %if len(c.user.repository_groups) > 0:
114 %if len(c.user.repository_groups) > 0:
115 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" /> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
115 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" /> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
116 %endif
116 %endif
117 </td>
117 </td>
118 </tr>
118 </tr>
119
119
120 <tr>
120 <tr>
121 <td>
121 <td>
122 ${ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
122 ${_ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
123 </td>
123 </td>
124 <td>
124 <td>
125 %if len(c.user.user_groups) > 0:
125 %if len(c.user.user_groups) > 0:
126 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked"/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
126 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked"/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
127 %endif
127 %endif
128 </td>
128 </td>
129 <td>
129 <td>
130 %if len(c.user.user_groups) > 0:
130 %if len(c.user.user_groups) > 0:
131 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" /> <label for="user_user_groups_2">${_('Delete repositories')}</label>
131 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" /> <label for="user_user_groups_2">${_('Delete repositories')}</label>
132 %endif
132 %endif
133 </td>
133 </td>
134 </tr>
134 </tr>
135 </table>
135 </table>
136 <div style="margin: 0 0 20px 0" class="fake-space"></div>
136 <div style="margin: 0 0 20px 0" class="fake-space"></div>
137
137
138 <div class="field">
138 <div class="field">
139 <button class="btn btn-small btn-danger" type="submit"
139 <button class="btn btn-small btn-danger" type="submit"
140 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
140 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
141 ${"disabled" if not c.can_delete_user else ""}>
141 ${"disabled" if not c.can_delete_user else ""}>
142 ${_('Delete this user')}
142 ${_('Delete this user')}
143 </button>
143 </button>
144 </div>
144 </div>
145 % if c.can_delete_user_message:
145 % if c.can_delete_user_message:
146 <p class="help-block pre-formatting">${c.can_delete_user_message}</p>
146 <p class="help-block pre-formatting">${c.can_delete_user_message}</p>
147 % endif
147 % endif
148
148
149 <div class="field">
149 <div class="field">
150 <span class="help-block">
150 <span class="help-block">
151 %if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
151 %if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
152 <p class="help-block">${_("When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!") % (c.first_admin.full_name)}</p>
152 <p class="help-block">${_("When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!") % (c.first_admin.full_name)}</p>
153 %endif
153 %endif
154 </span>
154 </span>
155 </div>
155 </div>
156
156
157 ${h.end_form()}
157 ${h.end_form()}
158 </div>
158 </div>
159 </div>
159 </div>
@@ -1,207 +1,207 b''
1 ## snippet for displaying permissions overview for users
1 ## snippet for displaying permissions overview for users
2 ## usage:
2 ## usage:
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 ## ${p.perms_summary(c.perm_user.permissions)}
4 ## ${p.perms_summary(c.perm_user.permissions)}
5
5
6 <%def name="perms_summary(permissions, show_all=False, actions=True)">
6 <%def name="perms_summary(permissions, show_all=False, actions=True)">
7 <div id="perms" class="table fields">
7 <div id="perms" class="table fields">
8 %for section in sorted(permissions.keys()):
8 %for section in sorted(permissions.keys()):
9 <div class="panel panel-default">
9 <div class="panel panel-default">
10 <div class="panel-heading">
10 <div class="panel-heading">
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 </div>
12 </div>
13 <div class="panel-body">
13 <div class="panel-body">
14 <div class="perms_section_head field">
14 <div class="perms_section_head field">
15 <div class="radios">
15 <div class="radios">
16 %if section != 'global':
16 %if section != 'global':
17 <span class="permissions_boxes">
17 <span class="permissions_boxes">
18 <span class="desc">${_('show')}: </span>
18 <span class="desc">${_('show')}: </span>
19 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
19 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
20 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
20 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
21 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
21 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
22 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
22 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
23 </span>
23 </span>
24 %endif
24 %endif
25 </div>
25 </div>
26 </div>
26 </div>
27 <div class="field">
27 <div class="field">
28 %if not permissions[section]:
28 %if not permissions[section]:
29 <p class="empty_data help-block">${_('No permissions defined')}</p>
29 <p class="empty_data help-block">${_('No permissions defined')}</p>
30 %else:
30 %else:
31 <div id='tbl_list_wrap_${section}'>
31 <div id='tbl_list_wrap_${section}'>
32 <table id="tbl_list_${section}" class="rctable">
32 <table id="tbl_list_${section}" class="rctable">
33 ## global permission box
33 ## global permission box
34 %if section == 'global':
34 %if section == 'global':
35 <thead>
35 <thead>
36 <tr>
36 <tr>
37 <th colspan="2" class="left">${_('Permission')}</th>
37 <th colspan="2" class="left">${_('Permission')}</th>
38 %if actions:
38 %if actions:
39 <th>${_('Edit Permission')}</th>
39 <th>${_('Edit Permission')}</th>
40 %endif
40 %endif
41 </thead>
41 </thead>
42 <tbody>
42 <tbody>
43
43
44 <%
44 <%
45 def get_section_perms(prefix, opts):
45 def get_section_perms(prefix, opts):
46 _selected = []
46 _selected = []
47 for op in opts:
47 for op in opts:
48 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
48 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
49 _selected.append(op)
49 _selected.append(op)
50 admin = 'hg.admin' in opts
50 admin = 'hg.admin' in opts
51 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
51 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
52 return admin, _selected_vals, _selected
52 return admin, _selected_vals, _selected
53 %>
53 %>
54 <%def name="glob(lbl, val, val_lbl=None, custom_url=None)">
54 <%def name="glob(lbl, val, val_lbl=None, custom_url=None)">
55 <tr>
55 <tr>
56 <td class="td-tags">
56 <td class="td-tags">
57 ${lbl}
57 ${lbl}
58 </td>
58 </td>
59 <td class="td-tags">
59 <td class="td-tags">
60 %if val[0]:
60 %if val[0]:
61 %if not val_lbl:
61 %if not val_lbl:
62 ${h.bool2icon(True)}
62 ${h.bool2icon(True)}
63 %else:
63 %else:
64 <span class="perm_tag admin">${val_lbl}.admin</span>
64 <span class="perm_tag admin">${val_lbl}.admin</span>
65 %endif
65 %endif
66 %else:
66 %else:
67 %if not val_lbl:
67 %if not val_lbl:
68 ${h.bool2icon({'false': False,
68 ${h.bool2icon({'false': False,
69 'true': True,
69 'true': True,
70 'none': False,
70 'none': False,
71 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false'))}
71 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false'))}
72 %else:
72 %else:
73 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
73 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
74 %endif
74 %endif
75 %endif
75 %endif
76 </td>
76 </td>
77 %if actions:
77 %if actions:
78 <td class="td-action">
78 <td class="td-action">
79 <a href="${custom_url or h.route_path('admin_permissions_global')}">${_('edit')}</a>
79 <a href="${custom_url or h.route_path('admin_permissions_global')}">${_('edit')}</a>
80 </td>
80 </td>
81 %endif
81 %endif
82 </tr>
82 </tr>
83 </%def>
83 </%def>
84
84
85 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]))}
85 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]))}
86
86
87 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository', h.route_path('admin_permissions_object'))}
87 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository', h.route_path('admin_permissions_object'))}
88 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group', h.route_path('admin_permissions_object'))}
88 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group', h.route_path('admin_permissions_object'))}
89 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup', h.route_path('admin_permissions_object'))}
89 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup', h.route_path('admin_permissions_object'))}
90
90
91 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
91 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
92 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
92 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
93 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
93 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
94 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
94 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
95
95
96
96
97 </tbody>
97 </tbody>
98 %else:
98 %else:
99 ## none/read/write/admin permissions on groups/repos etc
99 ## none/read/write/admin permissions on groups/repos etc
100 <thead>
100 <thead>
101 <tr>
101 <tr>
102 <th>${_('Name')}</th>
102 <th>${_('Name')}</th>
103 <th>${_('Permission')}</th>
103 <th>${_('Permission')}</th>
104 %if actions:
104 %if actions:
105 <th>${_('Edit Permission')}</th>
105 <th>${_('Edit Permission')}</th>
106 %endif
106 %endif
107 </thead>
107 </thead>
108 <tbody class="section_${section}">
108 <tbody class="section_${section}">
109 <%
109 <%
110 def sorter(permissions):
110 def sorter(permissions):
111 def custom_sorter(item):
111 def custom_sorter(item):
112 ## read/write/admin
112 ## read/write/admin
113 section = item[1].split('.')[-1]
113 section = item[1].split('.')[-1]
114 section_importance = {'none': u'0',
114 section_importance = {'none': u'0',
115 'read': u'1',
115 'read': u'1',
116 'write':u'2',
116 'write':u'2',
117 'admin':u'3'}.get(section)
117 'admin':u'3'}.get(section)
118 ## sort by group importance+name
118 ## sort by group importance+name
119 return section_importance+item[0]
119 return section_importance+item[0]
120 return sorted(permissions, key=custom_sorter)
120 return sorted(permissions, key=custom_sorter)
121 %>
121 %>
122 %for k, section_perm in sorter(permissions[section].items()):
122 %for k, section_perm in sorter(permissions[section].items()):
123 %if section_perm.split('.')[-1] != 'none' or show_all:
123 %if section_perm.split('.')[-1] != 'none' or show_all:
124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
125 <td class="td-componentname">
125 <td class="td-componentname">
126 %if section == 'repositories':
126 %if section == 'repositories':
127 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
127 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
128 %elif section == 'repositories_groups':
128 %elif section == 'repositories_groups':
129 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
129 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
130 %elif section == 'user_groups':
130 %elif section == 'user_groups':
131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
132 ${k}
132 ${k}
133 %endif
133 %endif
134 </td>
134 </td>
135 <td class="td-tags">
135 <td class="td-tags">
136 %if hasattr(permissions[section], 'perm_origin_stack'):
136 %if hasattr(permissions[section], 'perm_origin_stack'):
137 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
137 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
138 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
138 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
139 ${perm} (${origin})
139 ${perm} (${origin})
140 </span>
140 </span>
141 %endfor
141 %endfor
142 %else:
142 %else:
143 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
143 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
144 %endif
144 %endif
145 </td>
145 </td>
146 %if actions:
146 %if actions:
147 <td class="td-action">
147 <td class="td-action">
148 %if section == 'repositories':
148 %if section == 'repositories':
149 <a href="${h.route_path('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
149 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
150 %elif section == 'repositories_groups':
150 %elif section == 'repositories_groups':
151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 %elif section == 'user_groups':
152 %elif section == 'user_groups':
153 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
153 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
154 %endif
154 %endif
155 </td>
155 </td>
156 %endif
156 %endif
157 </tr>
157 </tr>
158 %endif
158 %endif
159 %endfor
159 %endfor
160
160
161 <tr id="empty_${section}" class="noborder" style="display:none;">
161 <tr id="empty_${section}" class="noborder" style="display:none;">
162 <td colspan="6">${_('No permission defined')}</td>
162 <td colspan="6">${_('No permission defined')}</td>
163 </tr>
163 </tr>
164
164
165 </tbody>
165 </tbody>
166 %endif
166 %endif
167 </table>
167 </table>
168 </div>
168 </div>
169 %endif
169 %endif
170 </div>
170 </div>
171 </div>
171 </div>
172 </div>
172 </div>
173 %endfor
173 %endfor
174 </div>
174 </div>
175
175
176 <script>
176 <script>
177 $(document).ready(function(){
177 $(document).ready(function(){
178 var show_empty = function(section){
178 var show_empty = function(section){
179 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
179 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
180 if(visible == 0){
180 if(visible == 0){
181 $('#empty_{0}'.format(section)).show();
181 $('#empty_{0}'.format(section)).show();
182 }
182 }
183 else{
183 else{
184 $('#empty_{0}'.format(section)).hide();
184 $('#empty_{0}'.format(section)).hide();
185 }
185 }
186 };
186 };
187 $('.perm_filter').on('change', function(e){
187 $('.perm_filter').on('change', function(e){
188 var self = this;
188 var self = this;
189 var section = $(this).attr('section');
189 var section = $(this).attr('section');
190
190
191 var opts = {};
191 var opts = {};
192 var elems = $('.filter_' + section).each(function(el){
192 var elems = $('.filter_' + section).each(function(el){
193 var perm_type = $(this).attr('perm_type');
193 var perm_type = $(this).attr('perm_type');
194 var checked = this.checked;
194 var checked = this.checked;
195 opts[perm_type] = checked;
195 opts[perm_type] = checked;
196 if(checked){
196 if(checked){
197 $('.'+section+'_'+perm_type).show();
197 $('.'+section+'_'+perm_type).show();
198 }
198 }
199 else{
199 else{
200 $('.'+section+'_'+perm_type).hide();
200 $('.'+section+'_'+perm_type).hide();
201 }
201 }
202 });
202 });
203 show_empty(section);
203 show_empty(section);
204 })
204 })
205 })
205 })
206 </script>
206 </script>
207 </%def>
207 </%def>
@@ -1,352 +1,352 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
4 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5
5
6 <%def name="title()">
6 <%def name="title()">
7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
8 %if c.rhodecode_name:
8 %if c.rhodecode_name:
9 &middot; ${h.branding(c.rhodecode_name)}
9 &middot; ${h.branding(c.rhodecode_name)}
10 %endif
10 %endif
11 </%def>
11 </%def>
12
12
13 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
14 ${self.menu_items(active='repositories')}
14 ${self.menu_items(active='repositories')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_subnav()">
17 <%def name="menu_bar_subnav()">
18 ${self.repo_menu(active='changelog')}
18 ${self.repo_menu(active='changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <script>
22 <script>
23 // TODO: marcink switch this to pyroutes
23 // TODO: marcink switch this to pyroutes
24 AJAX_COMMENT_DELETE_URL = "${h.url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
24 AJAX_COMMENT_DELETE_URL = "${h.url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
26 </script>
26 </script>
27 <div class="box">
27 <div class="box">
28 <div class="title">
28 <div class="title">
29 ${self.repo_page_title(c.rhodecode_db_repo)}
29 ${self.repo_page_title(c.rhodecode_db_repo)}
30 </div>
30 </div>
31
31
32 <div id="changeset_compare_view_content" class="summary changeset">
32 <div id="changeset_compare_view_content" class="summary changeset">
33 <div class="summary-detail">
33 <div class="summary-detail">
34 <div class="summary-detail-header">
34 <div class="summary-detail-header">
35 <span class="breadcrumbs files_location">
35 <span class="breadcrumbs files_location">
36 <h4>${_('Commit')}
36 <h4>${_('Commit')}
37 <code>
37 <code>
38 ${h.show_id(c.commit)}
38 ${h.show_id(c.commit)}
39 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
39 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
40 % if hasattr(c.commit, 'phase'):
40 % if hasattr(c.commit, 'phase'):
41 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span>
41 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span>
42 % endif
42 % endif
43
43
44 ## obsolete commits
44 ## obsolete commits
45 % if hasattr(c.commit, 'obsolete'):
45 % if hasattr(c.commit, 'obsolete'):
46 % if c.commit.obsolete:
46 % if c.commit.obsolete:
47 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
47 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
48 % endif
48 % endif
49 % endif
49 % endif
50
50
51 ## hidden commits
51 ## hidden commits
52 % if hasattr(c.commit, 'hidden'):
52 % if hasattr(c.commit, 'hidden'):
53 % if c.commit.hidden:
53 % if c.commit.hidden:
54 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
54 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
55 % endif
55 % endif
56 % endif
56 % endif
57
57
58 </code>
58 </code>
59 </h4>
59 </h4>
60 </span>
60 </span>
61 <div class="pull-right">
61 <div class="pull-right">
62 <span id="parent_link">
62 <span id="parent_link">
63 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
63 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
64 </span>
64 </span>
65 |
65 |
66 <span id="child_link">
66 <span id="child_link">
67 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
67 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
68 </span>
68 </span>
69 </div>
69 </div>
70 </div>
70 </div>
71
71
72 <div class="fieldset">
72 <div class="fieldset">
73 <div class="left-label">
73 <div class="left-label">
74 ${_('Description')}:
74 ${_('Description')}:
75 </div>
75 </div>
76 <div class="right-content">
76 <div class="right-content">
77 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
77 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
78 <div id="message_expand" style="display:none;">
78 <div id="message_expand" style="display:none;">
79 ${_('Expand')}
79 ${_('Expand')}
80 </div>
80 </div>
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 %if c.statuses:
84 %if c.statuses:
85 <div class="fieldset">
85 <div class="fieldset">
86 <div class="left-label">
86 <div class="left-label">
87 ${_('Commit status')}:
87 ${_('Commit status')}:
88 </div>
88 </div>
89 <div class="right-content">
89 <div class="right-content">
90 <div class="changeset-status-ico">
90 <div class="changeset-status-ico">
91 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
91 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
92 </div>
92 </div>
93 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
93 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
94 </div>
94 </div>
95 </div>
95 </div>
96 %endif
96 %endif
97
97
98 <div class="fieldset">
98 <div class="fieldset">
99 <div class="left-label">
99 <div class="left-label">
100 ${_('References')}:
100 ${_('References')}:
101 </div>
101 </div>
102 <div class="right-content">
102 <div class="right-content">
103 <div class="tags">
103 <div class="tags">
104
104
105 %if c.commit.merge:
105 %if c.commit.merge:
106 <span class="mergetag tag">
106 <span class="mergetag tag">
107 <i class="icon-merge"></i>${_('merge')}
107 <i class="icon-merge"></i>${_('merge')}
108 </span>
108 </span>
109 %endif
109 %endif
110
110
111 %if h.is_hg(c.rhodecode_repo):
111 %if h.is_hg(c.rhodecode_repo):
112 %for book in c.commit.bookmarks:
112 %for book in c.commit.bookmarks:
113 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
113 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
114 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
114 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
115 </span>
115 </span>
116 %endfor
116 %endfor
117 %endif
117 %endif
118
118
119 %for tag in c.commit.tags:
119 %for tag in c.commit.tags:
120 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
120 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
121 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
121 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
122 </span>
122 </span>
123 %endfor
123 %endfor
124
124
125 %if c.commit.branch:
125 %if c.commit.branch:
126 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}">
126 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}">
127 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=c.commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
127 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=c.commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
128 </span>
128 </span>
129 %endif
129 %endif
130 </div>
130 </div>
131 </div>
131 </div>
132 </div>
132 </div>
133
133
134 <div class="fieldset">
134 <div class="fieldset">
135 <div class="left-label">
135 <div class="left-label">
136 ${_('Diff options')}:
136 ${_('Diff options')}:
137 </div>
137 </div>
138 <div class="right-content">
138 <div class="right-content">
139 <div class="diff-actions">
139 <div class="diff-actions">
140 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
140 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
141 ${_('Raw Diff')}
141 ${_('Raw Diff')}
142 </a>
142 </a>
143 |
143 |
144 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
144 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
145 ${_('Patch Diff')}
145 ${_('Patch Diff')}
146 </a>
146 </a>
147 |
147 |
148 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
148 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
149 ${_('Download Diff')}
149 ${_('Download Diff')}
150 </a>
150 </a>
151 |
151 |
152 ${c.ignorews_url(request.GET)}
152 ${c.ignorews_url(request.GET)}
153 |
153 |
154 ${c.context_url(request.GET)}
154 ${c.context_url(request.GET)}
155 </div>
155 </div>
156 </div>
156 </div>
157 </div>
157 </div>
158
158
159 <div class="fieldset">
159 <div class="fieldset">
160 <div class="left-label">
160 <div class="left-label">
161 ${_('Comments')}:
161 ${_('Comments')}:
162 </div>
162 </div>
163 <div class="right-content">
163 <div class="right-content">
164 <div class="comments-number">
164 <div class="comments-number">
165 %if c.comments:
165 %if c.comments:
166 <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
166 <a href="#comments">${_ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
167 %else:
167 %else:
168 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
168 ${_ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
169 %endif
169 %endif
170 %if c.inline_cnt:
170 %if c.inline_cnt:
171 <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>
171 <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>
172 %else:
172 %else:
173 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
173 ${_ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
174 %endif
174 %endif
175 </div>
175 </div>
176 </div>
176 </div>
177 </div>
177 </div>
178
178
179 <div class="fieldset">
179 <div class="fieldset">
180 <div class="left-label">
180 <div class="left-label">
181 ${_('Unresolved TODOs')}:
181 ${_('Unresolved TODOs')}:
182 </div>
182 </div>
183 <div class="right-content">
183 <div class="right-content">
184 <div class="comments-number">
184 <div class="comments-number">
185 % if c.unresolved_comments:
185 % if c.unresolved_comments:
186 % for co in c.unresolved_comments:
186 % for co in c.unresolved_comments:
187 <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','}
187 <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','}
188 % endfor
188 % endfor
189 % else:
189 % else:
190 ${_('There are no unresolved TODOs')}
190 ${_('There are no unresolved TODOs')}
191 % endif
191 % endif
192 </div>
192 </div>
193 </div>
193 </div>
194 </div>
194 </div>
195
195
196 </div> <!-- end summary-detail -->
196 </div> <!-- end summary-detail -->
197
197
198 <div id="commit-stats" class="sidebar-right">
198 <div id="commit-stats" class="sidebar-right">
199 <div class="summary-detail-header">
199 <div class="summary-detail-header">
200 <h4 class="item">
200 <h4 class="item">
201 ${_('Author')}
201 ${_('Author')}
202 </h4>
202 </h4>
203 </div>
203 </div>
204 <div class="sidebar-right-content">
204 <div class="sidebar-right-content">
205 ${self.gravatar_with_user(c.commit.author)}
205 ${self.gravatar_with_user(c.commit.author)}
206 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
206 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
207 </div>
207 </div>
208 </div><!-- end sidebar -->
208 </div><!-- end sidebar -->
209 </div> <!-- end summary -->
209 </div> <!-- end summary -->
210 <div class="cs_files">
210 <div class="cs_files">
211 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
211 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
212 ${cbdiffs.render_diffset_menu()}
212 ${cbdiffs.render_diffset_menu()}
213 ${cbdiffs.render_diffset(
213 ${cbdiffs.render_diffset(
214 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
214 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
215 </div>
215 </div>
216
216
217 ## template for inline comment form
217 ## template for inline comment form
218 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
218 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
219
219
220 ## render comments
220 ## render comments
221 ${comment.generate_comments(c.comments)}
221 ${comment.generate_comments(c.comments)}
222
222
223 ## main comment form and it status
223 ## main comment form and it status
224 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
224 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
225 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
225 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
226 </div>
226 </div>
227
227
228 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
228 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
229 <script type="text/javascript">
229 <script type="text/javascript">
230
230
231 $(document).ready(function() {
231 $(document).ready(function() {
232
232
233 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
233 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
234 if($('#trimmed_message_box').height() === boxmax){
234 if($('#trimmed_message_box').height() === boxmax){
235 $('#message_expand').show();
235 $('#message_expand').show();
236 }
236 }
237
237
238 $('#message_expand').on('click', function(e){
238 $('#message_expand').on('click', function(e){
239 $('#trimmed_message_box').css('max-height', 'none');
239 $('#trimmed_message_box').css('max-height', 'none');
240 $(this).hide();
240 $(this).hide();
241 });
241 });
242
242
243 $('.show-inline-comments').on('click', function(e){
243 $('.show-inline-comments').on('click', function(e){
244 var boxid = $(this).attr('data-comment-id');
244 var boxid = $(this).attr('data-comment-id');
245 var button = $(this);
245 var button = $(this);
246
246
247 if(button.hasClass("comments-visible")) {
247 if(button.hasClass("comments-visible")) {
248 $('#{0} .inline-comments'.format(boxid)).each(function(index){
248 $('#{0} .inline-comments'.format(boxid)).each(function(index){
249 $(this).hide();
249 $(this).hide();
250 });
250 });
251 button.removeClass("comments-visible");
251 button.removeClass("comments-visible");
252 } else {
252 } else {
253 $('#{0} .inline-comments'.format(boxid)).each(function(index){
253 $('#{0} .inline-comments'.format(boxid)).each(function(index){
254 $(this).show();
254 $(this).show();
255 });
255 });
256 button.addClass("comments-visible");
256 button.addClass("comments-visible");
257 }
257 }
258 });
258 });
259
259
260
260
261 // next links
261 // next links
262 $('#child_link').on('click', function(e){
262 $('#child_link').on('click', function(e){
263 // fetch via ajax what is going to be the next link, if we have
263 // fetch via ajax what is going to be the next link, if we have
264 // >1 links show them to user to choose
264 // >1 links show them to user to choose
265 if(!$('#child_link').hasClass('disabled')){
265 if(!$('#child_link').hasClass('disabled')){
266 $.ajax({
266 $.ajax({
267 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
267 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
268 success: function(data) {
268 success: function(data) {
269 if(data.results.length === 0){
269 if(data.results.length === 0){
270 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
270 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
271 }
271 }
272 if(data.results.length === 1){
272 if(data.results.length === 1){
273 var commit = data.results[0];
273 var commit = data.results[0];
274 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
274 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
275 }
275 }
276 else if(data.results.length === 2){
276 else if(data.results.length === 2){
277 $('#child_link').addClass('disabled');
277 $('#child_link').addClass('disabled');
278 $('#child_link').addClass('double');
278 $('#child_link').addClass('double');
279 var _html = '';
279 var _html = '';
280 _html +='<a title="__title__" href="__url__">__rev__</a> '
280 _html +='<a title="__title__" href="__url__">__rev__</a> '
281 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
281 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
282 .replace('__title__', data.results[0].message)
282 .replace('__title__', data.results[0].message)
283 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
283 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
284 _html +=' | ';
284 _html +=' | ';
285 _html +='<a title="__title__" href="__url__">__rev__</a> '
285 _html +='<a title="__title__" href="__url__">__rev__</a> '
286 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
286 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
287 .replace('__title__', data.results[1].message)
287 .replace('__title__', data.results[1].message)
288 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
288 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
289 $('#child_link').html(_html);
289 $('#child_link').html(_html);
290 }
290 }
291 }
291 }
292 });
292 });
293 e.preventDefault();
293 e.preventDefault();
294 }
294 }
295 });
295 });
296
296
297 // prev links
297 // prev links
298 $('#parent_link').on('click', function(e){
298 $('#parent_link').on('click', function(e){
299 // fetch via ajax what is going to be the next link, if we have
299 // fetch via ajax what is going to be the next link, if we have
300 // >1 links show them to user to choose
300 // >1 links show them to user to choose
301 if(!$('#parent_link').hasClass('disabled')){
301 if(!$('#parent_link').hasClass('disabled')){
302 $.ajax({
302 $.ajax({
303 url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}',
303 url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}',
304 success: function(data) {
304 success: function(data) {
305 if(data.results.length === 0){
305 if(data.results.length === 0){
306 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
306 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
307 }
307 }
308 if(data.results.length === 1){
308 if(data.results.length === 1){
309 var commit = data.results[0];
309 var commit = data.results[0];
310 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
310 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
311 }
311 }
312 else if(data.results.length === 2){
312 else if(data.results.length === 2){
313 $('#parent_link').addClass('disabled');
313 $('#parent_link').addClass('disabled');
314 $('#parent_link').addClass('double');
314 $('#parent_link').addClass('double');
315 var _html = '';
315 var _html = '';
316 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
316 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
317 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
317 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
318 .replace('__title__', data.results[0].message)
318 .replace('__title__', data.results[0].message)
319 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
319 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
320 _html +=' | ';
320 _html +=' | ';
321 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
321 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
322 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
322 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
323 .replace('__title__', data.results[1].message)
323 .replace('__title__', data.results[1].message)
324 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
324 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
325 $('#parent_link').html(_html);
325 $('#parent_link').html(_html);
326 }
326 }
327 }
327 }
328 });
328 });
329 e.preventDefault();
329 e.preventDefault();
330 }
330 }
331 });
331 });
332
332
333 if (location.hash) {
333 if (location.hash) {
334 var result = splitDelimitedHash(location.hash);
334 var result = splitDelimitedHash(location.hash);
335 var line = $('html').find(result.loc);
335 var line = $('html').find(result.loc);
336 if (line.length > 0){
336 if (line.length > 0){
337 offsetScroll(line, 70);
337 offsetScroll(line, 70);
338 }
338 }
339 }
339 }
340
340
341 // browse tree @ revision
341 // browse tree @ revision
342 $('#files_link').on('click', function(e){
342 $('#files_link').on('click', function(e){
343 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
343 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
344 e.preventDefault();
344 e.preventDefault();
345 });
345 });
346
346
347 // inject comments into their proper positions
347 // inject comments into their proper positions
348 var file_comments = $('.inline-comment-placeholder');
348 var file_comments = $('.inline-comment-placeholder');
349 })
349 })
350 </script>
350 </script>
351
351
352 </%def>
352 </%def>
@@ -1,125 +1,125 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Commits') % c.repo_name} -
5 ${_('%s Commits') % c.repo_name} -
6 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
6 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
7 ...
7 ...
8 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
8 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
9 ${ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
9 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 ${_('Commits')} -
16 ${_('Commits')} -
17 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
17 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
18 ...
18 ...
19 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
19 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
20 ${ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
20 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='repositories')}
24 ${self.menu_items(active='repositories')}
25 </%def>
25 </%def>
26
26
27 <%def name="menu_bar_subnav()">
27 <%def name="menu_bar_subnav()">
28 ${self.repo_menu(active='changelog')}
28 ${self.repo_menu(active='changelog')}
29 </%def>
29 </%def>
30
30
31 <%def name="main()">
31 <%def name="main()">
32 <div class="summary-header">
32 <div class="summary-header">
33 <div class="title">
33 <div class="title">
34 ${self.repo_page_title(c.rhodecode_db_repo)}
34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 </div>
35 </div>
36 </div>
36 </div>
37
37
38
38
39 <div class="summary changeset">
39 <div class="summary changeset">
40 <div class="summary-detail">
40 <div class="summary-detail">
41 <div class="summary-detail-header">
41 <div class="summary-detail-header">
42 <span class="breadcrumbs files_location">
42 <span class="breadcrumbs files_location">
43 <h4>
43 <h4>
44 ${_('Commit Range')}
44 ${_('Commit Range')}
45 <code>
45 <code>
46 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
46 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
47 </code>
47 </code>
48 </h4>
48 </h4>
49 </span>
49 </span>
50 </div>
50 </div>
51
51
52 <div class="fieldset">
52 <div class="fieldset">
53 <div class="left-label">
53 <div class="left-label">
54 ${_('Diff option')}:
54 ${_('Diff option')}:
55 </div>
55 </div>
56 <div class="right-content">
56 <div class="right-content">
57 <div class="header-buttons">
57 <div class="header-buttons">
58 <a href="${h.url('compare_url', repo_name=c.repo_name, source_ref_type='rev', source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'), target_ref_type='rev', target_ref=c.commit_ranges[-1].raw_id)}">
58 <a href="${h.url('compare_url', repo_name=c.repo_name, source_ref_type='rev', source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'), target_ref_type='rev', target_ref=c.commit_ranges[-1].raw_id)}">
59 ${_('Show combined compare')}
59 ${_('Show combined compare')}
60 </a>
60 </a>
61 </div>
61 </div>
62 </div>
62 </div>
63 </div>
63 </div>
64
64
65 <%doc>
65 <%doc>
66 ##TODO(marcink): implement this and diff menus
66 ##TODO(marcink): implement this and diff menus
67 <div class="fieldset">
67 <div class="fieldset">
68 <div class="left-label">
68 <div class="left-label">
69 ${_('Diff options')}:
69 ${_('Diff options')}:
70 </div>
70 </div>
71 <div class="right-content">
71 <div class="right-content">
72 <div class="diff-actions">
72 <div class="diff-actions">
73 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
73 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
74 ${_('Raw Diff')}
74 ${_('Raw Diff')}
75 </a>
75 </a>
76 |
76 |
77 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
77 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
78 ${_('Patch Diff')}
78 ${_('Patch Diff')}
79 </a>
79 </a>
80 |
80 |
81 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
81 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
82 ${_('Download Diff')}
82 ${_('Download Diff')}
83 </a>
83 </a>
84 </div>
84 </div>
85 </div>
85 </div>
86 </div>
86 </div>
87 </%doc>
87 </%doc>
88 </div> <!-- end summary-detail -->
88 </div> <!-- end summary-detail -->
89
89
90 </div> <!-- end summary -->
90 </div> <!-- end summary -->
91
91
92 <div id="changeset_compare_view_content">
92 <div id="changeset_compare_view_content">
93 <div class="pull-left">
93 <div class="pull-left">
94 <div class="btn-group">
94 <div class="btn-group">
95 <a
95 <a
96 class="btn"
96 class="btn"
97 href="#"
97 href="#"
98 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
98 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
99 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
99 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
100 </a>
100 </a>
101 <a
101 <a
102 class="btn"
102 class="btn"
103 href="#"
103 href="#"
104 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
104 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
105 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
105 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
106 </a>
106 </a>
107 </div>
107 </div>
108 </div>
108 </div>
109 ## Commit range generated below
109 ## Commit range generated below
110 <%include file="../compare/compare_commits.mako"/>
110 <%include file="../compare/compare_commits.mako"/>
111 <div class="cs_files">
111 <div class="cs_files">
112 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
112 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
113 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
113 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
114 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
114 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
115 ${cbdiffs.render_diffset_menu()}
115 ${cbdiffs.render_diffset_menu()}
116 %for commit in c.commit_ranges:
116 %for commit in c.commit_ranges:
117 ${cbdiffs.render_diffset(
117 ${cbdiffs.render_diffset(
118 diffset=c.changes[commit.raw_id],
118 diffset=c.changes[commit.raw_id],
119 collapse_when_files_over=5,
119 collapse_when_files_over=5,
120 commit=commit,
120 commit=commit,
121 )}
121 )}
122 %endfor
122 %endfor
123 </div>
123 </div>
124 </div>
124 </div>
125 </%def>
125 </%def>
@@ -1,64 +1,64 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##usage:
2 ##usage:
3 ## <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
3 ## <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
4 ## ${diff_block.diff_block_changeset_table(change)}
4 ## ${diff_block.diff_block_changeset_table(change)}
5 ##
5 ##
6 <%def name="changeset_message()">
6 <%def name="changeset_message()">
7 <h5>${_('The requested commit is too big and content was truncated.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
7 <h5>${_('The requested commit is too big and content was truncated.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
8 </%def>
8 </%def>
9 <%def name="file_message()">
9 <%def name="file_message()">
10 <h5>${_('The requested file is too big and its content is not shown.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
10 <h5>${_('The requested file is too big and its content is not shown.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
11 </%def>
11 </%def>
12
12
13 <%def name="diff_block_changeset_table(change)">
13 <%def name="diff_block_changeset_table(change)">
14 <div class="diff-container" id="${'diff-container-%s' % (id(change))}">
14 <div class="diff-container" id="${'diff-container-%s' % (id(change))}">
15 %for FID,(cs1, cs2, change, filenode_path, diff, stats, file_data) in change.iteritems():
15 %for FID,(cs1, cs2, change, filenode_path, diff, stats, file_data) in change.iteritems():
16 <div id="${h.FID('',filenode_path)}_target" ></div>
16 <div id="${h.FID('',filenode_path)}_target" ></div>
17 <div id="${h.FID('',filenode_path)}" class="diffblock margined comm">
17 <div id="${h.FID('',filenode_path)}" class="diffblock margined comm">
18 <div class="code-body">
18 <div class="code-body">
19 <div class="full_f_path" path="${h.safe_unicode(filenode_path)}" style="display: none"></div>
19 <div class="full_f_path" path="${h.safe_unicode(filenode_path)}" style="display: none"></div>
20 ${diff|n}
20 ${diff|n}
21 % if file_data["is_limited_diff"]:
21 % if file_data["is_limited_diff"]:
22 % if file_data["exceeds_limit"]:
22 % if file_data["exceeds_limit"]:
23 ${self.file_message()}
23 ${self.file_message()}
24 % else:
24 % else:
25 <h5>${_('Diff was truncated. File content available only in full diff.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
25 <h5>${_('Diff was truncated. File content available only in full diff.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
26 % endif
26 % endif
27 % endif
27 % endif
28 </div>
28 </div>
29 </div>
29 </div>
30 %endfor
30 %endfor
31 </div>
31 </div>
32 </%def>
32 </%def>
33
33
34 <%def name="diff_block_simple(change)">
34 <%def name="diff_block_simple(change)">
35 <div class="diff-container" id="${'diff-container-%s' % (id(change))}">
35 <div class="diff-container" id="${'diff-container-%s' % (id(change))}">
36 %for op,filenode_path,diff,file_data in change:
36 %for op,filenode_path,diff,file_data in change:
37 <div id="${h.FID('',filenode_path)}_target" ></div>
37 <div id="${h.FID('',filenode_path)}_target" ></div>
38 <div id="${h.FID('',filenode_path)}" class="diffblock margined comm" >
38 <div id="${h.FID('',filenode_path)}" class="diffblock margined comm" >
39 <div class="code-body">
39 <div class="code-body">
40 <div class="full_f_path" path="${h.safe_unicode(filenode_path)}" style="display: none;"></div>
40 <div class="full_f_path" path="${h.safe_unicode(filenode_path)}" style="display: none;"></div>
41 ${diff|n}
41 ${diff|n}
42 % if file_data["is_limited_diff"]:
42 % if file_data["is_limited_diff"]:
43 % if file_data["exceeds_limit"]:
43 % if file_data["exceeds_limit"]:
44 ${self.file_message()}
44 ${self.file_message()}
45 % else:
45 % else:
46 <h5>${_('Diff was truncated. File content available only in full diff.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
46 <h5>${_('Diff was truncated. File content available only in full diff.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
47 % endif
47 % endif
48 % endif
48 % endif
49 </div>
49 </div>
50 </div>
50 </div>
51 %endfor
51 %endfor
52 </div>
52 </div>
53 </%def>
53 </%def>
54
54
55
55
56 <%def name="diff_summary_text(changed_files, lines_added, lines_deleted, limited_diff=False)">
56 <%def name="diff_summary_text(changed_files, lines_added, lines_deleted, limited_diff=False)">
57 % if limited_diff:
57 % if limited_diff:
58 ${ungettext('%(num)s file changed', '%(num)s files changed', changed_files) % {'num': changed_files}}
58 ${_ungettext('%(num)s file changed', '%(num)s files changed', changed_files) % {'num': changed_files}}
59 % else:
59 % else:
60 ${ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
60 ${_ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
61 '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', changed_files) % {'num': changed_files, 'linesadd': lines_added, 'linesdel': lines_deleted}}
61 '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', changed_files) % {'num': changed_files, 'linesadd': lines_added, 'linesdel': lines_deleted}}
62 %endif
62 %endif
63 </%def>
63 </%def>
64
64
@@ -1,671 +1,671 b''
1 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
1 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
2
2
3 <%def name="diff_line_anchor(filename, line, type)"><%
3 <%def name="diff_line_anchor(filename, line, type)"><%
4 return '%s_%s_%i' % (h.safeid(filename), type, line)
4 return '%s_%s_%i' % (h.safeid(filename), type, line)
5 %></%def>
5 %></%def>
6
6
7 <%def name="action_class(action)">
7 <%def name="action_class(action)">
8 <%
8 <%
9 return {
9 return {
10 '-': 'cb-deletion',
10 '-': 'cb-deletion',
11 '+': 'cb-addition',
11 '+': 'cb-addition',
12 ' ': 'cb-context',
12 ' ': 'cb-context',
13 }.get(action, 'cb-empty')
13 }.get(action, 'cb-empty')
14 %>
14 %>
15 </%def>
15 </%def>
16
16
17 <%def name="op_class(op_id)">
17 <%def name="op_class(op_id)">
18 <%
18 <%
19 return {
19 return {
20 DEL_FILENODE: 'deletion', # file deleted
20 DEL_FILENODE: 'deletion', # file deleted
21 BIN_FILENODE: 'warning' # binary diff hidden
21 BIN_FILENODE: 'warning' # binary diff hidden
22 }.get(op_id, 'addition')
22 }.get(op_id, 'addition')
23 %>
23 %>
24 </%def>
24 </%def>
25
25
26 <%def name="link_for(**kw)">
26 <%def name="link_for(**kw)">
27 <%
27 <%
28 new_args = request.GET.mixed()
28 new_args = request.GET.mixed()
29 new_args.update(kw)
29 new_args.update(kw)
30 return h.url('', **new_args)
30 return h.url('', **new_args)
31 %>
31 %>
32 </%def>
32 </%def>
33
33
34 <%def name="render_diffset(diffset, commit=None,
34 <%def name="render_diffset(diffset, commit=None,
35
35
36 # collapse all file diff entries when there are more than this amount of files in the diff
36 # collapse all file diff entries when there are more than this amount of files in the diff
37 collapse_when_files_over=20,
37 collapse_when_files_over=20,
38
38
39 # collapse lines in the diff when more than this amount of lines changed in the file diff
39 # collapse lines in the diff when more than this amount of lines changed in the file diff
40 lines_changed_limit=500,
40 lines_changed_limit=500,
41
41
42 # add a ruler at to the output
42 # add a ruler at to the output
43 ruler_at_chars=0,
43 ruler_at_chars=0,
44
44
45 # show inline comments
45 # show inline comments
46 use_comments=False,
46 use_comments=False,
47
47
48 # disable new comments
48 # disable new comments
49 disable_new_comments=False,
49 disable_new_comments=False,
50
50
51 # special file-comments that were deleted in previous versions
51 # special file-comments that were deleted in previous versions
52 # it's used for showing outdated comments for deleted files in a PR
52 # it's used for showing outdated comments for deleted files in a PR
53 deleted_files_comments=None
53 deleted_files_comments=None
54
54
55 )">
55 )">
56
56
57 %if use_comments:
57 %if use_comments:
58 <div id="cb-comments-inline-container-template" class="js-template">
58 <div id="cb-comments-inline-container-template" class="js-template">
59 ${inline_comments_container([])}
59 ${inline_comments_container([])}
60 </div>
60 </div>
61 <div class="js-template" id="cb-comment-inline-form-template">
61 <div class="js-template" id="cb-comment-inline-form-template">
62 <div class="comment-inline-form ac">
62 <div class="comment-inline-form ac">
63
63
64 %if c.rhodecode_user.username != h.DEFAULT_USER:
64 %if c.rhodecode_user.username != h.DEFAULT_USER:
65 ## render template for inline comments
65 ## render template for inline comments
66 ${commentblock.comment_form(form_type='inline')}
66 ${commentblock.comment_form(form_type='inline')}
67 %else:
67 %else:
68 ${h.form('', class_='inline-form comment-form-login', method='get')}
68 ${h.form('', class_='inline-form comment-form-login', method='get')}
69 <div class="pull-left">
69 <div class="pull-left">
70 <div class="comment-help pull-right">
70 <div class="comment-help pull-right">
71 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
71 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
72 </div>
72 </div>
73 </div>
73 </div>
74 <div class="comment-button pull-right">
74 <div class="comment-button pull-right">
75 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
75 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
76 ${_('Cancel')}
76 ${_('Cancel')}
77 </button>
77 </button>
78 </div>
78 </div>
79 <div class="clearfix"></div>
79 <div class="clearfix"></div>
80 ${h.end_form()}
80 ${h.end_form()}
81 %endif
81 %endif
82 </div>
82 </div>
83 </div>
83 </div>
84
84
85 %endif
85 %endif
86 <%
86 <%
87 collapse_all = len(diffset.files) > collapse_when_files_over
87 collapse_all = len(diffset.files) > collapse_when_files_over
88 %>
88 %>
89
89
90 %if c.diffmode == 'sideside':
90 %if c.diffmode == 'sideside':
91 <style>
91 <style>
92 .wrapper {
92 .wrapper {
93 max-width: 1600px !important;
93 max-width: 1600px !important;
94 }
94 }
95 </style>
95 </style>
96 %endif
96 %endif
97
97
98 %if ruler_at_chars:
98 %if ruler_at_chars:
99 <style>
99 <style>
100 .diff table.cb .cb-content:after {
100 .diff table.cb .cb-content:after {
101 content: "";
101 content: "";
102 border-left: 1px solid blue;
102 border-left: 1px solid blue;
103 position: absolute;
103 position: absolute;
104 top: 0;
104 top: 0;
105 height: 18px;
105 height: 18px;
106 opacity: .2;
106 opacity: .2;
107 z-index: 10;
107 z-index: 10;
108 //## +5 to account for diff action (+/-)
108 //## +5 to account for diff action (+/-)
109 left: ${ruler_at_chars + 5}ch;
109 left: ${ruler_at_chars + 5}ch;
110 </style>
110 </style>
111 %endif
111 %endif
112
112
113 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
113 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
115 %if commit:
115 %if commit:
116 <div class="pull-right">
116 <div class="pull-right">
117 <a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h.route_path('repo_files',repo_name=diffset.repo_name, commit_id=commit.raw_id, f_path='')}">
117 <a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h.route_path('repo_files',repo_name=diffset.repo_name, commit_id=commit.raw_id, f_path='')}">
118 ${_('Browse Files')}
118 ${_('Browse Files')}
119 </a>
119 </a>
120 </div>
120 </div>
121 %endif
121 %endif
122 <h2 class="clearinner">
122 <h2 class="clearinner">
123 %if commit:
123 %if commit:
124 <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> -
124 <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> -
125 ${h.age_component(commit.date)} -
125 ${h.age_component(commit.date)} -
126 %endif
126 %endif
127 %if diffset.limited_diff:
127 %if diffset.limited_diff:
128 ${_('The requested commit is too big and content was truncated.')}
128 ${_('The requested commit is too big and content was truncated.')}
129
129
130 ${ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}}
130 ${_ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}}
131 <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
131 <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
132 %else:
132 %else:
133 ${ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
133 ${_ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
134 '%(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}}
134 '%(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}}
135 %endif
135 %endif
136
136
137 </h2>
137 </h2>
138 </div>
138 </div>
139
139
140 %if not diffset.files:
140 %if not diffset.files:
141 <p class="empty_data">${_('No files')}</p>
141 <p class="empty_data">${_('No files')}</p>
142 %endif
142 %endif
143
143
144 <div class="filediffs">
144 <div class="filediffs">
145 ## initial value could be marked as False later on
145 ## initial value could be marked as False later on
146 <% over_lines_changed_limit = False %>
146 <% over_lines_changed_limit = False %>
147 %for i, filediff in enumerate(diffset.files):
147 %for i, filediff in enumerate(diffset.files):
148
148
149 <%
149 <%
150 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
150 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
151 over_lines_changed_limit = lines_changed > lines_changed_limit
151 over_lines_changed_limit = lines_changed > lines_changed_limit
152 %>
152 %>
153 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
153 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
154 <div
154 <div
155 class="filediff"
155 class="filediff"
156 data-f-path="${filediff.patch['filename']}"
156 data-f-path="${filediff.patch['filename']}"
157 id="a_${h.FID('', filediff.patch['filename'])}">
157 id="a_${h.FID('', filediff.patch['filename'])}">
158 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
158 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
159 <div class="filediff-collapse-indicator"></div>
159 <div class="filediff-collapse-indicator"></div>
160 ${diff_ops(filediff)}
160 ${diff_ops(filediff)}
161 </label>
161 </label>
162 ${diff_menu(filediff, use_comments=use_comments)}
162 ${diff_menu(filediff, use_comments=use_comments)}
163 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
163 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
164 %if not filediff.hunks:
164 %if not filediff.hunks:
165 %for op_id, op_text in filediff.patch['stats']['ops'].items():
165 %for op_id, op_text in filediff.patch['stats']['ops'].items():
166 <tr>
166 <tr>
167 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
167 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
168 %if op_id == DEL_FILENODE:
168 %if op_id == DEL_FILENODE:
169 ${_('File was deleted')}
169 ${_('File was deleted')}
170 %elif op_id == BIN_FILENODE:
170 %elif op_id == BIN_FILENODE:
171 ${_('Binary file hidden')}
171 ${_('Binary file hidden')}
172 %else:
172 %else:
173 ${op_text}
173 ${op_text}
174 %endif
174 %endif
175 </td>
175 </td>
176 </tr>
176 </tr>
177 %endfor
177 %endfor
178 %endif
178 %endif
179 %if filediff.limited_diff:
179 %if filediff.limited_diff:
180 <tr class="cb-warning cb-collapser">
180 <tr class="cb-warning cb-collapser">
181 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
181 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
182 ${_('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>
182 ${_('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>
183 </td>
183 </td>
184 </tr>
184 </tr>
185 %else:
185 %else:
186 %if over_lines_changed_limit:
186 %if over_lines_changed_limit:
187 <tr class="cb-warning cb-collapser">
187 <tr class="cb-warning cb-collapser">
188 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
188 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
189 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
189 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
190 <a href="#" class="cb-expand"
190 <a href="#" class="cb-expand"
191 onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')}
191 onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')}
192 </a>
192 </a>
193 <a href="#" class="cb-collapse"
193 <a href="#" class="cb-collapse"
194 onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')}
194 onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')}
195 </a>
195 </a>
196 </td>
196 </td>
197 </tr>
197 </tr>
198 %endif
198 %endif
199 %endif
199 %endif
200
200
201 %for hunk in filediff.hunks:
201 %for hunk in filediff.hunks:
202 <tr class="cb-hunk">
202 <tr class="cb-hunk">
203 <td ${c.diffmode == 'unified' and 'colspan=3' or ''}>
203 <td ${c.diffmode == 'unified' and 'colspan=3' or ''}>
204 ## TODO: dan: add ajax loading of more context here
204 ## TODO: dan: add ajax loading of more context here
205 ## <a href="#">
205 ## <a href="#">
206 <i class="icon-more"></i>
206 <i class="icon-more"></i>
207 ## </a>
207 ## </a>
208 </td>
208 </td>
209 <td ${c.diffmode == 'sideside' and 'colspan=5' or ''}>
209 <td ${c.diffmode == 'sideside' and 'colspan=5' or ''}>
210 @@
210 @@
211 -${hunk.source_start},${hunk.source_length}
211 -${hunk.source_start},${hunk.source_length}
212 +${hunk.target_start},${hunk.target_length}
212 +${hunk.target_start},${hunk.target_length}
213 ${hunk.section_header}
213 ${hunk.section_header}
214 </td>
214 </td>
215 </tr>
215 </tr>
216 %if c.diffmode == 'unified':
216 %if c.diffmode == 'unified':
217 ${render_hunk_lines_unified(hunk, use_comments=use_comments)}
217 ${render_hunk_lines_unified(hunk, use_comments=use_comments)}
218 %elif c.diffmode == 'sideside':
218 %elif c.diffmode == 'sideside':
219 ${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
219 ${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
220 %else:
220 %else:
221 <tr class="cb-line">
221 <tr class="cb-line">
222 <td>unknown diff mode</td>
222 <td>unknown diff mode</td>
223 </tr>
223 </tr>
224 %endif
224 %endif
225 %endfor
225 %endfor
226
226
227 ## outdated comments that do not fit into currently displayed lines
227 ## outdated comments that do not fit into currently displayed lines
228 % for lineno, comments in filediff.left_comments.items():
228 % for lineno, comments in filediff.left_comments.items():
229
229
230 %if c.diffmode == 'unified':
230 %if c.diffmode == 'unified':
231 <tr class="cb-line">
231 <tr class="cb-line">
232 <td class="cb-data cb-context"></td>
232 <td class="cb-data cb-context"></td>
233 <td class="cb-lineno cb-context"></td>
233 <td class="cb-lineno cb-context"></td>
234 <td class="cb-lineno cb-context"></td>
234 <td class="cb-lineno cb-context"></td>
235 <td class="cb-content cb-context">
235 <td class="cb-content cb-context">
236 ${inline_comments_container(comments)}
236 ${inline_comments_container(comments)}
237 </td>
237 </td>
238 </tr>
238 </tr>
239 %elif c.diffmode == 'sideside':
239 %elif c.diffmode == 'sideside':
240 <tr class="cb-line">
240 <tr class="cb-line">
241 <td class="cb-data cb-context"></td>
241 <td class="cb-data cb-context"></td>
242 <td class="cb-lineno cb-context"></td>
242 <td class="cb-lineno cb-context"></td>
243 <td class="cb-content cb-context"></td>
243 <td class="cb-content cb-context"></td>
244
244
245 <td class="cb-data cb-context"></td>
245 <td class="cb-data cb-context"></td>
246 <td class="cb-lineno cb-context"></td>
246 <td class="cb-lineno cb-context"></td>
247 <td class="cb-content cb-context">
247 <td class="cb-content cb-context">
248 ${inline_comments_container(comments)}
248 ${inline_comments_container(comments)}
249 </td>
249 </td>
250 </tr>
250 </tr>
251 %endif
251 %endif
252
252
253 % endfor
253 % endfor
254
254
255 </table>
255 </table>
256 </div>
256 </div>
257 %endfor
257 %endfor
258
258
259 ## outdated comments that are made for a file that has been deleted
259 ## outdated comments that are made for a file that has been deleted
260 % for filename, comments_dict in (deleted_files_comments or {}).items():
260 % for filename, comments_dict in (deleted_files_comments or {}).items():
261
261
262 <div class="filediffs filediff-outdated" style="display: none">
262 <div class="filediffs filediff-outdated" style="display: none">
263 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filename)}" type="checkbox">
263 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filename)}" type="checkbox">
264 <div class="filediff" data-f-path="${filename}" id="a_${h.FID('', filename)}">
264 <div class="filediff" data-f-path="${filename}" id="a_${h.FID('', filename)}">
265 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
265 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
266 <div class="filediff-collapse-indicator"></div>
266 <div class="filediff-collapse-indicator"></div>
267 <span class="pill">
267 <span class="pill">
268 ## file was deleted
268 ## file was deleted
269 <strong>${filename}</strong>
269 <strong>${filename}</strong>
270 </span>
270 </span>
271 <span class="pill-group" style="float: left">
271 <span class="pill-group" style="float: left">
272 ## file op, doesn't need translation
272 ## file op, doesn't need translation
273 <span class="pill" op="removed">removed in this version</span>
273 <span class="pill" op="removed">removed in this version</span>
274 </span>
274 </span>
275 <a class="pill filediff-anchor" href="#a_${h.FID('', filename)}">ΒΆ</a>
275 <a class="pill filediff-anchor" href="#a_${h.FID('', filename)}">ΒΆ</a>
276 <span class="pill-group" style="float: right">
276 <span class="pill-group" style="float: right">
277 <span class="pill" op="deleted">-${comments_dict['stats']}</span>
277 <span class="pill" op="deleted">-${comments_dict['stats']}</span>
278 </span>
278 </span>
279 </label>
279 </label>
280
280
281 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
281 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
282 <tr>
282 <tr>
283 % if c.diffmode == 'unified':
283 % if c.diffmode == 'unified':
284 <td></td>
284 <td></td>
285 %endif
285 %endif
286
286
287 <td></td>
287 <td></td>
288 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=5'}>
288 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=5'}>
289 ${_('File was deleted in this version, and outdated comments were made on it')}
289 ${_('File was deleted in this version, and outdated comments were made on it')}
290 </td>
290 </td>
291 </tr>
291 </tr>
292 %if c.diffmode == 'unified':
292 %if c.diffmode == 'unified':
293 <tr class="cb-line">
293 <tr class="cb-line">
294 <td class="cb-data cb-context"></td>
294 <td class="cb-data cb-context"></td>
295 <td class="cb-lineno cb-context"></td>
295 <td class="cb-lineno cb-context"></td>
296 <td class="cb-lineno cb-context"></td>
296 <td class="cb-lineno cb-context"></td>
297 <td class="cb-content cb-context">
297 <td class="cb-content cb-context">
298 ${inline_comments_container(comments_dict['comments'])}
298 ${inline_comments_container(comments_dict['comments'])}
299 </td>
299 </td>
300 </tr>
300 </tr>
301 %elif c.diffmode == 'sideside':
301 %elif c.diffmode == 'sideside':
302 <tr class="cb-line">
302 <tr class="cb-line">
303 <td class="cb-data cb-context"></td>
303 <td class="cb-data cb-context"></td>
304 <td class="cb-lineno cb-context"></td>
304 <td class="cb-lineno cb-context"></td>
305 <td class="cb-content cb-context"></td>
305 <td class="cb-content cb-context"></td>
306
306
307 <td class="cb-data cb-context"></td>
307 <td class="cb-data cb-context"></td>
308 <td class="cb-lineno cb-context"></td>
308 <td class="cb-lineno cb-context"></td>
309 <td class="cb-content cb-context">
309 <td class="cb-content cb-context">
310 ${inline_comments_container(comments_dict['comments'])}
310 ${inline_comments_container(comments_dict['comments'])}
311 </td>
311 </td>
312 </tr>
312 </tr>
313 %endif
313 %endif
314 </table>
314 </table>
315 </div>
315 </div>
316 </div>
316 </div>
317 % endfor
317 % endfor
318
318
319 </div>
319 </div>
320 </div>
320 </div>
321 </%def>
321 </%def>
322
322
323 <%def name="diff_ops(filediff)">
323 <%def name="diff_ops(filediff)">
324 <%
324 <%
325 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
325 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
326 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
326 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
327 %>
327 %>
328 <span class="pill">
328 <span class="pill">
329 %if filediff.source_file_path and filediff.target_file_path:
329 %if filediff.source_file_path and filediff.target_file_path:
330 %if filediff.source_file_path != filediff.target_file_path:
330 %if filediff.source_file_path != filediff.target_file_path:
331 ## file was renamed, or copied
331 ## file was renamed, or copied
332 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
332 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
333 <strong>${filediff.target_file_path}</strong> β¬… <del>${filediff.source_file_path}</del>
333 <strong>${filediff.target_file_path}</strong> β¬… <del>${filediff.source_file_path}</del>
334 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
334 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
335 <strong>${filediff.target_file_path}</strong> β¬… ${filediff.source_file_path}
335 <strong>${filediff.target_file_path}</strong> β¬… ${filediff.source_file_path}
336 %endif
336 %endif
337 %else:
337 %else:
338 ## file was modified
338 ## file was modified
339 <strong>${filediff.source_file_path}</strong>
339 <strong>${filediff.source_file_path}</strong>
340 %endif
340 %endif
341 %else:
341 %else:
342 %if filediff.source_file_path:
342 %if filediff.source_file_path:
343 ## file was deleted
343 ## file was deleted
344 <strong>${filediff.source_file_path}</strong>
344 <strong>${filediff.source_file_path}</strong>
345 %else:
345 %else:
346 ## file was added
346 ## file was added
347 <strong>${filediff.target_file_path}</strong>
347 <strong>${filediff.target_file_path}</strong>
348 %endif
348 %endif
349 %endif
349 %endif
350 </span>
350 </span>
351 <span class="pill-group" style="float: left">
351 <span class="pill-group" style="float: left">
352 %if filediff.limited_diff:
352 %if filediff.limited_diff:
353 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
353 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
354 %endif
354 %endif
355
355
356 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
356 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
357 <span class="pill" op="renamed">renamed</span>
357 <span class="pill" op="renamed">renamed</span>
358 %endif
358 %endif
359
359
360 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
360 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
361 <span class="pill" op="copied">copied</span>
361 <span class="pill" op="copied">copied</span>
362 %endif
362 %endif
363
363
364 %if NEW_FILENODE in filediff.patch['stats']['ops']:
364 %if NEW_FILENODE in filediff.patch['stats']['ops']:
365 <span class="pill" op="created">created</span>
365 <span class="pill" op="created">created</span>
366 %if filediff['target_mode'].startswith('120'):
366 %if filediff['target_mode'].startswith('120'):
367 <span class="pill" op="symlink">symlink</span>
367 <span class="pill" op="symlink">symlink</span>
368 %else:
368 %else:
369 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
369 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
370 %endif
370 %endif
371 %endif
371 %endif
372
372
373 %if DEL_FILENODE in filediff.patch['stats']['ops']:
373 %if DEL_FILENODE in filediff.patch['stats']['ops']:
374 <span class="pill" op="removed">removed</span>
374 <span class="pill" op="removed">removed</span>
375 %endif
375 %endif
376
376
377 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
377 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
378 <span class="pill" op="mode">
378 <span class="pill" op="mode">
379 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
379 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
380 </span>
380 </span>
381 %endif
381 %endif
382 </span>
382 </span>
383
383
384 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}">ΒΆ</a>
384 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}">ΒΆ</a>
385
385
386 <span class="pill-group" style="float: right">
386 <span class="pill-group" style="float: right">
387 %if BIN_FILENODE in filediff.patch['stats']['ops']:
387 %if BIN_FILENODE in filediff.patch['stats']['ops']:
388 <span class="pill" op="binary">binary</span>
388 <span class="pill" op="binary">binary</span>
389 %if MOD_FILENODE in filediff.patch['stats']['ops']:
389 %if MOD_FILENODE in filediff.patch['stats']['ops']:
390 <span class="pill" op="modified">modified</span>
390 <span class="pill" op="modified">modified</span>
391 %endif
391 %endif
392 %endif
392 %endif
393 %if filediff.patch['stats']['added']:
393 %if filediff.patch['stats']['added']:
394 <span class="pill" op="added">+${filediff.patch['stats']['added']}</span>
394 <span class="pill" op="added">+${filediff.patch['stats']['added']}</span>
395 %endif
395 %endif
396 %if filediff.patch['stats']['deleted']:
396 %if filediff.patch['stats']['deleted']:
397 <span class="pill" op="deleted">-${filediff.patch['stats']['deleted']}</span>
397 <span class="pill" op="deleted">-${filediff.patch['stats']['deleted']}</span>
398 %endif
398 %endif
399 </span>
399 </span>
400
400
401 </%def>
401 </%def>
402
402
403 <%def name="nice_mode(filemode)">
403 <%def name="nice_mode(filemode)">
404 ${filemode.startswith('100') and filemode[3:] or filemode}
404 ${filemode.startswith('100') and filemode[3:] or filemode}
405 </%def>
405 </%def>
406
406
407 <%def name="diff_menu(filediff, use_comments=False)">
407 <%def name="diff_menu(filediff, use_comments=False)">
408 <div class="filediff-menu">
408 <div class="filediff-menu">
409 %if filediff.diffset.source_ref:
409 %if filediff.diffset.source_ref:
410 %if filediff.operation in ['D', 'M']:
410 %if filediff.operation in ['D', 'M']:
411 <a
411 <a
412 class="tooltip"
412 class="tooltip"
413 href="${h.route_path('repo_files',repo_name=filediff.diffset.repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
413 href="${h.route_path('repo_files',repo_name=filediff.diffset.repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
414 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
414 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
415 >
415 >
416 ${_('Show file before')}
416 ${_('Show file before')}
417 </a> |
417 </a> |
418 %else:
418 %else:
419 <span
419 <span
420 class="tooltip"
420 class="tooltip"
421 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
421 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
422 >
422 >
423 ${_('Show file before')}
423 ${_('Show file before')}
424 </span> |
424 </span> |
425 %endif
425 %endif
426 %if filediff.operation in ['A', 'M']:
426 %if filediff.operation in ['A', 'M']:
427 <a
427 <a
428 class="tooltip"
428 class="tooltip"
429 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
429 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
430 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
430 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
431 >
431 >
432 ${_('Show file after')}
432 ${_('Show file after')}
433 </a> |
433 </a> |
434 %else:
434 %else:
435 <span
435 <span
436 class="tooltip"
436 class="tooltip"
437 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
437 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
438 >
438 >
439 ${_('Show file after')}
439 ${_('Show file after')}
440 </span> |
440 </span> |
441 %endif
441 %endif
442 <a
442 <a
443 class="tooltip"
443 class="tooltip"
444 title="${h.tooltip(_('Raw diff'))}"
444 title="${h.tooltip(_('Raw diff'))}"
445 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw'))}"
445 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw'))}"
446 >
446 >
447 ${_('Raw diff')}
447 ${_('Raw diff')}
448 </a> |
448 </a> |
449 <a
449 <a
450 class="tooltip"
450 class="tooltip"
451 title="${h.tooltip(_('Download diff'))}"
451 title="${h.tooltip(_('Download diff'))}"
452 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download'))}"
452 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download'))}"
453 >
453 >
454 ${_('Download diff')}
454 ${_('Download diff')}
455 </a>
455 </a>
456 % if use_comments:
456 % if use_comments:
457 |
457 |
458 % endif
458 % endif
459
459
460 ## 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)
460 ## 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)
461 %if hasattr(c, 'ignorews_url'):
461 %if hasattr(c, 'ignorews_url'):
462 ${c.ignorews_url(request.GET, h.FID('', filediff.patch['filename']))}
462 ${c.ignorews_url(request.GET, h.FID('', filediff.patch['filename']))}
463 %endif
463 %endif
464 %if hasattr(c, 'context_url'):
464 %if hasattr(c, 'context_url'):
465 ${c.context_url(request.GET, h.FID('', filediff.patch['filename']))}
465 ${c.context_url(request.GET, h.FID('', filediff.patch['filename']))}
466 %endif
466 %endif
467
467
468 %if use_comments:
468 %if use_comments:
469 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
469 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
470 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
470 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
471 </a>
471 </a>
472 %endif
472 %endif
473 %endif
473 %endif
474 </div>
474 </div>
475 </%def>
475 </%def>
476
476
477
477
478 <%def name="inline_comments_container(comments)">
478 <%def name="inline_comments_container(comments)">
479 <div class="inline-comments">
479 <div class="inline-comments">
480 %for comment in comments:
480 %for comment in comments:
481 ${commentblock.comment_block(comment, inline=True)}
481 ${commentblock.comment_block(comment, inline=True)}
482 %endfor
482 %endfor
483
483
484 % if comments and comments[-1].outdated:
484 % if comments and comments[-1].outdated:
485 <span class="btn btn-secondary cb-comment-add-button comment-outdated}"
485 <span class="btn btn-secondary cb-comment-add-button comment-outdated}"
486 style="display: none;}">
486 style="display: none;}">
487 ${_('Add another comment')}
487 ${_('Add another comment')}
488 </span>
488 </span>
489 % else:
489 % else:
490 <span onclick="return Rhodecode.comments.createComment(this)"
490 <span onclick="return Rhodecode.comments.createComment(this)"
491 class="btn btn-secondary cb-comment-add-button">
491 class="btn btn-secondary cb-comment-add-button">
492 ${_('Add another comment')}
492 ${_('Add another comment')}
493 </span>
493 </span>
494 % endif
494 % endif
495
495
496 </div>
496 </div>
497 </%def>
497 </%def>
498
498
499
499
500 <%def name="render_hunk_lines_sideside(hunk, use_comments=False)">
500 <%def name="render_hunk_lines_sideside(hunk, use_comments=False)">
501 %for i, line in enumerate(hunk.sideside):
501 %for i, line in enumerate(hunk.sideside):
502 <%
502 <%
503 old_line_anchor, new_line_anchor = None, None
503 old_line_anchor, new_line_anchor = None, None
504 if line.original.lineno:
504 if line.original.lineno:
505 old_line_anchor = diff_line_anchor(hunk.source_file_path, line.original.lineno, 'o')
505 old_line_anchor = diff_line_anchor(hunk.source_file_path, line.original.lineno, 'o')
506 if line.modified.lineno:
506 if line.modified.lineno:
507 new_line_anchor = diff_line_anchor(hunk.target_file_path, line.modified.lineno, 'n')
507 new_line_anchor = diff_line_anchor(hunk.target_file_path, line.modified.lineno, 'n')
508 %>
508 %>
509
509
510 <tr class="cb-line">
510 <tr class="cb-line">
511 <td class="cb-data ${action_class(line.original.action)}"
511 <td class="cb-data ${action_class(line.original.action)}"
512 data-line-number="${line.original.lineno}"
512 data-line-number="${line.original.lineno}"
513 >
513 >
514 <div>
514 <div>
515 %if line.original.comments:
515 %if line.original.comments:
516 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
516 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
517 %endif
517 %endif
518 </div>
518 </div>
519 </td>
519 </td>
520 <td class="cb-lineno ${action_class(line.original.action)}"
520 <td class="cb-lineno ${action_class(line.original.action)}"
521 data-line-number="${line.original.lineno}"
521 data-line-number="${line.original.lineno}"
522 %if old_line_anchor:
522 %if old_line_anchor:
523 id="${old_line_anchor}"
523 id="${old_line_anchor}"
524 %endif
524 %endif
525 >
525 >
526 %if line.original.lineno:
526 %if line.original.lineno:
527 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
527 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
528 %endif
528 %endif
529 </td>
529 </td>
530 <td class="cb-content ${action_class(line.original.action)}"
530 <td class="cb-content ${action_class(line.original.action)}"
531 data-line-number="o${line.original.lineno}"
531 data-line-number="o${line.original.lineno}"
532 >
532 >
533 %if use_comments and line.original.lineno:
533 %if use_comments and line.original.lineno:
534 ${render_add_comment_button()}
534 ${render_add_comment_button()}
535 %endif
535 %endif
536 <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
536 <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
537 %if use_comments and line.original.lineno and line.original.comments:
537 %if use_comments and line.original.lineno and line.original.comments:
538 ${inline_comments_container(line.original.comments)}
538 ${inline_comments_container(line.original.comments)}
539 %endif
539 %endif
540 </td>
540 </td>
541 <td class="cb-data ${action_class(line.modified.action)}"
541 <td class="cb-data ${action_class(line.modified.action)}"
542 data-line-number="${line.modified.lineno}"
542 data-line-number="${line.modified.lineno}"
543 >
543 >
544 <div>
544 <div>
545 %if line.modified.comments:
545 %if line.modified.comments:
546 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
546 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
547 %endif
547 %endif
548 </div>
548 </div>
549 </td>
549 </td>
550 <td class="cb-lineno ${action_class(line.modified.action)}"
550 <td class="cb-lineno ${action_class(line.modified.action)}"
551 data-line-number="${line.modified.lineno}"
551 data-line-number="${line.modified.lineno}"
552 %if new_line_anchor:
552 %if new_line_anchor:
553 id="${new_line_anchor}"
553 id="${new_line_anchor}"
554 %endif
554 %endif
555 >
555 >
556 %if line.modified.lineno:
556 %if line.modified.lineno:
557 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
557 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
558 %endif
558 %endif
559 </td>
559 </td>
560 <td class="cb-content ${action_class(line.modified.action)}"
560 <td class="cb-content ${action_class(line.modified.action)}"
561 data-line-number="n${line.modified.lineno}"
561 data-line-number="n${line.modified.lineno}"
562 >
562 >
563 %if use_comments and line.modified.lineno:
563 %if use_comments and line.modified.lineno:
564 ${render_add_comment_button()}
564 ${render_add_comment_button()}
565 %endif
565 %endif
566 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
566 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
567 %if use_comments and line.modified.lineno and line.modified.comments:
567 %if use_comments and line.modified.lineno and line.modified.comments:
568 ${inline_comments_container(line.modified.comments)}
568 ${inline_comments_container(line.modified.comments)}
569 %endif
569 %endif
570 </td>
570 </td>
571 </tr>
571 </tr>
572 %endfor
572 %endfor
573 </%def>
573 </%def>
574
574
575
575
576 <%def name="render_hunk_lines_unified(hunk, use_comments=False)">
576 <%def name="render_hunk_lines_unified(hunk, use_comments=False)">
577 %for old_line_no, new_line_no, action, content, comments in hunk.unified:
577 %for old_line_no, new_line_no, action, content, comments in hunk.unified:
578 <%
578 <%
579 old_line_anchor, new_line_anchor = None, None
579 old_line_anchor, new_line_anchor = None, None
580 if old_line_no:
580 if old_line_no:
581 old_line_anchor = diff_line_anchor(hunk.source_file_path, old_line_no, 'o')
581 old_line_anchor = diff_line_anchor(hunk.source_file_path, old_line_no, 'o')
582 if new_line_no:
582 if new_line_no:
583 new_line_anchor = diff_line_anchor(hunk.target_file_path, new_line_no, 'n')
583 new_line_anchor = diff_line_anchor(hunk.target_file_path, new_line_no, 'n')
584 %>
584 %>
585 <tr class="cb-line">
585 <tr class="cb-line">
586 <td class="cb-data ${action_class(action)}">
586 <td class="cb-data ${action_class(action)}">
587 <div>
587 <div>
588 %if comments:
588 %if comments:
589 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
589 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
590 %endif
590 %endif
591 </div>
591 </div>
592 </td>
592 </td>
593 <td class="cb-lineno ${action_class(action)}"
593 <td class="cb-lineno ${action_class(action)}"
594 data-line-number="${old_line_no}"
594 data-line-number="${old_line_no}"
595 %if old_line_anchor:
595 %if old_line_anchor:
596 id="${old_line_anchor}"
596 id="${old_line_anchor}"
597 %endif
597 %endif
598 >
598 >
599 %if old_line_anchor:
599 %if old_line_anchor:
600 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
600 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
601 %endif
601 %endif
602 </td>
602 </td>
603 <td class="cb-lineno ${action_class(action)}"
603 <td class="cb-lineno ${action_class(action)}"
604 data-line-number="${new_line_no}"
604 data-line-number="${new_line_no}"
605 %if new_line_anchor:
605 %if new_line_anchor:
606 id="${new_line_anchor}"
606 id="${new_line_anchor}"
607 %endif
607 %endif
608 >
608 >
609 %if new_line_anchor:
609 %if new_line_anchor:
610 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
610 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
611 %endif
611 %endif
612 </td>
612 </td>
613 <td class="cb-content ${action_class(action)}"
613 <td class="cb-content ${action_class(action)}"
614 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
614 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
615 >
615 >
616 %if use_comments:
616 %if use_comments:
617 ${render_add_comment_button()}
617 ${render_add_comment_button()}
618 %endif
618 %endif
619 <span class="cb-code">${action} ${content or '' | n}</span>
619 <span class="cb-code">${action} ${content or '' | n}</span>
620 %if use_comments and comments:
620 %if use_comments and comments:
621 ${inline_comments_container(comments)}
621 ${inline_comments_container(comments)}
622 %endif
622 %endif
623 </td>
623 </td>
624 </tr>
624 </tr>
625 %endfor
625 %endfor
626 </%def>
626 </%def>
627
627
628 <%def name="render_add_comment_button()">
628 <%def name="render_add_comment_button()">
629 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
629 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
630 <span><i class="icon-comment"></i></span>
630 <span><i class="icon-comment"></i></span>
631 </button>
631 </button>
632 </%def>
632 </%def>
633
633
634 <%def name="render_diffset_menu()">
634 <%def name="render_diffset_menu()">
635
635
636 <div class="diffset-menu clearinner">
636 <div class="diffset-menu clearinner">
637 <div class="pull-right">
637 <div class="pull-right">
638 <div class="btn-group">
638 <div class="btn-group">
639
639
640 <a
640 <a
641 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
641 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
642 title="${h.tooltip(_('View side by side'))}"
642 title="${h.tooltip(_('View side by side'))}"
643 href="${h.url_replace(diffmode='sideside')}">
643 href="${h.url_replace(diffmode='sideside')}">
644 <span>${_('Side by Side')}</span>
644 <span>${_('Side by Side')}</span>
645 </a>
645 </a>
646 <a
646 <a
647 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
647 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
648 title="${h.tooltip(_('View unified'))}" href="${h.url_replace(diffmode='unified')}">
648 title="${h.tooltip(_('View unified'))}" href="${h.url_replace(diffmode='unified')}">
649 <span>${_('Unified')}</span>
649 <span>${_('Unified')}</span>
650 </a>
650 </a>
651 </div>
651 </div>
652 </div>
652 </div>
653
653
654 <div class="pull-left">
654 <div class="pull-left">
655 <div class="btn-group">
655 <div class="btn-group">
656 <a
656 <a
657 class="btn"
657 class="btn"
658 href="#"
658 href="#"
659 onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a>
659 onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a>
660 <a
660 <a
661 class="btn"
661 class="btn"
662 href="#"
662 href="#"
663 onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a>
663 onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a>
664 <a
664 <a
665 class="btn"
665 class="btn"
666 href="#"
666 href="#"
667 onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a>
667 onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a>
668 </div>
668 </div>
669 </div>
669 </div>
670 </div>
670 </div>
671 </%def>
671 </%def>
@@ -1,114 +1,114 b''
1 ## Changesets table !
1 ## Changesets table !
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
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', repo_name=c.repo_name, revision=c.ancestor)}">
6 <a href="${h.url('changeset_home', repo_name=c.repo_name, revision=c.ancestor)}">
7 ${h.short_id(c.ancestor)}
7 ${h.short_id(c.ancestor)}
8 </a>. ${_('Compare was calculated based on this shared commit.')}
8 </a>. ${_('Compare was calculated based on this shared commit.')}
9 <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}">
9 <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}">
10 </div>
10 </div>
11 %endif
11 %endif
12
12
13 <div class="container">
13 <div class="container">
14 <input type="hidden" name="__start__" value="revisions:sequence">
14 <input type="hidden" name="__start__" value="revisions:sequence">
15 <table class="rctable compare_view_commits">
15 <table class="rctable compare_view_commits">
16 <tr>
16 <tr>
17 <th>${_('Time')}</th>
17 <th>${_('Time')}</th>
18 <th>${_('Author')}</th>
18 <th>${_('Author')}</th>
19 <th>${_('Commit')}</th>
19 <th>${_('Commit')}</th>
20 <th></th>
20 <th></th>
21 <th>${_('Description')}</th>
21 <th>${_('Description')}</th>
22 </tr>
22 </tr>
23 %for commit in c.commit_ranges:
23 %for commit in c.commit_ranges:
24 <tr id="row-${commit.raw_id}"
24 <tr id="row-${commit.raw_id}"
25 commit_id="${commit.raw_id}"
25 commit_id="${commit.raw_id}"
26 class="compare_select"
26 class="compare_select"
27 style="${'display: none' if c.collapse_all_commits else ''}"
27 style="${'display: none' if c.collapse_all_commits else ''}"
28 >
28 >
29 <td class="td-time">
29 <td class="td-time">
30 ${h.age_component(commit.date)}
30 ${h.age_component(commit.date)}
31 </td>
31 </td>
32 <td class="td-user">
32 <td class="td-user">
33 ${base.gravatar_with_user(commit.author, 16)}
33 ${base.gravatar_with_user(commit.author, 16)}
34 </td>
34 </td>
35 <td class="td-hash">
35 <td class="td-hash">
36 <code>
36 <code>
37 <a href="${h.url('changeset_home',
37 <a href="${h.url('changeset_home',
38 repo_name=c.target_repo.repo_name,
38 repo_name=c.target_repo.repo_name,
39 revision=commit.raw_id)}">
39 revision=commit.raw_id)}">
40 r${commit.revision}:${h.short_id(commit.raw_id)}
40 r${commit.revision}:${h.short_id(commit.raw_id)}
41 </a>
41 </a>
42 ${h.hidden('revisions',commit.raw_id)}
42 ${h.hidden('revisions',commit.raw_id)}
43 </code>
43 </code>
44 </td>
44 </td>
45 <td class="expand_commit"
45 <td class="expand_commit"
46 data-commit-id="${commit.raw_id}"
46 data-commit-id="${commit.raw_id}"
47 title="${_( 'Expand commit message')}"
47 title="${_( 'Expand commit message')}"
48 >
48 >
49 <div class="show_more_col">
49 <div class="show_more_col">
50 <i class="show_more"></i>
50 <i class="show_more"></i>
51 </div>
51 </div>
52 </td>
52 </td>
53 <td class="mid td-description">
53 <td class="mid td-description">
54 <div class="log-container truncate-wrap">
54 <div class="log-container truncate-wrap">
55 <div
55 <div
56 id="c-${commit.raw_id}"
56 id="c-${commit.raw_id}"
57 class="message truncate"
57 class="message truncate"
58 data-message-raw="${commit.message}"
58 data-message-raw="${commit.message}"
59 >
59 >
60 ${h.urlify_commit_message(commit.message, c.repo_name)}
60 ${h.urlify_commit_message(commit.message, c.repo_name)}
61 </div>
61 </div>
62 </div>
62 </div>
63 </td>
63 </td>
64 </tr>
64 </tr>
65 %endfor
65 %endfor
66 <tr class="compare_select_hidden" style="${'' if c.collapse_all_commits else 'display: none'}">
66 <tr class="compare_select_hidden" style="${'' if c.collapse_all_commits else 'display: none'}">
67 <td colspan="5">
67 <td colspan="5">
68 ${ungettext('%s commit hidden','%s commits hidden', len(c.commit_ranges)) % len(c.commit_ranges)},
68 ${_ungettext('%s commit hidden','%s commits hidden', len(c.commit_ranges)) % len(c.commit_ranges)},
69 <a href="#" onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">${ungettext('show it','show them', len(c.commit_ranges))}</a>
69 <a href="#" onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">${_ungettext('show it','show them', len(c.commit_ranges))}</a>
70 </td>
70 </td>
71 </tr>
71 </tr>
72 % if not c.commit_ranges:
72 % if not c.commit_ranges:
73 <tr class="compare_select">
73 <tr class="compare_select">
74 <td colspan="5">
74 <td colspan="5">
75 ${_('No commits in this compare')}
75 ${_('No commits in this compare')}
76 </td>
76 </td>
77 </tr>
77 </tr>
78 % endif
78 % endif
79 </table>
79 </table>
80 <input type="hidden" name="__end__" value="revisions:sequence">
80 <input type="hidden" name="__end__" value="revisions:sequence">
81
81
82 </div>
82 </div>
83
83
84 <script>
84 <script>
85 $('.expand_commit').on('click',function(e){
85 $('.expand_commit').on('click',function(e){
86 var target_expand = $(this);
86 var target_expand = $(this);
87 var cid = target_expand.data('commitId');
87 var cid = target_expand.data('commitId');
88
88
89 // ## TODO: dan: extract styles into css, and just toggleClass('open') here
89 // ## TODO: dan: extract styles into css, and just toggleClass('open') here
90 if (target_expand.hasClass('open')){
90 if (target_expand.hasClass('open')){
91 $('#c-'+cid).css({
91 $('#c-'+cid).css({
92 'height': '1.5em',
92 'height': '1.5em',
93 'white-space': 'nowrap',
93 'white-space': 'nowrap',
94 'text-overflow': 'ellipsis',
94 'text-overflow': 'ellipsis',
95 'overflow':'hidden'
95 'overflow':'hidden'
96 });
96 });
97 target_expand.removeClass('open');
97 target_expand.removeClass('open');
98 }
98 }
99 else {
99 else {
100 $('#c-'+cid).css({
100 $('#c-'+cid).css({
101 'height': 'auto',
101 'height': 'auto',
102 'white-space': 'pre-line',
102 'white-space': 'pre-line',
103 'text-overflow': 'initial',
103 'text-overflow': 'initial',
104 'overflow':'visible'
104 'overflow':'visible'
105 });
105 });
106 target_expand.addClass('open');
106 target_expand.addClass('open');
107 }
107 }
108 });
108 });
109
109
110 $('.compare_select').on('click',function(e){
110 $('.compare_select').on('click',function(e){
111 var cid = $(this).attr('commit_id');
111 var cid = $(this).attr('commit_id');
112 $('#row-'+cid).toggleClass('hl', !$('#row-'+cid).hasClass('hl'));
112 $('#row-'+cid).toggleClass('hl', !$('#row-'+cid).hasClass('hl'));
113 });
113 });
114 </script>
114 </script>
@@ -1,333 +1,333 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
3 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 %if c.compare_home:
6 %if c.compare_home:
7 ${_('%s Compare') % c.repo_name}
7 ${_('%s Compare') % c.repo_name}
8 %else:
8 %else:
9 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
9 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
10 %endif
10 %endif
11 %if c.rhodecode_name:
11 %if c.rhodecode_name:
12 &middot; ${h.branding(c.rhodecode_name)}
12 &middot; ${h.branding(c.rhodecode_name)}
13 %endif
13 %endif
14 </%def>
14 </%def>
15
15
16 <%def name="breadcrumbs_links()">
16 <%def name="breadcrumbs_links()">
17 ${ungettext('%s commit','%s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
17 ${_ungettext('%s commit','%s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
18 </%def>
18 </%def>
19
19
20 <%def name="menu_bar_nav()">
20 <%def name="menu_bar_nav()">
21 ${self.menu_items(active='repositories')}
21 ${self.menu_items(active='repositories')}
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_subnav()">
24 <%def name="menu_bar_subnav()">
25 ${self.repo_menu(active='compare')}
25 ${self.repo_menu(active='compare')}
26 </%def>
26 </%def>
27
27
28 <%def name="main()">
28 <%def name="main()">
29 <script type="text/javascript">
29 <script type="text/javascript">
30 // set fake commitId on this commit-range page
30 // set fake commitId on this commit-range page
31 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
31 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
32 </script>
32 </script>
33
33
34 <div class="box">
34 <div class="box">
35 <div class="title">
35 <div class="title">
36 ${self.repo_page_title(c.rhodecode_db_repo)}
36 ${self.repo_page_title(c.rhodecode_db_repo)}
37 </div>
37 </div>
38
38
39 <div class="summary changeset">
39 <div class="summary changeset">
40 <div class="summary-detail">
40 <div class="summary-detail">
41 <div class="summary-detail-header">
41 <div class="summary-detail-header">
42 <span class="breadcrumbs files_location">
42 <span class="breadcrumbs files_location">
43 <h4>
43 <h4>
44 ${_('Compare Commits')}
44 ${_('Compare Commits')}
45 % if c.file_path:
45 % if c.file_path:
46 ${_('for file')} <a href="#${'a_' + h.FID('',c.file_path)}">${c.file_path}</a>
46 ${_('for file')} <a href="#${'a_' + h.FID('',c.file_path)}">${c.file_path}</a>
47 % endif
47 % endif
48
48
49 % if c.commit_ranges:
49 % if c.commit_ranges:
50 <code>
50 <code>
51 r${c.source_commit.revision}:${h.short_id(c.source_commit.raw_id)}...r${c.target_commit.revision}:${h.short_id(c.target_commit.raw_id)}
51 r${c.source_commit.revision}:${h.short_id(c.source_commit.raw_id)}...r${c.target_commit.revision}:${h.short_id(c.target_commit.raw_id)}
52 </code>
52 </code>
53 % endif
53 % endif
54 </h4>
54 </h4>
55 </span>
55 </span>
56 </div>
56 </div>
57
57
58 <div class="fieldset">
58 <div class="fieldset">
59 <div class="left-label">
59 <div class="left-label">
60 ${_('Target')}:
60 ${_('Target')}:
61 </div>
61 </div>
62 <div class="right-content">
62 <div class="right-content">
63 <div>
63 <div>
64 <div class="code-header" >
64 <div class="code-header" >
65 <div class="compare_header">
65 <div class="compare_header">
66 ## The hidden elements are replaced with a select2 widget
66 ## The hidden elements are replaced with a select2 widget
67 ${h.hidden('compare_source')}
67 ${h.hidden('compare_source')}
68 </div>
68 </div>
69 </div>
69 </div>
70 </div>
70 </div>
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74 <div class="fieldset">
74 <div class="fieldset">
75 <div class="left-label">
75 <div class="left-label">
76 ${_('Source')}:
76 ${_('Source')}:
77 </div>
77 </div>
78 <div class="right-content">
78 <div class="right-content">
79 <div>
79 <div>
80 <div class="code-header" >
80 <div class="code-header" >
81 <div class="compare_header">
81 <div class="compare_header">
82 ## The hidden elements are replaced with a select2 widget
82 ## The hidden elements are replaced with a select2 widget
83 ${h.hidden('compare_target')}
83 ${h.hidden('compare_target')}
84 </div>
84 </div>
85 </div>
85 </div>
86 </div>
86 </div>
87 </div>
87 </div>
88 </div>
88 </div>
89
89
90 <div class="fieldset">
90 <div class="fieldset">
91 <div class="left-label">
91 <div class="left-label">
92 ${_('Actions')}:
92 ${_('Actions')}:
93 </div>
93 </div>
94 <div class="right-content">
94 <div class="right-content">
95 <div>
95 <div>
96 <div class="code-header" >
96 <div class="code-header" >
97 <div class="compare_header">
97 <div class="compare_header">
98
98
99 <div class="compare-buttons">
99 <div class="compare-buttons">
100 % if c.compare_home:
100 % if c.compare_home:
101 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
101 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
102
102
103 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
103 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
104 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
104 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
105 <div id="changeset_compare_view_content">
105 <div id="changeset_compare_view_content">
106 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
106 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
107 </div>
107 </div>
108
108
109 % elif c.preview_mode:
109 % elif c.preview_mode:
110 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a>
110 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a>
111 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
111 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
112 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
112 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
113
113
114 % else:
114 % else:
115 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
115 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
116 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a>
116 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a>
117
117
118 ## allow comment only if there are commits to comment on
118 ## allow comment only if there are commits to comment on
119 % if c.diffset and c.diffset.files and c.commit_ranges:
119 % if c.diffset and c.diffset.files and c.commit_ranges:
120 <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a>
120 <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a>
121 % else:
121 % else:
122 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
122 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
123 % endif
123 % endif
124 % endif
124 % endif
125 </div>
125 </div>
126 </div>
126 </div>
127 </div>
127 </div>
128 </div>
128 </div>
129 </div>
129 </div>
130 </div>
130 </div>
131
131
132 <%doc>
132 <%doc>
133 ##TODO(marcink): implement this and diff menus
133 ##TODO(marcink): implement this and diff menus
134 <div class="fieldset">
134 <div class="fieldset">
135 <div class="left-label">
135 <div class="left-label">
136 ${_('Diff options')}:
136 ${_('Diff options')}:
137 </div>
137 </div>
138 <div class="right-content">
138 <div class="right-content">
139 <div class="diff-actions">
139 <div class="diff-actions">
140 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
140 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
141 ${_('Raw Diff')}
141 ${_('Raw Diff')}
142 </a>
142 </a>
143 |
143 |
144 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
144 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
145 ${_('Patch Diff')}
145 ${_('Patch Diff')}
146 </a>
146 </a>
147 |
147 |
148 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
148 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
149 ${_('Download Diff')}
149 ${_('Download Diff')}
150 </a>
150 </a>
151 </div>
151 </div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </%doc>
154 </%doc>
155
155
156 ## commit status form
156 ## commit status form
157 <div class="fieldset" id="compare_changeset_status" style="display: none; margin-bottom: -80px;">
157 <div class="fieldset" id="compare_changeset_status" style="display: none; margin-bottom: -80px;">
158 <div class="left-label">
158 <div class="left-label">
159 ${_('Commit status')}:
159 ${_('Commit status')}:
160 </div>
160 </div>
161 <div class="right-content">
161 <div class="right-content">
162 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
162 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
163 ## main comment form and it status
163 ## main comment form and it status
164 <%
164 <%
165 def revs(_revs):
165 def revs(_revs):
166 form_inputs = []
166 form_inputs = []
167 for cs in _revs:
167 for cs in _revs:
168 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
168 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
169 form_inputs.append(tmpl)
169 form_inputs.append(tmpl)
170 return form_inputs
170 return form_inputs
171 %>
171 %>
172 <div>
172 <div>
173 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
173 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
174 </div>
174 </div>
175 </div>
175 </div>
176 </div>
176 </div>
177
177
178 </div> <!-- end summary-detail -->
178 </div> <!-- end summary-detail -->
179 </div> <!-- end summary -->
179 </div> <!-- end summary -->
180
180
181 ## use JS script to load it quickly before potentially large diffs render long time
181 ## use JS script to load it quickly before potentially large diffs render long time
182 ## this prevents from situation when large diffs block rendering of select2 fields
182 ## this prevents from situation when large diffs block rendering of select2 fields
183 <script type="text/javascript">
183 <script type="text/javascript">
184
184
185 var cache = {};
185 var cache = {};
186
186
187 var formatSelection = function(repoName){
187 var formatSelection = function(repoName){
188 return function(data, container, escapeMarkup) {
188 return function(data, container, escapeMarkup) {
189 var selection = data ? this.text(data) : "";
189 var selection = data ? this.text(data) : "";
190 return escapeMarkup('{0}@{1}'.format(repoName, selection));
190 return escapeMarkup('{0}@{1}'.format(repoName, selection));
191 }
191 }
192 };
192 };
193
193
194 var feedCompareData = function(query, cachedValue){
194 var feedCompareData = function(query, cachedValue){
195 var data = {results: []};
195 var data = {results: []};
196 //filter results
196 //filter results
197 $.each(cachedValue.results, function() {
197 $.each(cachedValue.results, function() {
198 var section = this.text;
198 var section = this.text;
199 var children = [];
199 var children = [];
200 $.each(this.children, function() {
200 $.each(this.children, function() {
201 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
201 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
202 children.push({
202 children.push({
203 'id': this.id,
203 'id': this.id,
204 'text': this.text,
204 'text': this.text,
205 'type': this.type
205 'type': this.type
206 })
206 })
207 }
207 }
208 });
208 });
209 data.results.push({
209 data.results.push({
210 'text': section,
210 'text': section,
211 'children': children
211 'children': children
212 })
212 })
213 });
213 });
214 //push the typed in changeset
214 //push the typed in changeset
215 data.results.push({
215 data.results.push({
216 'text': _gettext('specify commit'),
216 'text': _gettext('specify commit'),
217 'children': [{
217 'children': [{
218 'id': query.term,
218 'id': query.term,
219 'text': query.term,
219 'text': query.term,
220 'type': 'rev'
220 'type': 'rev'
221 }]
221 }]
222 });
222 });
223 query.callback(data);
223 query.callback(data);
224 };
224 };
225
225
226 var loadCompareData = function(repoName, query, cache){
226 var loadCompareData = function(repoName, query, cache){
227 $.ajax({
227 $.ajax({
228 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
228 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
229 data: {},
229 data: {},
230 dataType: 'json',
230 dataType: 'json',
231 type: 'GET',
231 type: 'GET',
232 success: function(data) {
232 success: function(data) {
233 cache[repoName] = data;
233 cache[repoName] = data;
234 query.callback({results: data.results});
234 query.callback({results: data.results});
235 }
235 }
236 })
236 })
237 };
237 };
238
238
239 var enable_fields = ${"false" if c.preview_mode else "true"};
239 var enable_fields = ${"false" if c.preview_mode else "true"};
240 $("#compare_source").select2({
240 $("#compare_source").select2({
241 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
241 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
242 containerCssClass: "drop-menu",
242 containerCssClass: "drop-menu",
243 dropdownCssClass: "drop-menu-dropdown",
243 dropdownCssClass: "drop-menu-dropdown",
244 formatSelection: formatSelection("${c.source_repo.repo_name}"),
244 formatSelection: formatSelection("${c.source_repo.repo_name}"),
245 dropdownAutoWidth: true,
245 dropdownAutoWidth: true,
246 query: function(query) {
246 query: function(query) {
247 var repoName = '${c.source_repo.repo_name}';
247 var repoName = '${c.source_repo.repo_name}';
248 var cachedValue = cache[repoName];
248 var cachedValue = cache[repoName];
249
249
250 if (cachedValue){
250 if (cachedValue){
251 feedCompareData(query, cachedValue);
251 feedCompareData(query, cachedValue);
252 }
252 }
253 else {
253 else {
254 loadCompareData(repoName, query, cache);
254 loadCompareData(repoName, query, cache);
255 }
255 }
256 }
256 }
257 }).select2("enable", enable_fields);
257 }).select2("enable", enable_fields);
258
258
259 $("#compare_target").select2({
259 $("#compare_target").select2({
260 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
260 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
261 dropdownAutoWidth: true,
261 dropdownAutoWidth: true,
262 containerCssClass: "drop-menu",
262 containerCssClass: "drop-menu",
263 dropdownCssClass: "drop-menu-dropdown",
263 dropdownCssClass: "drop-menu-dropdown",
264 formatSelection: formatSelection("${c.target_repo.repo_name}"),
264 formatSelection: formatSelection("${c.target_repo.repo_name}"),
265 query: function(query) {
265 query: function(query) {
266 var repoName = '${c.target_repo.repo_name}';
266 var repoName = '${c.target_repo.repo_name}';
267 var cachedValue = cache[repoName];
267 var cachedValue = cache[repoName];
268
268
269 if (cachedValue){
269 if (cachedValue){
270 feedCompareData(query, cachedValue);
270 feedCompareData(query, cachedValue);
271 }
271 }
272 else {
272 else {
273 loadCompareData(repoName, query, cache);
273 loadCompareData(repoName, query, cache);
274 }
274 }
275 }
275 }
276 }).select2("enable", enable_fields);
276 }).select2("enable", enable_fields);
277 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
277 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
278 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
278 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
279
279
280 $('#compare_revs').on('click', function(e) {
280 $('#compare_revs').on('click', function(e) {
281 var source = $('#compare_source').select2('data') || initial_compare_source;
281 var source = $('#compare_source').select2('data') || initial_compare_source;
282 var target = $('#compare_target').select2('data') || initial_compare_target;
282 var target = $('#compare_target').select2('data') || initial_compare_target;
283 if (source && target) {
283 if (source && target) {
284 var url_data = {
284 var url_data = {
285 repo_name: "${c.repo_name}",
285 repo_name: "${c.repo_name}",
286 source_ref: source.id,
286 source_ref: source.id,
287 source_ref_type: source.type,
287 source_ref_type: source.type,
288 target_ref: target.id,
288 target_ref: target.id,
289 target_ref_type: target.type
289 target_ref_type: target.type
290 };
290 };
291 window.location = pyroutes.url('compare_url', url_data);
291 window.location = pyroutes.url('compare_url', url_data);
292 }
292 }
293 });
293 });
294 $('#compare_changeset_status_toggle').on('click', function(e) {
294 $('#compare_changeset_status_toggle').on('click', function(e) {
295 $('#compare_changeset_status').toggle();
295 $('#compare_changeset_status').toggle();
296 });
296 });
297
297
298 </script>
298 </script>
299
299
300 ## table diff data
300 ## table diff data
301 <div class="table">
301 <div class="table">
302
302
303
303
304 % if not c.compare_home:
304 % if not c.compare_home:
305 <div id="changeset_compare_view_content">
305 <div id="changeset_compare_view_content">
306 <div class="pull-left">
306 <div class="pull-left">
307 <div class="btn-group">
307 <div class="btn-group">
308 <a
308 <a
309 class="btn"
309 class="btn"
310 href="#"
310 href="#"
311 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
311 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
312 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
312 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
313 </a>
313 </a>
314 <a
314 <a
315 class="btn"
315 class="btn"
316 href="#"
316 href="#"
317 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
317 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
318 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
318 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
319 </a>
319 </a>
320 </div>
320 </div>
321 </div>
321 </div>
322 <div style="padding:0 10px 10px 0px" class="pull-left"></div>
322 <div style="padding:0 10px 10px 0px" class="pull-left"></div>
323 ## commit compare generated below
323 ## commit compare generated below
324 <%include file="compare_commits.mako"/>
324 <%include file="compare_commits.mako"/>
325 ${cbdiffs.render_diffset_menu()}
325 ${cbdiffs.render_diffset_menu()}
326 ${cbdiffs.render_diffset(c.diffset)}
326 ${cbdiffs.render_diffset(c.diffset)}
327 </div>
327 </div>
328 % endif
328 % endif
329
329
330 </div>
330 </div>
331 </div>
331 </div>
332
332
333 </%def> No newline at end of file
333 </%def>
@@ -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.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
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.route_path('repo_summary',repo_name=repo_name)}">
12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',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.route_path('repo_changelog',repo_name=repo_name)}">
17 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',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.route_path('repo_files:default_commit',repo_name=repo_name)}">
22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',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.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',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.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
67 <a href="${h.route_path('repo_summary',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="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
95 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
96 %else:
96 %else:
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('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="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
103 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
104 %else:
104 %else:
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('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.tooltip(h.author_string(email))}">
110 <div class="rc-user tooltip" title="${h.tooltip(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.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
118 <a href="${h.route_path('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.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
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="${h.tooltip(repo_state)}">invalid</div>
137 <div class="tag alert2" title="${h.tooltip(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.route_path('repo_group_home', repo_group_name=repo_group_name)}">
149 <a href="${h.route_path('repo_group_home', repo_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.route_path('repo_group_home', repo_group_name=repo_group_name)}">
163 <a href="${h.route_path('repo_group_home', repo_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.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
244 <a href="${h.route_path('gist_show', 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.route_path('repo_summary',repo_name=repo_name))}
285 ${h.link_to(repo_name,h.route_path('repo_summary',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"></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.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
302 <a href="${h.route_path('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,197 +1,197 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.route_path('debug_style_home'))}
5 ${h.link_to(_('Style'), h.route_path('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 ${self.sidebar()}
18 ${self.sidebar()}
19
19
20 <div class="main-content">
20 <div class="main-content">
21
21
22 <h2>Buttons</h2>
22 <h2>Buttons</h2>
23
23
24 <p>
24 <p>
25 Form buttons in various sizes. Buttons are always capitalised.
25 Form buttons in various sizes. Buttons are always capitalised.
26 Use the following classes:
26 Use the following classes:
27 </p>
27 </p>
28
28
29 <ul>
29 <ul>
30 ## TODO: lisa: Are we actually using three sizes of buttons??
30 ## TODO: lisa: Are we actually using three sizes of buttons??
31 <li><code>.btn-lg</code> for large buttons</li>
31 <li><code>.btn-lg</code> for large buttons</li>
32 <li><code>.btn-sm</code> for small buttons</li>
32 <li><code>.btn-sm</code> for small buttons</li>
33 <li><code>.btn-xs</code> for xtra small buttons</li>
33 <li><code>.btn-xs</code> for xtra small buttons</li>
34 </ul>
34 </ul>
35
35
36 <p>Note that <code>.btn-mini</code> is supported for legacy reasons.</p>
36 <p>Note that <code>.btn-mini</code> is supported for legacy reasons.</p>
37
37
38 <div class="bs-example">
38 <div class="bs-example">
39 ## TODO: johbo: Should also work without the form element
39 ## TODO: johbo: Should also work without the form element
40 <form method='post' action=''>
40 <form method='post' action=''>
41 <div class='form'>
41 <div class='form'>
42
42
43 <div class="buttons">
43 <div class="buttons">
44 <input type="submit" value="Save .btn-lg" id="example_save" class="btn btn-lg">
44 <input type="submit" value="Save .btn-lg" id="example_save" class="btn btn-lg">
45 <input type="reset" value="Reset" id="example_reset" class="btn btn-lg">
45 <input type="reset" value="Reset" id="example_reset" class="btn btn-lg">
46 <button class="btn btn-lg">Large</button>
46 <button class="btn btn-lg">Large</button>
47 <a class="btn btn-lg" href="#">A link as button</a>
47 <a class="btn btn-lg" href="#">A link as button</a>
48 </div>
48 </div>
49
49
50 <div class="buttons">
50 <div class="buttons">
51 <input type="submit" value="Save" id="example_save" class="btn">
51 <input type="submit" value="Save" id="example_save" class="btn">
52 <input type="reset" value="Reset" id="example_reset" class="btn">
52 <input type="reset" value="Reset" id="example_reset" class="btn">
53 <button class="btn">Normal</button>
53 <button class="btn">Normal</button>
54 <button class="btn btn-danger">Normal</button>
54 <button class="btn btn-danger">Normal</button>
55 <a class="btn" href="#">A link as button</a>
55 <a class="btn" href="#">A link as button</a>
56 </div>
56 </div>
57
57
58 <div class="buttons">
58 <div class="buttons">
59 <input type="submit" value="Save .btn-sm" id="example_save" class="btn btn-sm">
59 <input type="submit" value="Save .btn-sm" id="example_save" class="btn btn-sm">
60 <input type="reset" value="Reset" id="example_reset" class="btn btn-sm">
60 <input type="reset" value="Reset" id="example_reset" class="btn btn-sm">
61 <button class="btn btn-sm">Small</button>
61 <button class="btn btn-sm">Small</button>
62 <button class="btn btn-sm btn-danger">Small</button>
62 <button class="btn btn-sm btn-danger">Small</button>
63 <a class="btn btn-sm" href="#">A link as button</a>
63 <a class="btn btn-sm" href="#">A link as button</a>
64 </div>
64 </div>
65
65
66 <div class="buttons">
66 <div class="buttons">
67 <input type="submit" value="Save .btn-xs" id="example_save" class="btn btn-xs">
67 <input type="submit" value="Save .btn-xs" id="example_save" class="btn btn-xs">
68 <input type="reset" value="Reset" id="example_reset" class="btn btn-xs">
68 <input type="reset" value="Reset" id="example_reset" class="btn btn-xs">
69 <button class="btn btn-xs">XSmall</button>
69 <button class="btn btn-xs">XSmall</button>
70 <button class="btn btn-xs btn-danger">XSmall</button>
70 <button class="btn btn-xs btn-danger">XSmall</button>
71 <a class="btn btn-xs" href="#">A link as button</a>
71 <a class="btn btn-xs" href="#">A link as button</a>
72 </div>
72 </div>
73
73
74 <div class="buttons">
74 <div class="buttons">
75 <input type="submit" value="Save .btn-mini" id="example_save" class="btn btn-mini">
75 <input type="submit" value="Save .btn-mini" id="example_save" class="btn btn-mini">
76 <input type="reset" value="Reset" id="example_reset" class="btn btn-mini">
76 <input type="reset" value="Reset" id="example_reset" class="btn btn-mini">
77 </div>
77 </div>
78
78
79 <div class="buttons">
79 <div class="buttons">
80 Buttons of style <code>.btn-link</code>:
80 Buttons of style <code>.btn-link</code>:
81 <input type="reset" value="Reset" id="example_reset" class="btn btn-link">
81 <input type="reset" value="Reset" id="example_reset" class="btn btn-link">
82 <button class="btn btn-link">Edit</button>
82 <button class="btn btn-link">Edit</button>
83 <button class="btn btn-danger btn-link">Delete</button>
83 <button class="btn btn-danger btn-link">Delete</button>
84 </div>
84 </div>
85 </div>
85 </div>
86 </form>
86 </form>
87 </div>
87 </div>
88
88
89
89
90 <h2>Buttons as Links</h2>
90 <h2>Buttons as Links</h2>
91 <p>
91 <p>
92 Most of our Edit/Delete buttons come in the following form.
92 Most of our Edit/Delete buttons come in the following form.
93 Inside of a table, these are "action buttons", and while an
93 Inside of a table, these are "action buttons", and while an
94 Edit <em>link</em> is a typical blue link, a Delete <em>button</em>
94 Edit <em>link</em> is a typical blue link, a Delete <em>button</em>
95 is red as per the 'btn-danger' styling and use <code>.btn-link</code>.
95 is red as per the 'btn-danger' styling and use <code>.btn-link</code>.
96 </p>
96 </p>
97 <p>
97 <p>
98 We use "Delete" when the thing being deleted cannot be undone;
98 We use "Delete" when the thing being deleted cannot be undone;
99 "Reset", and "Revoke" are used where applicable.
99 "Reset", and "Revoke" are used where applicable.
100 </p>
100 </p>
101 <p>
101 <p>
102 Note: Should there be a need for a change in the wording, be
102 Note: Should there be a need for a change in the wording, be
103 aware that corresponding documentation may also need updating.
103 aware that corresponding documentation may also need updating.
104 </p>
104 </p>
105 <div class="bs-example">
105 <div class="bs-example">
106 <table class="rctable edit_fields">
106 <table class="rctable edit_fields">
107 <tr><td></td><td></td></tr>
107 <tr><td></td><td></td></tr>
108 <tr>
108 <tr>
109 <td></td>
109 <td></td>
110 <td class=" td-action">
110 <td class=" td-action">
111 <div class="grid_edit">
111 <div class="grid_edit">
112 <a href="/_admin/repo_groups/breads/edit" title="Edit">Edit</a>
112 <a href="/_admin/repo_groups/breads/edit" title="Edit">Edit</a>
113 </div>
113 </div>
114 <div class="grid_delete">
114 <div class="grid_delete">
115 <form action="/_admin/repo_groups/breads" method="post"><div style="display:none">
115 <form action="/_admin/repo_groups/breads" method="post"><div style="display:none">
116 <input name="_method" type="hidden" value="delete">
116 <input name="_method" type="hidden" value="delete">
117 </div>
117 </div>
118 <div style="display: none;"><input id="csrf_token" name="csrf_token" type="hidden" value="03d6cc48726b885039b2f7675e85596b7dae6ecf"></div>
118 <div style="display: none;"><input id="csrf_token" name="csrf_token" type="hidden" value="03d6cc48726b885039b2f7675e85596b7dae6ecf"></div>
119 <button class="btn btn-link btn-danger" type="submit" onclick="return confirm('" +ungettext('confirm="" to="" delete="" this="" group:="" %s="" with="" repository','confirm="" repositories',gr_count)="" %="" (repo_group_name,="" gr_count)+"');"="">
119 <button class="btn btn-link btn-danger" type="submit" onclick="return confirm('" +_ungettext('confirm="" to="" delete="" this="" group:="" %s="" with="" repository','confirm="" repositories',gr_count)="" %="" (repo_group_name,="" gr_count)+"');"="">
120 Delete
120 Delete
121 </button>
121 </button>
122 </form>
122 </form>
123 </div>
123 </div>
124 </td>
124 </td>
125 </tr>
125 </tr>
126 </table>
126 </table>
127 <div class="highlight-html"><xmp>
127 <div class="highlight-html"><xmp>
128 <a href="some-link" title="${_('Edit')}">${_('Edit')}</a>
128 <a href="some-link" title="${_('Edit')}">${_('Edit')}</a>
129
129
130 <button class="btn btn-link btn-danger" type="submit"
130 <button class="btn btn-link btn-danger" type="submit"
131 onclick="return confirm('${_('Confirm to remove this field: Field')}');">
131 onclick="return confirm('${_('Confirm to remove this field: Field')}');">
132 ${_('Delete')}
132 ${_('Delete')}
133 </button>
133 </button>
134 </xmp></div>
134 </xmp></div>
135 </div>
135 </div>
136
136
137
137
138 <h2>Buttons disabled</h2>
138 <h2>Buttons disabled</h2>
139
139
140 <p>Note that our application still uses the class <code>.disabled</code>
140 <p>Note that our application still uses the class <code>.disabled</code>
141 in some places. Interim we support both but prefer to use the
141 in some places. Interim we support both but prefer to use the
142 attribute <code>disabled</code> where possible.</p>
142 attribute <code>disabled</code> where possible.</p>
143
143
144 <div class="bs-example">
144 <div class="bs-example">
145 ## TODO: johbo: Should also work without the form element
145 ## TODO: johbo: Should also work without the form element
146 <form method='post' action=''>
146 <form method='post' action=''>
147 <div class='form'>
147 <div class='form'>
148
148
149 <div class="buttons">
149 <div class="buttons">
150 <input type="submit" value="Save .btn-lg" id="example_save" class="btn btn-lg" disabled>
150 <input type="submit" value="Save .btn-lg" id="example_save" class="btn btn-lg" disabled>
151 <input type="reset" value="Reset" id="example_reset" class="btn btn-lg" disabled>
151 <input type="reset" value="Reset" id="example_reset" class="btn btn-lg" disabled>
152 <button class="btn btn-lg" disabled>Large</button>
152 <button class="btn btn-lg" disabled>Large</button>
153 </div>
153 </div>
154
154
155 <div class="buttons">
155 <div class="buttons">
156 <input type="submit" value="Save" id="example_save" class="btn" disabled>
156 <input type="submit" value="Save" id="example_save" class="btn" disabled>
157 <input type="reset" value="Reset" id="example_reset" class="btn" disabled>
157 <input type="reset" value="Reset" id="example_reset" class="btn" disabled>
158 <button class="btn" disabled>Normal</button>
158 <button class="btn" disabled>Normal</button>
159 <button class="btn btn-danger" disabled>Normal</button>
159 <button class="btn btn-danger" disabled>Normal</button>
160 </div>
160 </div>
161
161
162 <div class="buttons">
162 <div class="buttons">
163 <input type="submit" value="Save .btn-sm" id="example_save" class="btn btn-sm" disabled>
163 <input type="submit" value="Save .btn-sm" id="example_save" class="btn btn-sm" disabled>
164 <input type="reset" value="Reset" id="example_reset" class="btn btn-sm" disabled>
164 <input type="reset" value="Reset" id="example_reset" class="btn btn-sm" disabled>
165 <button class="btn btn-sm" disabled>Small</button>
165 <button class="btn btn-sm" disabled>Small</button>
166 <button class="btn btn-sm btn-danger" disabled>Small</button>
166 <button class="btn btn-sm btn-danger" disabled>Small</button>
167 </div>
167 </div>
168
168
169 <div class="buttons">
169 <div class="buttons">
170 <input type="submit" value="Save .btn-xs" id="example_save" class="btn btn-xs" disabled>
170 <input type="submit" value="Save .btn-xs" id="example_save" class="btn btn-xs" disabled>
171 <input type="reset" value="Reset" id="example_reset" class="btn btn-xs" disabled>
171 <input type="reset" value="Reset" id="example_reset" class="btn btn-xs" disabled>
172 <button class="btn btn-xs" disabled>XSmall</button>
172 <button class="btn btn-xs" disabled>XSmall</button>
173 <button class="btn btn-xs btn-danger" disabled>XSmall</button>
173 <button class="btn btn-xs btn-danger" disabled>XSmall</button>
174 </div>
174 </div>
175
175
176 <div class="buttons">
176 <div class="buttons">
177 <input type="submit" value="Save .btn-mini" id="example_save" class="btn btn-mini" disabled>
177 <input type="submit" value="Save .btn-mini" id="example_save" class="btn btn-mini" disabled>
178 <input type="reset" value="Reset" id="example_reset" class="btn btn-mini" disabled>
178 <input type="reset" value="Reset" id="example_reset" class="btn btn-mini" disabled>
179 </div>
179 </div>
180
180
181 <div class="buttons">
181 <div class="buttons">
182 Buttons of style <code>.btn-link</code>:
182 Buttons of style <code>.btn-link</code>:
183 <input type="reset" value="Reset" id="example_reset" class="btn btn-link" disabled>
183 <input type="reset" value="Reset" id="example_reset" class="btn btn-link" disabled>
184 <button class="btn btn-link" disabled>Edit</button>
184 <button class="btn btn-link" disabled>Edit</button>
185 <button class="btn btn-link btn-danger" disabled>Delete</button>
185 <button class="btn btn-link btn-danger" disabled>Delete</button>
186 </div>
186 </div>
187
187
188 </div>
188 </div>
189 </form>
189 </form>
190 </div>
190 </div>
191
191
192
192
193
193
194 </div>
194 </div>
195 </div> <!-- .main-content -->
195 </div> <!-- .main-content -->
196 </div> <!-- .box -->
196 </div> <!-- .box -->
197 </%def>
197 </%def>
@@ -1,85 +1,85 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 <%def name="subject()" filter="n,trim,whitespace_filter">
5 <%def name="subject()" filter="n,trim,whitespace_filter">
6 <%
6 <%
7 data = {
7 data = {
8 'user': h.person(user),
8 'user': h.person(user),
9 'pr_id': pull_request.pull_request_id,
9 'pr_id': pull_request.pull_request_id,
10 'pr_title': pull_request.title,
10 'pr_title': pull_request.title,
11 }
11 }
12 %>
12 %>
13
13
14 ${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s"') % data |n}
14 ${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s"') % data |n}
15 </%def>
15 </%def>
16
16
17
17
18 <%def name="body_plaintext()" filter="n,trim">
18 <%def name="body_plaintext()" filter="n,trim">
19 <%
19 <%
20 data = {
20 data = {
21 'user': h.person(user),
21 'user': h.person(user),
22 'pr_id': pull_request.pull_request_id,
22 'pr_id': pull_request.pull_request_id,
23 'pr_title': pull_request.title,
23 'pr_title': pull_request.title,
24 'source_ref_type': pull_request.source_ref_parts.type,
24 'source_ref_type': pull_request.source_ref_parts.type,
25 'source_ref_name': pull_request.source_ref_parts.name,
25 'source_ref_name': pull_request.source_ref_parts.name,
26 'target_ref_type': pull_request.target_ref_parts.type,
26 'target_ref_type': pull_request.target_ref_parts.type,
27 'target_ref_name': pull_request.target_ref_parts.name,
27 'target_ref_name': pull_request.target_ref_parts.name,
28 'repo_url': pull_request_source_repo_url
28 'repo_url': pull_request_source_repo_url
29 }
29 }
30 %>
30 %>
31 ${self.subject()}
31 ${self.subject()}
32
32
33
33
34 ${h.literal(_('Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s') % data)}
34 ${h.literal(_('Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s') % data)}
35
35
36
36
37 * ${_('Link')}: ${pull_request_url}
37 * ${_('Link')}: ${pull_request_url}
38
38
39 * ${_('Title')}: ${pull_request.title}
39 * ${_('Title')}: ${pull_request.title}
40
40
41 * ${_('Description')}:
41 * ${_('Description')}:
42
42
43 ${pull_request.description}
43 ${pull_request.description}
44
44
45
45
46 * ${ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
46 * ${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
47
47
48 % for commit_id, message in pull_request_commits:
48 % for commit_id, message in pull_request_commits:
49 - ${h.short_id(commit_id)}
49 - ${h.short_id(commit_id)}
50 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
50 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
51
51
52 % endfor
52 % endfor
53
53
54 ${self.plaintext_footer()}
54 ${self.plaintext_footer()}
55 </%def>
55 </%def>
56 <%
56 <%
57 data = {
57 data = {
58 'user': h.person(user),
58 'user': h.person(user),
59 'pr_id': pull_request.pull_request_id,
59 'pr_id': pull_request.pull_request_id,
60 'pr_title': pull_request.title,
60 'pr_title': pull_request.title,
61 'source_ref_type': pull_request.source_ref_parts.type,
61 'source_ref_type': pull_request.source_ref_parts.type,
62 'source_ref_name': pull_request.source_ref_parts.name,
62 'source_ref_name': pull_request.source_ref_parts.name,
63 'target_ref_type': pull_request.target_ref_parts.type,
63 'target_ref_type': pull_request.target_ref_parts.type,
64 'target_ref_name': pull_request.target_ref_parts.name,
64 'target_ref_name': pull_request.target_ref_parts.name,
65 'repo_url': pull_request_source_repo_url,
65 'repo_url': pull_request_source_repo_url,
66 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
66 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
67 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url)
67 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url)
68 }
68 }
69 %>
69 %>
70 <table style="text-align:left;vertical-align:middle;">
70 <table style="text-align:left;vertical-align:middle;">
71 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${pull_request_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s".') % data }</a></h4></td></tr>
71 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${pull_request_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s".') % data }</a></h4></td></tr>
72 <tr><td style="padding-right:20px;padding-top:15px;">${_('Title')}</td><td style="padding-top:15px;">${pull_request.title}</td></tr>
72 <tr><td style="padding-right:20px;padding-top:15px;">${_('Title')}</td><td style="padding-top:15px;">${pull_request.title}</td></tr>
73 <tr><td style="padding-right:20px;">${_('Source')}</td><td>${base.tag_button(pull_request.source_ref_parts.name)} ${h.literal(_('%(source_ref_type)s of %(source_repo_url)s') % data)}</td></tr>
73 <tr><td style="padding-right:20px;">${_('Source')}</td><td>${base.tag_button(pull_request.source_ref_parts.name)} ${h.literal(_('%(source_ref_type)s of %(source_repo_url)s') % data)}</td></tr>
74 <tr><td style="padding-right:20px;">${_('Target')}</td><td>${base.tag_button(pull_request.target_ref_parts.name)} ${h.literal(_('%(target_ref_type)s of %(target_repo_url)s') % data)}</td></tr>
74 <tr><td style="padding-right:20px;">${_('Target')}</td><td>${base.tag_button(pull_request.target_ref_parts.name)} ${h.literal(_('%(target_ref_type)s of %(target_repo_url)s') % data)}</td></tr>
75 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${pull_request.description}</td></tr>
75 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${pull_request.description}</td></tr>
76 <tr><td style="padding-right:20px;">${ungettext('%(num)s Commit', '%(num)s Commits', len(pull_request_commits)) % {'num': len(pull_request_commits)}}</td>
76 <tr><td style="padding-right:20px;">${_ungettext('%(num)s Commit', '%(num)s Commits', len(pull_request_commits)) % {'num': len(pull_request_commits)}}</td>
77 <td><ol style="margin:0 0 0 1em;padding:0;text-align:left;">
77 <td><ol style="margin:0 0 0 1em;padding:0;text-align:left;">
78 % for commit_id, message in pull_request_commits:
78 % for commit_id, message in pull_request_commits:
79 <li style="margin:0 0 1em;"><pre style="margin:0 0 .5em">${h.short_id(commit_id)}</pre>
79 <li style="margin:0 0 1em;"><pre style="margin:0 0 .5em">${h.short_id(commit_id)}</pre>
80 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
80 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
81 </li>
81 </li>
82 % endfor
82 % endfor
83 </ol></td>
83 </ol></td>
84 </tr>
84 </tr>
85 </table>
85 </table>
@@ -1,526 +1,526 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('New pull request')}
8 ${_('New pull request')}
9 </%def>
9 </%def>
10
10
11 <%def name="menu_bar_nav()">
11 <%def name="menu_bar_nav()">
12 ${self.menu_items(active='repositories')}
12 ${self.menu_items(active='repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_subnav()">
15 <%def name="menu_bar_subnav()">
16 ${self.repo_menu(active='showpullrequest')}
16 ${self.repo_menu(active='showpullrequest')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.repo_page_title(c.rhodecode_db_repo)}
22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 </div>
23 </div>
24
24
25 ${h.secure_form(h.url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
25 ${h.secure_form(h.url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
26
26
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28
28
29 <div class="box pr-summary">
29 <div class="box pr-summary">
30
30
31 <div class="summary-details block-left">
31 <div class="summary-details block-left">
32
32
33
33
34 <div class="pr-details-title">
34 <div class="pr-details-title">
35 ${_('Pull request summary')}
35 ${_('Pull request summary')}
36 </div>
36 </div>
37
37
38 <div class="form" style="padding-top: 10px">
38 <div class="form" style="padding-top: 10px">
39 <!-- fields -->
39 <!-- fields -->
40
40
41 <div class="fields" >
41 <div class="fields" >
42
42
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="pullrequest_title">${_('Title')}:</label>
45 <label for="pullrequest_title">${_('Title')}:</label>
46 </div>
46 </div>
47 <div class="input">
47 <div class="input">
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 <div class="field">
52 <div class="field">
53 <div class="label label-textarea">
53 <div class="label label-textarea">
54 <label for="pullrequest_desc">${_('Description')}:</label>
54 <label for="pullrequest_desc">${_('Description')}:</label>
55 </div>
55 </div>
56 <div class="textarea text-area editor">
56 <div class="textarea text-area editor">
57 ${h.textarea('pullrequest_desc',size=30, )}
57 ${h.textarea('pullrequest_desc',size=30, )}
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
59 </div>
59 </div>
60 </div>
60 </div>
61
61
62 <div class="field">
62 <div class="field">
63 <div class="label label-textarea">
63 <div class="label label-textarea">
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
65 </div>
65 </div>
66
66
67 ## TODO: johbo: Abusing the "content" class here to get the
67 ## TODO: johbo: Abusing the "content" class here to get the
68 ## desired effect. Should be replaced by a proper solution.
68 ## desired effect. Should be replaced by a proper solution.
69
69
70 ##ORG
70 ##ORG
71 <div class="content">
71 <div class="content">
72 <strong>${_('Source repository')}:</strong>
72 <strong>${_('Source repository')}:</strong>
73 ${c.rhodecode_db_repo.description}
73 ${c.rhodecode_db_repo.description}
74 </div>
74 </div>
75 <div class="content">
75 <div class="content">
76 ${h.hidden('source_repo')}
76 ${h.hidden('source_repo')}
77 ${h.hidden('source_ref')}
77 ${h.hidden('source_ref')}
78 </div>
78 </div>
79
79
80 ##OTHER, most Probably the PARENT OF THIS FORK
80 ##OTHER, most Probably the PARENT OF THIS FORK
81 <div class="content">
81 <div class="content">
82 ## filled with JS
82 ## filled with JS
83 <div id="target_repo_desc"></div>
83 <div id="target_repo_desc"></div>
84 </div>
84 </div>
85
85
86 <div class="content">
86 <div class="content">
87 ${h.hidden('target_repo')}
87 ${h.hidden('target_repo')}
88 ${h.hidden('target_ref')}
88 ${h.hidden('target_ref')}
89 <span id="target_ref_loading" style="display: none">
89 <span id="target_ref_loading" style="display: none">
90 ${_('Loading refs...')}
90 ${_('Loading refs...')}
91 </span>
91 </span>
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="field">
95 <div class="field">
96 <div class="label label-textarea">
96 <div class="label label-textarea">
97 <label for="pullrequest_submit"></label>
97 <label for="pullrequest_submit"></label>
98 </div>
98 </div>
99 <div class="input">
99 <div class="input">
100 <div class="pr-submit-button">
100 <div class="pr-submit-button">
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
102 </div>
102 </div>
103 <div id="pr_open_message"></div>
103 <div id="pr_open_message"></div>
104 </div>
104 </div>
105 </div>
105 </div>
106
106
107 <div class="pr-spacing-container"></div>
107 <div class="pr-spacing-container"></div>
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 <div>
111 <div>
112 ## AUTHOR
112 ## AUTHOR
113 <div class="reviewers-title block-right">
113 <div class="reviewers-title block-right">
114 <div class="pr-details-title">
114 <div class="pr-details-title">
115 ${_('Author of this pull request')}
115 ${_('Author of this pull request')}
116 </div>
116 </div>
117 </div>
117 </div>
118 <div class="block-right pr-details-content reviewers">
118 <div class="block-right pr-details-content reviewers">
119 <ul class="group_members">
119 <ul class="group_members">
120 <li>
120 <li>
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 </li>
122 </li>
123 </ul>
123 </ul>
124 </div>
124 </div>
125
125
126 ## REVIEW RULES
126 ## REVIEW RULES
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 <div class="pr-details-title">
128 <div class="pr-details-title">
129 ${_('Reviewer rules')}
129 ${_('Reviewer rules')}
130 </div>
130 </div>
131 <div class="pr-reviewer-rules">
131 <div class="pr-reviewer-rules">
132 ## review rules will be appended here, by default reviewers logic
132 ## review rules will be appended here, by default reviewers logic
133 </div>
133 </div>
134 </div>
134 </div>
135
135
136 ## REVIEWERS
136 ## REVIEWERS
137 <div class="reviewers-title block-right">
137 <div class="reviewers-title block-right">
138 <div class="pr-details-title">
138 <div class="pr-details-title">
139 ${_('Pull request reviewers')}
139 ${_('Pull request reviewers')}
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
141 </div>
141 </div>
142 </div>
142 </div>
143 <div id="reviewers" class="block-right pr-details-content reviewers">
143 <div id="reviewers" class="block-right pr-details-content reviewers">
144 ## members goes here, filled via JS based on initial selection !
144 ## members goes here, filled via JS based on initial selection !
145 <input type="hidden" name="__start__" value="review_members:sequence">
145 <input type="hidden" name="__start__" value="review_members:sequence">
146 <ul id="review_members" class="group_members"></ul>
146 <ul id="review_members" class="group_members"></ul>
147 <input type="hidden" name="__end__" value="review_members:sequence">
147 <input type="hidden" name="__end__" value="review_members:sequence">
148 <div id="add_reviewer_input" class='ac'>
148 <div id="add_reviewer_input" class='ac'>
149 <div class="reviewer_ac">
149 <div class="reviewer_ac">
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
151 <div id="reviewers_container"></div>
151 <div id="reviewers_container"></div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 <div class="box">
157 <div class="box">
158 <div>
158 <div>
159 ## overview pulled by ajax
159 ## overview pulled by ajax
160 <div id="pull_request_overview"></div>
160 <div id="pull_request_overview"></div>
161 </div>
161 </div>
162 </div>
162 </div>
163 ${h.end_form()}
163 ${h.end_form()}
164 </div>
164 </div>
165
165
166 <script type="text/javascript">
166 <script type="text/javascript">
167 $(function(){
167 $(function(){
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
172
172
173 var $pullRequestForm = $('#pull_request_form');
173 var $pullRequestForm = $('#pull_request_form');
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
178
178
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
181
181
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
184
184
185 var calculateContainerWidth = function() {
185 var calculateContainerWidth = function() {
186 var maxWidth = 0;
186 var maxWidth = 0;
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
188 $.each(repoSelect2Containers, function(idx, value) {
188 $.each(repoSelect2Containers, function(idx, value) {
189 $(value).select2('container').width('auto');
189 $(value).select2('container').width('auto');
190 var curWidth = $(value).select2('container').width();
190 var curWidth = $(value).select2('container').width();
191 if (maxWidth <= curWidth) {
191 if (maxWidth <= curWidth) {
192 maxWidth = curWidth;
192 maxWidth = curWidth;
193 }
193 }
194 $.each(repoSelect2Containers, function(idx, value) {
194 $.each(repoSelect2Containers, function(idx, value) {
195 $(value).select2('container').width(maxWidth + 10);
195 $(value).select2('container').width(maxWidth + 10);
196 });
196 });
197 });
197 });
198 };
198 };
199
199
200 var initRefSelection = function(selectedRef) {
200 var initRefSelection = function(selectedRef) {
201 return function(element, callback) {
201 return function(element, callback) {
202 // translate our select2 id into a text, it's a mapping to show
202 // translate our select2 id into a text, it's a mapping to show
203 // simple label when selecting by internal ID.
203 // simple label when selecting by internal ID.
204 var id, refData;
204 var id, refData;
205 if (selectedRef === undefined) {
205 if (selectedRef === undefined) {
206 id = element.val();
206 id = element.val();
207 refData = element.val().split(':');
207 refData = element.val().split(':');
208 } else {
208 } else {
209 id = selectedRef;
209 id = selectedRef;
210 refData = selectedRef.split(':');
210 refData = selectedRef.split(':');
211 }
211 }
212
212
213 var text = refData[1];
213 var text = refData[1];
214 if (refData[0] === 'rev') {
214 if (refData[0] === 'rev') {
215 text = text.substring(0, 12);
215 text = text.substring(0, 12);
216 }
216 }
217
217
218 var data = {id: id, text: text};
218 var data = {id: id, text: text};
219
219
220 callback(data);
220 callback(data);
221 };
221 };
222 };
222 };
223
223
224 var formatRefSelection = function(item) {
224 var formatRefSelection = function(item) {
225 var prefix = '';
225 var prefix = '';
226 var refData = item.id.split(':');
226 var refData = item.id.split(':');
227 if (refData[0] === 'branch') {
227 if (refData[0] === 'branch') {
228 prefix = '<i class="icon-branch"></i>';
228 prefix = '<i class="icon-branch"></i>';
229 }
229 }
230 else if (refData[0] === 'book') {
230 else if (refData[0] === 'book') {
231 prefix = '<i class="icon-bookmark"></i>';
231 prefix = '<i class="icon-bookmark"></i>';
232 }
232 }
233 else if (refData[0] === 'tag') {
233 else if (refData[0] === 'tag') {
234 prefix = '<i class="icon-tag"></i>';
234 prefix = '<i class="icon-tag"></i>';
235 }
235 }
236
236
237 var originalOption = item.element;
237 var originalOption = item.element;
238 return prefix + item.text;
238 return prefix + item.text;
239 };
239 };
240
240
241 // custom code mirror
241 // custom code mirror
242 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
242 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
243
243
244 reviewersController = new ReviewersController();
244 reviewersController = new ReviewersController();
245
245
246 var queryTargetRepo = function(self, query) {
246 var queryTargetRepo = function(self, query) {
247 // cache ALL results if query is empty
247 // cache ALL results if query is empty
248 var cacheKey = query.term || '__';
248 var cacheKey = query.term || '__';
249 var cachedData = self.cachedDataSource[cacheKey];
249 var cachedData = self.cachedDataSource[cacheKey];
250
250
251 if (cachedData) {
251 if (cachedData) {
252 query.callback({results: cachedData.results});
252 query.callback({results: cachedData.results});
253 } else {
253 } else {
254 $.ajax({
254 $.ajax({
255 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
255 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
256 data: {query: query.term},
256 data: {query: query.term},
257 dataType: 'json',
257 dataType: 'json',
258 type: 'GET',
258 type: 'GET',
259 success: function(data) {
259 success: function(data) {
260 self.cachedDataSource[cacheKey] = data;
260 self.cachedDataSource[cacheKey] = data;
261 query.callback({results: data.results});
261 query.callback({results: data.results});
262 },
262 },
263 error: function(data, textStatus, errorThrown) {
263 error: function(data, textStatus, errorThrown) {
264 alert(
264 alert(
265 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
265 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
266 }
266 }
267 });
267 });
268 }
268 }
269 };
269 };
270
270
271 var queryTargetRefs = function(initialData, query) {
271 var queryTargetRefs = function(initialData, query) {
272 var data = {results: []};
272 var data = {results: []};
273 // filter initialData
273 // filter initialData
274 $.each(initialData, function() {
274 $.each(initialData, function() {
275 var section = this.text;
275 var section = this.text;
276 var children = [];
276 var children = [];
277 $.each(this.children, function() {
277 $.each(this.children, function() {
278 if (query.term.length === 0 ||
278 if (query.term.length === 0 ||
279 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
279 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
280 children.push({'id': this.id, 'text': this.text})
280 children.push({'id': this.id, 'text': this.text})
281 }
281 }
282 });
282 });
283 data.results.push({'text': section, 'children': children})
283 data.results.push({'text': section, 'children': children})
284 });
284 });
285 query.callback({results: data.results});
285 query.callback({results: data.results});
286 };
286 };
287
287
288 var loadRepoRefDiffPreview = function() {
288 var loadRepoRefDiffPreview = function() {
289
289
290 var url_data = {
290 var url_data = {
291 'repo_name': targetRepo(),
291 'repo_name': targetRepo(),
292 'target_repo': sourceRepo(),
292 'target_repo': sourceRepo(),
293 'source_ref': targetRef()[2],
293 'source_ref': targetRef()[2],
294 'source_ref_type': 'rev',
294 'source_ref_type': 'rev',
295 'target_ref': sourceRef()[2],
295 'target_ref': sourceRef()[2],
296 'target_ref_type': 'rev',
296 'target_ref_type': 'rev',
297 'merge': true,
297 'merge': true,
298 '_': Date.now() // bypass browser caching
298 '_': Date.now() // bypass browser caching
299 }; // gather the source/target ref and repo here
299 }; // gather the source/target ref and repo here
300
300
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
302 prButtonLock(true, "${_('Please select source and target')}");
302 prButtonLock(true, "${_('Please select source and target')}");
303 return;
303 return;
304 }
304 }
305 var url = pyroutes.url('compare_url', url_data);
305 var url = pyroutes.url('compare_url', url_data);
306
306
307 // lock PR button, so we cannot send PR before it's calculated
307 // lock PR button, so we cannot send PR before it's calculated
308 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
308 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
309
309
310 if (loadRepoRefDiffPreview._currentRequest) {
310 if (loadRepoRefDiffPreview._currentRequest) {
311 loadRepoRefDiffPreview._currentRequest.abort();
311 loadRepoRefDiffPreview._currentRequest.abort();
312 }
312 }
313
313
314 loadRepoRefDiffPreview._currentRequest = $.get(url)
314 loadRepoRefDiffPreview._currentRequest = $.get(url)
315 .error(function(data, textStatus, errorThrown) {
315 .error(function(data, textStatus, errorThrown) {
316 alert(
316 alert(
317 "Error while processing request.\nError code {0} ({1}).".format(
317 "Error while processing request.\nError code {0} ({1}).".format(
318 data.status, data.statusText));
318 data.status, data.statusText));
319 })
319 })
320 .done(function(data) {
320 .done(function(data) {
321 loadRepoRefDiffPreview._currentRequest = null;
321 loadRepoRefDiffPreview._currentRequest = null;
322 $('#pull_request_overview').html(data);
322 $('#pull_request_overview').html(data);
323
323
324 var commitElements = $(data).find('tr[commit_id]');
324 var commitElements = $(data).find('tr[commit_id]');
325
325
326 var prTitleAndDesc = getTitleAndDescription(
326 var prTitleAndDesc = getTitleAndDescription(
327 sourceRef()[1], commitElements, 5);
327 sourceRef()[1], commitElements, 5);
328
328
329 var title = prTitleAndDesc[0];
329 var title = prTitleAndDesc[0];
330 var proposedDescription = prTitleAndDesc[1];
330 var proposedDescription = prTitleAndDesc[1];
331
331
332 var useGeneratedTitle = (
332 var useGeneratedTitle = (
333 $('#pullrequest_title').hasClass('autogenerated-title') ||
333 $('#pullrequest_title').hasClass('autogenerated-title') ||
334 $('#pullrequest_title').val() === "");
334 $('#pullrequest_title').val() === "");
335
335
336 if (title && useGeneratedTitle) {
336 if (title && useGeneratedTitle) {
337 // use generated title if we haven't specified our own
337 // use generated title if we haven't specified our own
338 $('#pullrequest_title').val(title);
338 $('#pullrequest_title').val(title);
339 $('#pullrequest_title').addClass('autogenerated-title');
339 $('#pullrequest_title').addClass('autogenerated-title');
340
340
341 }
341 }
342
342
343 var useGeneratedDescription = (
343 var useGeneratedDescription = (
344 !codeMirrorInstance._userDefinedDesc ||
344 !codeMirrorInstance._userDefinedDesc ||
345 codeMirrorInstance.getValue() === "");
345 codeMirrorInstance.getValue() === "");
346
346
347 if (proposedDescription && useGeneratedDescription) {
347 if (proposedDescription && useGeneratedDescription) {
348 // set proposed content, if we haven't defined our own,
348 // set proposed content, if we haven't defined our own,
349 // or we don't have description written
349 // or we don't have description written
350 codeMirrorInstance._userDefinedDesc = false; // reset state
350 codeMirrorInstance._userDefinedDesc = false; // reset state
351 codeMirrorInstance.setValue(proposedDescription);
351 codeMirrorInstance.setValue(proposedDescription);
352 }
352 }
353
353
354 var msg = '';
354 var msg = '';
355 if (commitElements.length === 1) {
355 if (commitElements.length === 1) {
356 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
356 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
357 } else {
357 } else {
358 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
358 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
359 }
359 }
360
360
361 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
361 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
362
362
363 if (commitElements.length) {
363 if (commitElements.length) {
364 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
364 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
365 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
365 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
366 }
366 }
367 else {
367 else {
368 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
368 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
369 }
369 }
370
370
371
371
372 });
372 });
373 };
373 };
374
374
375 var Select2Box = function(element, overrides) {
375 var Select2Box = function(element, overrides) {
376 var globalDefaults = {
376 var globalDefaults = {
377 dropdownAutoWidth: true,
377 dropdownAutoWidth: true,
378 containerCssClass: "drop-menu",
378 containerCssClass: "drop-menu",
379 dropdownCssClass: "drop-menu-dropdown"
379 dropdownCssClass: "drop-menu-dropdown"
380 };
380 };
381
381
382 var initSelect2 = function(defaultOptions) {
382 var initSelect2 = function(defaultOptions) {
383 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
383 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
384 element.select2(options);
384 element.select2(options);
385 };
385 };
386
386
387 return {
387 return {
388 initRef: function() {
388 initRef: function() {
389 var defaultOptions = {
389 var defaultOptions = {
390 minimumResultsForSearch: 5,
390 minimumResultsForSearch: 5,
391 formatSelection: formatRefSelection
391 formatSelection: formatRefSelection
392 };
392 };
393
393
394 initSelect2(defaultOptions);
394 initSelect2(defaultOptions);
395 },
395 },
396
396
397 initRepo: function(defaultValue, readOnly) {
397 initRepo: function(defaultValue, readOnly) {
398 var defaultOptions = {
398 var defaultOptions = {
399 initSelection : function (element, callback) {
399 initSelection : function (element, callback) {
400 var data = {id: defaultValue, text: defaultValue};
400 var data = {id: defaultValue, text: defaultValue};
401 callback(data);
401 callback(data);
402 }
402 }
403 };
403 };
404
404
405 initSelect2(defaultOptions);
405 initSelect2(defaultOptions);
406
406
407 element.select2('val', defaultSourceRepo);
407 element.select2('val', defaultSourceRepo);
408 if (readOnly === true) {
408 if (readOnly === true) {
409 element.select2('readonly', true);
409 element.select2('readonly', true);
410 }
410 }
411 }
411 }
412 };
412 };
413 };
413 };
414
414
415 var initTargetRefs = function(refsData, selectedRef){
415 var initTargetRefs = function(refsData, selectedRef){
416 Select2Box($targetRef, {
416 Select2Box($targetRef, {
417 query: function(query) {
417 query: function(query) {
418 queryTargetRefs(refsData, query);
418 queryTargetRefs(refsData, query);
419 },
419 },
420 initSelection : initRefSelection(selectedRef)
420 initSelection : initRefSelection(selectedRef)
421 }).initRef();
421 }).initRef();
422
422
423 if (!(selectedRef === undefined)) {
423 if (!(selectedRef === undefined)) {
424 $targetRef.select2('val', selectedRef);
424 $targetRef.select2('val', selectedRef);
425 }
425 }
426 };
426 };
427
427
428 var targetRepoChanged = function(repoData) {
428 var targetRepoChanged = function(repoData) {
429 // generate new DESC of target repo displayed next to select
429 // generate new DESC of target repo displayed next to select
430 $('#target_repo_desc').html(
430 $('#target_repo_desc').html(
431 "<strong>${_('Target repository')}</strong>: {0}".format(repoData['description'])
431 "<strong>${_('Target repository')}</strong>: {0}".format(repoData['description'])
432 );
432 );
433
433
434 // generate dynamic select2 for refs.
434 // generate dynamic select2 for refs.
435 initTargetRefs(repoData['refs']['select2_refs'],
435 initTargetRefs(repoData['refs']['select2_refs'],
436 repoData['refs']['selected_ref']);
436 repoData['refs']['selected_ref']);
437
437
438 };
438 };
439
439
440 var sourceRefSelect2 = Select2Box($sourceRef, {
440 var sourceRefSelect2 = Select2Box($sourceRef, {
441 placeholder: "${_('Select commit reference')}",
441 placeholder: "${_('Select commit reference')}",
442 query: function(query) {
442 query: function(query) {
443 var initialData = defaultSourceRepoData['refs']['select2_refs'];
443 var initialData = defaultSourceRepoData['refs']['select2_refs'];
444 queryTargetRefs(initialData, query)
444 queryTargetRefs(initialData, query)
445 },
445 },
446 initSelection: initRefSelection()
446 initSelection: initRefSelection()
447 }
447 }
448 );
448 );
449
449
450 var sourceRepoSelect2 = Select2Box($sourceRepo, {
450 var sourceRepoSelect2 = Select2Box($sourceRepo, {
451 query: function(query) {}
451 query: function(query) {}
452 });
452 });
453
453
454 var targetRepoSelect2 = Select2Box($targetRepo, {
454 var targetRepoSelect2 = Select2Box($targetRepo, {
455 cachedDataSource: {},
455 cachedDataSource: {},
456 query: $.debounce(250, function(query) {
456 query: $.debounce(250, function(query) {
457 queryTargetRepo(this, query);
457 queryTargetRepo(this, query);
458 }),
458 }),
459 formatResult: formatResult
459 formatResult: formatResult
460 });
460 });
461
461
462 sourceRefSelect2.initRef();
462 sourceRefSelect2.initRef();
463
463
464 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
464 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
465
465
466 targetRepoSelect2.initRepo(defaultTargetRepo, false);
466 targetRepoSelect2.initRepo(defaultTargetRepo, false);
467
467
468 $sourceRef.on('change', function(e){
468 $sourceRef.on('change', function(e){
469 loadRepoRefDiffPreview();
469 loadRepoRefDiffPreview();
470 reviewersController.loadDefaultReviewers(
470 reviewersController.loadDefaultReviewers(
471 sourceRepo(), sourceRef(), targetRepo(), targetRef());
471 sourceRepo(), sourceRef(), targetRepo(), targetRef());
472 });
472 });
473
473
474 $targetRef.on('change', function(e){
474 $targetRef.on('change', function(e){
475 loadRepoRefDiffPreview();
475 loadRepoRefDiffPreview();
476 reviewersController.loadDefaultReviewers(
476 reviewersController.loadDefaultReviewers(
477 sourceRepo(), sourceRef(), targetRepo(), targetRef());
477 sourceRepo(), sourceRef(), targetRepo(), targetRef());
478 });
478 });
479
479
480 $targetRepo.on('change', function(e){
480 $targetRepo.on('change', function(e){
481 var repoName = $(this).val();
481 var repoName = $(this).val();
482 calculateContainerWidth();
482 calculateContainerWidth();
483 $targetRef.select2('destroy');
483 $targetRef.select2('destroy');
484 $('#target_ref_loading').show();
484 $('#target_ref_loading').show();
485
485
486 $.ajax({
486 $.ajax({
487 url: pyroutes.url('pullrequest_repo_refs',
487 url: pyroutes.url('pullrequest_repo_refs',
488 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
488 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
489 data: {},
489 data: {},
490 dataType: 'json',
490 dataType: 'json',
491 type: 'GET',
491 type: 'GET',
492 success: function(data) {
492 success: function(data) {
493 $('#target_ref_loading').hide();
493 $('#target_ref_loading').hide();
494 targetRepoChanged(data);
494 targetRepoChanged(data);
495 loadRepoRefDiffPreview();
495 loadRepoRefDiffPreview();
496 },
496 },
497 error: function(data, textStatus, errorThrown) {
497 error: function(data, textStatus, errorThrown) {
498 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
498 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
499 }
499 }
500 })
500 })
501
501
502 });
502 });
503
503
504 prButtonLock(true, "${_('Please select source and target')}", 'all');
504 prButtonLock(true, "${_('Please select source and target')}", 'all');
505
505
506 // auto-load on init, the target refs select2
506 // auto-load on init, the target refs select2
507 calculateContainerWidth();
507 calculateContainerWidth();
508 targetRepoChanged(defaultTargetRepoData);
508 targetRepoChanged(defaultTargetRepoData);
509
509
510 $('#pullrequest_title').on('keyup', function(e){
510 $('#pullrequest_title').on('keyup', function(e){
511 $(this).removeClass('autogenerated-title');
511 $(this).removeClass('autogenerated-title');
512 });
512 });
513
513
514 % if c.default_source_ref:
514 % if c.default_source_ref:
515 // in case we have a pre-selected value, use it now
515 // in case we have a pre-selected value, use it now
516 $sourceRef.select2('val', '${c.default_source_ref}');
516 $sourceRef.select2('val', '${c.default_source_ref}');
517 loadRepoRefDiffPreview();
517 loadRepoRefDiffPreview();
518 reviewersController.loadDefaultReviewers(
518 reviewersController.loadDefaultReviewers(
519 sourceRepo(), sourceRef(), targetRepo(), targetRef());
519 sourceRepo(), sourceRef(), targetRepo(), targetRef());
520 % endif
520 % endif
521
521
522 ReviewerAutoComplete('#user');
522 ReviewerAutoComplete('#user');
523 });
523 });
524 </script>
524 </script>
525
525
526 </%def>
526 </%def>
@@ -1,860 +1,860 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <span id="pr-title">
12 <span id="pr-title">
13 ${c.pull_request.title}
13 ${c.pull_request.title}
14 %if c.pull_request.is_closed():
14 %if c.pull_request.is_closed():
15 (${_('Closed')})
15 (${_('Closed')})
16 %endif
16 %endif
17 </span>
17 </span>
18 <div id="pr-title-edit" class="input" style="display: none;">
18 <div id="pr-title-edit" class="input" style="display: none;">
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 </div>
20 </div>
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='repositories')}
24 ${self.menu_items(active='repositories')}
25 </%def>
25 </%def>
26
26
27 <%def name="menu_bar_subnav()">
27 <%def name="menu_bar_subnav()">
28 ${self.repo_menu(active='showpullrequest')}
28 ${self.repo_menu(active='showpullrequest')}
29 </%def>
29 </%def>
30
30
31 <%def name="main()">
31 <%def name="main()">
32
32
33 <script type="text/javascript">
33 <script type="text/javascript">
34 // TODO: marcink switch this to pyroutes
34 // TODO: marcink switch this to pyroutes
35 AJAX_COMMENT_DELETE_URL = "${h.url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
35 AJAX_COMMENT_DELETE_URL = "${h.url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 </script>
37 </script>
38 <div class="box">
38 <div class="box">
39
39
40 <div class="title">
40 <div class="title">
41 ${self.repo_page_title(c.rhodecode_db_repo)}
41 ${self.repo_page_title(c.rhodecode_db_repo)}
42 </div>
42 </div>
43
43
44 ${self.breadcrumbs()}
44 ${self.breadcrumbs()}
45
45
46 <div class="box pr-summary">
46 <div class="box pr-summary">
47
47
48 <div class="summary-details block-left">
48 <div class="summary-details block-left">
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 <div class="pr-details-title">
50 <div class="pr-details-title">
51 <a href="${h.route_path('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)}
51 <a href="${h.route_path('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)}
52 %if c.allowed_to_update:
52 %if c.allowed_to_update:
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 % if c.allowed_to_delete:
54 % if c.allowed_to_delete:
55 ${h.secure_form(h.url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
55 ${h.secure_form(h.url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 ${h.end_form()}
58 ${h.end_form()}
59 % else:
59 % else:
60 ${_('Delete')}
60 ${_('Delete')}
61 % endif
61 % endif
62 </div>
62 </div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
65 %endif
65 %endif
66 </div>
66 </div>
67
67
68 <div id="summary" class="fields pr-details-content">
68 <div id="summary" class="fields pr-details-content">
69 <div class="field">
69 <div class="field">
70 <div class="label-summary">
70 <div class="label-summary">
71 <label>${_('Source')}:</label>
71 <label>${_('Source')}:</label>
72 </div>
72 </div>
73 <div class="input">
73 <div class="input">
74 <div class="pr-origininfo">
74 <div class="pr-origininfo">
75 ## branch link is only valid if it is a branch
75 ## branch link is only valid if it is a branch
76 <span class="tag">
76 <span class="tag">
77 %if c.pull_request.source_ref_parts.type == 'branch':
77 %if c.pull_request.source_ref_parts.type == 'branch':
78 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
78 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
79 %else:
79 %else:
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
81 %endif
81 %endif
82 </span>
82 </span>
83 <span class="clone-url">
83 <span class="clone-url">
84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
85 </span>
85 </span>
86 <br/>
86 <br/>
87 % if c.ancestor_commit:
87 % if c.ancestor_commit:
88 ${_('Common ancestor')}:
88 ${_('Common ancestor')}:
89 <code><a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
89 <code><a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
90 % endif
90 % endif
91 </div>
91 </div>
92 <div class="pr-pullinfo">
92 <div class="pr-pullinfo">
93 %if h.is_hg(c.pull_request.source_repo):
93 %if h.is_hg(c.pull_request.source_repo):
94 <input type="text" class="input-monospace" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
94 <input type="text" class="input-monospace" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
95 %elif h.is_git(c.pull_request.source_repo):
95 %elif h.is_git(c.pull_request.source_repo):
96 <input type="text" class="input-monospace" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
96 <input type="text" class="input-monospace" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
97 %endif
97 %endif
98 </div>
98 </div>
99 </div>
99 </div>
100 </div>
100 </div>
101 <div class="field">
101 <div class="field">
102 <div class="label-summary">
102 <div class="label-summary">
103 <label>${_('Target')}:</label>
103 <label>${_('Target')}:</label>
104 </div>
104 </div>
105 <div class="input">
105 <div class="input">
106 <div class="pr-targetinfo">
106 <div class="pr-targetinfo">
107 ## branch link is only valid if it is a branch
107 ## branch link is only valid if it is a branch
108 <span class="tag">
108 <span class="tag">
109 %if c.pull_request.target_ref_parts.type == 'branch':
109 %if c.pull_request.target_ref_parts.type == 'branch':
110 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
110 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
111 %else:
111 %else:
112 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
112 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
113 %endif
113 %endif
114 </span>
114 </span>
115 <span class="clone-url">
115 <span class="clone-url">
116 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
116 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 </span>
117 </span>
118 </div>
118 </div>
119 </div>
119 </div>
120 </div>
120 </div>
121
121
122 ## Link to the shadow repository.
122 ## Link to the shadow repository.
123 <div class="field">
123 <div class="field">
124 <div class="label-summary">
124 <div class="label-summary">
125 <label>${_('Merge')}:</label>
125 <label>${_('Merge')}:</label>
126 </div>
126 </div>
127 <div class="input">
127 <div class="input">
128 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
128 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
129 <div class="pr-mergeinfo">
129 <div class="pr-mergeinfo">
130 %if h.is_hg(c.pull_request.target_repo):
130 %if h.is_hg(c.pull_request.target_repo):
131 <input type="text" class="input-monospace" 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">
131 <input type="text" class="input-monospace" 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">
132 %elif h.is_git(c.pull_request.target_repo):
132 %elif h.is_git(c.pull_request.target_repo):
133 <input type="text" class="input-monospace" 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">
133 <input type="text" class="input-monospace" 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">
134 %endif
134 %endif
135 </div>
135 </div>
136 % else:
136 % else:
137 <div class="">
137 <div class="">
138 ${_('Shadow repository data not available')}.
138 ${_('Shadow repository data not available')}.
139 </div>
139 </div>
140 % endif
140 % endif
141 </div>
141 </div>
142 </div>
142 </div>
143
143
144 <div class="field">
144 <div class="field">
145 <div class="label-summary">
145 <div class="label-summary">
146 <label>${_('Review')}:</label>
146 <label>${_('Review')}:</label>
147 </div>
147 </div>
148 <div class="input">
148 <div class="input">
149 %if c.pull_request_review_status:
149 %if c.pull_request_review_status:
150 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
150 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
151 <span class="changeset-status-lbl tooltip">
151 <span class="changeset-status-lbl tooltip">
152 %if c.pull_request.is_closed():
152 %if c.pull_request.is_closed():
153 ${_('Closed')},
153 ${_('Closed')},
154 %endif
154 %endif
155 ${h.commit_status_lbl(c.pull_request_review_status)}
155 ${h.commit_status_lbl(c.pull_request_review_status)}
156 </span>
156 </span>
157 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
157 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
158 %endif
158 %endif
159 </div>
159 </div>
160 </div>
160 </div>
161 <div class="field">
161 <div class="field">
162 <div class="pr-description-label label-summary">
162 <div class="pr-description-label label-summary">
163 <label>${_('Description')}:</label>
163 <label>${_('Description')}:</label>
164 </div>
164 </div>
165 <div id="pr-desc" class="input">
165 <div id="pr-desc" class="input">
166 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
166 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
167 </div>
167 </div>
168 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
168 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
169 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
169 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
170 </div>
170 </div>
171 </div>
171 </div>
172
172
173 <div class="field">
173 <div class="field">
174 <div class="label-summary">
174 <div class="label-summary">
175 <label>${_('Versions')}:</label>
175 <label>${_('Versions')}:</label>
176 </div>
176 </div>
177
177
178 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
178 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
179 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
179 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
180
180
181 <div class="pr-versions">
181 <div class="pr-versions">
182 % if c.show_version_changes:
182 % if c.show_version_changes:
183 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
183 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
184 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
184 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
185 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
185 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
186 data-toggle-on="${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
186 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
187 data-toggle-off="${_('Hide all versions of this pull request')}">
187 data-toggle-off="${_('Hide all versions of this pull request')}">
188 ${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
188 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
189 </a>
189 </a>
190 <table>
190 <table>
191 ## SHOW ALL VERSIONS OF PR
191 ## SHOW ALL VERSIONS OF PR
192 <% ver_pr = None %>
192 <% ver_pr = None %>
193
193
194 % for data in reversed(list(enumerate(c.versions, 1))):
194 % for data in reversed(list(enumerate(c.versions, 1))):
195 <% ver_pos = data[0] %>
195 <% ver_pos = data[0] %>
196 <% ver = data[1] %>
196 <% ver = data[1] %>
197 <% ver_pr = ver.pull_request_version_id %>
197 <% ver_pr = ver.pull_request_version_id %>
198 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
198 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
199
199
200 <tr class="version-pr" style="display: ${display_row}">
200 <tr class="version-pr" style="display: ${display_row}">
201 <td>
201 <td>
202 <code>
202 <code>
203 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
203 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
204 </code>
204 </code>
205 </td>
205 </td>
206 <td>
206 <td>
207 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
207 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
208 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
208 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
209 </td>
209 </td>
210 <td>
210 <td>
211 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
211 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
212 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
212 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
213 </div>
213 </div>
214 </td>
214 </td>
215 <td>
215 <td>
216 % if c.at_version_num != ver_pr:
216 % if c.at_version_num != ver_pr:
217 <i class="icon-comment"></i>
217 <i class="icon-comment"></i>
218 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
218 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
219 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
219 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
220 </code>
220 </code>
221 % endif
221 % endif
222 </td>
222 </td>
223 <td>
223 <td>
224 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
224 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
225 </td>
225 </td>
226 <td>
226 <td>
227 ${h.age_component(ver.updated_on, time_is_local=True)}
227 ${h.age_component(ver.updated_on, time_is_local=True)}
228 </td>
228 </td>
229 </tr>
229 </tr>
230 % endfor
230 % endfor
231
231
232 <tr>
232 <tr>
233 <td colspan="6">
233 <td colspan="6">
234 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
234 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
235 data-label-text-locked="${_('select versions to show changes')}"
235 data-label-text-locked="${_('select versions to show changes')}"
236 data-label-text-diff="${_('show changes between versions')}"
236 data-label-text-diff="${_('show changes between versions')}"
237 data-label-text-show="${_('show pull request for this version')}"
237 data-label-text-show="${_('show pull request for this version')}"
238 >
238 >
239 ${_('select versions to show changes')}
239 ${_('select versions to show changes')}
240 </button>
240 </button>
241 </td>
241 </td>
242 </tr>
242 </tr>
243
243
244 ## show comment/inline comments summary
244 ## show comment/inline comments summary
245 <%def name="comments_summary()">
245 <%def name="comments_summary()">
246 <tr>
246 <tr>
247 <td colspan="6" class="comments-summary-td">
247 <td colspan="6" class="comments-summary-td">
248
248
249 % if c.at_version:
249 % if c.at_version:
250 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
250 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
251 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
251 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
252 ${_('Comments at this version')}:
252 ${_('Comments at this version')}:
253 % else:
253 % else:
254 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
254 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
255 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
255 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
256 ${_('Comments for this pull request')}:
256 ${_('Comments for this pull request')}:
257 % endif
257 % endif
258
258
259
259
260 %if general_comm_count_ver:
260 %if general_comm_count_ver:
261 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
261 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
262 %else:
262 %else:
263 ${_("%d General ") % general_comm_count_ver}
263 ${_("%d General ") % general_comm_count_ver}
264 %endif
264 %endif
265
265
266 %if inline_comm_count_ver:
266 %if inline_comm_count_ver:
267 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
267 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
268 %else:
268 %else:
269 , ${_("%d Inline") % inline_comm_count_ver}
269 , ${_("%d Inline") % inline_comm_count_ver}
270 %endif
270 %endif
271
271
272 %if outdated_comm_count_ver:
272 %if outdated_comm_count_ver:
273 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
273 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
274 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
274 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
275 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
275 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
276 %else:
276 %else:
277 , ${_("%d Outdated") % outdated_comm_count_ver}
277 , ${_("%d Outdated") % outdated_comm_count_ver}
278 %endif
278 %endif
279 </td>
279 </td>
280 </tr>
280 </tr>
281 </%def>
281 </%def>
282 ${comments_summary()}
282 ${comments_summary()}
283 </table>
283 </table>
284 % else:
284 % else:
285 <div class="input">
285 <div class="input">
286 ${_('Pull request versions not available')}.
286 ${_('Pull request versions not available')}.
287 </div>
287 </div>
288 <div>
288 <div>
289 <table>
289 <table>
290 ${comments_summary()}
290 ${comments_summary()}
291 </table>
291 </table>
292 </div>
292 </div>
293 % endif
293 % endif
294 </div>
294 </div>
295 </div>
295 </div>
296
296
297 <div id="pr-save" class="field" style="display: none;">
297 <div id="pr-save" class="field" style="display: none;">
298 <div class="label-summary"></div>
298 <div class="label-summary"></div>
299 <div class="input">
299 <div class="input">
300 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
300 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
301 </div>
301 </div>
302 </div>
302 </div>
303 </div>
303 </div>
304 </div>
304 </div>
305 <div>
305 <div>
306 ## AUTHOR
306 ## AUTHOR
307 <div class="reviewers-title block-right">
307 <div class="reviewers-title block-right">
308 <div class="pr-details-title">
308 <div class="pr-details-title">
309 ${_('Author of this pull request')}
309 ${_('Author of this pull request')}
310 </div>
310 </div>
311 </div>
311 </div>
312 <div class="block-right pr-details-content reviewers">
312 <div class="block-right pr-details-content reviewers">
313 <ul class="group_members">
313 <ul class="group_members">
314 <li>
314 <li>
315 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
315 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
316 </li>
316 </li>
317 </ul>
317 </ul>
318 </div>
318 </div>
319
319
320 ## REVIEW RULES
320 ## REVIEW RULES
321 <div id="review_rules" style="display: none" class="reviewers-title block-right">
321 <div id="review_rules" style="display: none" class="reviewers-title block-right">
322 <div class="pr-details-title">
322 <div class="pr-details-title">
323 ${_('Reviewer rules')}
323 ${_('Reviewer rules')}
324 %if c.allowed_to_update:
324 %if c.allowed_to_update:
325 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
325 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
326 %endif
326 %endif
327 </div>
327 </div>
328 <div class="pr-reviewer-rules">
328 <div class="pr-reviewer-rules">
329 ## review rules will be appended here, by default reviewers logic
329 ## review rules will be appended here, by default reviewers logic
330 </div>
330 </div>
331 <input id="review_data" type="hidden" name="review_data" value="">
331 <input id="review_data" type="hidden" name="review_data" value="">
332 </div>
332 </div>
333
333
334 ## REVIEWERS
334 ## REVIEWERS
335 <div class="reviewers-title block-right">
335 <div class="reviewers-title block-right">
336 <div class="pr-details-title">
336 <div class="pr-details-title">
337 ${_('Pull request reviewers')}
337 ${_('Pull request reviewers')}
338 %if c.allowed_to_update:
338 %if c.allowed_to_update:
339 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
339 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
340 %endif
340 %endif
341 </div>
341 </div>
342 </div>
342 </div>
343 <div id="reviewers" class="block-right pr-details-content reviewers">
343 <div id="reviewers" class="block-right pr-details-content reviewers">
344 ## members goes here !
344 ## members goes here !
345 <input type="hidden" name="__start__" value="review_members:sequence">
345 <input type="hidden" name="__start__" value="review_members:sequence">
346 <ul id="review_members" class="group_members">
346 <ul id="review_members" class="group_members">
347 %for member,reasons,mandatory,status in c.pull_request_reviewers:
347 %for member,reasons,mandatory,status in c.pull_request_reviewers:
348 <li id="reviewer_${member.user_id}" class="reviewer_entry">
348 <li id="reviewer_${member.user_id}" class="reviewer_entry">
349 <div class="reviewers_member">
349 <div class="reviewers_member">
350 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
350 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
351 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
351 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
352 </div>
352 </div>
353 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
353 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
354 ${self.gravatar_with_user(member.email, 16)}
354 ${self.gravatar_with_user(member.email, 16)}
355 </div>
355 </div>
356 <input type="hidden" name="__start__" value="reviewer:mapping">
356 <input type="hidden" name="__start__" value="reviewer:mapping">
357 <input type="hidden" name="__start__" value="reasons:sequence">
357 <input type="hidden" name="__start__" value="reasons:sequence">
358 %for reason in reasons:
358 %for reason in reasons:
359 <div class="reviewer_reason">- ${reason}</div>
359 <div class="reviewer_reason">- ${reason}</div>
360 <input type="hidden" name="reason" value="${reason}">
360 <input type="hidden" name="reason" value="${reason}">
361
361
362 %endfor
362 %endfor
363 <input type="hidden" name="__end__" value="reasons:sequence">
363 <input type="hidden" name="__end__" value="reasons:sequence">
364 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
364 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
365 <input type="hidden" name="mandatory" value="${mandatory}"/>
365 <input type="hidden" name="mandatory" value="${mandatory}"/>
366 <input type="hidden" name="__end__" value="reviewer:mapping">
366 <input type="hidden" name="__end__" value="reviewer:mapping">
367 % if mandatory:
367 % if mandatory:
368 <div class="reviewer_member_mandatory_remove">
368 <div class="reviewer_member_mandatory_remove">
369 <i class="icon-remove-sign"></i>
369 <i class="icon-remove-sign"></i>
370 </div>
370 </div>
371 <div class="reviewer_member_mandatory">
371 <div class="reviewer_member_mandatory">
372 <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i>
372 <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i>
373 </div>
373 </div>
374 % else:
374 % else:
375 %if c.allowed_to_update:
375 %if c.allowed_to_update:
376 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
376 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
377 <i class="icon-remove-sign" ></i>
377 <i class="icon-remove-sign" ></i>
378 </div>
378 </div>
379 %endif
379 %endif
380 % endif
380 % endif
381 </div>
381 </div>
382 </li>
382 </li>
383 %endfor
383 %endfor
384 </ul>
384 </ul>
385 <input type="hidden" name="__end__" value="review_members:sequence">
385 <input type="hidden" name="__end__" value="review_members:sequence">
386
386
387 %if not c.pull_request.is_closed():
387 %if not c.pull_request.is_closed():
388 <div id="add_reviewer" class="ac" style="display: none;">
388 <div id="add_reviewer" class="ac" style="display: none;">
389 %if c.allowed_to_update:
389 %if c.allowed_to_update:
390 % if not c.forbid_adding_reviewers:
390 % if not c.forbid_adding_reviewers:
391 <div id="add_reviewer_input" class="reviewer_ac">
391 <div id="add_reviewer_input" class="reviewer_ac">
392 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
392 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
393 <div id="reviewers_container"></div>
393 <div id="reviewers_container"></div>
394 </div>
394 </div>
395 % endif
395 % endif
396 <div class="pull-right">
396 <div class="pull-right">
397 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
397 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
398 </div>
398 </div>
399 %endif
399 %endif
400 </div>
400 </div>
401 %endif
401 %endif
402 </div>
402 </div>
403 </div>
403 </div>
404 </div>
404 </div>
405 <div class="box">
405 <div class="box">
406 ##DIFF
406 ##DIFF
407 <div class="table" >
407 <div class="table" >
408 <div id="changeset_compare_view_content">
408 <div id="changeset_compare_view_content">
409 ##CS
409 ##CS
410 % if c.missing_requirements:
410 % if c.missing_requirements:
411 <div class="box">
411 <div class="box">
412 <div class="alert alert-warning">
412 <div class="alert alert-warning">
413 <div>
413 <div>
414 <strong>${_('Missing requirements:')}</strong>
414 <strong>${_('Missing requirements:')}</strong>
415 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
415 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
416 </div>
416 </div>
417 </div>
417 </div>
418 </div>
418 </div>
419 % elif c.missing_commits:
419 % elif c.missing_commits:
420 <div class="box">
420 <div class="box">
421 <div class="alert alert-warning">
421 <div class="alert alert-warning">
422 <div>
422 <div>
423 <strong>${_('Missing commits')}:</strong>
423 <strong>${_('Missing commits')}:</strong>
424 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
424 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
425 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
425 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
426 </div>
426 </div>
427 </div>
427 </div>
428 </div>
428 </div>
429 % endif
429 % endif
430
430
431 <div class="compare_view_commits_title">
431 <div class="compare_view_commits_title">
432 % if not c.compare_mode:
432 % if not c.compare_mode:
433
433
434 % if c.at_version_pos:
434 % if c.at_version_pos:
435 <h4>
435 <h4>
436 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
436 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
437 </h4>
437 </h4>
438 % endif
438 % endif
439
439
440 <div class="pull-left">
440 <div class="pull-left">
441 <div class="btn-group">
441 <div class="btn-group">
442 <a
442 <a
443 class="btn"
443 class="btn"
444 href="#"
444 href="#"
445 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
445 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
446 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
446 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
447 </a>
447 </a>
448 <a
448 <a
449 class="btn"
449 class="btn"
450 href="#"
450 href="#"
451 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
451 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
452 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
452 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
453 </a>
453 </a>
454 </div>
454 </div>
455 </div>
455 </div>
456
456
457 <div class="pull-right">
457 <div class="pull-right">
458 % if c.allowed_to_update and not c.pull_request.is_closed():
458 % if c.allowed_to_update and not c.pull_request.is_closed():
459 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
459 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
460 % else:
460 % else:
461 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
461 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
462 % endif
462 % endif
463
463
464 </div>
464 </div>
465 % endif
465 % endif
466 </div>
466 </div>
467
467
468 % if not c.missing_commits:
468 % if not c.missing_commits:
469 % if c.compare_mode:
469 % if c.compare_mode:
470 % if c.at_version:
470 % if c.at_version:
471 <h4>
471 <h4>
472 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
472 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
473 </h4>
473 </h4>
474
474
475 <div class="subtitle-compare">
475 <div class="subtitle-compare">
476 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
476 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
477 </div>
477 </div>
478
478
479 <div class="container">
479 <div class="container">
480 <table class="rctable compare_view_commits">
480 <table class="rctable compare_view_commits">
481 <tr>
481 <tr>
482 <th></th>
482 <th></th>
483 <th>${_('Time')}</th>
483 <th>${_('Time')}</th>
484 <th>${_('Author')}</th>
484 <th>${_('Author')}</th>
485 <th>${_('Commit')}</th>
485 <th>${_('Commit')}</th>
486 <th></th>
486 <th></th>
487 <th>${_('Description')}</th>
487 <th>${_('Description')}</th>
488 </tr>
488 </tr>
489
489
490 % for c_type, commit in c.commit_changes:
490 % for c_type, commit in c.commit_changes:
491 % if c_type in ['a', 'r']:
491 % if c_type in ['a', 'r']:
492 <%
492 <%
493 if c_type == 'a':
493 if c_type == 'a':
494 cc_title = _('Commit added in displayed changes')
494 cc_title = _('Commit added in displayed changes')
495 elif c_type == 'r':
495 elif c_type == 'r':
496 cc_title = _('Commit removed in displayed changes')
496 cc_title = _('Commit removed in displayed changes')
497 else:
497 else:
498 cc_title = ''
498 cc_title = ''
499 %>
499 %>
500 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
500 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
501 <td>
501 <td>
502 <div class="commit-change-indicator color-${c_type}-border">
502 <div class="commit-change-indicator color-${c_type}-border">
503 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
503 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
504 ${c_type.upper()}
504 ${c_type.upper()}
505 </div>
505 </div>
506 </div>
506 </div>
507 </td>
507 </td>
508 <td class="td-time">
508 <td class="td-time">
509 ${h.age_component(commit.date)}
509 ${h.age_component(commit.date)}
510 </td>
510 </td>
511 <td class="td-user">
511 <td class="td-user">
512 ${base.gravatar_with_user(commit.author, 16)}
512 ${base.gravatar_with_user(commit.author, 16)}
513 </td>
513 </td>
514 <td class="td-hash">
514 <td class="td-hash">
515 <code>
515 <code>
516 <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
516 <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
517 r${commit.revision}:${h.short_id(commit.raw_id)}
517 r${commit.revision}:${h.short_id(commit.raw_id)}
518 </a>
518 </a>
519 ${h.hidden('revisions', commit.raw_id)}
519 ${h.hidden('revisions', commit.raw_id)}
520 </code>
520 </code>
521 </td>
521 </td>
522 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
522 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
523 <div class="show_more_col">
523 <div class="show_more_col">
524 <i class="show_more"></i>
524 <i class="show_more"></i>
525 </div>
525 </div>
526 </td>
526 </td>
527 <td class="mid td-description">
527 <td class="mid td-description">
528 <div class="log-container truncate-wrap">
528 <div class="log-container truncate-wrap">
529 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
529 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
530 ${h.urlify_commit_message(commit.message, c.repo_name)}
530 ${h.urlify_commit_message(commit.message, c.repo_name)}
531 </div>
531 </div>
532 </div>
532 </div>
533 </td>
533 </td>
534 </tr>
534 </tr>
535 % endif
535 % endif
536 % endfor
536 % endfor
537 </table>
537 </table>
538 </div>
538 </div>
539
539
540 <script>
540 <script>
541 $('.expand_commit').on('click',function(e){
541 $('.expand_commit').on('click',function(e){
542 var target_expand = $(this);
542 var target_expand = $(this);
543 var cid = target_expand.data('commitId');
543 var cid = target_expand.data('commitId');
544
544
545 if (target_expand.hasClass('open')){
545 if (target_expand.hasClass('open')){
546 $('#c-'+cid).css({
546 $('#c-'+cid).css({
547 'height': '1.5em',
547 'height': '1.5em',
548 'white-space': 'nowrap',
548 'white-space': 'nowrap',
549 'text-overflow': 'ellipsis',
549 'text-overflow': 'ellipsis',
550 'overflow':'hidden'
550 'overflow':'hidden'
551 });
551 });
552 target_expand.removeClass('open');
552 target_expand.removeClass('open');
553 }
553 }
554 else {
554 else {
555 $('#c-'+cid).css({
555 $('#c-'+cid).css({
556 'height': 'auto',
556 'height': 'auto',
557 'white-space': 'pre-line',
557 'white-space': 'pre-line',
558 'text-overflow': 'initial',
558 'text-overflow': 'initial',
559 'overflow':'visible'
559 'overflow':'visible'
560 });
560 });
561 target_expand.addClass('open');
561 target_expand.addClass('open');
562 }
562 }
563 });
563 });
564 </script>
564 </script>
565
565
566 % endif
566 % endif
567
567
568 % else:
568 % else:
569 <%include file="/compare/compare_commits.mako" />
569 <%include file="/compare/compare_commits.mako" />
570 % endif
570 % endif
571
571
572 <div class="cs_files">
572 <div class="cs_files">
573 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
573 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
574 ${cbdiffs.render_diffset_menu()}
574 ${cbdiffs.render_diffset_menu()}
575 ${cbdiffs.render_diffset(
575 ${cbdiffs.render_diffset(
576 c.diffset, use_comments=True,
576 c.diffset, use_comments=True,
577 collapse_when_files_over=30,
577 collapse_when_files_over=30,
578 disable_new_comments=not c.allowed_to_comment,
578 disable_new_comments=not c.allowed_to_comment,
579 deleted_files_comments=c.deleted_files_comments)}
579 deleted_files_comments=c.deleted_files_comments)}
580 </div>
580 </div>
581 % else:
581 % else:
582 ## skipping commits we need to clear the view for missing commits
582 ## skipping commits we need to clear the view for missing commits
583 <div style="clear:both;"></div>
583 <div style="clear:both;"></div>
584 % endif
584 % endif
585
585
586 </div>
586 </div>
587 </div>
587 </div>
588
588
589 ## template for inline comment form
589 ## template for inline comment form
590 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
590 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
591
591
592 ## render general comments
592 ## render general comments
593
593
594 <div id="comment-tr-show">
594 <div id="comment-tr-show">
595 <div class="comment">
595 <div class="comment">
596 % if general_outdated_comm_count_ver:
596 % if general_outdated_comm_count_ver:
597 <div class="meta">
597 <div class="meta">
598 % if general_outdated_comm_count_ver == 1:
598 % if general_outdated_comm_count_ver == 1:
599 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
599 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
600 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
600 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
601 % else:
601 % else:
602 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
602 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
603 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
603 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
604 % endif
604 % endif
605 </div>
605 </div>
606 % endif
606 % endif
607 </div>
607 </div>
608 </div>
608 </div>
609
609
610 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
610 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
611
611
612 % if not c.pull_request.is_closed():
612 % if not c.pull_request.is_closed():
613 ## merge status, and merge action
613 ## merge status, and merge action
614 <div class="pull-request-merge">
614 <div class="pull-request-merge">
615 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
615 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
616 </div>
616 </div>
617
617
618 ## main comment form and it status
618 ## main comment form and it status
619 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
619 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
620 pull_request_id=c.pull_request.pull_request_id),
620 pull_request_id=c.pull_request.pull_request_id),
621 c.pull_request_review_status,
621 c.pull_request_review_status,
622 is_pull_request=True, change_status=c.allowed_to_change_status)}
622 is_pull_request=True, change_status=c.allowed_to_change_status)}
623 %endif
623 %endif
624
624
625 <script type="text/javascript">
625 <script type="text/javascript">
626 if (location.hash) {
626 if (location.hash) {
627 var result = splitDelimitedHash(location.hash);
627 var result = splitDelimitedHash(location.hash);
628 var line = $('html').find(result.loc);
628 var line = $('html').find(result.loc);
629 // show hidden comments if we use location.hash
629 // show hidden comments if we use location.hash
630 if (line.hasClass('comment-general')) {
630 if (line.hasClass('comment-general')) {
631 $(line).show();
631 $(line).show();
632 } else if (line.hasClass('comment-inline')) {
632 } else if (line.hasClass('comment-inline')) {
633 $(line).show();
633 $(line).show();
634 var $cb = $(line).closest('.cb');
634 var $cb = $(line).closest('.cb');
635 $cb.removeClass('cb-collapsed')
635 $cb.removeClass('cb-collapsed')
636 }
636 }
637 if (line.length > 0){
637 if (line.length > 0){
638 offsetScroll(line, 70);
638 offsetScroll(line, 70);
639 }
639 }
640 }
640 }
641
641
642 versionController = new VersionController();
642 versionController = new VersionController();
643 versionController.init();
643 versionController.init();
644
644
645 reviewersController = new ReviewersController();
645 reviewersController = new ReviewersController();
646
646
647 $(function(){
647 $(function(){
648
648
649 // custom code mirror
649 // custom code mirror
650 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
650 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
651
651
652 var PRDetails = {
652 var PRDetails = {
653 editButton: $('#open_edit_pullrequest'),
653 editButton: $('#open_edit_pullrequest'),
654 closeButton: $('#close_edit_pullrequest'),
654 closeButton: $('#close_edit_pullrequest'),
655 deleteButton: $('#delete_pullrequest'),
655 deleteButton: $('#delete_pullrequest'),
656 viewFields: $('#pr-desc, #pr-title'),
656 viewFields: $('#pr-desc, #pr-title'),
657 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
657 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
658
658
659 init: function() {
659 init: function() {
660 var that = this;
660 var that = this;
661 this.editButton.on('click', function(e) { that.edit(); });
661 this.editButton.on('click', function(e) { that.edit(); });
662 this.closeButton.on('click', function(e) { that.view(); });
662 this.closeButton.on('click', function(e) { that.view(); });
663 },
663 },
664
664
665 edit: function(event) {
665 edit: function(event) {
666 this.viewFields.hide();
666 this.viewFields.hide();
667 this.editButton.hide();
667 this.editButton.hide();
668 this.deleteButton.hide();
668 this.deleteButton.hide();
669 this.closeButton.show();
669 this.closeButton.show();
670 this.editFields.show();
670 this.editFields.show();
671 codeMirrorInstance.refresh();
671 codeMirrorInstance.refresh();
672 },
672 },
673
673
674 view: function(event) {
674 view: function(event) {
675 this.editButton.show();
675 this.editButton.show();
676 this.deleteButton.show();
676 this.deleteButton.show();
677 this.editFields.hide();
677 this.editFields.hide();
678 this.closeButton.hide();
678 this.closeButton.hide();
679 this.viewFields.show();
679 this.viewFields.show();
680 }
680 }
681 };
681 };
682
682
683 var ReviewersPanel = {
683 var ReviewersPanel = {
684 editButton: $('#open_edit_reviewers'),
684 editButton: $('#open_edit_reviewers'),
685 closeButton: $('#close_edit_reviewers'),
685 closeButton: $('#close_edit_reviewers'),
686 addButton: $('#add_reviewer'),
686 addButton: $('#add_reviewer'),
687 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
687 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
688
688
689 init: function() {
689 init: function() {
690 var self = this;
690 var self = this;
691 this.editButton.on('click', function(e) { self.edit(); });
691 this.editButton.on('click', function(e) { self.edit(); });
692 this.closeButton.on('click', function(e) { self.close(); });
692 this.closeButton.on('click', function(e) { self.close(); });
693 },
693 },
694
694
695 edit: function(event) {
695 edit: function(event) {
696 this.editButton.hide();
696 this.editButton.hide();
697 this.closeButton.show();
697 this.closeButton.show();
698 this.addButton.show();
698 this.addButton.show();
699 this.removeButtons.css('visibility', 'visible');
699 this.removeButtons.css('visibility', 'visible');
700 // review rules
700 // review rules
701 reviewersController.loadReviewRules(
701 reviewersController.loadReviewRules(
702 ${c.pull_request.reviewer_data_json | n});
702 ${c.pull_request.reviewer_data_json | n});
703 },
703 },
704
704
705 close: function(event) {
705 close: function(event) {
706 this.editButton.show();
706 this.editButton.show();
707 this.closeButton.hide();
707 this.closeButton.hide();
708 this.addButton.hide();
708 this.addButton.hide();
709 this.removeButtons.css('visibility', 'hidden');
709 this.removeButtons.css('visibility', 'hidden');
710 // hide review rules
710 // hide review rules
711 reviewersController.hideReviewRules()
711 reviewersController.hideReviewRules()
712 }
712 }
713 };
713 };
714
714
715 PRDetails.init();
715 PRDetails.init();
716 ReviewersPanel.init();
716 ReviewersPanel.init();
717
717
718 showOutdated = function(self){
718 showOutdated = function(self){
719 $('.comment-inline.comment-outdated').show();
719 $('.comment-inline.comment-outdated').show();
720 $('.filediff-outdated').show();
720 $('.filediff-outdated').show();
721 $('.showOutdatedComments').hide();
721 $('.showOutdatedComments').hide();
722 $('.hideOutdatedComments').show();
722 $('.hideOutdatedComments').show();
723 };
723 };
724
724
725 hideOutdated = function(self){
725 hideOutdated = function(self){
726 $('.comment-inline.comment-outdated').hide();
726 $('.comment-inline.comment-outdated').hide();
727 $('.filediff-outdated').hide();
727 $('.filediff-outdated').hide();
728 $('.hideOutdatedComments').hide();
728 $('.hideOutdatedComments').hide();
729 $('.showOutdatedComments').show();
729 $('.showOutdatedComments').show();
730 };
730 };
731
731
732 refreshMergeChecks = function(){
732 refreshMergeChecks = function(){
733 var loadUrl = "${h.url.current(merge_checks=1)}";
733 var loadUrl = "${h.url.current(merge_checks=1)}";
734 $('.pull-request-merge').css('opacity', 0.3);
734 $('.pull-request-merge').css('opacity', 0.3);
735 $('.action-buttons-extra').css('opacity', 0.3);
735 $('.action-buttons-extra').css('opacity', 0.3);
736
736
737 $('.pull-request-merge').load(
737 $('.pull-request-merge').load(
738 loadUrl, function() {
738 loadUrl, function() {
739 $('.pull-request-merge').css('opacity', 1);
739 $('.pull-request-merge').css('opacity', 1);
740
740
741 $('.action-buttons-extra').css('opacity', 1);
741 $('.action-buttons-extra').css('opacity', 1);
742 injectCloseAction();
742 injectCloseAction();
743 }
743 }
744 );
744 );
745 };
745 };
746
746
747 injectCloseAction = function() {
747 injectCloseAction = function() {
748 var closeAction = $('#close-pull-request-action').html();
748 var closeAction = $('#close-pull-request-action').html();
749 var $actionButtons = $('.action-buttons-extra');
749 var $actionButtons = $('.action-buttons-extra');
750 // clear the action before
750 // clear the action before
751 $actionButtons.html("");
751 $actionButtons.html("");
752 $actionButtons.html(closeAction);
752 $actionButtons.html(closeAction);
753 };
753 };
754
754
755 closePullRequest = function (status) {
755 closePullRequest = function (status) {
756 // inject closing flag
756 // inject closing flag
757 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
757 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
758 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
758 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
759 $(generalCommentForm.submitForm).submit();
759 $(generalCommentForm.submitForm).submit();
760 };
760 };
761
761
762 $('#show-outdated-comments').on('click', function(e){
762 $('#show-outdated-comments').on('click', function(e){
763 var button = $(this);
763 var button = $(this);
764 var outdated = $('.comment-outdated');
764 var outdated = $('.comment-outdated');
765
765
766 if (button.html() === "(Show)") {
766 if (button.html() === "(Show)") {
767 button.html("(Hide)");
767 button.html("(Hide)");
768 outdated.show();
768 outdated.show();
769 } else {
769 } else {
770 button.html("(Show)");
770 button.html("(Show)");
771 outdated.hide();
771 outdated.hide();
772 }
772 }
773 });
773 });
774
774
775 $('.show-inline-comments').on('change', function(e){
775 $('.show-inline-comments').on('change', function(e){
776 var show = 'none';
776 var show = 'none';
777 var target = e.currentTarget;
777 var target = e.currentTarget;
778 if(target.checked){
778 if(target.checked){
779 show = ''
779 show = ''
780 }
780 }
781 var boxid = $(target).attr('id_for');
781 var boxid = $(target).attr('id_for');
782 var comments = $('#{0} .inline-comments'.format(boxid));
782 var comments = $('#{0} .inline-comments'.format(boxid));
783 var fn_display = function(idx){
783 var fn_display = function(idx){
784 $(this).css('display', show);
784 $(this).css('display', show);
785 };
785 };
786 $(comments).each(fn_display);
786 $(comments).each(fn_display);
787 var btns = $('#{0} .inline-comments-button'.format(boxid));
787 var btns = $('#{0} .inline-comments-button'.format(boxid));
788 $(btns).each(fn_display);
788 $(btns).each(fn_display);
789 });
789 });
790
790
791 $('#merge_pull_request_form').submit(function() {
791 $('#merge_pull_request_form').submit(function() {
792 if (!$('#merge_pull_request').attr('disabled')) {
792 if (!$('#merge_pull_request').attr('disabled')) {
793 $('#merge_pull_request').attr('disabled', 'disabled');
793 $('#merge_pull_request').attr('disabled', 'disabled');
794 }
794 }
795 return true;
795 return true;
796 });
796 });
797
797
798 $('#edit_pull_request').on('click', function(e){
798 $('#edit_pull_request').on('click', function(e){
799 var title = $('#pr-title-input').val();
799 var title = $('#pr-title-input').val();
800 var description = codeMirrorInstance.getValue();
800 var description = codeMirrorInstance.getValue();
801 editPullRequest(
801 editPullRequest(
802 "${c.repo_name}", "${c.pull_request.pull_request_id}",
802 "${c.repo_name}", "${c.pull_request.pull_request_id}",
803 title, description);
803 title, description);
804 });
804 });
805
805
806 $('#update_pull_request').on('click', function(e){
806 $('#update_pull_request').on('click', function(e){
807 $(this).attr('disabled', 'disabled');
807 $(this).attr('disabled', 'disabled');
808 $(this).addClass('disabled');
808 $(this).addClass('disabled');
809 $(this).html(_gettext('Saving...'));
809 $(this).html(_gettext('Saving...'));
810 reviewersController.updateReviewers(
810 reviewersController.updateReviewers(
811 "${c.repo_name}", "${c.pull_request.pull_request_id}");
811 "${c.repo_name}", "${c.pull_request.pull_request_id}");
812 });
812 });
813
813
814 $('#update_commits').on('click', function(e){
814 $('#update_commits').on('click', function(e){
815 var isDisabled = !$(e.currentTarget).attr('disabled');
815 var isDisabled = !$(e.currentTarget).attr('disabled');
816 $(e.currentTarget).attr('disabled', 'disabled');
816 $(e.currentTarget).attr('disabled', 'disabled');
817 $(e.currentTarget).addClass('disabled');
817 $(e.currentTarget).addClass('disabled');
818 $(e.currentTarget).removeClass('btn-primary');
818 $(e.currentTarget).removeClass('btn-primary');
819 $(e.currentTarget).text(_gettext('Updating...'));
819 $(e.currentTarget).text(_gettext('Updating...'));
820 if(isDisabled){
820 if(isDisabled){
821 updateCommits(
821 updateCommits(
822 "${c.repo_name}", "${c.pull_request.pull_request_id}");
822 "${c.repo_name}", "${c.pull_request.pull_request_id}");
823 }
823 }
824 });
824 });
825 // fixing issue with caches on firefox
825 // fixing issue with caches on firefox
826 $('#update_commits').removeAttr("disabled");
826 $('#update_commits').removeAttr("disabled");
827
827
828 $('.show-inline-comments').on('click', function(e){
828 $('.show-inline-comments').on('click', function(e){
829 var boxid = $(this).attr('data-comment-id');
829 var boxid = $(this).attr('data-comment-id');
830 var button = $(this);
830 var button = $(this);
831
831
832 if(button.hasClass("comments-visible")) {
832 if(button.hasClass("comments-visible")) {
833 $('#{0} .inline-comments'.format(boxid)).each(function(index){
833 $('#{0} .inline-comments'.format(boxid)).each(function(index){
834 $(this).hide();
834 $(this).hide();
835 });
835 });
836 button.removeClass("comments-visible");
836 button.removeClass("comments-visible");
837 } else {
837 } else {
838 $('#{0} .inline-comments'.format(boxid)).each(function(index){
838 $('#{0} .inline-comments'.format(boxid)).each(function(index){
839 $(this).show();
839 $(this).show();
840 });
840 });
841 button.addClass("comments-visible");
841 button.addClass("comments-visible");
842 }
842 }
843 });
843 });
844
844
845 // register submit callback on commentForm form to track TODOs
845 // register submit callback on commentForm form to track TODOs
846 window.commentFormGlobalSubmitSuccessCallback = function(){
846 window.commentFormGlobalSubmitSuccessCallback = function(){
847 refreshMergeChecks();
847 refreshMergeChecks();
848 };
848 };
849 // initial injection
849 // initial injection
850 injectCloseAction();
850 injectCloseAction();
851
851
852 ReviewerAutoComplete('#user');
852 ReviewerAutoComplete('#user');
853
853
854 })
854 })
855 </script>
855 </script>
856
856
857 </div>
857 </div>
858 </div>
858 </div>
859
859
860 </%def>
860 </%def>
@@ -1,211 +1,211 b''
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 <span class="branchtag tag">
2 <span class="branchtag tag">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${_ungettext(
4 <i class="icon-branch"></i>${_ungettext(
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 </span>
6 </span>
7
7
8 %if closed_branches:
8 %if closed_branches:
9 <span class="branchtag tag">
9 <span class="branchtag tag">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${_ungettext(
11 <i class="icon-branch"></i>${_ungettext(
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 </span>
13 </span>
14 %endif
14 %endif
15
15
16 <span class="tagtag tag">
16 <span class="tagtag tag">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${_ungettext(
18 <i class="icon-tag"></i>${_ungettext(
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 </span>
20 </span>
21
21
22 %if bookmarks:
22 %if bookmarks:
23 <span class="booktag tag">
23 <span class="booktag tag">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${_ungettext(
25 <i class="icon-bookmark"></i>${_ungettext(
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 </span>
27 </span>
28 %endif
28 %endif
29 </%def>
29 </%def>
30
30
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33
33
34 <div id="summary-menu-stats" class="summary-detail">
34 <div id="summary-menu-stats" class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <div class="breadcrumbs files_location">
36 <div class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${breadcrumbs_links}
38 ${breadcrumbs_links}
39 </h4>
39 </h4>
40 </div>
40 </div>
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 ${_('Show More')}
42 ${_('Show More')}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="fieldset">
46 <div class="fieldset">
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
48 <div class="left-label disabled">
48 <div class="left-label disabled">
49 ${_('Read-only url')}:
49 ${_('Read-only url')}:
50 </div>
50 </div>
51 <div class="right-content disabled">
51 <div class="right-content disabled">
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
53 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
53 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
54
54
55 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
55 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
56 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
56 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
57
57
58 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
58 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
59 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
59 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
60
60
61 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
61 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
62 </div>
62 </div>
63 %else:
63 %else:
64 <div class="left-label">
64 <div class="left-label">
65 ${_('Clone url')}:
65 ${_('Clone url')}:
66 </div>
66 </div>
67 <div class="right-content">
67 <div class="right-content">
68 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
68 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
69 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
69 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
70
70
71 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
71 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
72 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
72 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
73
73
74 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
74 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
75 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
75 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
76 </div>
76 </div>
77 %endif
77 %endif
78 </div>
78 </div>
79
79
80 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
80 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
81 <div class="left-label">
81 <div class="left-label">
82 ${_('Description')}:
82 ${_('Description')}:
83 </div>
83 </div>
84 <div class="right-content">
84 <div class="right-content">
85 %if c.visual.stylify_metatags:
85 %if c.visual.stylify_metatags:
86 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
86 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
87 %else:
87 %else:
88 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
88 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
89 %endif
89 %endif
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
93 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
94 <div class="left-label">
94 <div class="left-label">
95 ${_('Information')}:
95 ${_('Information')}:
96 </div>
96 </div>
97 <div class="right-content">
97 <div class="right-content">
98
98
99 <div class="repo-size">
99 <div class="repo-size">
100 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
100 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
101
101
102 ## commits
102 ## commits
103 % if commit_rev == -1:
103 % if commit_rev == -1:
104 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
104 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
105 % else:
105 % else:
106 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
106 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
107 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
107 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
108 % endif
108 % endif
109
109
110 ## forks
110 ## forks
111 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
111 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
112 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
112 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
113
113
114 ## repo size
114 ## repo size
115 % if commit_rev == -1:
115 % if commit_rev == -1:
116 <span class="stats-bullet">0 B</span>
116 <span class="stats-bullet">0 B</span>
117 % else:
117 % else:
118 <span class="stats-bullet" id="repo_size_container">
118 <span class="stats-bullet" id="repo_size_container">
119 ${_('Calculating Repository Size...')}
119 ${_('Calculating Repository Size...')}
120 </span>
120 </span>
121 % endif
121 % endif
122 </div>
122 </div>
123
123
124 <div class="commit-info">
124 <div class="commit-info">
125 <div class="tags">
125 <div class="tags">
126 % if c.rhodecode_repo:
126 % if c.rhodecode_repo:
127 ${refs_counters(
127 ${refs_counters(
128 c.rhodecode_repo.branches,
128 c.rhodecode_repo.branches,
129 c.rhodecode_repo.branches_closed,
129 c.rhodecode_repo.branches_closed,
130 c.rhodecode_repo.tags,
130 c.rhodecode_repo.tags,
131 c.rhodecode_repo.bookmarks)}
131 c.rhodecode_repo.bookmarks)}
132 % else:
132 % else:
133 ## missing requirements can make c.rhodecode_repo None
133 ## missing requirements can make c.rhodecode_repo None
134 ${refs_counters([], [], [], [])}
134 ${refs_counters([], [], [], [])}
135 % endif
135 % endif
136 </div>
136 </div>
137 </div>
137 </div>
138
138
139 </div>
139 </div>
140 </div>
140 </div>
141
141
142 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
142 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
143 <div class="left-label">
143 <div class="left-label">
144 ${_('Statistics')}:
144 ${_('Statistics')}:
145 </div>
145 </div>
146 <div class="right-content">
146 <div class="right-content">
147 <div class="input ${summary(c.show_stats)} statistics">
147 <div class="input ${summary(c.show_stats)} statistics">
148 % if c.show_stats:
148 % if c.show_stats:
149 <div id="lang_stats" class="enabled">
149 <div id="lang_stats" class="enabled">
150 ${_('Calculating Code Statistics...')}
150 ${_('Calculating Code Statistics...')}
151 </div>
151 </div>
152 % else:
152 % else:
153 <span class="disabled">
153 <span class="disabled">
154 ${_('Statistics are disabled for this repository')}
154 ${_('Statistics are disabled for this repository')}
155 </span>
155 </span>
156 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
156 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
157 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
157 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
158 % endif
158 % endif
159 % endif
159 % endif
160 </div>
160 </div>
161
161
162 </div>
162 </div>
163 </div>
163 </div>
164
164
165 % if show_downloads:
165 % if show_downloads:
166 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
166 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
167 <div class="left-label">
167 <div class="left-label">
168 ${_('Downloads')}:
168 ${_('Downloads')}:
169 </div>
169 </div>
170 <div class="right-content">
170 <div class="right-content">
171 <div class="input ${summary(c.show_stats)} downloads">
171 <div class="input ${summary(c.show_stats)} downloads">
172 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
172 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
173 <span class="disabled">
173 <span class="disabled">
174 ${_('There are no downloads yet')}
174 ${_('There are no downloads yet')}
175 </span>
175 </span>
176 % elif not c.enable_downloads:
176 % elif not c.enable_downloads:
177 <span class="disabled">
177 <span class="disabled">
178 ${_('Downloads are disabled for this repository')}
178 ${_('Downloads are disabled for this repository')}
179 </span>
179 </span>
180 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
180 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
181 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
181 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
182 % endif
182 % endif
183 % else:
183 % else:
184 <span class="enabled">
184 <span class="enabled">
185 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
185 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
186 <i class="icon-archive"></i> tip.zip
186 <i class="icon-archive"></i> tip.zip
187 ## replaced by some JS on select
187 ## replaced by some JS on select
188 </a>
188 </a>
189 </span>
189 </span>
190 ${h.hidden('download_options')}
190 ${h.hidden('download_options')}
191 % endif
191 % endif
192 </div>
192 </div>
193 </div>
193 </div>
194 </div>
194 </div>
195 % endif
195 % endif
196
196
197 </div><!--end summary-detail-->
197 </div><!--end summary-detail-->
198 </%def>
198 </%def>
199
199
200 <%def name="summary_stats(gravatar_function)">
200 <%def name="summary_stats(gravatar_function)">
201 <div class="sidebar-right">
201 <div class="sidebar-right">
202 <div class="summary-detail-header">
202 <div class="summary-detail-header">
203 <h4 class="item">
203 <h4 class="item">
204 ${_('Owner')}
204 ${_('Owner')}
205 </h4>
205 </h4>
206 </div>
206 </div>
207 <div class="sidebar-right-content">
207 <div class="sidebar-right-content">
208 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
208 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
209 </div>
209 </div>
210 </div><!--end sidebar-right-->
210 </div><!--end sidebar-right-->
211 </%def>
211 </%def>
General Comments 0
You need to be logged in to leave comments. Login now