##// END OF EJS Templates
file: new file editors...
dan -
r3754:5cee44bd new-ui
parent child Browse files
Show More
@@ -1,81 +1,85 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 from rhodecode.config import routing_links
20 from rhodecode.config import routing_links
21
21
22
22
23 class VCSCallPredicate(object):
23 class VCSCallPredicate(object):
24 def __init__(self, val, config):
24 def __init__(self, val, config):
25 self.val = val
25 self.val = val
26
26
27 def text(self):
27 def text(self):
28 return 'vcs_call route = %s' % self.val
28 return 'vcs_call route = %s' % self.val
29
29
30 phash = text
30 phash = text
31
31
32 def __call__(self, info, request):
32 def __call__(self, info, request):
33 if hasattr(request, 'vcs_call'):
33 if hasattr(request, 'vcs_call'):
34 # skip vcs calls
34 # skip vcs calls
35 return False
35 return False
36
36
37 return True
37 return True
38
38
39
39
40 def includeme(config):
40 def includeme(config):
41
41
42 config.add_route(
42 config.add_route(
43 name='home',
43 name='home',
44 pattern='/')
44 pattern='/')
45
45
46 config.add_route(
46 config.add_route(
47 name='user_autocomplete_data',
47 name='user_autocomplete_data',
48 pattern='/_users')
48 pattern='/_users')
49
49
50 config.add_route(
50 config.add_route(
51 name='user_group_autocomplete_data',
51 name='user_group_autocomplete_data',
52 pattern='/_user_groups')
52 pattern='/_user_groups')
53
53
54 config.add_route(
54 config.add_route(
55 name='repo_list_data',
55 name='repo_list_data',
56 pattern='/_repos')
56 pattern='/_repos')
57
57
58 config.add_route(
58 config.add_route(
59 name='repo_group_list_data',
59 name='repo_group_list_data',
60 pattern='/_repo_groups')
60 pattern='/_repo_groups')
61
61
62 config.add_route(
62 config.add_route(
63 name='goto_switcher_data',
63 name='goto_switcher_data',
64 pattern='/_goto_data')
64 pattern='/_goto_data')
65
65
66 config.add_route(
66 config.add_route(
67 name='markup_preview',
67 name='markup_preview',
68 pattern='/_markup_preview')
68 pattern='/_markup_preview')
69
69
70 config.add_route(
70 config.add_route(
71 name='file_preview',
72 pattern='/_file_preview')
73
74 config.add_route(
71 name='store_user_session_value',
75 name='store_user_session_value',
72 pattern='/_store_session_attr')
76 pattern='/_store_session_attr')
73
77
74 # register our static links via redirection mechanism
78 # register our static links via redirection mechanism
75 routing_links.connect_redirection_links(config)
79 routing_links.connect_redirection_links(config)
76
80
77 # Scan module for configuration decorators.
81 # Scan module for configuration decorators.
78 config.scan('.views', ignore='.tests')
82 config.scan('.views', ignore='.tests')
79
83
80 config.add_route_predicate(
84 config.add_route_predicate(
81 'skip_vcs_call', VCSCallPredicate)
85 'skip_vcs_call', VCSCallPredicate)
@@ -1,756 +1,785 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 import re
21 import re
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
31 CSRFRequired)
31 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
32 from rhodecode.lib.index import searcher_from_config
32 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.nodes import FileNode
35 from rhodecode.model.db import (
36 from rhodecode.model.db import (
36 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.scm import RepoGroupList, RepoList
40 from rhodecode.model.scm import RepoGroupList, RepoList
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.user import UserModel
41 from rhodecode.model.user_group import UserGroupModel
42 from rhodecode.model.user_group import UserGroupModel
42
43
43 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
44
45
45
46
46 class HomeView(BaseAppView):
47 class HomeView(BaseAppView):
47
48
48 def load_default_context(self):
49 def load_default_context(self):
49 c = self._get_local_tmpl_context()
50 c = self._get_local_tmpl_context()
50 c.user = c.auth_user.get_instance()
51 c.user = c.auth_user.get_instance()
51
52
52 return c
53 return c
53
54
54 @LoginRequired()
55 @LoginRequired()
55 @view_config(
56 @view_config(
56 route_name='user_autocomplete_data', request_method='GET',
57 route_name='user_autocomplete_data', request_method='GET',
57 renderer='json_ext', xhr=True)
58 renderer='json_ext', xhr=True)
58 def user_autocomplete_data(self):
59 def user_autocomplete_data(self):
59 self.load_default_context()
60 self.load_default_context()
60 query = self.request.GET.get('query')
61 query = self.request.GET.get('query')
61 active = str2bool(self.request.GET.get('active') or True)
62 active = str2bool(self.request.GET.get('active') or True)
62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 include_groups = str2bool(self.request.GET.get('user_groups'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65
66
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 query, active, include_groups)
68 query, active, include_groups)
68
69
69 _users = UserModel().get_users(
70 _users = UserModel().get_users(
70 name_contains=query, only_active=active)
71 name_contains=query, only_active=active)
71
72
72 def maybe_skip_default_user(usr):
73 def maybe_skip_default_user(usr):
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 return False
75 return False
75 return True
76 return True
76 _users = filter(maybe_skip_default_user, _users)
77 _users = filter(maybe_skip_default_user, _users)
77
78
78 if include_groups:
79 if include_groups:
79 # extend with user groups
80 # extend with user groups
80 _user_groups = UserGroupModel().get_user_groups(
81 _user_groups = UserGroupModel().get_user_groups(
81 name_contains=query, only_active=active,
82 name_contains=query, only_active=active,
82 expand_groups=expand_groups)
83 expand_groups=expand_groups)
83 _users = _users + _user_groups
84 _users = _users + _user_groups
84
85
85 return {'suggestions': _users}
86 return {'suggestions': _users}
86
87
87 @LoginRequired()
88 @LoginRequired()
88 @NotAnonymous()
89 @NotAnonymous()
89 @view_config(
90 @view_config(
90 route_name='user_group_autocomplete_data', request_method='GET',
91 route_name='user_group_autocomplete_data', request_method='GET',
91 renderer='json_ext', xhr=True)
92 renderer='json_ext', xhr=True)
92 def user_group_autocomplete_data(self):
93 def user_group_autocomplete_data(self):
93 self.load_default_context()
94 self.load_default_context()
94 query = self.request.GET.get('query')
95 query = self.request.GET.get('query')
95 active = str2bool(self.request.GET.get('active') or True)
96 active = str2bool(self.request.GET.get('active') or True)
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97
98
98 log.debug('generating user group list, query:%s, active:%s',
99 log.debug('generating user group list, query:%s, active:%s',
99 query, active)
100 query, active)
100
101
101 _user_groups = UserGroupModel().get_user_groups(
102 _user_groups = UserGroupModel().get_user_groups(
102 name_contains=query, only_active=active,
103 name_contains=query, only_active=active,
103 expand_groups=expand_groups)
104 expand_groups=expand_groups)
104 _user_groups = _user_groups
105 _user_groups = _user_groups
105
106
106 return {'suggestions': _user_groups}
107 return {'suggestions': _user_groups}
107
108
108 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
109 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
109 org_query = name_contains
110 org_query = name_contains
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 ['repository.read', 'repository.write', 'repository.admin'],
112 ['repository.read', 'repository.write', 'repository.admin'],
112 cache=False, name_filter=name_contains) or [-1]
113 cache=False, name_filter=name_contains) or [-1]
113
114
114 query = Repository.query()\
115 query = Repository.query()\
115 .filter(Repository.archived.isnot(true()))\
116 .filter(Repository.archived.isnot(true()))\
116 .filter(or_(
117 .filter(or_(
117 # generate multiple IN to fix limitation problems
118 # generate multiple IN to fix limitation problems
118 *in_filter_generator(Repository.repo_id, allowed_ids)
119 *in_filter_generator(Repository.repo_id, allowed_ids)
119 ))
120 ))
120
121
121 query = query.order_by(case(
122 query = query.order_by(case(
122 [
123 [
123 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
124 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
124 ],
125 ],
125 ))
126 ))
126 query = query.order_by(func.length(Repository.repo_name))
127 query = query.order_by(func.length(Repository.repo_name))
127 query = query.order_by(Repository.repo_name)
128 query = query.order_by(Repository.repo_name)
128
129
129 if repo_type:
130 if repo_type:
130 query = query.filter(Repository.repo_type == repo_type)
131 query = query.filter(Repository.repo_type == repo_type)
131
132
132 if name_contains:
133 if name_contains:
133 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
134 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
134 query = query.filter(
135 query = query.filter(
135 Repository.repo_name.ilike(ilike_expression))
136 Repository.repo_name.ilike(ilike_expression))
136 query = query.limit(limit)
137 query = query.limit(limit)
137
138
138 acl_iter = query
139 acl_iter = query
139
140
140 return [
141 return [
141 {
142 {
142 'id': obj.repo_name,
143 'id': obj.repo_name,
143 'value': org_query,
144 'value': org_query,
144 'value_display': obj.repo_name,
145 'value_display': obj.repo_name,
145 'text': obj.repo_name,
146 'text': obj.repo_name,
146 'type': 'repo',
147 'type': 'repo',
147 'repo_id': obj.repo_id,
148 'repo_id': obj.repo_id,
148 'repo_type': obj.repo_type,
149 'repo_type': obj.repo_type,
149 'private': obj.private,
150 'private': obj.private,
150 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
151 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
151 }
152 }
152 for obj in acl_iter]
153 for obj in acl_iter]
153
154
154 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
155 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
155 org_query = name_contains
156 org_query = name_contains
156 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
157 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
157 ['group.read', 'group.write', 'group.admin'],
158 ['group.read', 'group.write', 'group.admin'],
158 cache=False, name_filter=name_contains) or [-1]
159 cache=False, name_filter=name_contains) or [-1]
159
160
160 query = RepoGroup.query()\
161 query = RepoGroup.query()\
161 .filter(or_(
162 .filter(or_(
162 # generate multiple IN to fix limitation problems
163 # generate multiple IN to fix limitation problems
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
164 *in_filter_generator(RepoGroup.group_id, allowed_ids)
164 ))
165 ))
165
166
166 query = query.order_by(case(
167 query = query.order_by(case(
167 [
168 [
168 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
169 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
169 ],
170 ],
170 ))
171 ))
171 query = query.order_by(func.length(RepoGroup.group_name))
172 query = query.order_by(func.length(RepoGroup.group_name))
172 query = query.order_by(RepoGroup.group_name)
173 query = query.order_by(RepoGroup.group_name)
173
174
174 if name_contains:
175 if name_contains:
175 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
176 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
176 query = query.filter(
177 query = query.filter(
177 RepoGroup.group_name.ilike(ilike_expression))
178 RepoGroup.group_name.ilike(ilike_expression))
178 query = query.limit(limit)
179 query = query.limit(limit)
179
180
180 acl_iter = query
181 acl_iter = query
181
182
182 return [
183 return [
183 {
184 {
184 'id': obj.group_name,
185 'id': obj.group_name,
185 'value': org_query,
186 'value': org_query,
186 'value_display': obj.group_name,
187 'value_display': obj.group_name,
187 'text': obj.group_name,
188 'text': obj.group_name,
188 'type': 'repo_group',
189 'type': 'repo_group',
189 'repo_group_id': obj.group_id,
190 'repo_group_id': obj.group_id,
190 'url': h.route_path(
191 'url': h.route_path(
191 'repo_group_home', repo_group_name=obj.group_name)
192 'repo_group_home', repo_group_name=obj.group_name)
192 }
193 }
193 for obj in acl_iter]
194 for obj in acl_iter]
194
195
195 def _get_user_list(self, name_contains=None, limit=20):
196 def _get_user_list(self, name_contains=None, limit=20):
196 org_query = name_contains
197 org_query = name_contains
197 if not name_contains:
198 if not name_contains:
198 return [], False
199 return [], False
199
200
200 # TODO(marcink): should all logged in users be allowed to search others?
201 # TODO(marcink): should all logged in users be allowed to search others?
201 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
202 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
202 if not allowed_user_search:
203 if not allowed_user_search:
203 return [], False
204 return [], False
204
205
205 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
206 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
206 if len(name_contains) != 1:
207 if len(name_contains) != 1:
207 return [], False
208 return [], False
208
209
209 name_contains = name_contains[0]
210 name_contains = name_contains[0]
210
211
211 query = User.query()\
212 query = User.query()\
212 .order_by(func.length(User.username))\
213 .order_by(func.length(User.username))\
213 .order_by(User.username) \
214 .order_by(User.username) \
214 .filter(User.username != User.DEFAULT_USER)
215 .filter(User.username != User.DEFAULT_USER)
215
216
216 if name_contains:
217 if name_contains:
217 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
218 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
218 query = query.filter(
219 query = query.filter(
219 User.username.ilike(ilike_expression))
220 User.username.ilike(ilike_expression))
220 query = query.limit(limit)
221 query = query.limit(limit)
221
222
222 acl_iter = query
223 acl_iter = query
223
224
224 return [
225 return [
225 {
226 {
226 'id': obj.user_id,
227 'id': obj.user_id,
227 'value': org_query,
228 'value': org_query,
228 'value_display': 'user: `{}`'.format(obj.username),
229 'value_display': 'user: `{}`'.format(obj.username),
229 'type': 'user',
230 'type': 'user',
230 'icon_link': h.gravatar_url(obj.email, 30),
231 'icon_link': h.gravatar_url(obj.email, 30),
231 'url': h.route_path(
232 'url': h.route_path(
232 'user_profile', username=obj.username)
233 'user_profile', username=obj.username)
233 }
234 }
234 for obj in acl_iter], True
235 for obj in acl_iter], True
235
236
236 def _get_user_groups_list(self, name_contains=None, limit=20):
237 def _get_user_groups_list(self, name_contains=None, limit=20):
237 org_query = name_contains
238 org_query = name_contains
238 if not name_contains:
239 if not name_contains:
239 return [], False
240 return [], False
240
241
241 # TODO(marcink): should all logged in users be allowed to search others?
242 # TODO(marcink): should all logged in users be allowed to search others?
242 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
243 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
243 if not allowed_user_search:
244 if not allowed_user_search:
244 return [], False
245 return [], False
245
246
246 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
247 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
247 if len(name_contains) != 1:
248 if len(name_contains) != 1:
248 return [], False
249 return [], False
249
250
250 name_contains = name_contains[0]
251 name_contains = name_contains[0]
251
252
252 query = UserGroup.query()\
253 query = UserGroup.query()\
253 .order_by(func.length(UserGroup.users_group_name))\
254 .order_by(func.length(UserGroup.users_group_name))\
254 .order_by(UserGroup.users_group_name)
255 .order_by(UserGroup.users_group_name)
255
256
256 if name_contains:
257 if name_contains:
257 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
258 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
258 query = query.filter(
259 query = query.filter(
259 UserGroup.users_group_name.ilike(ilike_expression))
260 UserGroup.users_group_name.ilike(ilike_expression))
260 query = query.limit(limit)
261 query = query.limit(limit)
261
262
262 acl_iter = query
263 acl_iter = query
263
264
264 return [
265 return [
265 {
266 {
266 'id': obj.users_group_id,
267 'id': obj.users_group_id,
267 'value': org_query,
268 'value': org_query,
268 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
269 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
269 'type': 'user_group',
270 'type': 'user_group',
270 'url': h.route_path(
271 'url': h.route_path(
271 'user_group_profile', user_group_name=obj.users_group_name)
272 'user_group_profile', user_group_name=obj.users_group_name)
272 }
273 }
273 for obj in acl_iter], True
274 for obj in acl_iter], True
274
275
275 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
276 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
276 repo_name = repo_group_name = None
277 repo_name = repo_group_name = None
277 if repo:
278 if repo:
278 repo_name = repo.repo_name
279 repo_name = repo.repo_name
279 if repo_group:
280 if repo_group:
280 repo_group_name = repo_group.group_name
281 repo_group_name = repo_group.group_name
281
282
282 org_query = query
283 org_query = query
283 if not query or len(query) < 3 or not searcher:
284 if not query or len(query) < 3 or not searcher:
284 return [], False
285 return [], False
285
286
286 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
287 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
287
288
288 if len(commit_hashes) != 1:
289 if len(commit_hashes) != 1:
289 return [], False
290 return [], False
290
291
291 commit_hash = commit_hashes[0]
292 commit_hash = commit_hashes[0]
292
293
293 result = searcher.search(
294 result = searcher.search(
294 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
295 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
295 repo_name, repo_group_name, raise_on_exc=False)
296 repo_name, repo_group_name, raise_on_exc=False)
296
297
297 commits = []
298 commits = []
298 for entry in result['results']:
299 for entry in result['results']:
299 repo_data = {
300 repo_data = {
300 'repository_id': entry.get('repository_id'),
301 'repository_id': entry.get('repository_id'),
301 'repository_type': entry.get('repo_type'),
302 'repository_type': entry.get('repo_type'),
302 'repository_name': entry.get('repository'),
303 'repository_name': entry.get('repository'),
303 }
304 }
304
305
305 commit_entry = {
306 commit_entry = {
306 'id': entry['commit_id'],
307 'id': entry['commit_id'],
307 'value': org_query,
308 'value': org_query,
308 'value_display': '`{}` commit: {}'.format(
309 'value_display': '`{}` commit: {}'.format(
309 entry['repository'], entry['commit_id']),
310 entry['repository'], entry['commit_id']),
310 'type': 'commit',
311 'type': 'commit',
311 'repo': entry['repository'],
312 'repo': entry['repository'],
312 'repo_data': repo_data,
313 'repo_data': repo_data,
313
314
314 'url': h.route_path(
315 'url': h.route_path(
315 'repo_commit',
316 'repo_commit',
316 repo_name=entry['repository'], commit_id=entry['commit_id'])
317 repo_name=entry['repository'], commit_id=entry['commit_id'])
317 }
318 }
318
319
319 commits.append(commit_entry)
320 commits.append(commit_entry)
320 return commits, True
321 return commits, True
321
322
322 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
323 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
323 repo_name = repo_group_name = None
324 repo_name = repo_group_name = None
324 if repo:
325 if repo:
325 repo_name = repo.repo_name
326 repo_name = repo.repo_name
326 if repo_group:
327 if repo_group:
327 repo_group_name = repo_group.group_name
328 repo_group_name = repo_group.group_name
328
329
329 org_query = query
330 org_query = query
330 if not query or len(query) < 3 or not searcher:
331 if not query or len(query) < 3 or not searcher:
331 return [], False
332 return [], False
332
333
333 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
334 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
334 if len(paths_re) != 1:
335 if len(paths_re) != 1:
335 return [], False
336 return [], False
336
337
337 file_path = paths_re[0]
338 file_path = paths_re[0]
338
339
339 search_path = searcher.escape_specials(file_path)
340 search_path = searcher.escape_specials(file_path)
340 result = searcher.search(
341 result = searcher.search(
341 'file.raw:*{}*'.format(search_path), 'path', auth_user,
342 'file.raw:*{}*'.format(search_path), 'path', auth_user,
342 repo_name, repo_group_name, raise_on_exc=False)
343 repo_name, repo_group_name, raise_on_exc=False)
343
344
344 files = []
345 files = []
345 for entry in result['results']:
346 for entry in result['results']:
346 repo_data = {
347 repo_data = {
347 'repository_id': entry.get('repository_id'),
348 'repository_id': entry.get('repository_id'),
348 'repository_type': entry.get('repo_type'),
349 'repository_type': entry.get('repo_type'),
349 'repository_name': entry.get('repository'),
350 'repository_name': entry.get('repository'),
350 }
351 }
351
352
352 file_entry = {
353 file_entry = {
353 'id': entry['commit_id'],
354 'id': entry['commit_id'],
354 'value': org_query,
355 'value': org_query,
355 'value_display': '`{}` file: {}'.format(
356 'value_display': '`{}` file: {}'.format(
356 entry['repository'], entry['file']),
357 entry['repository'], entry['file']),
357 'type': 'file',
358 'type': 'file',
358 'repo': entry['repository'],
359 'repo': entry['repository'],
359 'repo_data': repo_data,
360 'repo_data': repo_data,
360
361
361 'url': h.route_path(
362 'url': h.route_path(
362 'repo_files',
363 'repo_files',
363 repo_name=entry['repository'], commit_id=entry['commit_id'],
364 repo_name=entry['repository'], commit_id=entry['commit_id'],
364 f_path=entry['file'])
365 f_path=entry['file'])
365 }
366 }
366
367
367 files.append(file_entry)
368 files.append(file_entry)
368 return files, True
369 return files, True
369
370
370 @LoginRequired()
371 @LoginRequired()
371 @view_config(
372 @view_config(
372 route_name='repo_list_data', request_method='GET',
373 route_name='repo_list_data', request_method='GET',
373 renderer='json_ext', xhr=True)
374 renderer='json_ext', xhr=True)
374 def repo_list_data(self):
375 def repo_list_data(self):
375 _ = self.request.translate
376 _ = self.request.translate
376 self.load_default_context()
377 self.load_default_context()
377
378
378 query = self.request.GET.get('query')
379 query = self.request.GET.get('query')
379 repo_type = self.request.GET.get('repo_type')
380 repo_type = self.request.GET.get('repo_type')
380 log.debug('generating repo list, query:%s, repo_type:%s',
381 log.debug('generating repo list, query:%s, repo_type:%s',
381 query, repo_type)
382 query, repo_type)
382
383
383 res = []
384 res = []
384 repos = self._get_repo_list(query, repo_type=repo_type)
385 repos = self._get_repo_list(query, repo_type=repo_type)
385 if repos:
386 if repos:
386 res.append({
387 res.append({
387 'text': _('Repositories'),
388 'text': _('Repositories'),
388 'children': repos
389 'children': repos
389 })
390 })
390
391
391 data = {
392 data = {
392 'more': False,
393 'more': False,
393 'results': res
394 'results': res
394 }
395 }
395 return data
396 return data
396
397
397 @LoginRequired()
398 @LoginRequired()
398 @view_config(
399 @view_config(
399 route_name='repo_group_list_data', request_method='GET',
400 route_name='repo_group_list_data', request_method='GET',
400 renderer='json_ext', xhr=True)
401 renderer='json_ext', xhr=True)
401 def repo_group_list_data(self):
402 def repo_group_list_data(self):
402 _ = self.request.translate
403 _ = self.request.translate
403 self.load_default_context()
404 self.load_default_context()
404
405
405 query = self.request.GET.get('query')
406 query = self.request.GET.get('query')
406
407
407 log.debug('generating repo group list, query:%s',
408 log.debug('generating repo group list, query:%s',
408 query)
409 query)
409
410
410 res = []
411 res = []
411 repo_groups = self._get_repo_group_list(query)
412 repo_groups = self._get_repo_group_list(query)
412 if repo_groups:
413 if repo_groups:
413 res.append({
414 res.append({
414 'text': _('Repository Groups'),
415 'text': _('Repository Groups'),
415 'children': repo_groups
416 'children': repo_groups
416 })
417 })
417
418
418 data = {
419 data = {
419 'more': False,
420 'more': False,
420 'results': res
421 'results': res
421 }
422 }
422 return data
423 return data
423
424
424 def _get_default_search_queries(self, search_context, searcher, query):
425 def _get_default_search_queries(self, search_context, searcher, query):
425 if not searcher:
426 if not searcher:
426 return []
427 return []
427
428
428 is_es_6 = searcher.is_es_6
429 is_es_6 = searcher.is_es_6
429
430
430 queries = []
431 queries = []
431 repo_group_name, repo_name, repo_context = None, None, None
432 repo_group_name, repo_name, repo_context = None, None, None
432
433
433 # repo group context
434 # repo group context
434 if search_context.get('search_context[repo_group_name]'):
435 if search_context.get('search_context[repo_group_name]'):
435 repo_group_name = search_context.get('search_context[repo_group_name]')
436 repo_group_name = search_context.get('search_context[repo_group_name]')
436 if search_context.get('search_context[repo_name]'):
437 if search_context.get('search_context[repo_name]'):
437 repo_name = search_context.get('search_context[repo_name]')
438 repo_name = search_context.get('search_context[repo_name]')
438 repo_context = search_context.get('search_context[repo_view_type]')
439 repo_context = search_context.get('search_context[repo_view_type]')
439
440
440 if is_es_6 and repo_name:
441 if is_es_6 and repo_name:
441 # files
442 # files
442 def query_modifier():
443 def query_modifier():
443 qry = query
444 qry = query
444 return {'q': qry, 'type': 'content'}
445 return {'q': qry, 'type': 'content'}
445
446
446 label = u'File search for `{}`'.format(h.escape(query))
447 label = u'File search for `{}`'.format(h.escape(query))
447 file_qry = {
448 file_qry = {
448 'id': -10,
449 'id': -10,
449 'value': query,
450 'value': query,
450 'value_display': label,
451 'value_display': label,
451 'type': 'search',
452 'type': 'search',
452 'subtype': 'repo',
453 'subtype': 'repo',
453 'url': h.route_path('search_repo',
454 'url': h.route_path('search_repo',
454 repo_name=repo_name,
455 repo_name=repo_name,
455 _query=query_modifier())
456 _query=query_modifier())
456 }
457 }
457
458
458 # commits
459 # commits
459 def query_modifier():
460 def query_modifier():
460 qry = query
461 qry = query
461 return {'q': qry, 'type': 'commit'}
462 return {'q': qry, 'type': 'commit'}
462
463
463 label = u'Commit search for `{}`'.format(h.escape(query))
464 label = u'Commit search for `{}`'.format(h.escape(query))
464 commit_qry = {
465 commit_qry = {
465 'id': -20,
466 'id': -20,
466 'value': query,
467 'value': query,
467 'value_display': label,
468 'value_display': label,
468 'type': 'search',
469 'type': 'search',
469 'subtype': 'repo',
470 'subtype': 'repo',
470 'url': h.route_path('search_repo',
471 'url': h.route_path('search_repo',
471 repo_name=repo_name,
472 repo_name=repo_name,
472 _query=query_modifier())
473 _query=query_modifier())
473 }
474 }
474
475
475 if repo_context in ['commit', 'commits']:
476 if repo_context in ['commit', 'commits']:
476 queries.extend([commit_qry, file_qry])
477 queries.extend([commit_qry, file_qry])
477 elif repo_context in ['files', 'summary']:
478 elif repo_context in ['files', 'summary']:
478 queries.extend([file_qry, commit_qry])
479 queries.extend([file_qry, commit_qry])
479 else:
480 else:
480 queries.extend([commit_qry, file_qry])
481 queries.extend([commit_qry, file_qry])
481
482
482 elif is_es_6 and repo_group_name:
483 elif is_es_6 and repo_group_name:
483 # files
484 # files
484 def query_modifier():
485 def query_modifier():
485 qry = query
486 qry = query
486 return {'q': qry, 'type': 'content'}
487 return {'q': qry, 'type': 'content'}
487
488
488 label = u'File search for `{}`'.format(query)
489 label = u'File search for `{}`'.format(query)
489 file_qry = {
490 file_qry = {
490 'id': -30,
491 'id': -30,
491 'value': query,
492 'value': query,
492 'value_display': label,
493 'value_display': label,
493 'type': 'search',
494 'type': 'search',
494 'subtype': 'repo_group',
495 'subtype': 'repo_group',
495 'url': h.route_path('search_repo_group',
496 'url': h.route_path('search_repo_group',
496 repo_group_name=repo_group_name,
497 repo_group_name=repo_group_name,
497 _query=query_modifier())
498 _query=query_modifier())
498 }
499 }
499
500
500 # commits
501 # commits
501 def query_modifier():
502 def query_modifier():
502 qry = query
503 qry = query
503 return {'q': qry, 'type': 'commit'}
504 return {'q': qry, 'type': 'commit'}
504
505
505 label = u'Commit search for `{}`'.format(query)
506 label = u'Commit search for `{}`'.format(query)
506 commit_qry = {
507 commit_qry = {
507 'id': -40,
508 'id': -40,
508 'value': query,
509 'value': query,
509 'value_display': label,
510 'value_display': label,
510 'type': 'search',
511 'type': 'search',
511 'subtype': 'repo_group',
512 'subtype': 'repo_group',
512 'url': h.route_path('search_repo_group',
513 'url': h.route_path('search_repo_group',
513 repo_group_name=repo_group_name,
514 repo_group_name=repo_group_name,
514 _query=query_modifier())
515 _query=query_modifier())
515 }
516 }
516
517
517 if repo_context in ['commit', 'commits']:
518 if repo_context in ['commit', 'commits']:
518 queries.extend([commit_qry, file_qry])
519 queries.extend([commit_qry, file_qry])
519 elif repo_context in ['files', 'summary']:
520 elif repo_context in ['files', 'summary']:
520 queries.extend([file_qry, commit_qry])
521 queries.extend([file_qry, commit_qry])
521 else:
522 else:
522 queries.extend([commit_qry, file_qry])
523 queries.extend([commit_qry, file_qry])
523
524
524 # Global, not scoped
525 # Global, not scoped
525 if not queries:
526 if not queries:
526 queries.append(
527 queries.append(
527 {
528 {
528 'id': -1,
529 'id': -1,
529 'value': query,
530 'value': query,
530 'value_display': u'File search for: `{}`'.format(query),
531 'value_display': u'File search for: `{}`'.format(query),
531 'type': 'search',
532 'type': 'search',
532 'subtype': 'global',
533 'subtype': 'global',
533 'url': h.route_path('search',
534 'url': h.route_path('search',
534 _query={'q': query, 'type': 'content'})
535 _query={'q': query, 'type': 'content'})
535 })
536 })
536 queries.append(
537 queries.append(
537 {
538 {
538 'id': -2,
539 'id': -2,
539 'value': query,
540 'value': query,
540 'value_display': u'Commit search for: `{}`'.format(query),
541 'value_display': u'Commit search for: `{}`'.format(query),
541 'type': 'search',
542 'type': 'search',
542 'subtype': 'global',
543 'subtype': 'global',
543 'url': h.route_path('search',
544 'url': h.route_path('search',
544 _query={'q': query, 'type': 'commit'})
545 _query={'q': query, 'type': 'commit'})
545 })
546 })
546
547
547 return queries
548 return queries
548
549
549 @LoginRequired()
550 @LoginRequired()
550 @view_config(
551 @view_config(
551 route_name='goto_switcher_data', request_method='GET',
552 route_name='goto_switcher_data', request_method='GET',
552 renderer='json_ext', xhr=True)
553 renderer='json_ext', xhr=True)
553 def goto_switcher_data(self):
554 def goto_switcher_data(self):
554 c = self.load_default_context()
555 c = self.load_default_context()
555
556
556 _ = self.request.translate
557 _ = self.request.translate
557
558
558 query = self.request.GET.get('query')
559 query = self.request.GET.get('query')
559 log.debug('generating main filter data, query %s', query)
560 log.debug('generating main filter data, query %s', query)
560
561
561 res = []
562 res = []
562 if not query:
563 if not query:
563 return {'suggestions': res}
564 return {'suggestions': res}
564
565
565 def no_match(name):
566 def no_match(name):
566 return {
567 return {
567 'id': -1,
568 'id': -1,
568 'value': "",
569 'value': "",
569 'value_display': name,
570 'value_display': name,
570 'type': 'text',
571 'type': 'text',
571 'url': ""
572 'url': ""
572 }
573 }
573 searcher = searcher_from_config(self.request.registry.settings)
574 searcher = searcher_from_config(self.request.registry.settings)
574 has_specialized_search = False
575 has_specialized_search = False
575
576
576 # set repo context
577 # set repo context
577 repo = None
578 repo = None
578 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
579 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
579 if repo_id:
580 if repo_id:
580 repo = Repository.get(repo_id)
581 repo = Repository.get(repo_id)
581
582
582 # set group context
583 # set group context
583 repo_group = None
584 repo_group = None
584 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
585 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
585 if repo_group_id:
586 if repo_group_id:
586 repo_group = RepoGroup.get(repo_group_id)
587 repo_group = RepoGroup.get(repo_group_id)
587 prefix_match = False
588 prefix_match = False
588
589
589 # user: type search
590 # user: type search
590 if not prefix_match:
591 if not prefix_match:
591 users, prefix_match = self._get_user_list(query)
592 users, prefix_match = self._get_user_list(query)
592 if users:
593 if users:
593 has_specialized_search = True
594 has_specialized_search = True
594 for serialized_user in users:
595 for serialized_user in users:
595 res.append(serialized_user)
596 res.append(serialized_user)
596 elif prefix_match:
597 elif prefix_match:
597 has_specialized_search = True
598 has_specialized_search = True
598 res.append(no_match('No matching users found'))
599 res.append(no_match('No matching users found'))
599
600
600 # user_group: type search
601 # user_group: type search
601 if not prefix_match:
602 if not prefix_match:
602 user_groups, prefix_match = self._get_user_groups_list(query)
603 user_groups, prefix_match = self._get_user_groups_list(query)
603 if user_groups:
604 if user_groups:
604 has_specialized_search = True
605 has_specialized_search = True
605 for serialized_user_group in user_groups:
606 for serialized_user_group in user_groups:
606 res.append(serialized_user_group)
607 res.append(serialized_user_group)
607 elif prefix_match:
608 elif prefix_match:
608 has_specialized_search = True
609 has_specialized_search = True
609 res.append(no_match('No matching user groups found'))
610 res.append(no_match('No matching user groups found'))
610
611
611 # FTS commit: type search
612 # FTS commit: type search
612 if not prefix_match:
613 if not prefix_match:
613 commits, prefix_match = self._get_hash_commit_list(
614 commits, prefix_match = self._get_hash_commit_list(
614 c.auth_user, searcher, query, repo, repo_group)
615 c.auth_user, searcher, query, repo, repo_group)
615 if commits:
616 if commits:
616 has_specialized_search = True
617 has_specialized_search = True
617 unique_repos = collections.OrderedDict()
618 unique_repos = collections.OrderedDict()
618 for commit in commits:
619 for commit in commits:
619 repo_name = commit['repo']
620 repo_name = commit['repo']
620 unique_repos.setdefault(repo_name, []).append(commit)
621 unique_repos.setdefault(repo_name, []).append(commit)
621
622
622 for _repo, commits in unique_repos.items():
623 for _repo, commits in unique_repos.items():
623 for commit in commits:
624 for commit in commits:
624 res.append(commit)
625 res.append(commit)
625 elif prefix_match:
626 elif prefix_match:
626 has_specialized_search = True
627 has_specialized_search = True
627 res.append(no_match('No matching commits found'))
628 res.append(no_match('No matching commits found'))
628
629
629 # FTS file: type search
630 # FTS file: type search
630 if not prefix_match:
631 if not prefix_match:
631 paths, prefix_match = self._get_path_list(
632 paths, prefix_match = self._get_path_list(
632 c.auth_user, searcher, query, repo, repo_group)
633 c.auth_user, searcher, query, repo, repo_group)
633 if paths:
634 if paths:
634 has_specialized_search = True
635 has_specialized_search = True
635 unique_repos = collections.OrderedDict()
636 unique_repos = collections.OrderedDict()
636 for path in paths:
637 for path in paths:
637 repo_name = path['repo']
638 repo_name = path['repo']
638 unique_repos.setdefault(repo_name, []).append(path)
639 unique_repos.setdefault(repo_name, []).append(path)
639
640
640 for repo, paths in unique_repos.items():
641 for repo, paths in unique_repos.items():
641 for path in paths:
642 for path in paths:
642 res.append(path)
643 res.append(path)
643 elif prefix_match:
644 elif prefix_match:
644 has_specialized_search = True
645 has_specialized_search = True
645 res.append(no_match('No matching files found'))
646 res.append(no_match('No matching files found'))
646
647
647 # main suggestions
648 # main suggestions
648 if not has_specialized_search:
649 if not has_specialized_search:
649 repo_group_name = ''
650 repo_group_name = ''
650 if repo_group:
651 if repo_group:
651 repo_group_name = repo_group.group_name
652 repo_group_name = repo_group.group_name
652
653
653 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
654 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
654 res.append(_q)
655 res.append(_q)
655
656
656 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
657 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
657 for serialized_repo_group in repo_groups:
658 for serialized_repo_group in repo_groups:
658 res.append(serialized_repo_group)
659 res.append(serialized_repo_group)
659
660
660 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
661 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
661 for serialized_repo in repos:
662 for serialized_repo in repos:
662 res.append(serialized_repo)
663 res.append(serialized_repo)
663
664
664 if not repos and not repo_groups:
665 if not repos and not repo_groups:
665 res.append(no_match('No matches found'))
666 res.append(no_match('No matches found'))
666
667
667 return {'suggestions': res}
668 return {'suggestions': res}
668
669
669 def _get_groups_and_repos(self, repo_group_id=None):
670 def _get_groups_and_repos(self, repo_group_id=None):
670 # repo groups groups
671 # repo groups groups
671 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
672 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
672 _perms = ['group.read', 'group.write', 'group.admin']
673 _perms = ['group.read', 'group.write', 'group.admin']
673 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
674 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
674 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
675 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
675 repo_group_list=repo_group_list_acl, admin=False)
676 repo_group_list=repo_group_list_acl, admin=False)
676
677
677 # repositories
678 # repositories
678 repo_list = Repository.get_all_repos(group_id=repo_group_id)
679 repo_list = Repository.get_all_repos(group_id=repo_group_id)
679 _perms = ['repository.read', 'repository.write', 'repository.admin']
680 _perms = ['repository.read', 'repository.write', 'repository.admin']
680 repo_list_acl = RepoList(repo_list, perm_set=_perms)
681 repo_list_acl = RepoList(repo_list, perm_set=_perms)
681 repo_data = RepoModel().get_repos_as_dict(
682 repo_data = RepoModel().get_repos_as_dict(
682 repo_list=repo_list_acl, admin=False)
683 repo_list=repo_list_acl, admin=False)
683
684
684 return repo_data, repo_group_data
685 return repo_data, repo_group_data
685
686
686 @LoginRequired()
687 @LoginRequired()
687 @view_config(
688 @view_config(
688 route_name='home', request_method='GET',
689 route_name='home', request_method='GET',
689 renderer='rhodecode:templates/index.mako')
690 renderer='rhodecode:templates/index.mako')
690 def main_page(self):
691 def main_page(self):
691 c = self.load_default_context()
692 c = self.load_default_context()
692 c.repo_group = None
693 c.repo_group = None
693
694
694 repo_data, repo_group_data = self._get_groups_and_repos()
695 repo_data, repo_group_data = self._get_groups_and_repos()
695 # json used to render the grids
696 # json used to render the grids
696 c.repos_data = json.dumps(repo_data)
697 c.repos_data = json.dumps(repo_data)
697 c.repo_groups_data = json.dumps(repo_group_data)
698 c.repo_groups_data = json.dumps(repo_group_data)
698
699
699 return self._get_template_context(c)
700 return self._get_template_context(c)
700
701
701 @LoginRequired()
702 @LoginRequired()
702 @HasRepoGroupPermissionAnyDecorator(
703 @HasRepoGroupPermissionAnyDecorator(
703 'group.read', 'group.write', 'group.admin')
704 'group.read', 'group.write', 'group.admin')
704 @view_config(
705 @view_config(
705 route_name='repo_group_home', request_method='GET',
706 route_name='repo_group_home', request_method='GET',
706 renderer='rhodecode:templates/index_repo_group.mako')
707 renderer='rhodecode:templates/index_repo_group.mako')
707 @view_config(
708 @view_config(
708 route_name='repo_group_home_slash', request_method='GET',
709 route_name='repo_group_home_slash', request_method='GET',
709 renderer='rhodecode:templates/index_repo_group.mako')
710 renderer='rhodecode:templates/index_repo_group.mako')
710 def repo_group_main_page(self):
711 def repo_group_main_page(self):
711 c = self.load_default_context()
712 c = self.load_default_context()
712 c.repo_group = self.request.db_repo_group
713 c.repo_group = self.request.db_repo_group
713 repo_data, repo_group_data = self._get_groups_and_repos(c.repo_group.group_id)
714 repo_data, repo_group_data = self._get_groups_and_repos(c.repo_group.group_id)
714
715
715 # update every 5 min
716 # update every 5 min
716 if self.request.db_repo_group.last_commit_cache_update_diff > 60 * 5:
717 if self.request.db_repo_group.last_commit_cache_update_diff > 60 * 5:
717 self.request.db_repo_group.update_commit_cache()
718 self.request.db_repo_group.update_commit_cache()
718
719
719 # json used to render the grids
720 # json used to render the grids
720 c.repos_data = json.dumps(repo_data)
721 c.repos_data = json.dumps(repo_data)
721 c.repo_groups_data = json.dumps(repo_group_data)
722 c.repo_groups_data = json.dumps(repo_group_data)
722
723
723 return self._get_template_context(c)
724 return self._get_template_context(c)
724
725
725 @LoginRequired()
726 @LoginRequired()
726 @CSRFRequired()
727 @CSRFRequired()
727 @view_config(
728 @view_config(
728 route_name='markup_preview', request_method='POST',
729 route_name='markup_preview', request_method='POST',
729 renderer='string', xhr=True)
730 renderer='string', xhr=True)
730 def markup_preview(self):
731 def markup_preview(self):
731 # Technically a CSRF token is not needed as no state changes with this
732 # Technically a CSRF token is not needed as no state changes with this
732 # call. However, as this is a POST is better to have it, so automated
733 # call. However, as this is a POST is better to have it, so automated
733 # tools don't flag it as potential CSRF.
734 # tools don't flag it as potential CSRF.
734 # Post is required because the payload could be bigger than the maximum
735 # Post is required because the payload could be bigger than the maximum
735 # allowed by GET.
736 # allowed by GET.
736
737
737 text = self.request.POST.get('text')
738 text = self.request.POST.get('text')
738 renderer = self.request.POST.get('renderer') or 'rst'
739 renderer = self.request.POST.get('renderer') or 'rst'
739 if text:
740 if text:
740 return h.render(text, renderer=renderer, mentions=True)
741 return h.render(text, renderer=renderer, mentions=True)
741 return ''
742 return ''
742
743
743 @LoginRequired()
744 @LoginRequired()
744 @CSRFRequired()
745 @CSRFRequired()
745 @view_config(
746 @view_config(
747 route_name='file_preview', request_method='POST',
748 renderer='string', xhr=True)
749 def file_preview(self):
750 # Technically a CSRF token is not needed as no state changes with this
751 # call. However, as this is a POST is better to have it, so automated
752 # tools don't flag it as potential CSRF.
753 # Post is required because the payload could be bigger than the maximum
754 # allowed by GET.
755
756 text = self.request.POST.get('text')
757 file_path = self.request.POST.get('file_path')
758
759 renderer = h.renderer_from_filename(file_path)
760
761 if renderer:
762 return h.render(text, renderer=renderer, mentions=True)
763 else:
764 self.load_default_context()
765 _render = self.request.get_partial_renderer(
766 'rhodecode:templates/files/file_content.mako')
767
768 lines = filenode_as_lines_tokens(FileNode(file_path, text))
769
770 return _render('render_lines', lines)
771
772 @LoginRequired()
773 @CSRFRequired()
774 @view_config(
746 route_name='store_user_session_value', request_method='POST',
775 route_name='store_user_session_value', request_method='POST',
747 renderer='string', xhr=True)
776 renderer='string', xhr=True)
748 def store_user_session_attr(self):
777 def store_user_session_attr(self):
749 key = self.request.POST.get('key')
778 key = self.request.POST.get('key')
750 val = self.request.POST.get('val')
779 val = self.request.POST.get('val')
751
780
752 existing_value = self.request.session.get(key)
781 existing_value = self.request.session.get(key)
753 if existing_value != val:
782 if existing_value != val:
754 self.request.session[key] = val
783 self.request.session[key] = val
755
784
756 return 'stored:{}:{}'.format(key, val)
785 return 'stored:{}:{}'.format(key, val)
@@ -1,1417 +1,1528 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 import itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27 import urllib
27 import urllib
28 import pathlib2
28
29
29 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 from pyramid.view import view_config
31 from pyramid.view import view_config
31 from pyramid.renderers import render
32 from pyramid.renderers import render
32 from pyramid.response import Response
33 from pyramid.response import Response
33
34
34 import rhodecode
35 import rhodecode
35 from rhodecode.apps._base import RepoAppView
36 from rhodecode.apps._base import RepoAppView
36
37
37
38
38 from rhodecode.lib import diffs, helpers as h, rc_cache
39 from rhodecode.lib import diffs, helpers as h, rc_cache
39 from rhodecode.lib import audit_logger
40 from rhodecode.lib import audit_logger
40 from rhodecode.lib.view_utils import parse_path_ref
41 from rhodecode.lib.view_utils import parse_path_ref
41 from rhodecode.lib.exceptions import NonRelativePathError
42 from rhodecode.lib.exceptions import NonRelativePathError
42 from rhodecode.lib.codeblocks import (
43 from rhodecode.lib.codeblocks import (
43 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
45 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1)
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
46 from rhodecode.lib.auth import (
47 from rhodecode.lib.auth import (
47 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 from rhodecode.lib.vcs import path as vcspath
49 from rhodecode.lib.vcs import path as vcspath
49 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 from rhodecode.lib.vcs.conf import settings
51 from rhodecode.lib.vcs.conf import settings
51 from rhodecode.lib.vcs.nodes import FileNode
52 from rhodecode.lib.vcs.nodes import FileNode
52 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
53 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 NodeDoesNotExistError, CommitError, NodeError)
56 NodeDoesNotExistError, CommitError, NodeError)
56
57
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.db import Repository
59 from rhodecode.model.db import Repository
59
60
60 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
61
62
62
63
63 class RepoFilesView(RepoAppView):
64 class RepoFilesView(RepoAppView):
64
65
65 @staticmethod
66 @staticmethod
66 def adjust_file_path_for_svn(f_path, repo):
67 def adjust_file_path_for_svn(f_path, repo):
67 """
68 """
68 Computes the relative path of `f_path`.
69 Computes the relative path of `f_path`.
69
70
70 This is mainly based on prefix matching of the recognized tags and
71 This is mainly based on prefix matching of the recognized tags and
71 branches in the underlying repository.
72 branches in the underlying repository.
72 """
73 """
73 tags_and_branches = itertools.chain(
74 tags_and_branches = itertools.chain(
74 repo.branches.iterkeys(),
75 repo.branches.iterkeys(),
75 repo.tags.iterkeys())
76 repo.tags.iterkeys())
76 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77
78
78 for name in tags_and_branches:
79 for name in tags_and_branches:
79 if f_path.startswith('{}/'.format(name)):
80 if f_path.startswith('{}/'.format(name)):
80 f_path = vcspath.relpath(f_path, name)
81 f_path = vcspath.relpath(f_path, name)
81 break
82 break
82 return f_path
83 return f_path
83
84
84 def load_default_context(self):
85 def load_default_context(self):
85 c = self._get_local_tmpl_context(include_app_defaults=True)
86 c = self._get_local_tmpl_context(include_app_defaults=True)
86 c.rhodecode_repo = self.rhodecode_vcs_repo
87 c.rhodecode_repo = self.rhodecode_vcs_repo
87 c.enable_downloads = self.db_repo.enable_downloads
88 c.enable_downloads = self.db_repo.enable_downloads
88 return c
89 return c
89
90
90 def _ensure_not_locked(self):
91 def _ensure_not_locked(self, commit_id='tip'):
91 _ = self.request.translate
92 _ = self.request.translate
92
93
93 repo = self.db_repo
94 repo = self.db_repo
94 if repo.enable_locking and repo.locked[0]:
95 if repo.enable_locking and repo.locked[0]:
95 h.flash(_('This repository has been locked by %s on %s')
96 h.flash(_('This repository has been locked by %s on %s')
96 % (h.person_by_id(repo.locked[0]),
97 % (h.person_by_id(repo.locked[0]),
97 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 'warning')
99 'warning')
99 files_url = h.route_path(
100 files_url = h.route_path(
100 'repo_files:default_path',
101 'repo_files:default_path',
101 repo_name=self.db_repo_name, commit_id='tip')
102 repo_name=self.db_repo_name, commit_id=commit_id)
102 raise HTTPFound(files_url)
103 raise HTTPFound(files_url)
103
104
104 def check_branch_permission(self, branch_name):
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 _ = self.request.translate
107
108 if not is_head:
109 message = _('You can only modify files with commit being a valid branch head.')
110 h.flash(message, category='warning')
111
112 if json_mode:
113 return message
114
115 files_url = h.route_path(
116 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
117 f_path=f_path)
118 raise HTTPFound(files_url)
119
120 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
105 _ = self.request.translate
121 _ = self.request.translate
106
122
107 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
123 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
108 self.db_repo_name, branch_name)
124 self.db_repo_name, branch_name)
109 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
125 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
110 h.flash(
126 message = _('Branch `{}` changes forbidden by rule {}.').format(
111 _('Branch `{}` changes forbidden by rule {}.').format(branch_name, rule),
127 branch_name, rule)
112 'warning')
128 h.flash(message, 'warning')
129
130 if json_mode:
131 return message
132
113 files_url = h.route_path(
133 files_url = h.route_path(
114 'repo_files:default_path',
134 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
115 repo_name=self.db_repo_name, commit_id='tip')
135
116 raise HTTPFound(files_url)
136 raise HTTPFound(files_url)
117
137
118 def _get_commit_and_path(self):
138 def _get_commit_and_path(self):
119 default_commit_id = self.db_repo.landing_rev[1]
139 default_commit_id = self.db_repo.landing_rev[1]
120 default_f_path = '/'
140 default_f_path = '/'
121
141
122 commit_id = self.request.matchdict.get(
142 commit_id = self.request.matchdict.get(
123 'commit_id', default_commit_id)
143 'commit_id', default_commit_id)
124 f_path = self._get_f_path(self.request.matchdict, default_f_path)
144 f_path = self._get_f_path(self.request.matchdict, default_f_path)
125 return commit_id, f_path
145 return commit_id, f_path
126
146
127 def _get_default_encoding(self, c):
147 def _get_default_encoding(self, c):
128 enc_list = getattr(c, 'default_encodings', [])
148 enc_list = getattr(c, 'default_encodings', [])
129 return enc_list[0] if enc_list else 'UTF-8'
149 return enc_list[0] if enc_list else 'UTF-8'
130
150
131 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
151 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
132 """
152 """
133 This is a safe way to get commit. If an error occurs it redirects to
153 This is a safe way to get commit. If an error occurs it redirects to
134 tip with proper message
154 tip with proper message
135
155
136 :param commit_id: id of commit to fetch
156 :param commit_id: id of commit to fetch
137 :param redirect_after: toggle redirection
157 :param redirect_after: toggle redirection
138 """
158 """
139 _ = self.request.translate
159 _ = self.request.translate
140
160
141 try:
161 try:
142 return self.rhodecode_vcs_repo.get_commit(commit_id)
162 return self.rhodecode_vcs_repo.get_commit(commit_id)
143 except EmptyRepositoryError:
163 except EmptyRepositoryError:
144 if not redirect_after:
164 if not redirect_after:
145 return None
165 return None
146
166
147 _url = h.route_path(
167 _url = h.route_path(
148 'repo_files_add_file',
168 'repo_files_add_file',
149 repo_name=self.db_repo_name, commit_id=0, f_path='',
169 repo_name=self.db_repo_name, commit_id=0, f_path='')
150 _anchor='edit')
151
170
152 if h.HasRepoPermissionAny(
171 if h.HasRepoPermissionAny(
153 'repository.write', 'repository.admin')(self.db_repo_name):
172 'repository.write', 'repository.admin')(self.db_repo_name):
154 add_new = h.link_to(
173 add_new = h.link_to(
155 _('Click here to add a new file.'), _url, class_="alert-link")
174 _('Click here to add a new file.'), _url, class_="alert-link")
156 else:
175 else:
157 add_new = ""
176 add_new = ""
158
177
159 h.flash(h.literal(
178 h.flash(h.literal(
160 _('There are no files yet. %s') % add_new), category='warning')
179 _('There are no files yet. %s') % add_new), category='warning')
161 raise HTTPFound(
180 raise HTTPFound(
162 h.route_path('repo_summary', repo_name=self.db_repo_name))
181 h.route_path('repo_summary', repo_name=self.db_repo_name))
163
182
164 except (CommitDoesNotExistError, LookupError):
183 except (CommitDoesNotExistError, LookupError):
165 msg = _('No such commit exists for this repository')
184 msg = _('No such commit exists for this repository')
166 h.flash(msg, category='error')
185 h.flash(msg, category='error')
167 raise HTTPNotFound()
186 raise HTTPNotFound()
168 except RepositoryError as e:
187 except RepositoryError as e:
169 h.flash(safe_str(h.escape(e)), category='error')
188 h.flash(safe_str(h.escape(e)), category='error')
170 raise HTTPNotFound()
189 raise HTTPNotFound()
171
190
172 def _get_filenode_or_redirect(self, commit_obj, path):
191 def _get_filenode_or_redirect(self, commit_obj, path):
173 """
192 """
174 Returns file_node, if error occurs or given path is directory,
193 Returns file_node, if error occurs or given path is directory,
175 it'll redirect to top level path
194 it'll redirect to top level path
176 """
195 """
177 _ = self.request.translate
196 _ = self.request.translate
178
197
179 try:
198 try:
180 file_node = commit_obj.get_node(path)
199 file_node = commit_obj.get_node(path)
181 if file_node.is_dir():
200 if file_node.is_dir():
182 raise RepositoryError('The given path is a directory')
201 raise RepositoryError('The given path is a directory')
183 except CommitDoesNotExistError:
202 except CommitDoesNotExistError:
184 log.exception('No such commit exists for this repository')
203 log.exception('No such commit exists for this repository')
185 h.flash(_('No such commit exists for this repository'), category='error')
204 h.flash(_('No such commit exists for this repository'), category='error')
186 raise HTTPNotFound()
205 raise HTTPNotFound()
187 except RepositoryError as e:
206 except RepositoryError as e:
188 log.warning('Repository error while fetching '
207 log.warning('Repository error while fetching '
189 'filenode `%s`. Err:%s', path, e)
208 'filenode `%s`. Err:%s', path, e)
190 h.flash(safe_str(h.escape(e)), category='error')
209 h.flash(safe_str(h.escape(e)), category='error')
191 raise HTTPNotFound()
210 raise HTTPNotFound()
192
211
193 return file_node
212 return file_node
194
213
195 def _is_valid_head(self, commit_id, repo):
214 def _is_valid_head(self, commit_id, repo):
196 branch_name = sha_commit_id = ''
215 branch_name = sha_commit_id = ''
197 is_head = False
216 is_head = False
198
217
199 if h.is_svn(repo) and not repo.is_empty():
218 if h.is_svn(repo) and not repo.is_empty():
200 # Note: Subversion only has one head.
219 # Note: Subversion only has one head.
201 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
220 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
202 is_head = True
221 is_head = True
203 return branch_name, sha_commit_id, is_head
222 return branch_name, sha_commit_id, is_head
204
223
205 for _branch_name, branch_commit_id in repo.branches.items():
224 for _branch_name, branch_commit_id in repo.branches.items():
206 # simple case we pass in branch name, it's a HEAD
225 # simple case we pass in branch name, it's a HEAD
207 if commit_id == _branch_name:
226 if commit_id == _branch_name:
208 is_head = True
227 is_head = True
209 branch_name = _branch_name
228 branch_name = _branch_name
210 sha_commit_id = branch_commit_id
229 sha_commit_id = branch_commit_id
211 break
230 break
212 # case when we pass in full sha commit_id, which is a head
231 # case when we pass in full sha commit_id, which is a head
213 elif commit_id == branch_commit_id:
232 elif commit_id == branch_commit_id:
214 is_head = True
233 is_head = True
215 branch_name = _branch_name
234 branch_name = _branch_name
216 sha_commit_id = branch_commit_id
235 sha_commit_id = branch_commit_id
217 break
236 break
218
237
219 # checked branches, means we only need to try to get the branch/commit_sha
238 # checked branches, means we only need to try to get the branch/commit_sha
220 if not repo.is_empty:
239 if not repo.is_empty():
221 commit = repo.get_commit(commit_id=commit_id)
240 commit = repo.get_commit(commit_id=commit_id)
222 if commit:
241 if commit:
223 branch_name = commit.branch
242 branch_name = commit.branch
224 sha_commit_id = commit.raw_id
243 sha_commit_id = commit.raw_id
225
244
226 return branch_name, sha_commit_id, is_head
245 return branch_name, sha_commit_id, is_head
227
246
228 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
247 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
229
248
230 repo_id = self.db_repo.repo_id
249 repo_id = self.db_repo.repo_id
231 force_recache = self.get_recache_flag()
250 force_recache = self.get_recache_flag()
232
251
233 cache_seconds = safe_int(
252 cache_seconds = safe_int(
234 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
253 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
235 cache_on = not force_recache and cache_seconds > 0
254 cache_on = not force_recache and cache_seconds > 0
236 log.debug(
255 log.debug(
237 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
256 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
238 'with caching: %s[TTL: %ss]' % (
257 'with caching: %s[TTL: %ss]' % (
239 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
258 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
240
259
241 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
260 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
242 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
261 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
243
262
244 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
263 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
245 condition=cache_on)
264 condition=cache_on)
246 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
265 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
247 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
266 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
248 ver, repo_id, commit_id, f_path)
267 ver, repo_id, commit_id, f_path)
249
268
250 c.full_load = full_load
269 c.full_load = full_load
251 return render(
270 return render(
252 'rhodecode:templates/files/files_browser_tree.mako',
271 'rhodecode:templates/files/files_browser_tree.mako',
253 self._get_template_context(c), self.request)
272 self._get_template_context(c), self.request)
254
273
255 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
274 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
256
275
257 def _get_archive_spec(self, fname):
276 def _get_archive_spec(self, fname):
258 log.debug('Detecting archive spec for: `%s`', fname)
277 log.debug('Detecting archive spec for: `%s`', fname)
259
278
260 fileformat = None
279 fileformat = None
261 ext = None
280 ext = None
262 content_type = None
281 content_type = None
263 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
282 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
264
283
265 if fname.endswith(extension):
284 if fname.endswith(extension):
266 fileformat = a_type
285 fileformat = a_type
267 log.debug('archive is of type: %s', fileformat)
286 log.debug('archive is of type: %s', fileformat)
268 ext = extension
287 ext = extension
269 break
288 break
270
289
271 if not fileformat:
290 if not fileformat:
272 raise ValueError()
291 raise ValueError()
273
292
274 # left over part of whole fname is the commit
293 # left over part of whole fname is the commit
275 commit_id = fname[:-len(ext)]
294 commit_id = fname[:-len(ext)]
276
295
277 return commit_id, ext, fileformat, content_type
296 return commit_id, ext, fileformat, content_type
278
297
298 def create_pure_path(self, *parts):
299 # Split paths and sanitize them, removing any ../ etc
300 sanitized_path = [
301 x for x in pathlib2.PurePath(*parts).parts
302 if x not in ['.', '..']]
303
304 pure_path = pathlib2.PurePath(*sanitized_path)
305 return pure_path
306
279 @LoginRequired()
307 @LoginRequired()
280 @HasRepoPermissionAnyDecorator(
308 @HasRepoPermissionAnyDecorator(
281 'repository.read', 'repository.write', 'repository.admin')
309 'repository.read', 'repository.write', 'repository.admin')
282 @view_config(
310 @view_config(
283 route_name='repo_archivefile', request_method='GET',
311 route_name='repo_archivefile', request_method='GET',
284 renderer=None)
312 renderer=None)
285 def repo_archivefile(self):
313 def repo_archivefile(self):
286 # archive cache config
314 # archive cache config
287 from rhodecode import CONFIG
315 from rhodecode import CONFIG
288 _ = self.request.translate
316 _ = self.request.translate
289 self.load_default_context()
317 self.load_default_context()
290 default_at_path = '/'
318 default_at_path = '/'
291 fname = self.request.matchdict['fname']
319 fname = self.request.matchdict['fname']
292 subrepos = self.request.GET.get('subrepos') == 'true'
320 subrepos = self.request.GET.get('subrepos') == 'true'
293 at_path = self.request.GET.get('at_path') or default_at_path
321 at_path = self.request.GET.get('at_path') or default_at_path
294
322
295 if not self.db_repo.enable_downloads:
323 if not self.db_repo.enable_downloads:
296 return Response(_('Downloads disabled'))
324 return Response(_('Downloads disabled'))
297
325
298 try:
326 try:
299 commit_id, ext, fileformat, content_type = \
327 commit_id, ext, fileformat, content_type = \
300 self._get_archive_spec(fname)
328 self._get_archive_spec(fname)
301 except ValueError:
329 except ValueError:
302 return Response(_('Unknown archive type for: `{}`').format(
330 return Response(_('Unknown archive type for: `{}`').format(
303 h.escape(fname)))
331 h.escape(fname)))
304
332
305 try:
333 try:
306 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
334 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
307 except CommitDoesNotExistError:
335 except CommitDoesNotExistError:
308 return Response(_('Unknown commit_id {}').format(
336 return Response(_('Unknown commit_id {}').format(
309 h.escape(commit_id)))
337 h.escape(commit_id)))
310 except EmptyRepositoryError:
338 except EmptyRepositoryError:
311 return Response(_('Empty repository'))
339 return Response(_('Empty repository'))
312
340
313 try:
341 try:
314 at_path = commit.get_node(at_path).path or default_at_path
342 at_path = commit.get_node(at_path).path or default_at_path
315 except Exception:
343 except Exception:
316 return Response(_('No node at path {} for this repository').format(at_path))
344 return Response(_('No node at path {} for this repository').format(at_path))
317
345
318 path_sha = sha1(at_path)[:8]
346 path_sha = sha1(at_path)[:8]
319
347
320 # original backward compat name of archive
348 # original backward compat name of archive
321 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
349 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
322 short_sha = safe_str(commit.short_id)
350 short_sha = safe_str(commit.short_id)
323
351
324 if at_path == default_at_path:
352 if at_path == default_at_path:
325 archive_name = '{}-{}{}{}'.format(
353 archive_name = '{}-{}{}{}'.format(
326 clean_name,
354 clean_name,
327 '-sub' if subrepos else '',
355 '-sub' if subrepos else '',
328 short_sha,
356 short_sha,
329 ext)
357 ext)
330 # custom path and new name
358 # custom path and new name
331 else:
359 else:
332 archive_name = '{}-{}{}-{}{}'.format(
360 archive_name = '{}-{}{}-{}{}'.format(
333 clean_name,
361 clean_name,
334 '-sub' if subrepos else '',
362 '-sub' if subrepos else '',
335 short_sha,
363 short_sha,
336 path_sha,
364 path_sha,
337 ext)
365 ext)
338
366
339 use_cached_archive = False
367 use_cached_archive = False
340 archive_cache_enabled = CONFIG.get(
368 archive_cache_enabled = CONFIG.get(
341 'archive_cache_dir') and not self.request.GET.get('no_cache')
369 'archive_cache_dir') and not self.request.GET.get('no_cache')
342 cached_archive_path = None
370 cached_archive_path = None
343
371
344 if archive_cache_enabled:
372 if archive_cache_enabled:
345 # check if we it's ok to write
373 # check if we it's ok to write
346 if not os.path.isdir(CONFIG['archive_cache_dir']):
374 if not os.path.isdir(CONFIG['archive_cache_dir']):
347 os.makedirs(CONFIG['archive_cache_dir'])
375 os.makedirs(CONFIG['archive_cache_dir'])
348 cached_archive_path = os.path.join(
376 cached_archive_path = os.path.join(
349 CONFIG['archive_cache_dir'], archive_name)
377 CONFIG['archive_cache_dir'], archive_name)
350 if os.path.isfile(cached_archive_path):
378 if os.path.isfile(cached_archive_path):
351 log.debug('Found cached archive in %s', cached_archive_path)
379 log.debug('Found cached archive in %s', cached_archive_path)
352 fd, archive = None, cached_archive_path
380 fd, archive = None, cached_archive_path
353 use_cached_archive = True
381 use_cached_archive = True
354 else:
382 else:
355 log.debug('Archive %s is not yet cached', archive_name)
383 log.debug('Archive %s is not yet cached', archive_name)
356
384
357 if not use_cached_archive:
385 if not use_cached_archive:
358 # generate new archive
386 # generate new archive
359 fd, archive = tempfile.mkstemp()
387 fd, archive = tempfile.mkstemp()
360 log.debug('Creating new temp archive in %s', archive)
388 log.debug('Creating new temp archive in %s', archive)
361 try:
389 try:
362 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
390 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
363 archive_at_path=at_path)
391 archive_at_path=at_path)
364 except ImproperArchiveTypeError:
392 except ImproperArchiveTypeError:
365 return _('Unknown archive type')
393 return _('Unknown archive type')
366 if archive_cache_enabled:
394 if archive_cache_enabled:
367 # if we generated the archive and we have cache enabled
395 # if we generated the archive and we have cache enabled
368 # let's use this for future
396 # let's use this for future
369 log.debug('Storing new archive in %s', cached_archive_path)
397 log.debug('Storing new archive in %s', cached_archive_path)
370 shutil.move(archive, cached_archive_path)
398 shutil.move(archive, cached_archive_path)
371 archive = cached_archive_path
399 archive = cached_archive_path
372
400
373 # store download action
401 # store download action
374 audit_logger.store_web(
402 audit_logger.store_web(
375 'repo.archive.download', action_data={
403 'repo.archive.download', action_data={
376 'user_agent': self.request.user_agent,
404 'user_agent': self.request.user_agent,
377 'archive_name': archive_name,
405 'archive_name': archive_name,
378 'archive_spec': fname,
406 'archive_spec': fname,
379 'archive_cached': use_cached_archive},
407 'archive_cached': use_cached_archive},
380 user=self._rhodecode_user,
408 user=self._rhodecode_user,
381 repo=self.db_repo,
409 repo=self.db_repo,
382 commit=True
410 commit=True
383 )
411 )
384
412
385 def get_chunked_archive(archive_path):
413 def get_chunked_archive(archive_path):
386 with open(archive_path, 'rb') as stream:
414 with open(archive_path, 'rb') as stream:
387 while True:
415 while True:
388 data = stream.read(16 * 1024)
416 data = stream.read(16 * 1024)
389 if not data:
417 if not data:
390 if fd: # fd means we used temporary file
418 if fd: # fd means we used temporary file
391 os.close(fd)
419 os.close(fd)
392 if not archive_cache_enabled:
420 if not archive_cache_enabled:
393 log.debug('Destroying temp archive %s', archive_path)
421 log.debug('Destroying temp archive %s', archive_path)
394 os.remove(archive_path)
422 os.remove(archive_path)
395 break
423 break
396 yield data
424 yield data
397
425
398 response = Response(app_iter=get_chunked_archive(archive))
426 response = Response(app_iter=get_chunked_archive(archive))
399 response.content_disposition = str(
427 response.content_disposition = str(
400 'attachment; filename=%s' % archive_name)
428 'attachment; filename=%s' % archive_name)
401 response.content_type = str(content_type)
429 response.content_type = str(content_type)
402
430
403 return response
431 return response
404
432
405 def _get_file_node(self, commit_id, f_path):
433 def _get_file_node(self, commit_id, f_path):
406 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
434 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
407 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
435 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
408 try:
436 try:
409 node = commit.get_node(f_path)
437 node = commit.get_node(f_path)
410 if node.is_dir():
438 if node.is_dir():
411 raise NodeError('%s path is a %s not a file'
439 raise NodeError('%s path is a %s not a file'
412 % (node, type(node)))
440 % (node, type(node)))
413 except NodeDoesNotExistError:
441 except NodeDoesNotExistError:
414 commit = EmptyCommit(
442 commit = EmptyCommit(
415 commit_id=commit_id,
443 commit_id=commit_id,
416 idx=commit.idx,
444 idx=commit.idx,
417 repo=commit.repository,
445 repo=commit.repository,
418 alias=commit.repository.alias,
446 alias=commit.repository.alias,
419 message=commit.message,
447 message=commit.message,
420 author=commit.author,
448 author=commit.author,
421 date=commit.date)
449 date=commit.date)
422 node = FileNode(f_path, '', commit=commit)
450 node = FileNode(f_path, '', commit=commit)
423 else:
451 else:
424 commit = EmptyCommit(
452 commit = EmptyCommit(
425 repo=self.rhodecode_vcs_repo,
453 repo=self.rhodecode_vcs_repo,
426 alias=self.rhodecode_vcs_repo.alias)
454 alias=self.rhodecode_vcs_repo.alias)
427 node = FileNode(f_path, '', commit=commit)
455 node = FileNode(f_path, '', commit=commit)
428 return node
456 return node
429
457
430 @LoginRequired()
458 @LoginRequired()
431 @HasRepoPermissionAnyDecorator(
459 @HasRepoPermissionAnyDecorator(
432 'repository.read', 'repository.write', 'repository.admin')
460 'repository.read', 'repository.write', 'repository.admin')
433 @view_config(
461 @view_config(
434 route_name='repo_files_diff', request_method='GET',
462 route_name='repo_files_diff', request_method='GET',
435 renderer=None)
463 renderer=None)
436 def repo_files_diff(self):
464 def repo_files_diff(self):
437 c = self.load_default_context()
465 c = self.load_default_context()
438 f_path = self._get_f_path(self.request.matchdict)
466 f_path = self._get_f_path(self.request.matchdict)
439 diff1 = self.request.GET.get('diff1', '')
467 diff1 = self.request.GET.get('diff1', '')
440 diff2 = self.request.GET.get('diff2', '')
468 diff2 = self.request.GET.get('diff2', '')
441
469
442 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
470 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
443
471
444 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
472 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
445 line_context = self.request.GET.get('context', 3)
473 line_context = self.request.GET.get('context', 3)
446
474
447 if not any((diff1, diff2)):
475 if not any((diff1, diff2)):
448 h.flash(
476 h.flash(
449 'Need query parameter "diff1" or "diff2" to generate a diff.',
477 'Need query parameter "diff1" or "diff2" to generate a diff.',
450 category='error')
478 category='error')
451 raise HTTPBadRequest()
479 raise HTTPBadRequest()
452
480
453 c.action = self.request.GET.get('diff')
481 c.action = self.request.GET.get('diff')
454 if c.action not in ['download', 'raw']:
482 if c.action not in ['download', 'raw']:
455 compare_url = h.route_path(
483 compare_url = h.route_path(
456 'repo_compare',
484 'repo_compare',
457 repo_name=self.db_repo_name,
485 repo_name=self.db_repo_name,
458 source_ref_type='rev',
486 source_ref_type='rev',
459 source_ref=diff1,
487 source_ref=diff1,
460 target_repo=self.db_repo_name,
488 target_repo=self.db_repo_name,
461 target_ref_type='rev',
489 target_ref_type='rev',
462 target_ref=diff2,
490 target_ref=diff2,
463 _query=dict(f_path=f_path))
491 _query=dict(f_path=f_path))
464 # redirect to new view if we render diff
492 # redirect to new view if we render diff
465 raise HTTPFound(compare_url)
493 raise HTTPFound(compare_url)
466
494
467 try:
495 try:
468 node1 = self._get_file_node(diff1, path1)
496 node1 = self._get_file_node(diff1, path1)
469 node2 = self._get_file_node(diff2, f_path)
497 node2 = self._get_file_node(diff2, f_path)
470 except (RepositoryError, NodeError):
498 except (RepositoryError, NodeError):
471 log.exception("Exception while trying to get node from repository")
499 log.exception("Exception while trying to get node from repository")
472 raise HTTPFound(
500 raise HTTPFound(
473 h.route_path('repo_files', repo_name=self.db_repo_name,
501 h.route_path('repo_files', repo_name=self.db_repo_name,
474 commit_id='tip', f_path=f_path))
502 commit_id='tip', f_path=f_path))
475
503
476 if all(isinstance(node.commit, EmptyCommit)
504 if all(isinstance(node.commit, EmptyCommit)
477 for node in (node1, node2)):
505 for node in (node1, node2)):
478 raise HTTPNotFound()
506 raise HTTPNotFound()
479
507
480 c.commit_1 = node1.commit
508 c.commit_1 = node1.commit
481 c.commit_2 = node2.commit
509 c.commit_2 = node2.commit
482
510
483 if c.action == 'download':
511 if c.action == 'download':
484 _diff = diffs.get_gitdiff(node1, node2,
512 _diff = diffs.get_gitdiff(node1, node2,
485 ignore_whitespace=ignore_whitespace,
513 ignore_whitespace=ignore_whitespace,
486 context=line_context)
514 context=line_context)
487 diff = diffs.DiffProcessor(_diff, format='gitdiff')
515 diff = diffs.DiffProcessor(_diff, format='gitdiff')
488
516
489 response = Response(self.path_filter.get_raw_patch(diff))
517 response = Response(self.path_filter.get_raw_patch(diff))
490 response.content_type = 'text/plain'
518 response.content_type = 'text/plain'
491 response.content_disposition = (
519 response.content_disposition = (
492 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
520 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
493 )
521 )
494 charset = self._get_default_encoding(c)
522 charset = self._get_default_encoding(c)
495 if charset:
523 if charset:
496 response.charset = charset
524 response.charset = charset
497 return response
525 return response
498
526
499 elif c.action == 'raw':
527 elif c.action == 'raw':
500 _diff = diffs.get_gitdiff(node1, node2,
528 _diff = diffs.get_gitdiff(node1, node2,
501 ignore_whitespace=ignore_whitespace,
529 ignore_whitespace=ignore_whitespace,
502 context=line_context)
530 context=line_context)
503 diff = diffs.DiffProcessor(_diff, format='gitdiff')
531 diff = diffs.DiffProcessor(_diff, format='gitdiff')
504
532
505 response = Response(self.path_filter.get_raw_patch(diff))
533 response = Response(self.path_filter.get_raw_patch(diff))
506 response.content_type = 'text/plain'
534 response.content_type = 'text/plain'
507 charset = self._get_default_encoding(c)
535 charset = self._get_default_encoding(c)
508 if charset:
536 if charset:
509 response.charset = charset
537 response.charset = charset
510 return response
538 return response
511
539
512 # in case we ever end up here
540 # in case we ever end up here
513 raise HTTPNotFound()
541 raise HTTPNotFound()
514
542
515 @LoginRequired()
543 @LoginRequired()
516 @HasRepoPermissionAnyDecorator(
544 @HasRepoPermissionAnyDecorator(
517 'repository.read', 'repository.write', 'repository.admin')
545 'repository.read', 'repository.write', 'repository.admin')
518 @view_config(
546 @view_config(
519 route_name='repo_files_diff_2way_redirect', request_method='GET',
547 route_name='repo_files_diff_2way_redirect', request_method='GET',
520 renderer=None)
548 renderer=None)
521 def repo_files_diff_2way_redirect(self):
549 def repo_files_diff_2way_redirect(self):
522 """
550 """
523 Kept only to make OLD links work
551 Kept only to make OLD links work
524 """
552 """
525 f_path = self._get_f_path_unchecked(self.request.matchdict)
553 f_path = self._get_f_path_unchecked(self.request.matchdict)
526 diff1 = self.request.GET.get('diff1', '')
554 diff1 = self.request.GET.get('diff1', '')
527 diff2 = self.request.GET.get('diff2', '')
555 diff2 = self.request.GET.get('diff2', '')
528
556
529 if not any((diff1, diff2)):
557 if not any((diff1, diff2)):
530 h.flash(
558 h.flash(
531 'Need query parameter "diff1" or "diff2" to generate a diff.',
559 'Need query parameter "diff1" or "diff2" to generate a diff.',
532 category='error')
560 category='error')
533 raise HTTPBadRequest()
561 raise HTTPBadRequest()
534
562
535 compare_url = h.route_path(
563 compare_url = h.route_path(
536 'repo_compare',
564 'repo_compare',
537 repo_name=self.db_repo_name,
565 repo_name=self.db_repo_name,
538 source_ref_type='rev',
566 source_ref_type='rev',
539 source_ref=diff1,
567 source_ref=diff1,
540 target_ref_type='rev',
568 target_ref_type='rev',
541 target_ref=diff2,
569 target_ref=diff2,
542 _query=dict(f_path=f_path, diffmode='sideside',
570 _query=dict(f_path=f_path, diffmode='sideside',
543 target_repo=self.db_repo_name,))
571 target_repo=self.db_repo_name,))
544 raise HTTPFound(compare_url)
572 raise HTTPFound(compare_url)
545
573
546 @LoginRequired()
574 @LoginRequired()
547 @HasRepoPermissionAnyDecorator(
575 @HasRepoPermissionAnyDecorator(
548 'repository.read', 'repository.write', 'repository.admin')
576 'repository.read', 'repository.write', 'repository.admin')
549 @view_config(
577 @view_config(
550 route_name='repo_files', request_method='GET',
578 route_name='repo_files', request_method='GET',
551 renderer=None)
579 renderer=None)
552 @view_config(
580 @view_config(
553 route_name='repo_files:default_path', request_method='GET',
581 route_name='repo_files:default_path', request_method='GET',
554 renderer=None)
582 renderer=None)
555 @view_config(
583 @view_config(
556 route_name='repo_files:default_commit', request_method='GET',
584 route_name='repo_files:default_commit', request_method='GET',
557 renderer=None)
585 renderer=None)
558 @view_config(
586 @view_config(
559 route_name='repo_files:rendered', request_method='GET',
587 route_name='repo_files:rendered', request_method='GET',
560 renderer=None)
588 renderer=None)
561 @view_config(
589 @view_config(
562 route_name='repo_files:annotated', request_method='GET',
590 route_name='repo_files:annotated', request_method='GET',
563 renderer=None)
591 renderer=None)
564 def repo_files(self):
592 def repo_files(self):
565 c = self.load_default_context()
593 c = self.load_default_context()
566
594
567 view_name = getattr(self.request.matched_route, 'name', None)
595 view_name = getattr(self.request.matched_route, 'name', None)
568
596
569 c.annotate = view_name == 'repo_files:annotated'
597 c.annotate = view_name == 'repo_files:annotated'
570 # default is false, but .rst/.md files later are auto rendered, we can
598 # default is false, but .rst/.md files later are auto rendered, we can
571 # overwrite auto rendering by setting this GET flag
599 # overwrite auto rendering by setting this GET flag
572 c.renderer = view_name == 'repo_files:rendered' or \
600 c.renderer = view_name == 'repo_files:rendered' or \
573 not self.request.GET.get('no-render', False)
601 not self.request.GET.get('no-render', False)
574
602
575 # redirect to given commit_id from form if given
603 # redirect to given commit_id from form if given
576 get_commit_id = self.request.GET.get('at_rev', None)
604 get_commit_id = self.request.GET.get('at_rev', None)
577 if get_commit_id:
605 if get_commit_id:
578 self._get_commit_or_redirect(get_commit_id)
606 self._get_commit_or_redirect(get_commit_id)
579
607
580 commit_id, f_path = self._get_commit_and_path()
608 commit_id, f_path = self._get_commit_and_path()
581 c.commit = self._get_commit_or_redirect(commit_id)
609 c.commit = self._get_commit_or_redirect(commit_id)
582 c.branch = self.request.GET.get('branch', None)
610 c.branch = self.request.GET.get('branch', None)
583 c.f_path = f_path
611 c.f_path = f_path
584
612
585 # prev link
613 # prev link
586 try:
614 try:
587 prev_commit = c.commit.prev(c.branch)
615 prev_commit = c.commit.prev(c.branch)
588 c.prev_commit = prev_commit
616 c.prev_commit = prev_commit
589 c.url_prev = h.route_path(
617 c.url_prev = h.route_path(
590 'repo_files', repo_name=self.db_repo_name,
618 'repo_files', repo_name=self.db_repo_name,
591 commit_id=prev_commit.raw_id, f_path=f_path)
619 commit_id=prev_commit.raw_id, f_path=f_path)
592 if c.branch:
620 if c.branch:
593 c.url_prev += '?branch=%s' % c.branch
621 c.url_prev += '?branch=%s' % c.branch
594 except (CommitDoesNotExistError, VCSError):
622 except (CommitDoesNotExistError, VCSError):
595 c.url_prev = '#'
623 c.url_prev = '#'
596 c.prev_commit = EmptyCommit()
624 c.prev_commit = EmptyCommit()
597
625
598 # next link
626 # next link
599 try:
627 try:
600 next_commit = c.commit.next(c.branch)
628 next_commit = c.commit.next(c.branch)
601 c.next_commit = next_commit
629 c.next_commit = next_commit
602 c.url_next = h.route_path(
630 c.url_next = h.route_path(
603 'repo_files', repo_name=self.db_repo_name,
631 'repo_files', repo_name=self.db_repo_name,
604 commit_id=next_commit.raw_id, f_path=f_path)
632 commit_id=next_commit.raw_id, f_path=f_path)
605 if c.branch:
633 if c.branch:
606 c.url_next += '?branch=%s' % c.branch
634 c.url_next += '?branch=%s' % c.branch
607 except (CommitDoesNotExistError, VCSError):
635 except (CommitDoesNotExistError, VCSError):
608 c.url_next = '#'
636 c.url_next = '#'
609 c.next_commit = EmptyCommit()
637 c.next_commit = EmptyCommit()
610
638
611 # files or dirs
639 # files or dirs
612 try:
640 try:
613 c.file = c.commit.get_node(f_path)
641 c.file = c.commit.get_node(f_path)
614 c.file_author = True
642 c.file_author = True
615 c.file_tree = ''
643 c.file_tree = ''
616
644
617 # load file content
645 # load file content
618 if c.file.is_file():
646 if c.file.is_file():
619 c.lf_node = c.file.get_largefile_node()
647 c.lf_node = c.file.get_largefile_node()
620
648
621 c.file_source_page = 'true'
649 c.file_source_page = 'true'
622 c.file_last_commit = c.file.last_commit
650 c.file_last_commit = c.file.last_commit
623 if c.file.size < c.visual.cut_off_limit_diff:
651 if c.file.size < c.visual.cut_off_limit_diff:
624 if c.annotate: # annotation has precedence over renderer
652 if c.annotate: # annotation has precedence over renderer
625 c.annotated_lines = filenode_as_annotated_lines_tokens(
653 c.annotated_lines = filenode_as_annotated_lines_tokens(
626 c.file
654 c.file
627 )
655 )
628 else:
656 else:
629 c.renderer = (
657 c.renderer = (
630 c.renderer and h.renderer_from_filename(c.file.path)
658 c.renderer and h.renderer_from_filename(c.file.path)
631 )
659 )
632 if not c.renderer:
660 if not c.renderer:
633 c.lines = filenode_as_lines_tokens(c.file)
661 c.lines = filenode_as_lines_tokens(c.file)
634
662
635 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
663 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
636 commit_id, self.rhodecode_vcs_repo)
664 commit_id, self.rhodecode_vcs_repo)
637 c.on_branch_head = is_head
665 c.on_branch_head = is_head
638
666
639 branch = c.commit.branch if (
667 branch = c.commit.branch if (
640 c.commit.branch and '/' not in c.commit.branch) else None
668 c.commit.branch and '/' not in c.commit.branch) else None
641 c.branch_or_raw_id = branch or c.commit.raw_id
669 c.branch_or_raw_id = branch or c.commit.raw_id
642 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
670 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
643
671
644 author = c.file_last_commit.author
672 author = c.file_last_commit.author
645 c.authors = [[
673 c.authors = [[
646 h.email(author),
674 h.email(author),
647 h.person(author, 'username_or_name_or_email'),
675 h.person(author, 'username_or_name_or_email'),
648 1
676 1
649 ]]
677 ]]
650
678
651 else: # load tree content at path
679 else: # load tree content at path
652 c.file_source_page = 'false'
680 c.file_source_page = 'false'
653 c.authors = []
681 c.authors = []
654 # this loads a simple tree without metadata to speed things up
682 # this loads a simple tree without metadata to speed things up
655 # later via ajax we call repo_nodetree_full and fetch whole
683 # later via ajax we call repo_nodetree_full and fetch whole
656 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
684 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
657
685
658 except RepositoryError as e:
686 except RepositoryError as e:
659 h.flash(safe_str(h.escape(e)), category='error')
687 h.flash(safe_str(h.escape(e)), category='error')
660 raise HTTPNotFound()
688 raise HTTPNotFound()
661
689
662 if self.request.environ.get('HTTP_X_PJAX'):
690 if self.request.environ.get('HTTP_X_PJAX'):
663 html = render('rhodecode:templates/files/files_pjax.mako',
691 html = render('rhodecode:templates/files/files_pjax.mako',
664 self._get_template_context(c), self.request)
692 self._get_template_context(c), self.request)
665 else:
693 else:
666 html = render('rhodecode:templates/files/files.mako',
694 html = render('rhodecode:templates/files/files.mako',
667 self._get_template_context(c), self.request)
695 self._get_template_context(c), self.request)
668 return Response(html)
696 return Response(html)
669
697
670 @HasRepoPermissionAnyDecorator(
698 @HasRepoPermissionAnyDecorator(
671 'repository.read', 'repository.write', 'repository.admin')
699 'repository.read', 'repository.write', 'repository.admin')
672 @view_config(
700 @view_config(
673 route_name='repo_files:annotated_previous', request_method='GET',
701 route_name='repo_files:annotated_previous', request_method='GET',
674 renderer=None)
702 renderer=None)
675 def repo_files_annotated_previous(self):
703 def repo_files_annotated_previous(self):
676 self.load_default_context()
704 self.load_default_context()
677
705
678 commit_id, f_path = self._get_commit_and_path()
706 commit_id, f_path = self._get_commit_and_path()
679 commit = self._get_commit_or_redirect(commit_id)
707 commit = self._get_commit_or_redirect(commit_id)
680 prev_commit_id = commit.raw_id
708 prev_commit_id = commit.raw_id
681 line_anchor = self.request.GET.get('line_anchor')
709 line_anchor = self.request.GET.get('line_anchor')
682 is_file = False
710 is_file = False
683 try:
711 try:
684 _file = commit.get_node(f_path)
712 _file = commit.get_node(f_path)
685 is_file = _file.is_file()
713 is_file = _file.is_file()
686 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
714 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
687 pass
715 pass
688
716
689 if is_file:
717 if is_file:
690 history = commit.get_path_history(f_path)
718 history = commit.get_path_history(f_path)
691 prev_commit_id = history[1].raw_id \
719 prev_commit_id = history[1].raw_id \
692 if len(history) > 1 else prev_commit_id
720 if len(history) > 1 else prev_commit_id
693 prev_url = h.route_path(
721 prev_url = h.route_path(
694 'repo_files:annotated', repo_name=self.db_repo_name,
722 'repo_files:annotated', repo_name=self.db_repo_name,
695 commit_id=prev_commit_id, f_path=f_path,
723 commit_id=prev_commit_id, f_path=f_path,
696 _anchor='L{}'.format(line_anchor))
724 _anchor='L{}'.format(line_anchor))
697
725
698 raise HTTPFound(prev_url)
726 raise HTTPFound(prev_url)
699
727
700 @LoginRequired()
728 @LoginRequired()
701 @HasRepoPermissionAnyDecorator(
729 @HasRepoPermissionAnyDecorator(
702 'repository.read', 'repository.write', 'repository.admin')
730 'repository.read', 'repository.write', 'repository.admin')
703 @view_config(
731 @view_config(
704 route_name='repo_nodetree_full', request_method='GET',
732 route_name='repo_nodetree_full', request_method='GET',
705 renderer=None, xhr=True)
733 renderer=None, xhr=True)
706 @view_config(
734 @view_config(
707 route_name='repo_nodetree_full:default_path', request_method='GET',
735 route_name='repo_nodetree_full:default_path', request_method='GET',
708 renderer=None, xhr=True)
736 renderer=None, xhr=True)
709 def repo_nodetree_full(self):
737 def repo_nodetree_full(self):
710 """
738 """
711 Returns rendered html of file tree that contains commit date,
739 Returns rendered html of file tree that contains commit date,
712 author, commit_id for the specified combination of
740 author, commit_id for the specified combination of
713 repo, commit_id and file path
741 repo, commit_id and file path
714 """
742 """
715 c = self.load_default_context()
743 c = self.load_default_context()
716
744
717 commit_id, f_path = self._get_commit_and_path()
745 commit_id, f_path = self._get_commit_and_path()
718 commit = self._get_commit_or_redirect(commit_id)
746 commit = self._get_commit_or_redirect(commit_id)
719 try:
747 try:
720 dir_node = commit.get_node(f_path)
748 dir_node = commit.get_node(f_path)
721 except RepositoryError as e:
749 except RepositoryError as e:
722 return Response('error: {}'.format(h.escape(safe_str(e))))
750 return Response('error: {}'.format(h.escape(safe_str(e))))
723
751
724 if dir_node.is_file():
752 if dir_node.is_file():
725 return Response('')
753 return Response('')
726
754
727 c.file = dir_node
755 c.file = dir_node
728 c.commit = commit
756 c.commit = commit
729
757
730 html = self._get_tree_at_commit(
758 html = self._get_tree_at_commit(
731 c, commit.raw_id, dir_node.path, full_load=True)
759 c, commit.raw_id, dir_node.path, full_load=True)
732
760
733 return Response(html)
761 return Response(html)
734
762
735 def _get_attachement_headers(self, f_path):
763 def _get_attachement_headers(self, f_path):
736 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
764 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
737 safe_path = f_name.replace('"', '\\"')
765 safe_path = f_name.replace('"', '\\"')
738 encoded_path = urllib.quote(f_name)
766 encoded_path = urllib.quote(f_name)
739
767
740 return "attachment; " \
768 return "attachment; " \
741 "filename=\"{}\"; " \
769 "filename=\"{}\"; " \
742 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
770 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
743
771
744 @LoginRequired()
772 @LoginRequired()
745 @HasRepoPermissionAnyDecorator(
773 @HasRepoPermissionAnyDecorator(
746 'repository.read', 'repository.write', 'repository.admin')
774 'repository.read', 'repository.write', 'repository.admin')
747 @view_config(
775 @view_config(
748 route_name='repo_file_raw', request_method='GET',
776 route_name='repo_file_raw', request_method='GET',
749 renderer=None)
777 renderer=None)
750 def repo_file_raw(self):
778 def repo_file_raw(self):
751 """
779 """
752 Action for show as raw, some mimetypes are "rendered",
780 Action for show as raw, some mimetypes are "rendered",
753 those include images, icons.
781 those include images, icons.
754 """
782 """
755 c = self.load_default_context()
783 c = self.load_default_context()
756
784
757 commit_id, f_path = self._get_commit_and_path()
785 commit_id, f_path = self._get_commit_and_path()
758 commit = self._get_commit_or_redirect(commit_id)
786 commit = self._get_commit_or_redirect(commit_id)
759 file_node = self._get_filenode_or_redirect(commit, f_path)
787 file_node = self._get_filenode_or_redirect(commit, f_path)
760
788
761 raw_mimetype_mapping = {
789 raw_mimetype_mapping = {
762 # map original mimetype to a mimetype used for "show as raw"
790 # map original mimetype to a mimetype used for "show as raw"
763 # you can also provide a content-disposition to override the
791 # you can also provide a content-disposition to override the
764 # default "attachment" disposition.
792 # default "attachment" disposition.
765 # orig_type: (new_type, new_dispo)
793 # orig_type: (new_type, new_dispo)
766
794
767 # show images inline:
795 # show images inline:
768 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
796 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
769 # for example render an SVG with javascript inside or even render
797 # for example render an SVG with javascript inside or even render
770 # HTML.
798 # HTML.
771 'image/x-icon': ('image/x-icon', 'inline'),
799 'image/x-icon': ('image/x-icon', 'inline'),
772 'image/png': ('image/png', 'inline'),
800 'image/png': ('image/png', 'inline'),
773 'image/gif': ('image/gif', 'inline'),
801 'image/gif': ('image/gif', 'inline'),
774 'image/jpeg': ('image/jpeg', 'inline'),
802 'image/jpeg': ('image/jpeg', 'inline'),
775 'application/pdf': ('application/pdf', 'inline'),
803 'application/pdf': ('application/pdf', 'inline'),
776 }
804 }
777
805
778 mimetype = file_node.mimetype
806 mimetype = file_node.mimetype
779 try:
807 try:
780 mimetype, disposition = raw_mimetype_mapping[mimetype]
808 mimetype, disposition = raw_mimetype_mapping[mimetype]
781 except KeyError:
809 except KeyError:
782 # we don't know anything special about this, handle it safely
810 # we don't know anything special about this, handle it safely
783 if file_node.is_binary:
811 if file_node.is_binary:
784 # do same as download raw for binary files
812 # do same as download raw for binary files
785 mimetype, disposition = 'application/octet-stream', 'attachment'
813 mimetype, disposition = 'application/octet-stream', 'attachment'
786 else:
814 else:
787 # do not just use the original mimetype, but force text/plain,
815 # do not just use the original mimetype, but force text/plain,
788 # otherwise it would serve text/html and that might be unsafe.
816 # otherwise it would serve text/html and that might be unsafe.
789 # Note: underlying vcs library fakes text/plain mimetype if the
817 # Note: underlying vcs library fakes text/plain mimetype if the
790 # mimetype can not be determined and it thinks it is not
818 # mimetype can not be determined and it thinks it is not
791 # binary.This might lead to erroneous text display in some
819 # binary.This might lead to erroneous text display in some
792 # cases, but helps in other cases, like with text files
820 # cases, but helps in other cases, like with text files
793 # without extension.
821 # without extension.
794 mimetype, disposition = 'text/plain', 'inline'
822 mimetype, disposition = 'text/plain', 'inline'
795
823
796 if disposition == 'attachment':
824 if disposition == 'attachment':
797 disposition = self._get_attachement_headers(f_path)
825 disposition = self._get_attachement_headers(f_path)
798
826
799 def stream_node():
827 def stream_node():
800 yield file_node.raw_bytes
828 yield file_node.raw_bytes
801
829
802 response = Response(app_iter=stream_node())
830 response = Response(app_iter=stream_node())
803 response.content_disposition = disposition
831 response.content_disposition = disposition
804 response.content_type = mimetype
832 response.content_type = mimetype
805
833
806 charset = self._get_default_encoding(c)
834 charset = self._get_default_encoding(c)
807 if charset:
835 if charset:
808 response.charset = charset
836 response.charset = charset
809
837
810 return response
838 return response
811
839
812 @LoginRequired()
840 @LoginRequired()
813 @HasRepoPermissionAnyDecorator(
841 @HasRepoPermissionAnyDecorator(
814 'repository.read', 'repository.write', 'repository.admin')
842 'repository.read', 'repository.write', 'repository.admin')
815 @view_config(
843 @view_config(
816 route_name='repo_file_download', request_method='GET',
844 route_name='repo_file_download', request_method='GET',
817 renderer=None)
845 renderer=None)
818 @view_config(
846 @view_config(
819 route_name='repo_file_download:legacy', request_method='GET',
847 route_name='repo_file_download:legacy', request_method='GET',
820 renderer=None)
848 renderer=None)
821 def repo_file_download(self):
849 def repo_file_download(self):
822 c = self.load_default_context()
850 c = self.load_default_context()
823
851
824 commit_id, f_path = self._get_commit_and_path()
852 commit_id, f_path = self._get_commit_and_path()
825 commit = self._get_commit_or_redirect(commit_id)
853 commit = self._get_commit_or_redirect(commit_id)
826 file_node = self._get_filenode_or_redirect(commit, f_path)
854 file_node = self._get_filenode_or_redirect(commit, f_path)
827
855
828 if self.request.GET.get('lf'):
856 if self.request.GET.get('lf'):
829 # only if lf get flag is passed, we download this file
857 # only if lf get flag is passed, we download this file
830 # as LFS/Largefile
858 # as LFS/Largefile
831 lf_node = file_node.get_largefile_node()
859 lf_node = file_node.get_largefile_node()
832 if lf_node:
860 if lf_node:
833 # overwrite our pointer with the REAL large-file
861 # overwrite our pointer with the REAL large-file
834 file_node = lf_node
862 file_node = lf_node
835
863
836 disposition = self._get_attachement_headers(f_path)
864 disposition = self._get_attachement_headers(f_path)
837
865
838 def stream_node():
866 def stream_node():
839 yield file_node.raw_bytes
867 yield file_node.raw_bytes
840
868
841 response = Response(app_iter=stream_node())
869 response = Response(app_iter=stream_node())
842 response.content_disposition = disposition
870 response.content_disposition = disposition
843 response.content_type = file_node.mimetype
871 response.content_type = file_node.mimetype
844
872
845 charset = self._get_default_encoding(c)
873 charset = self._get_default_encoding(c)
846 if charset:
874 if charset:
847 response.charset = charset
875 response.charset = charset
848
876
849 return response
877 return response
850
878
851 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
879 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
852
880
853 cache_seconds = safe_int(
881 cache_seconds = safe_int(
854 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
882 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
855 cache_on = cache_seconds > 0
883 cache_on = cache_seconds > 0
856 log.debug(
884 log.debug(
857 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
885 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
858 'with caching: %s[TTL: %ss]' % (
886 'with caching: %s[TTL: %ss]' % (
859 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
887 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
860
888
861 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
889 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
862 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
890 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
863
891
864 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
892 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
865 condition=cache_on)
893 condition=cache_on)
866 def compute_file_search(repo_id, commit_id, f_path):
894 def compute_file_search(repo_id, commit_id, f_path):
867 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
895 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
868 repo_id, commit_id, f_path)
896 repo_id, commit_id, f_path)
869 try:
897 try:
870 _d, _f = ScmModel().get_nodes(
898 _d, _f = ScmModel().get_nodes(
871 repo_name, commit_id, f_path, flat=False)
899 repo_name, commit_id, f_path, flat=False)
872 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
900 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
873 log.exception(safe_str(e))
901 log.exception(safe_str(e))
874 h.flash(safe_str(h.escape(e)), category='error')
902 h.flash(safe_str(h.escape(e)), category='error')
875 raise HTTPFound(h.route_path(
903 raise HTTPFound(h.route_path(
876 'repo_files', repo_name=self.db_repo_name,
904 'repo_files', repo_name=self.db_repo_name,
877 commit_id='tip', f_path='/'))
905 commit_id='tip', f_path='/'))
878 return _d + _f
906 return _d + _f
879
907
880 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
908 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
881
909
882 @LoginRequired()
910 @LoginRequired()
883 @HasRepoPermissionAnyDecorator(
911 @HasRepoPermissionAnyDecorator(
884 'repository.read', 'repository.write', 'repository.admin')
912 'repository.read', 'repository.write', 'repository.admin')
885 @view_config(
913 @view_config(
886 route_name='repo_files_nodelist', request_method='GET',
914 route_name='repo_files_nodelist', request_method='GET',
887 renderer='json_ext', xhr=True)
915 renderer='json_ext', xhr=True)
888 def repo_nodelist(self):
916 def repo_nodelist(self):
889 self.load_default_context()
917 self.load_default_context()
890
918
891 commit_id, f_path = self._get_commit_and_path()
919 commit_id, f_path = self._get_commit_and_path()
892 commit = self._get_commit_or_redirect(commit_id)
920 commit = self._get_commit_or_redirect(commit_id)
893
921
894 metadata = self._get_nodelist_at_commit(
922 metadata = self._get_nodelist_at_commit(
895 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
923 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
896 return {'nodes': metadata}
924 return {'nodes': metadata}
897
925
898 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
926 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
899 items = []
927 items = []
900 for name, commit_id in branches_or_tags.items():
928 for name, commit_id in branches_or_tags.items():
901 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
929 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
902 items.append((sym_ref, name, ref_type))
930 items.append((sym_ref, name, ref_type))
903 return items
931 return items
904
932
905 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
933 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
906 return commit_id
934 return commit_id
907
935
908 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
936 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
909 new_f_path = vcspath.join(name, f_path)
937 new_f_path = vcspath.join(name, f_path)
910 return u'%s@%s' % (new_f_path, commit_id)
938 return u'%s@%s' % (new_f_path, commit_id)
911
939
912 def _get_node_history(self, commit_obj, f_path, commits=None):
940 def _get_node_history(self, commit_obj, f_path, commits=None):
913 """
941 """
914 get commit history for given node
942 get commit history for given node
915
943
916 :param commit_obj: commit to calculate history
944 :param commit_obj: commit to calculate history
917 :param f_path: path for node to calculate history for
945 :param f_path: path for node to calculate history for
918 :param commits: if passed don't calculate history and take
946 :param commits: if passed don't calculate history and take
919 commits defined in this list
947 commits defined in this list
920 """
948 """
921 _ = self.request.translate
949 _ = self.request.translate
922
950
923 # calculate history based on tip
951 # calculate history based on tip
924 tip = self.rhodecode_vcs_repo.get_commit()
952 tip = self.rhodecode_vcs_repo.get_commit()
925 if commits is None:
953 if commits is None:
926 pre_load = ["author", "branch"]
954 pre_load = ["author", "branch"]
927 try:
955 try:
928 commits = tip.get_path_history(f_path, pre_load=pre_load)
956 commits = tip.get_path_history(f_path, pre_load=pre_load)
929 except (NodeDoesNotExistError, CommitError):
957 except (NodeDoesNotExistError, CommitError):
930 # this node is not present at tip!
958 # this node is not present at tip!
931 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
959 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
932
960
933 history = []
961 history = []
934 commits_group = ([], _("Changesets"))
962 commits_group = ([], _("Changesets"))
935 for commit in commits:
963 for commit in commits:
936 branch = ' (%s)' % commit.branch if commit.branch else ''
964 branch = ' (%s)' % commit.branch if commit.branch else ''
937 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
965 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
938 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
966 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
939 history.append(commits_group)
967 history.append(commits_group)
940
968
941 symbolic_reference = self._symbolic_reference
969 symbolic_reference = self._symbolic_reference
942
970
943 if self.rhodecode_vcs_repo.alias == 'svn':
971 if self.rhodecode_vcs_repo.alias == 'svn':
944 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
972 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
945 f_path, self.rhodecode_vcs_repo)
973 f_path, self.rhodecode_vcs_repo)
946 if adjusted_f_path != f_path:
974 if adjusted_f_path != f_path:
947 log.debug(
975 log.debug(
948 'Recognized svn tag or branch in file "%s", using svn '
976 'Recognized svn tag or branch in file "%s", using svn '
949 'specific symbolic references', f_path)
977 'specific symbolic references', f_path)
950 f_path = adjusted_f_path
978 f_path = adjusted_f_path
951 symbolic_reference = self._symbolic_reference_svn
979 symbolic_reference = self._symbolic_reference_svn
952
980
953 branches = self._create_references(
981 branches = self._create_references(
954 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
982 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
955 branches_group = (branches, _("Branches"))
983 branches_group = (branches, _("Branches"))
956
984
957 tags = self._create_references(
985 tags = self._create_references(
958 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
986 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
959 tags_group = (tags, _("Tags"))
987 tags_group = (tags, _("Tags"))
960
988
961 history.append(branches_group)
989 history.append(branches_group)
962 history.append(tags_group)
990 history.append(tags_group)
963
991
964 return history, commits
992 return history, commits
965
993
966 @LoginRequired()
994 @LoginRequired()
967 @HasRepoPermissionAnyDecorator(
995 @HasRepoPermissionAnyDecorator(
968 'repository.read', 'repository.write', 'repository.admin')
996 'repository.read', 'repository.write', 'repository.admin')
969 @view_config(
997 @view_config(
970 route_name='repo_file_history', request_method='GET',
998 route_name='repo_file_history', request_method='GET',
971 renderer='json_ext')
999 renderer='json_ext')
972 def repo_file_history(self):
1000 def repo_file_history(self):
973 self.load_default_context()
1001 self.load_default_context()
974
1002
975 commit_id, f_path = self._get_commit_and_path()
1003 commit_id, f_path = self._get_commit_and_path()
976 commit = self._get_commit_or_redirect(commit_id)
1004 commit = self._get_commit_or_redirect(commit_id)
977 file_node = self._get_filenode_or_redirect(commit, f_path)
1005 file_node = self._get_filenode_or_redirect(commit, f_path)
978
1006
979 if file_node.is_file():
1007 if file_node.is_file():
980 file_history, _hist = self._get_node_history(commit, f_path)
1008 file_history, _hist = self._get_node_history(commit, f_path)
981
1009
982 res = []
1010 res = []
983 for obj in file_history:
1011 for obj in file_history:
984 res.append({
1012 res.append({
985 'text': obj[1],
1013 'text': obj[1],
986 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1014 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
987 })
1015 })
988
1016
989 data = {
1017 data = {
990 'more': False,
1018 'more': False,
991 'results': res
1019 'results': res
992 }
1020 }
993 return data
1021 return data
994
1022
995 log.warning('Cannot fetch history for directory')
1023 log.warning('Cannot fetch history for directory')
996 raise HTTPBadRequest()
1024 raise HTTPBadRequest()
997
1025
998 @LoginRequired()
1026 @LoginRequired()
999 @HasRepoPermissionAnyDecorator(
1027 @HasRepoPermissionAnyDecorator(
1000 'repository.read', 'repository.write', 'repository.admin')
1028 'repository.read', 'repository.write', 'repository.admin')
1001 @view_config(
1029 @view_config(
1002 route_name='repo_file_authors', request_method='GET',
1030 route_name='repo_file_authors', request_method='GET',
1003 renderer='rhodecode:templates/files/file_authors_box.mako')
1031 renderer='rhodecode:templates/files/file_authors_box.mako')
1004 def repo_file_authors(self):
1032 def repo_file_authors(self):
1005 c = self.load_default_context()
1033 c = self.load_default_context()
1006
1034
1007 commit_id, f_path = self._get_commit_and_path()
1035 commit_id, f_path = self._get_commit_and_path()
1008 commit = self._get_commit_or_redirect(commit_id)
1036 commit = self._get_commit_or_redirect(commit_id)
1009 file_node = self._get_filenode_or_redirect(commit, f_path)
1037 file_node = self._get_filenode_or_redirect(commit, f_path)
1010
1038
1011 if not file_node.is_file():
1039 if not file_node.is_file():
1012 raise HTTPBadRequest()
1040 raise HTTPBadRequest()
1013
1041
1014 c.file_last_commit = file_node.last_commit
1042 c.file_last_commit = file_node.last_commit
1015 if self.request.GET.get('annotate') == '1':
1043 if self.request.GET.get('annotate') == '1':
1016 # use _hist from annotation if annotation mode is on
1044 # use _hist from annotation if annotation mode is on
1017 commit_ids = set(x[1] for x in file_node.annotate)
1045 commit_ids = set(x[1] for x in file_node.annotate)
1018 _hist = (
1046 _hist = (
1019 self.rhodecode_vcs_repo.get_commit(commit_id)
1047 self.rhodecode_vcs_repo.get_commit(commit_id)
1020 for commit_id in commit_ids)
1048 for commit_id in commit_ids)
1021 else:
1049 else:
1022 _f_history, _hist = self._get_node_history(commit, f_path)
1050 _f_history, _hist = self._get_node_history(commit, f_path)
1023 c.file_author = False
1051 c.file_author = False
1024
1052
1025 unique = collections.OrderedDict()
1053 unique = collections.OrderedDict()
1026 for commit in _hist:
1054 for commit in _hist:
1027 author = commit.author
1055 author = commit.author
1028 if author not in unique:
1056 if author not in unique:
1029 unique[commit.author] = [
1057 unique[commit.author] = [
1030 h.email(author),
1058 h.email(author),
1031 h.person(author, 'username_or_name_or_email'),
1059 h.person(author, 'username_or_name_or_email'),
1032 1 # counter
1060 1 # counter
1033 ]
1061 ]
1034
1062
1035 else:
1063 else:
1036 # increase counter
1064 # increase counter
1037 unique[commit.author][2] += 1
1065 unique[commit.author][2] += 1
1038
1066
1039 c.authors = [val for val in unique.values()]
1067 c.authors = [val for val in unique.values()]
1040
1068
1041 return self._get_template_context(c)
1069 return self._get_template_context(c)
1042
1070
1043 @LoginRequired()
1071 @LoginRequired()
1044 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1072 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1045 @view_config(
1073 @view_config(
1046 route_name='repo_files_remove_file', request_method='GET',
1074 route_name='repo_files_remove_file', request_method='GET',
1047 renderer='rhodecode:templates/files/files_delete.mako')
1075 renderer='rhodecode:templates/files/files_delete.mako')
1048 def repo_files_remove_file(self):
1076 def repo_files_remove_file(self):
1049 _ = self.request.translate
1077 _ = self.request.translate
1050 c = self.load_default_context()
1078 c = self.load_default_context()
1051 commit_id, f_path = self._get_commit_and_path()
1079 commit_id, f_path = self._get_commit_and_path()
1052
1080
1053 self._ensure_not_locked()
1081 self._ensure_not_locked()
1054 _branch_name, _sha_commit_id, is_head = \
1082 _branch_name, _sha_commit_id, is_head = \
1055 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1083 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1056
1084
1057 if not is_head:
1085 self.forbid_non_head(is_head, f_path)
1058 h.flash(_('You can only delete files with commit '
1086 self.check_branch_permission(_branch_name)
1059 'being a valid branch head.'), category='warning')
1060 raise HTTPFound(
1061 h.route_path('repo_files',
1062 repo_name=self.db_repo_name, commit_id='tip',
1063 f_path=f_path))
1064
1087
1065 self.check_branch_permission(_branch_name)
1066 c.commit = self._get_commit_or_redirect(commit_id)
1088 c.commit = self._get_commit_or_redirect(commit_id)
1067 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1089 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1068
1090
1069 c.default_message = _(
1091 c.default_message = _(
1070 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1092 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1071 c.f_path = f_path
1093 c.f_path = f_path
1072
1094
1073 return self._get_template_context(c)
1095 return self._get_template_context(c)
1074
1096
1075 @LoginRequired()
1097 @LoginRequired()
1076 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1098 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1077 @CSRFRequired()
1099 @CSRFRequired()
1078 @view_config(
1100 @view_config(
1079 route_name='repo_files_delete_file', request_method='POST',
1101 route_name='repo_files_delete_file', request_method='POST',
1080 renderer=None)
1102 renderer=None)
1081 def repo_files_delete_file(self):
1103 def repo_files_delete_file(self):
1082 _ = self.request.translate
1104 _ = self.request.translate
1083
1105
1084 c = self.load_default_context()
1106 c = self.load_default_context()
1085 commit_id, f_path = self._get_commit_and_path()
1107 commit_id, f_path = self._get_commit_and_path()
1086
1108
1087 self._ensure_not_locked()
1109 self._ensure_not_locked()
1088 _branch_name, _sha_commit_id, is_head = \
1110 _branch_name, _sha_commit_id, is_head = \
1089 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1111 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1090
1112
1091 if not is_head:
1113 self.forbid_non_head(is_head, f_path)
1092 h.flash(_('You can only delete files with commit '
1093 'being a valid branch head.'), category='warning')
1094 raise HTTPFound(
1095 h.route_path('repo_files',
1096 repo_name=self.db_repo_name, commit_id='tip',
1097 f_path=f_path))
1098 self.check_branch_permission(_branch_name)
1114 self.check_branch_permission(_branch_name)
1099
1115
1100 c.commit = self._get_commit_or_redirect(commit_id)
1116 c.commit = self._get_commit_or_redirect(commit_id)
1101 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1117 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1102
1118
1103 c.default_message = _(
1119 c.default_message = _(
1104 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1120 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1105 c.f_path = f_path
1121 c.f_path = f_path
1106 node_path = f_path
1122 node_path = f_path
1107 author = self._rhodecode_db_user.full_contact
1123 author = self._rhodecode_db_user.full_contact
1108 message = self.request.POST.get('message') or c.default_message
1124 message = self.request.POST.get('message') or c.default_message
1109 try:
1125 try:
1110 nodes = {
1126 nodes = {
1111 node_path: {
1127 node_path: {
1112 'content': ''
1128 'content': ''
1113 }
1129 }
1114 }
1130 }
1115 ScmModel().delete_nodes(
1131 ScmModel().delete_nodes(
1116 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1132 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1117 message=message,
1133 message=message,
1118 nodes=nodes,
1134 nodes=nodes,
1119 parent_commit=c.commit,
1135 parent_commit=c.commit,
1120 author=author,
1136 author=author,
1121 )
1137 )
1122
1138
1123 h.flash(
1139 h.flash(
1124 _('Successfully deleted file `{}`').format(
1140 _('Successfully deleted file `{}`').format(
1125 h.escape(f_path)), category='success')
1141 h.escape(f_path)), category='success')
1126 except Exception:
1142 except Exception:
1127 log.exception('Error during commit operation')
1143 log.exception('Error during commit operation')
1128 h.flash(_('Error occurred during commit'), category='error')
1144 h.flash(_('Error occurred during commit'), category='error')
1129 raise HTTPFound(
1145 raise HTTPFound(
1130 h.route_path('repo_commit', repo_name=self.db_repo_name,
1146 h.route_path('repo_commit', repo_name=self.db_repo_name,
1131 commit_id='tip'))
1147 commit_id='tip'))
1132
1148
1133 @LoginRequired()
1149 @LoginRequired()
1134 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1150 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1135 @view_config(
1151 @view_config(
1136 route_name='repo_files_edit_file', request_method='GET',
1152 route_name='repo_files_edit_file', request_method='GET',
1137 renderer='rhodecode:templates/files/files_edit.mako')
1153 renderer='rhodecode:templates/files/files_edit.mako')
1138 def repo_files_edit_file(self):
1154 def repo_files_edit_file(self):
1139 _ = self.request.translate
1155 _ = self.request.translate
1140 c = self.load_default_context()
1156 c = self.load_default_context()
1141 commit_id, f_path = self._get_commit_and_path()
1157 commit_id, f_path = self._get_commit_and_path()
1142
1158
1143 self._ensure_not_locked()
1159 self._ensure_not_locked()
1144 _branch_name, _sha_commit_id, is_head = \
1160 _branch_name, _sha_commit_id, is_head = \
1145 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1161 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1146
1162
1147 if not is_head:
1163 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1148 h.flash(_('You can only edit files with commit '
1164 self.check_branch_permission(_branch_name, commit_id=commit_id)
1149 'being a valid branch head.'), category='warning')
1150 raise HTTPFound(
1151 h.route_path('repo_files',
1152 repo_name=self.db_repo_name, commit_id='tip',
1153 f_path=f_path))
1154 self.check_branch_permission(_branch_name)
1155
1165
1156 c.commit = self._get_commit_or_redirect(commit_id)
1166 c.commit = self._get_commit_or_redirect(commit_id)
1157 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1167 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1158
1168
1159 if c.file.is_binary:
1169 if c.file.is_binary:
1160 files_url = h.route_path(
1170 files_url = h.route_path(
1161 'repo_files',
1171 'repo_files',
1162 repo_name=self.db_repo_name,
1172 repo_name=self.db_repo_name,
1163 commit_id=c.commit.raw_id, f_path=f_path)
1173 commit_id=c.commit.raw_id, f_path=f_path)
1164 raise HTTPFound(files_url)
1174 raise HTTPFound(files_url)
1165
1175
1166 c.default_message = _(
1176 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1167 'Edited file {} via RhodeCode Enterprise').format(f_path)
1168 c.f_path = f_path
1177 c.f_path = f_path
1169
1178
1170 return self._get_template_context(c)
1179 return self._get_template_context(c)
1171
1180
1172 @LoginRequired()
1181 @LoginRequired()
1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1182 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1174 @CSRFRequired()
1183 @CSRFRequired()
1175 @view_config(
1184 @view_config(
1176 route_name='repo_files_update_file', request_method='POST',
1185 route_name='repo_files_update_file', request_method='POST',
1177 renderer=None)
1186 renderer=None)
1178 def repo_files_update_file(self):
1187 def repo_files_update_file(self):
1179 _ = self.request.translate
1188 _ = self.request.translate
1180 c = self.load_default_context()
1189 c = self.load_default_context()
1181 commit_id, f_path = self._get_commit_and_path()
1190 commit_id, f_path = self._get_commit_and_path()
1182
1191
1183 self._ensure_not_locked()
1192 self._ensure_not_locked()
1184 _branch_name, _sha_commit_id, is_head = \
1185 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1186
1187 if not is_head:
1188 h.flash(_('You can only edit files with commit '
1189 'being a valid branch head.'), category='warning')
1190 raise HTTPFound(
1191 h.route_path('repo_files',
1192 repo_name=self.db_repo_name, commit_id='tip',
1193 f_path=f_path))
1194
1195 self.check_branch_permission(_branch_name)
1196
1193
1197 c.commit = self._get_commit_or_redirect(commit_id)
1194 c.commit = self._get_commit_or_redirect(commit_id)
1198 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1195 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1199
1196
1200 if c.file.is_binary:
1197 if c.file.is_binary:
1201 raise HTTPFound(
1198 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1202 h.route_path('repo_files',
1199 commit_id=c.commit.raw_id, f_path=f_path))
1203 repo_name=self.db_repo_name,
1200
1204 commit_id=c.commit.raw_id,
1201 _branch_name, _sha_commit_id, is_head = \
1205 f_path=f_path))
1202 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1206
1203
1207 c.default_message = _(
1204 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1208 'Edited file {} via RhodeCode Enterprise').format(f_path)
1205 self.check_branch_permission(_branch_name, commit_id=commit_id)
1206
1207 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1209 c.f_path = f_path
1208 c.f_path = f_path
1209
1210 old_content = c.file.content
1210 old_content = c.file.content
1211 sl = old_content.splitlines(1)
1211 sl = old_content.splitlines(1)
1212 first_line = sl[0] if sl else ''
1212 first_line = sl[0] if sl else ''
1213
1213
1214 r_post = self.request.POST
1214 r_post = self.request.POST
1215 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1215 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1216 line_ending_mode = detect_mode(first_line, 0)
1216 line_ending_mode = detect_mode(first_line, 0)
1217 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1217 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1218
1218
1219 message = r_post.get('message') or c.default_message
1219 message = r_post.get('message') or c.default_message
1220 org_f_path = c.file.unicode_path
1220 org_node_path = c.file.unicode_path
1221 filename = r_post['filename']
1221 filename = r_post['filename']
1222 org_filename = c.file.name
1222
1223 root_path = c.file.dir_path
1224 pure_path = self.create_pure_path(root_path, filename)
1225 node_path = safe_unicode(bytes(pure_path))
1223
1226
1224 if content == old_content and filename == org_filename:
1227 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1225 h.flash(_('No changes'), category='warning')
1228 commit_id=commit_id)
1226 raise HTTPFound(
1229 if content == old_content and node_path == org_node_path:
1227 h.route_path('repo_commit', repo_name=self.db_repo_name,
1230 h.flash(_('No changes detected on {}').format(org_node_path),
1228 commit_id='tip'))
1231 category='warning')
1232 raise HTTPFound(default_redirect_url)
1233
1229 try:
1234 try:
1230 mapping = {
1235 mapping = {
1231 org_f_path: {
1236 org_node_path: {
1232 'org_filename': org_f_path,
1237 'org_filename': org_node_path,
1233 'filename': os.path.join(c.file.dir_path, filename),
1238 'filename': node_path,
1234 'content': content,
1239 'content': content,
1235 'lexer': '',
1240 'lexer': '',
1236 'op': 'mod',
1241 'op': 'mod',
1237 'mode': c.file.mode
1242 'mode': c.file.mode
1238 }
1243 }
1239 }
1244 }
1240
1245
1241 ScmModel().update_nodes(
1246 commit = ScmModel().update_nodes(
1242 user=self._rhodecode_db_user.user_id,
1247 user=self._rhodecode_db_user.user_id,
1243 repo=self.db_repo,
1248 repo=self.db_repo,
1244 message=message,
1249 message=message,
1245 nodes=mapping,
1250 nodes=mapping,
1246 parent_commit=c.commit,
1251 parent_commit=c.commit,
1247 )
1252 )
1248
1253
1249 h.flash(
1254 h.flash(_('Successfully committed changes to file `{}`').format(
1250 _('Successfully committed changes to file `{}`').format(
1251 h.escape(f_path)), category='success')
1255 h.escape(f_path)), category='success')
1256 default_redirect_url = h.route_path(
1257 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1258
1252 except Exception:
1259 except Exception:
1253 log.exception('Error occurred during commit')
1260 log.exception('Error occurred during commit')
1254 h.flash(_('Error occurred during commit'), category='error')
1261 h.flash(_('Error occurred during commit'), category='error')
1255 raise HTTPFound(
1262
1256 h.route_path('repo_commit', repo_name=self.db_repo_name,
1263 raise HTTPFound(default_redirect_url)
1257 commit_id='tip'))
1258
1264
1259 @LoginRequired()
1265 @LoginRequired()
1260 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1261 @view_config(
1267 @view_config(
1262 route_name='repo_files_add_file', request_method='GET',
1268 route_name='repo_files_add_file', request_method='GET',
1263 renderer='rhodecode:templates/files/files_add.mako')
1269 renderer='rhodecode:templates/files/files_add.mako')
1264 @view_config(
1270 @view_config(
1265 route_name='repo_files_upload_file', request_method='GET',
1271 route_name='repo_files_upload_file', request_method='GET',
1266 renderer='rhodecode:templates/files/files_upload.mako')
1272 renderer='rhodecode:templates/files/files_upload.mako')
1267 def repo_files_add_file(self):
1273 def repo_files_add_file(self):
1268 _ = self.request.translate
1274 _ = self.request.translate
1269 c = self.load_default_context()
1275 c = self.load_default_context()
1270 commit_id, f_path = self._get_commit_and_path()
1276 commit_id, f_path = self._get_commit_and_path()
1271
1277
1272 self._ensure_not_locked()
1278 self._ensure_not_locked()
1273
1279
1274 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1280 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1275 if c.commit is None:
1281 if c.commit is None:
1276 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1282 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1277 c.default_message = (_('Added file via RhodeCode Enterprise'))
1278 c.f_path = f_path.lstrip('/') # ensure not relative path
1279
1283
1280 if self.rhodecode_vcs_repo.is_empty:
1284 if self.rhodecode_vcs_repo.is_empty():
1281 # for empty repository we cannot check for current branch, we rely on
1285 # for empty repository we cannot check for current branch, we rely on
1282 # c.commit.branch instead
1286 # c.commit.branch instead
1283 _branch_name = c.commit.branch
1287 _branch_name = c.commit.branch
1284 is_head = True
1288 is_head = True
1285 else:
1289 else:
1286 _branch_name, _sha_commit_id, is_head = \
1290 _branch_name, _sha_commit_id, is_head = \
1287 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1291 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1288
1292
1289 if not is_head:
1293 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1290 h.flash(_('You can only add files with commit '
1294 self.check_branch_permission(_branch_name, commit_id=commit_id)
1291 'being a valid branch head.'), category='warning')
1292 raise HTTPFound(
1293 h.route_path('repo_files',
1294 repo_name=self.db_repo_name, commit_id='tip',
1295 f_path=f_path))
1296
1295
1297 self.check_branch_permission(_branch_name)
1296 c.default_message = (_('Added file via RhodeCode Enterprise'))
1297 c.f_path = f_path.lstrip('/') # ensure not relative path
1298
1298
1299 return self._get_template_context(c)
1299 return self._get_template_context(c)
1300
1300
1301 @LoginRequired()
1301 @LoginRequired()
1302 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1302 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1303 @CSRFRequired()
1303 @CSRFRequired()
1304 @view_config(
1304 @view_config(
1305 route_name='repo_files_create_file', request_method='POST',
1305 route_name='repo_files_create_file', request_method='POST',
1306 renderer=None)
1306 renderer=None)
1307 def repo_files_create_file(self):
1307 def repo_files_create_file(self):
1308 _ = self.request.translate
1308 _ = self.request.translate
1309 c = self.load_default_context()
1309 c = self.load_default_context()
1310 commit_id, f_path = self._get_commit_and_path()
1310 commit_id, f_path = self._get_commit_and_path()
1311
1311
1312 self._ensure_not_locked()
1312 self._ensure_not_locked()
1313
1313
1314 r_post = self.request.POST
1314 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1315
1316 c.commit = self._get_commit_or_redirect(
1317 commit_id, redirect_after=False)
1318 if c.commit is None:
1315 if c.commit is None:
1319 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1316 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1320
1317
1321 if self.rhodecode_vcs_repo.is_empty:
1318 # calculate redirect URL
1319 if self.rhodecode_vcs_repo.is_empty():
1320 default_redirect_url = h.route_path(
1321 'repo_summary', repo_name=self.db_repo_name)
1322 else:
1323 default_redirect_url = h.route_path(
1324 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1325
1326 if self.rhodecode_vcs_repo.is_empty():
1322 # for empty repository we cannot check for current branch, we rely on
1327 # for empty repository we cannot check for current branch, we rely on
1323 # c.commit.branch instead
1328 # c.commit.branch instead
1324 _branch_name = c.commit.branch
1329 _branch_name = c.commit.branch
1325 is_head = True
1330 is_head = True
1326 else:
1331 else:
1327 _branch_name, _sha_commit_id, is_head = \
1332 _branch_name, _sha_commit_id, is_head = \
1328 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1333 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1329
1334
1330 if not is_head:
1335 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1331 h.flash(_('You can only add files with commit '
1336 self.check_branch_permission(_branch_name, commit_id=commit_id)
1332 'being a valid branch head.'), category='warning')
1333 raise HTTPFound(
1334 h.route_path('repo_files',
1335 repo_name=self.db_repo_name, commit_id='tip',
1336 f_path=f_path))
1337
1338 self.check_branch_permission(_branch_name)
1339
1337
1340 c.default_message = (_('Added file via RhodeCode Enterprise'))
1338 c.default_message = (_('Added file via RhodeCode Enterprise'))
1341 c.f_path = f_path
1339 c.f_path = f_path
1340
1341 r_post = self.request.POST
1342 message = r_post.get('message') or c.default_message
1343 filename = r_post.get('filename')
1342 unix_mode = 0
1344 unix_mode = 0
1343 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1345 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1344
1346
1345 message = r_post.get('message') or c.default_message
1346 filename = r_post.get('filename')
1347 location = r_post.get('location', '') # dir location
1348 file_obj = r_post.get('upload_file', None)
1349
1350 if file_obj is not None and hasattr(file_obj, 'filename'):
1351 filename = r_post.get('filename_upload')
1352 content = file_obj.file
1353
1354 if hasattr(content, 'file'):
1355 # non posix systems store real file under file attr
1356 content = content.file
1357
1358 if self.rhodecode_vcs_repo.is_empty:
1359 default_redirect_url = h.route_path(
1360 'repo_summary', repo_name=self.db_repo_name)
1361 else:
1362 default_redirect_url = h.route_path(
1363 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1364
1365 # If there's no commit, redirect to repo summary
1366 if type(c.commit) is EmptyCommit:
1367 redirect_url = h.route_path(
1368 'repo_summary', repo_name=self.db_repo_name)
1369 else:
1370 redirect_url = default_redirect_url
1371
1372 if not filename:
1347 if not filename:
1373 h.flash(_('No filename'), category='warning')
1348 # If there's no commit, redirect to repo summary
1349 if type(c.commit) is EmptyCommit:
1350 redirect_url = h.route_path(
1351 'repo_summary', repo_name=self.db_repo_name)
1352 else:
1353 redirect_url = default_redirect_url
1354 h.flash(_('No filename specified'), category='warning')
1374 raise HTTPFound(redirect_url)
1355 raise HTTPFound(redirect_url)
1375
1356
1376 # extract the location from filename,
1357 root_path = f_path
1377 # allows using foo/bar.txt syntax to create subdirectories
1358 pure_path = self.create_pure_path(root_path, filename)
1378 subdir_loc = filename.rsplit('/', 1)
1359 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1379 if len(subdir_loc) == 2:
1380 location = os.path.join(location, subdir_loc[0])
1381
1360
1382 # strip all crap out of file, just leave the basename
1383 filename = os.path.basename(filename)
1384 node_path = os.path.join(location, filename)
1385 author = self._rhodecode_db_user.full_contact
1361 author = self._rhodecode_db_user.full_contact
1362 nodes = {
1363 node_path: {
1364 'content': content
1365 }
1366 }
1386
1367
1387 try:
1368 try:
1388 nodes = {
1369
1389 node_path: {
1370 commit = ScmModel().create_nodes(
1390 'content': content
1391 }
1392 }
1393 ScmModel().create_nodes(
1394 user=self._rhodecode_db_user.user_id,
1371 user=self._rhodecode_db_user.user_id,
1395 repo=self.db_repo,
1372 repo=self.db_repo,
1396 message=message,
1373 message=message,
1397 nodes=nodes,
1374 nodes=nodes,
1398 parent_commit=c.commit,
1375 parent_commit=c.commit,
1399 author=author,
1376 author=author,
1400 )
1377 )
1401
1378
1402 h.flash(
1379 h.flash(_('Successfully committed new file `{}`').format(
1403 _('Successfully committed new file `{}`').format(
1404 h.escape(node_path)), category='success')
1380 h.escape(node_path)), category='success')
1381
1382 default_redirect_url = h.route_path(
1383 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1384
1405 except NonRelativePathError:
1385 except NonRelativePathError:
1406 log.exception('Non Relative path found')
1386 log.exception('Non Relative path found')
1407 h.flash(_(
1387 h.flash(_('The location specified must be a relative path and must not '
1408 'The location specified must be a relative path and must not '
1388 'contain .. in the path'), category='warning')
1409 'contain .. in the path'), category='warning')
1410 raise HTTPFound(default_redirect_url)
1389 raise HTTPFound(default_redirect_url)
1411 except (NodeError, NodeAlreadyExistsError) as e:
1390 except (NodeError, NodeAlreadyExistsError) as e:
1412 h.flash(_(h.escape(e)), category='error')
1391 h.flash(_(h.escape(e)), category='error')
1413 except Exception:
1392 except Exception:
1414 log.exception('Error occurred during commit')
1393 log.exception('Error occurred during commit')
1415 h.flash(_('Error occurred during commit'), category='error')
1394 h.flash(_('Error occurred during commit'), category='error')
1416
1395
1417 raise HTTPFound(default_redirect_url)
1396 raise HTTPFound(default_redirect_url)
1397
1398 @LoginRequired()
1399 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1400 @CSRFRequired()
1401 @view_config(
1402 route_name='repo_files_upload_file', request_method='POST',
1403 renderer='json_ext')
1404 def repo_files_upload_file(self):
1405 _ = self.request.translate
1406 c = self.load_default_context()
1407 commit_id, f_path = self._get_commit_and_path()
1408
1409 self._ensure_not_locked()
1410
1411 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1412 if c.commit is None:
1413 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1414
1415 # calculate redirect URL
1416 if self.rhodecode_vcs_repo.is_empty():
1417 default_redirect_url = h.route_path(
1418 'repo_summary', repo_name=self.db_repo_name)
1419 else:
1420 default_redirect_url = h.route_path(
1421 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1422
1423 if self.rhodecode_vcs_repo.is_empty():
1424 # for empty repository we cannot check for current branch, we rely on
1425 # c.commit.branch instead
1426 _branch_name = c.commit.branch
1427 is_head = True
1428 else:
1429 _branch_name, _sha_commit_id, is_head = \
1430 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1431
1432 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1433 if error:
1434 return {
1435 'error': error,
1436 'redirect_url': default_redirect_url
1437 }
1438 error = self.check_branch_permission(_branch_name, json_mode=True)
1439 if error:
1440 return {
1441 'error': error,
1442 'redirect_url': default_redirect_url
1443 }
1444
1445 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1446 c.f_path = f_path
1447
1448 r_post = self.request.POST
1449
1450 message = c.default_message
1451 user_message = r_post.getall('message')
1452 if isinstance(user_message, list) and user_message:
1453 # we take the first from duplicated results if it's not empty
1454 message = user_message[0] if user_message[0] else message
1455
1456 nodes = {}
1457
1458 for file_obj in r_post.getall('files_upload') or []:
1459 content = file_obj.file
1460 filename = file_obj.filename
1461
1462 root_path = f_path
1463 pure_path = self.create_pure_path(root_path, filename)
1464 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1465
1466 nodes[node_path] = {
1467 'content': content
1468 }
1469
1470 if not nodes:
1471 error = 'missing files'
1472 return {
1473 'error': error,
1474 'redirect_url': default_redirect_url
1475 }
1476
1477 author = self._rhodecode_db_user.full_contact
1478
1479 try:
1480 commit = ScmModel().create_nodes(
1481 user=self._rhodecode_db_user.user_id,
1482 repo=self.db_repo,
1483 message=message,
1484 nodes=nodes,
1485 parent_commit=c.commit,
1486 author=author,
1487 )
1488 if len(nodes) == 1:
1489 flash_message = _('Successfully committed {} new files').format(len(nodes))
1490 else:
1491 flash_message = _('Successfully committed 1 new file')
1492
1493 h.flash(flash_message, category='success')
1494
1495 default_redirect_url = h.route_path(
1496 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1497
1498 except NonRelativePathError:
1499 log.exception('Non Relative path found')
1500 error = _('The location specified must be a relative path and must not '
1501 'contain .. in the path')
1502 h.flash(error, category='warning')
1503
1504 return {
1505 'error': error,
1506 'redirect_url': default_redirect_url
1507 }
1508 except (NodeError, NodeAlreadyExistsError) as e:
1509 error = h.escape(e)
1510 h.flash(error, category='error')
1511
1512 return {
1513 'error': error,
1514 'redirect_url': default_redirect_url
1515 }
1516 except Exception:
1517 log.exception('Error occurred during commit')
1518 error = _('Error occurred during commit')
1519 h.flash(error, category='error')
1520 return {
1521 'error': error,
1522 'redirect_url': default_redirect_url
1523 }
1524
1525 return {
1526 'error': None,
1527 'redirect_url': default_redirect_url
1528 }
@@ -1,2075 +1,2078 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers.html import literal, HTML, escape
56 from webhelpers.html import literal, HTML, escape
57 from webhelpers.html.tools import *
57 from webhelpers.html.tools import *
58 from webhelpers.html.builder import make_tag
58 from webhelpers.html.builder import make_tag
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
62 submit, text, password, textarea, title, ul, xml_declaration, radio
62 submit, text, password, textarea, title, ul, xml_declaration, radio
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
67 replace_whitespace, urlify, truncate, wrap_paragraphs
67 replace_whitespace, urlify, truncate, wrap_paragraphs
68 from webhelpers.date import time_ago_in_words
68 from webhelpers.date import time_ago_in_words
69 from webhelpers.paginate import Page as _Page
69 from webhelpers.paginate import Page as _Page
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
72 from webhelpers2.number import format_byte_size
72 from webhelpers2.number import format_byte_size
73
73
74 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.action_parser import action_parser
75 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.ext_json import json
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
79 AttributeDict, safe_int, md5, md5_safe
79 AttributeDict, safe_int, md5, md5_safe
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89
89
90
90
91 log = logging.getLogger(__name__)
91 log = logging.getLogger(__name__)
92
92
93
93
94 DEFAULT_USER = User.DEFAULT_USER
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
96
97
97
98 def asset(path, ver=None, **kwargs):
98 def asset(path, ver=None, **kwargs):
99 """
99 """
100 Helper to generate a static asset file path for rhodecode assets
100 Helper to generate a static asset file path for rhodecode assets
101
101
102 eg. h.asset('images/image.png', ver='3923')
102 eg. h.asset('images/image.png', ver='3923')
103
103
104 :param path: path of asset
104 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
105 :param ver: optional version query param to append as ?ver=
106 """
106 """
107 request = get_current_request()
107 request = get_current_request()
108 query = {}
108 query = {}
109 query.update(kwargs)
109 query.update(kwargs)
110 if ver:
110 if ver:
111 query = {'ver': ver}
111 query = {'ver': ver}
112 return request.static_path(
112 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
113 'rhodecode:public/{}'.format(path), _query=query)
114
114
115
115
116 default_html_escape_table = {
116 default_html_escape_table = {
117 ord('&'): u'&amp;',
117 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
118 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
119 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
120 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
121 ord("'"): u'&#39;',
122 }
122 }
123
123
124
124
125 def html_escape(text, html_escape_table=default_html_escape_table):
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
126 """Produce entities within text."""
127 return text.translate(html_escape_table)
127 return text.translate(html_escape_table)
128
128
129
129
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
131 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
132 Truncate string ``s`` at the first occurrence of ``sub``.
133
133
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
135 """
136 suffix_if_chopped = suffix_if_chopped or ''
136 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
137 pos = s.find(sub)
138 if pos == -1:
138 if pos == -1:
139 return s
139 return s
140
140
141 if inclusive:
141 if inclusive:
142 pos += len(sub)
142 pos += len(sub)
143
143
144 chopped = s[:pos]
144 chopped = s[:pos]
145 left = s[pos:].strip()
145 left = s[pos:].strip()
146
146
147 if left and suffix_if_chopped:
147 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
148 chopped += suffix_if_chopped
149
149
150 return chopped
150 return chopped
151
151
152
152
153 def shorter(text, size=20):
153 def shorter(text, size=20):
154 postfix = '...'
154 postfix = '...'
155 if len(text) > size:
155 if len(text) > size:
156 return text[:size - len(postfix)] + postfix
156 return text[:size - len(postfix)] + postfix
157 return text
157 return text
158
158
159
159
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
161 """
161 """
162 Reset button
162 Reset button
163 """
163 """
164 _set_input_attrs(attrs, type, name, value)
164 _set_input_attrs(attrs, type, name, value)
165 _set_id_attr(attrs, id, name)
165 _set_id_attr(attrs, id, name)
166 convert_boolean_attrs(attrs, ["disabled"])
166 convert_boolean_attrs(attrs, ["disabled"])
167 return HTML.input(**attrs)
167 return HTML.input(**attrs)
168
168
169 reset = _reset
169 reset = _reset
170 safeid = _make_safe_id_component
170 safeid = _make_safe_id_component
171
171
172
172
173 def branding(name, length=40):
173 def branding(name, length=40):
174 return truncate(name, length, indicator="")
174 return truncate(name, length, indicator="")
175
175
176
176
177 def FID(raw_id, path):
177 def FID(raw_id, path):
178 """
178 """
179 Creates a unique ID for filenode based on it's hash of path and commit
179 Creates a unique ID for filenode based on it's hash of path and commit
180 it's safe to use in urls
180 it's safe to use in urls
181
181
182 :param raw_id:
182 :param raw_id:
183 :param path:
183 :param path:
184 """
184 """
185
185
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
187
187
188
188
189 class _GetError(object):
189 class _GetError(object):
190 """Get error from form_errors, and represent it as span wrapped error
190 """Get error from form_errors, and represent it as span wrapped error
191 message
191 message
192
192
193 :param field_name: field to fetch errors for
193 :param field_name: field to fetch errors for
194 :param form_errors: form errors dict
194 :param form_errors: form errors dict
195 """
195 """
196
196
197 def __call__(self, field_name, form_errors):
197 def __call__(self, field_name, form_errors):
198 tmpl = """<span class="error_msg">%s</span>"""
198 tmpl = """<span class="error_msg">%s</span>"""
199 if form_errors and field_name in form_errors:
199 if form_errors and field_name in form_errors:
200 return literal(tmpl % form_errors.get(field_name))
200 return literal(tmpl % form_errors.get(field_name))
201
201
202
202
203 get_error = _GetError()
203 get_error = _GetError()
204
204
205
205
206 class _ToolTip(object):
206 class _ToolTip(object):
207
207
208 def __call__(self, tooltip_title, trim_at=50):
208 def __call__(self, tooltip_title, trim_at=50):
209 """
209 """
210 Special function just to wrap our text into nice formatted
210 Special function just to wrap our text into nice formatted
211 autowrapped text
211 autowrapped text
212
212
213 :param tooltip_title:
213 :param tooltip_title:
214 """
214 """
215 tooltip_title = escape(tooltip_title)
215 tooltip_title = escape(tooltip_title)
216 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
216 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
217 return tooltip_title
217 return tooltip_title
218
218
219
219
220 tooltip = _ToolTip()
220 tooltip = _ToolTip()
221
221
222 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
222 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
223
223
224
224
225 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
225 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
226 if isinstance(file_path, str):
226 if isinstance(file_path, str):
227 file_path = safe_unicode(file_path)
227 file_path = safe_unicode(file_path)
228
228
229 route_qry = {'at': at_ref} if at_ref else None
229 route_qry = {'at': at_ref} if at_ref else None
230
230
231 # first segment is a `..` link to repo files
231 # first segment is a `..` link to repo files
232 root_name = literal(u'<i class="icon-home"></i>')
232 root_name = literal(u'<i class="icon-home"></i>')
233 url_segments = [
233 url_segments = [
234 link_to(
234 link_to(
235 root_name,
235 root_name,
236 route_path(
236 route_path(
237 'repo_files',
237 'repo_files',
238 repo_name=repo_name,
238 repo_name=repo_name,
239 commit_id=commit_id,
239 commit_id=commit_id,
240 f_path='',
240 f_path='',
241 _query=route_qry),
241 _query=route_qry),
242 )]
242 )]
243
243
244 path_segments = file_path.split('/')
244 path_segments = file_path.split('/')
245 last_cnt = len(path_segments) - 1
245 last_cnt = len(path_segments) - 1
246 for cnt, segment in enumerate(path_segments):
246 for cnt, segment in enumerate(path_segments):
247 if not segment:
247 if not segment:
248 continue
248 continue
249 segment_html = escape(segment)
249 segment_html = escape(segment)
250
250
251 last_item = cnt == last_cnt
251 last_item = cnt == last_cnt
252
252
253 if last_item and linkify_last_item is False:
253 if last_item and linkify_last_item is False:
254 # plain version
254 # plain version
255 url_segments.append(segment_html)
255 url_segments.append(segment_html)
256 else:
256 else:
257 url_segments.append(
257 url_segments.append(
258 link_to(
258 link_to(
259 segment_html,
259 segment_html,
260 route_path(
260 route_path(
261 'repo_files',
261 'repo_files',
262 repo_name=repo_name,
262 repo_name=repo_name,
263 commit_id=commit_id,
263 commit_id=commit_id,
264 f_path='/'.join(path_segments[:cnt + 1]),
264 f_path='/'.join(path_segments[:cnt + 1]),
265 _query=route_qry),
265 _query=route_qry),
266 ))
266 ))
267
267
268 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
268 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
269 if limit_items and len(limited_url_segments) < len(url_segments):
269 if limit_items and len(limited_url_segments) < len(url_segments):
270 url_segments = limited_url_segments
270 url_segments = limited_url_segments
271
271
272 full_path = file_path
272 full_path = file_path
273 icon = files_icon.format(escape(full_path))
273 icon = files_icon.format(escape(full_path))
274 if file_path == '':
274 if file_path == '':
275 return root_name
275 return root_name
276 else:
276 else:
277 return literal(' / '.join(url_segments) + icon)
277 return literal(' / '.join(url_segments) + icon)
278
278
279
279
280 def files_url_data(request):
280 def files_url_data(request):
281 matchdict = request.matchdict
281 matchdict = request.matchdict
282
282
283 if 'f_path' not in matchdict:
283 if 'f_path' not in matchdict:
284 matchdict['f_path'] = ''
284 matchdict['f_path'] = ''
285
285
286 if 'commit_id' not in matchdict:
286 if 'commit_id' not in matchdict:
287 matchdict['commit_id'] = 'tip'
287 matchdict['commit_id'] = 'tip'
288
288
289 return json.dumps(matchdict)
289 return json.dumps(matchdict)
290
290
291
291
292 def code_highlight(code, lexer, formatter, use_hl_filter=False):
292 def code_highlight(code, lexer, formatter, use_hl_filter=False):
293 """
293 """
294 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
294 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
295
295
296 If ``outfile`` is given and a valid file object (an object
296 If ``outfile`` is given and a valid file object (an object
297 with a ``write`` method), the result will be written to it, otherwise
297 with a ``write`` method), the result will be written to it, otherwise
298 it is returned as a string.
298 it is returned as a string.
299 """
299 """
300 if use_hl_filter:
300 if use_hl_filter:
301 # add HL filter
301 # add HL filter
302 from rhodecode.lib.index import search_utils
302 from rhodecode.lib.index import search_utils
303 lexer.add_filter(search_utils.ElasticSearchHLFilter())
303 lexer.add_filter(search_utils.ElasticSearchHLFilter())
304 return pygments.format(pygments.lex(code, lexer), formatter)
304 return pygments.format(pygments.lex(code, lexer), formatter)
305
305
306
306
307 class CodeHtmlFormatter(HtmlFormatter):
307 class CodeHtmlFormatter(HtmlFormatter):
308 """
308 """
309 My code Html Formatter for source codes
309 My code Html Formatter for source codes
310 """
310 """
311
311
312 def wrap(self, source, outfile):
312 def wrap(self, source, outfile):
313 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
313 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
314
314
315 def _wrap_code(self, source):
315 def _wrap_code(self, source):
316 for cnt, it in enumerate(source):
316 for cnt, it in enumerate(source):
317 i, t = it
317 i, t = it
318 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
318 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
319 yield i, t
319 yield i, t
320
320
321 def _wrap_tablelinenos(self, inner):
321 def _wrap_tablelinenos(self, inner):
322 dummyoutfile = StringIO.StringIO()
322 dummyoutfile = StringIO.StringIO()
323 lncount = 0
323 lncount = 0
324 for t, line in inner:
324 for t, line in inner:
325 if t:
325 if t:
326 lncount += 1
326 lncount += 1
327 dummyoutfile.write(line)
327 dummyoutfile.write(line)
328
328
329 fl = self.linenostart
329 fl = self.linenostart
330 mw = len(str(lncount + fl - 1))
330 mw = len(str(lncount + fl - 1))
331 sp = self.linenospecial
331 sp = self.linenospecial
332 st = self.linenostep
332 st = self.linenostep
333 la = self.lineanchors
333 la = self.lineanchors
334 aln = self.anchorlinenos
334 aln = self.anchorlinenos
335 nocls = self.noclasses
335 nocls = self.noclasses
336 if sp:
336 if sp:
337 lines = []
337 lines = []
338
338
339 for i in range(fl, fl + lncount):
339 for i in range(fl, fl + lncount):
340 if i % st == 0:
340 if i % st == 0:
341 if i % sp == 0:
341 if i % sp == 0:
342 if aln:
342 if aln:
343 lines.append('<a href="#%s%d" class="special">%*d</a>' %
343 lines.append('<a href="#%s%d" class="special">%*d</a>' %
344 (la, i, mw, i))
344 (la, i, mw, i))
345 else:
345 else:
346 lines.append('<span class="special">%*d</span>' % (mw, i))
346 lines.append('<span class="special">%*d</span>' % (mw, i))
347 else:
347 else:
348 if aln:
348 if aln:
349 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
349 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
350 else:
350 else:
351 lines.append('%*d' % (mw, i))
351 lines.append('%*d' % (mw, i))
352 else:
352 else:
353 lines.append('')
353 lines.append('')
354 ls = '\n'.join(lines)
354 ls = '\n'.join(lines)
355 else:
355 else:
356 lines = []
356 lines = []
357 for i in range(fl, fl + lncount):
357 for i in range(fl, fl + lncount):
358 if i % st == 0:
358 if i % st == 0:
359 if aln:
359 if aln:
360 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
360 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
361 else:
361 else:
362 lines.append('%*d' % (mw, i))
362 lines.append('%*d' % (mw, i))
363 else:
363 else:
364 lines.append('')
364 lines.append('')
365 ls = '\n'.join(lines)
365 ls = '\n'.join(lines)
366
366
367 # in case you wonder about the seemingly redundant <div> here: since the
367 # in case you wonder about the seemingly redundant <div> here: since the
368 # content in the other cell also is wrapped in a div, some browsers in
368 # content in the other cell also is wrapped in a div, some browsers in
369 # some configurations seem to mess up the formatting...
369 # some configurations seem to mess up the formatting...
370 if nocls:
370 if nocls:
371 yield 0, ('<table class="%stable">' % self.cssclass +
371 yield 0, ('<table class="%stable">' % self.cssclass +
372 '<tr><td><div class="linenodiv" '
372 '<tr><td><div class="linenodiv" '
373 'style="background-color: #f0f0f0; padding-right: 10px">'
373 'style="background-color: #f0f0f0; padding-right: 10px">'
374 '<pre style="line-height: 125%">' +
374 '<pre style="line-height: 125%">' +
375 ls + '</pre></div></td><td id="hlcode" class="code">')
375 ls + '</pre></div></td><td id="hlcode" class="code">')
376 else:
376 else:
377 yield 0, ('<table class="%stable">' % self.cssclass +
377 yield 0, ('<table class="%stable">' % self.cssclass +
378 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
378 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
379 ls + '</pre></div></td><td id="hlcode" class="code">')
379 ls + '</pre></div></td><td id="hlcode" class="code">')
380 yield 0, dummyoutfile.getvalue()
380 yield 0, dummyoutfile.getvalue()
381 yield 0, '</td></tr></table>'
381 yield 0, '</td></tr></table>'
382
382
383
383
384 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
384 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
385 def __init__(self, **kw):
385 def __init__(self, **kw):
386 # only show these line numbers if set
386 # only show these line numbers if set
387 self.only_lines = kw.pop('only_line_numbers', [])
387 self.only_lines = kw.pop('only_line_numbers', [])
388 self.query_terms = kw.pop('query_terms', [])
388 self.query_terms = kw.pop('query_terms', [])
389 self.max_lines = kw.pop('max_lines', 5)
389 self.max_lines = kw.pop('max_lines', 5)
390 self.line_context = kw.pop('line_context', 3)
390 self.line_context = kw.pop('line_context', 3)
391 self.url = kw.pop('url', None)
391 self.url = kw.pop('url', None)
392
392
393 super(CodeHtmlFormatter, self).__init__(**kw)
393 super(CodeHtmlFormatter, self).__init__(**kw)
394
394
395 def _wrap_code(self, source):
395 def _wrap_code(self, source):
396 for cnt, it in enumerate(source):
396 for cnt, it in enumerate(source):
397 i, t = it
397 i, t = it
398 t = '<pre>%s</pre>' % t
398 t = '<pre>%s</pre>' % t
399 yield i, t
399 yield i, t
400
400
401 def _wrap_tablelinenos(self, inner):
401 def _wrap_tablelinenos(self, inner):
402 yield 0, '<table class="code-highlight %stable">' % self.cssclass
402 yield 0, '<table class="code-highlight %stable">' % self.cssclass
403
403
404 last_shown_line_number = 0
404 last_shown_line_number = 0
405 current_line_number = 1
405 current_line_number = 1
406
406
407 for t, line in inner:
407 for t, line in inner:
408 if not t:
408 if not t:
409 yield t, line
409 yield t, line
410 continue
410 continue
411
411
412 if current_line_number in self.only_lines:
412 if current_line_number in self.only_lines:
413 if last_shown_line_number + 1 != current_line_number:
413 if last_shown_line_number + 1 != current_line_number:
414 yield 0, '<tr>'
414 yield 0, '<tr>'
415 yield 0, '<td class="line">...</td>'
415 yield 0, '<td class="line">...</td>'
416 yield 0, '<td id="hlcode" class="code"></td>'
416 yield 0, '<td id="hlcode" class="code"></td>'
417 yield 0, '</tr>'
417 yield 0, '</tr>'
418
418
419 yield 0, '<tr>'
419 yield 0, '<tr>'
420 if self.url:
420 if self.url:
421 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
421 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
422 self.url, current_line_number, current_line_number)
422 self.url, current_line_number, current_line_number)
423 else:
423 else:
424 yield 0, '<td class="line"><a href="">%i</a></td>' % (
424 yield 0, '<td class="line"><a href="">%i</a></td>' % (
425 current_line_number)
425 current_line_number)
426 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
426 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
427 yield 0, '</tr>'
427 yield 0, '</tr>'
428
428
429 last_shown_line_number = current_line_number
429 last_shown_line_number = current_line_number
430
430
431 current_line_number += 1
431 current_line_number += 1
432
432
433 yield 0, '</table>'
433 yield 0, '</table>'
434
434
435
435
436 def hsv_to_rgb(h, s, v):
436 def hsv_to_rgb(h, s, v):
437 """ Convert hsv color values to rgb """
437 """ Convert hsv color values to rgb """
438
438
439 if s == 0.0:
439 if s == 0.0:
440 return v, v, v
440 return v, v, v
441 i = int(h * 6.0) # XXX assume int() truncates!
441 i = int(h * 6.0) # XXX assume int() truncates!
442 f = (h * 6.0) - i
442 f = (h * 6.0) - i
443 p = v * (1.0 - s)
443 p = v * (1.0 - s)
444 q = v * (1.0 - s * f)
444 q = v * (1.0 - s * f)
445 t = v * (1.0 - s * (1.0 - f))
445 t = v * (1.0 - s * (1.0 - f))
446 i = i % 6
446 i = i % 6
447 if i == 0:
447 if i == 0:
448 return v, t, p
448 return v, t, p
449 if i == 1:
449 if i == 1:
450 return q, v, p
450 return q, v, p
451 if i == 2:
451 if i == 2:
452 return p, v, t
452 return p, v, t
453 if i == 3:
453 if i == 3:
454 return p, q, v
454 return p, q, v
455 if i == 4:
455 if i == 4:
456 return t, p, v
456 return t, p, v
457 if i == 5:
457 if i == 5:
458 return v, p, q
458 return v, p, q
459
459
460
460
461 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
461 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
462 """
462 """
463 Generator for getting n of evenly distributed colors using
463 Generator for getting n of evenly distributed colors using
464 hsv color and golden ratio. It always return same order of colors
464 hsv color and golden ratio. It always return same order of colors
465
465
466 :param n: number of colors to generate
466 :param n: number of colors to generate
467 :param saturation: saturation of returned colors
467 :param saturation: saturation of returned colors
468 :param lightness: lightness of returned colors
468 :param lightness: lightness of returned colors
469 :returns: RGB tuple
469 :returns: RGB tuple
470 """
470 """
471
471
472 golden_ratio = 0.618033988749895
472 golden_ratio = 0.618033988749895
473 h = 0.22717784590367374
473 h = 0.22717784590367374
474
474
475 for _ in xrange(n):
475 for _ in xrange(n):
476 h += golden_ratio
476 h += golden_ratio
477 h %= 1
477 h %= 1
478 HSV_tuple = [h, saturation, lightness]
478 HSV_tuple = [h, saturation, lightness]
479 RGB_tuple = hsv_to_rgb(*HSV_tuple)
479 RGB_tuple = hsv_to_rgb(*HSV_tuple)
480 yield map(lambda x: str(int(x * 256)), RGB_tuple)
480 yield map(lambda x: str(int(x * 256)), RGB_tuple)
481
481
482
482
483 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
483 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
484 """
484 """
485 Returns a function which when called with an argument returns a unique
485 Returns a function which when called with an argument returns a unique
486 color for that argument, eg.
486 color for that argument, eg.
487
487
488 :param n: number of colors to generate
488 :param n: number of colors to generate
489 :param saturation: saturation of returned colors
489 :param saturation: saturation of returned colors
490 :param lightness: lightness of returned colors
490 :param lightness: lightness of returned colors
491 :returns: css RGB string
491 :returns: css RGB string
492
492
493 >>> color_hash = color_hasher()
493 >>> color_hash = color_hasher()
494 >>> color_hash('hello')
494 >>> color_hash('hello')
495 'rgb(34, 12, 59)'
495 'rgb(34, 12, 59)'
496 >>> color_hash('hello')
496 >>> color_hash('hello')
497 'rgb(34, 12, 59)'
497 'rgb(34, 12, 59)'
498 >>> color_hash('other')
498 >>> color_hash('other')
499 'rgb(90, 224, 159)'
499 'rgb(90, 224, 159)'
500 """
500 """
501
501
502 color_dict = {}
502 color_dict = {}
503 cgenerator = unique_color_generator(
503 cgenerator = unique_color_generator(
504 saturation=saturation, lightness=lightness)
504 saturation=saturation, lightness=lightness)
505
505
506 def get_color_string(thing):
506 def get_color_string(thing):
507 if thing in color_dict:
507 if thing in color_dict:
508 col = color_dict[thing]
508 col = color_dict[thing]
509 else:
509 else:
510 col = color_dict[thing] = cgenerator.next()
510 col = color_dict[thing] = cgenerator.next()
511 return "rgb(%s)" % (', '.join(col))
511 return "rgb(%s)" % (', '.join(col))
512
512
513 return get_color_string
513 return get_color_string
514
514
515
515
516 def get_lexer_safe(mimetype=None, filepath=None):
516 def get_lexer_safe(mimetype=None, filepath=None):
517 """
517 """
518 Tries to return a relevant pygments lexer using mimetype/filepath name,
518 Tries to return a relevant pygments lexer using mimetype/filepath name,
519 defaulting to plain text if none could be found
519 defaulting to plain text if none could be found
520 """
520 """
521 lexer = None
521 lexer = None
522 try:
522 try:
523 if mimetype:
523 if mimetype:
524 lexer = get_lexer_for_mimetype(mimetype)
524 lexer = get_lexer_for_mimetype(mimetype)
525 if not lexer:
525 if not lexer:
526 lexer = get_lexer_for_filename(filepath)
526 lexer = get_lexer_for_filename(filepath)
527 except pygments.util.ClassNotFound:
527 except pygments.util.ClassNotFound:
528 pass
528 pass
529
529
530 if not lexer:
530 if not lexer:
531 lexer = get_lexer_by_name('text')
531 lexer = get_lexer_by_name('text')
532
532
533 return lexer
533 return lexer
534
534
535
535
536 def get_lexer_for_filenode(filenode):
536 def get_lexer_for_filenode(filenode):
537 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
537 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
538 return lexer
538 return lexer
539
539
540
540
541 def pygmentize(filenode, **kwargs):
541 def pygmentize(filenode, **kwargs):
542 """
542 """
543 pygmentize function using pygments
543 pygmentize function using pygments
544
544
545 :param filenode:
545 :param filenode:
546 """
546 """
547 lexer = get_lexer_for_filenode(filenode)
547 lexer = get_lexer_for_filenode(filenode)
548 return literal(code_highlight(filenode.content, lexer,
548 return literal(code_highlight(filenode.content, lexer,
549 CodeHtmlFormatter(**kwargs)))
549 CodeHtmlFormatter(**kwargs)))
550
550
551
551
552 def is_following_repo(repo_name, user_id):
552 def is_following_repo(repo_name, user_id):
553 from rhodecode.model.scm import ScmModel
553 from rhodecode.model.scm import ScmModel
554 return ScmModel().is_following_repo(repo_name, user_id)
554 return ScmModel().is_following_repo(repo_name, user_id)
555
555
556
556
557 class _Message(object):
557 class _Message(object):
558 """A message returned by ``Flash.pop_messages()``.
558 """A message returned by ``Flash.pop_messages()``.
559
559
560 Converting the message to a string returns the message text. Instances
560 Converting the message to a string returns the message text. Instances
561 also have the following attributes:
561 also have the following attributes:
562
562
563 * ``message``: the message text.
563 * ``message``: the message text.
564 * ``category``: the category specified when the message was created.
564 * ``category``: the category specified when the message was created.
565 """
565 """
566
566
567 def __init__(self, category, message):
567 def __init__(self, category, message):
568 self.category = category
568 self.category = category
569 self.message = message
569 self.message = message
570
570
571 def __str__(self):
571 def __str__(self):
572 return self.message
572 return self.message
573
573
574 __unicode__ = __str__
574 __unicode__ = __str__
575
575
576 def __html__(self):
576 def __html__(self):
577 return escape(safe_unicode(self.message))
577 return escape(safe_unicode(self.message))
578
578
579
579
580 class Flash(object):
580 class Flash(object):
581 # List of allowed categories. If None, allow any category.
581 # List of allowed categories. If None, allow any category.
582 categories = ["warning", "notice", "error", "success"]
582 categories = ["warning", "notice", "error", "success"]
583
583
584 # Default category if none is specified.
584 # Default category if none is specified.
585 default_category = "notice"
585 default_category = "notice"
586
586
587 def __init__(self, session_key="flash", categories=None,
587 def __init__(self, session_key="flash", categories=None,
588 default_category=None):
588 default_category=None):
589 """
589 """
590 Instantiate a ``Flash`` object.
590 Instantiate a ``Flash`` object.
591
591
592 ``session_key`` is the key to save the messages under in the user's
592 ``session_key`` is the key to save the messages under in the user's
593 session.
593 session.
594
594
595 ``categories`` is an optional list which overrides the default list
595 ``categories`` is an optional list which overrides the default list
596 of categories.
596 of categories.
597
597
598 ``default_category`` overrides the default category used for messages
598 ``default_category`` overrides the default category used for messages
599 when none is specified.
599 when none is specified.
600 """
600 """
601 self.session_key = session_key
601 self.session_key = session_key
602 if categories is not None:
602 if categories is not None:
603 self.categories = categories
603 self.categories = categories
604 if default_category is not None:
604 if default_category is not None:
605 self.default_category = default_category
605 self.default_category = default_category
606 if self.categories and self.default_category not in self.categories:
606 if self.categories and self.default_category not in self.categories:
607 raise ValueError(
607 raise ValueError(
608 "unrecognized default category %r" % (self.default_category,))
608 "unrecognized default category %r" % (self.default_category,))
609
609
610 def pop_messages(self, session=None, request=None):
610 def pop_messages(self, session=None, request=None):
611 """
611 """
612 Return all accumulated messages and delete them from the session.
612 Return all accumulated messages and delete them from the session.
613
613
614 The return value is a list of ``Message`` objects.
614 The return value is a list of ``Message`` objects.
615 """
615 """
616 messages = []
616 messages = []
617
617
618 if not session:
618 if not session:
619 if not request:
619 if not request:
620 request = get_current_request()
620 request = get_current_request()
621 session = request.session
621 session = request.session
622
622
623 # Pop the 'old' pylons flash messages. They are tuples of the form
623 # Pop the 'old' pylons flash messages. They are tuples of the form
624 # (category, message)
624 # (category, message)
625 for cat, msg in session.pop(self.session_key, []):
625 for cat, msg in session.pop(self.session_key, []):
626 messages.append(_Message(cat, msg))
626 messages.append(_Message(cat, msg))
627
627
628 # Pop the 'new' pyramid flash messages for each category as list
628 # Pop the 'new' pyramid flash messages for each category as list
629 # of strings.
629 # of strings.
630 for cat in self.categories:
630 for cat in self.categories:
631 for msg in session.pop_flash(queue=cat):
631 for msg in session.pop_flash(queue=cat):
632 messages.append(_Message(cat, msg))
632 messages.append(_Message(cat, msg))
633 # Map messages from the default queue to the 'notice' category.
633 # Map messages from the default queue to the 'notice' category.
634 for msg in session.pop_flash():
634 for msg in session.pop_flash():
635 messages.append(_Message('notice', msg))
635 messages.append(_Message('notice', msg))
636
636
637 session.save()
637 session.save()
638 return messages
638 return messages
639
639
640 def json_alerts(self, session=None, request=None):
640 def json_alerts(self, session=None, request=None):
641 payloads = []
641 payloads = []
642 messages = flash.pop_messages(session=session, request=request)
642 messages = flash.pop_messages(session=session, request=request)
643 if messages:
643 if messages:
644 for message in messages:
644 for message in messages:
645 subdata = {}
645 subdata = {}
646 if hasattr(message.message, 'rsplit'):
646 if hasattr(message.message, 'rsplit'):
647 flash_data = message.message.rsplit('|DELIM|', 1)
647 flash_data = message.message.rsplit('|DELIM|', 1)
648 org_message = flash_data[0]
648 org_message = flash_data[0]
649 if len(flash_data) > 1:
649 if len(flash_data) > 1:
650 subdata = json.loads(flash_data[1])
650 subdata = json.loads(flash_data[1])
651 else:
651 else:
652 org_message = message.message
652 org_message = message.message
653 payloads.append({
653 payloads.append({
654 'message': {
654 'message': {
655 'message': u'{}'.format(org_message),
655 'message': u'{}'.format(org_message),
656 'level': message.category,
656 'level': message.category,
657 'force': True,
657 'force': True,
658 'subdata': subdata
658 'subdata': subdata
659 }
659 }
660 })
660 })
661 return json.dumps(payloads)
661 return json.dumps(payloads)
662
662
663 def __call__(self, message, category=None, ignore_duplicate=False,
663 def __call__(self, message, category=None, ignore_duplicate=False,
664 session=None, request=None):
664 session=None, request=None):
665
665
666 if not session:
666 if not session:
667 if not request:
667 if not request:
668 request = get_current_request()
668 request = get_current_request()
669 session = request.session
669 session = request.session
670
670
671 session.flash(
671 session.flash(
672 message, queue=category, allow_duplicate=not ignore_duplicate)
672 message, queue=category, allow_duplicate=not ignore_duplicate)
673
673
674
674
675 flash = Flash()
675 flash = Flash()
676
676
677 #==============================================================================
677 #==============================================================================
678 # SCM FILTERS available via h.
678 # SCM FILTERS available via h.
679 #==============================================================================
679 #==============================================================================
680 from rhodecode.lib.vcs.utils import author_name, author_email
680 from rhodecode.lib.vcs.utils import author_name, author_email
681 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
681 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
682 from rhodecode.model.db import User, ChangesetStatus
682 from rhodecode.model.db import User, ChangesetStatus
683
683
684 capitalize = lambda x: x.capitalize()
684 capitalize = lambda x: x.capitalize()
685 email = author_email
685 email = author_email
686 short_id = lambda x: x[:12]
686 short_id = lambda x: x[:12]
687 hide_credentials = lambda x: ''.join(credentials_filter(x))
687 hide_credentials = lambda x: ''.join(credentials_filter(x))
688
688
689
689
690 import pytz
690 import pytz
691 import tzlocal
691 import tzlocal
692 local_timezone = tzlocal.get_localzone()
692 local_timezone = tzlocal.get_localzone()
693
693
694
694
695 def age_component(datetime_iso, value=None, time_is_local=False):
695 def age_component(datetime_iso, value=None, time_is_local=False):
696 title = value or format_date(datetime_iso)
696 title = value or format_date(datetime_iso)
697 tzinfo = '+00:00'
697 tzinfo = '+00:00'
698
698
699 # detect if we have a timezone info, otherwise, add it
699 # detect if we have a timezone info, otherwise, add it
700 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
700 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
701 force_timezone = os.environ.get('RC_TIMEZONE', '')
701 force_timezone = os.environ.get('RC_TIMEZONE', '')
702 if force_timezone:
702 if force_timezone:
703 force_timezone = pytz.timezone(force_timezone)
703 force_timezone = pytz.timezone(force_timezone)
704 timezone = force_timezone or local_timezone
704 timezone = force_timezone or local_timezone
705 offset = timezone.localize(datetime_iso).strftime('%z')
705 offset = timezone.localize(datetime_iso).strftime('%z')
706 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
706 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
707
707
708 return literal(
708 return literal(
709 '<time class="timeago tooltip" '
709 '<time class="timeago tooltip" '
710 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
710 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
711 datetime_iso, title, tzinfo))
711 datetime_iso, title, tzinfo))
712
712
713
713
714 def _shorten_commit_id(commit_id, commit_len=None):
714 def _shorten_commit_id(commit_id, commit_len=None):
715 if commit_len is None:
715 if commit_len is None:
716 request = get_current_request()
716 request = get_current_request()
717 commit_len = request.call_context.visual.show_sha_length
717 commit_len = request.call_context.visual.show_sha_length
718 return commit_id[:commit_len]
718 return commit_id[:commit_len]
719
719
720
720
721 def show_id(commit, show_idx=None, commit_len=None):
721 def show_id(commit, show_idx=None, commit_len=None):
722 """
722 """
723 Configurable function that shows ID
723 Configurable function that shows ID
724 by default it's r123:fffeeefffeee
724 by default it's r123:fffeeefffeee
725
725
726 :param commit: commit instance
726 :param commit: commit instance
727 """
727 """
728 if show_idx is None:
728 if show_idx is None:
729 request = get_current_request()
729 request = get_current_request()
730 show_idx = request.call_context.visual.show_revision_number
730 show_idx = request.call_context.visual.show_revision_number
731
731
732 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
732 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
733 if show_idx:
733 if show_idx:
734 return 'r%s:%s' % (commit.idx, raw_id)
734 return 'r%s:%s' % (commit.idx, raw_id)
735 else:
735 else:
736 return '%s' % (raw_id, )
736 return '%s' % (raw_id, )
737
737
738
738
739 def format_date(date):
739 def format_date(date):
740 """
740 """
741 use a standardized formatting for dates used in RhodeCode
741 use a standardized formatting for dates used in RhodeCode
742
742
743 :param date: date/datetime object
743 :param date: date/datetime object
744 :return: formatted date
744 :return: formatted date
745 """
745 """
746
746
747 if date:
747 if date:
748 _fmt = "%a, %d %b %Y %H:%M:%S"
748 _fmt = "%a, %d %b %Y %H:%M:%S"
749 return safe_unicode(date.strftime(_fmt))
749 return safe_unicode(date.strftime(_fmt))
750
750
751 return u""
751 return u""
752
752
753
753
754 class _RepoChecker(object):
754 class _RepoChecker(object):
755
755
756 def __init__(self, backend_alias):
756 def __init__(self, backend_alias):
757 self._backend_alias = backend_alias
757 self._backend_alias = backend_alias
758
758
759 def __call__(self, repository):
759 def __call__(self, repository):
760 if hasattr(repository, 'alias'):
760 if hasattr(repository, 'alias'):
761 _type = repository.alias
761 _type = repository.alias
762 elif hasattr(repository, 'repo_type'):
762 elif hasattr(repository, 'repo_type'):
763 _type = repository.repo_type
763 _type = repository.repo_type
764 else:
764 else:
765 _type = repository
765 _type = repository
766 return _type == self._backend_alias
766 return _type == self._backend_alias
767
767
768
768
769 is_git = _RepoChecker('git')
769 is_git = _RepoChecker('git')
770 is_hg = _RepoChecker('hg')
770 is_hg = _RepoChecker('hg')
771 is_svn = _RepoChecker('svn')
771 is_svn = _RepoChecker('svn')
772
772
773
773
774 def get_repo_type_by_name(repo_name):
774 def get_repo_type_by_name(repo_name):
775 repo = Repository.get_by_repo_name(repo_name)
775 repo = Repository.get_by_repo_name(repo_name)
776 if repo:
776 if repo:
777 return repo.repo_type
777 return repo.repo_type
778
778
779
779
780 def is_svn_without_proxy(repository):
780 def is_svn_without_proxy(repository):
781 if is_svn(repository):
781 if is_svn(repository):
782 from rhodecode.model.settings import VcsSettingsModel
782 from rhodecode.model.settings import VcsSettingsModel
783 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
783 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
784 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
784 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
785 return False
785 return False
786
786
787
787
788 def discover_user(author):
788 def discover_user(author):
789 """
789 """
790 Tries to discover RhodeCode User based on the autho string. Author string
790 Tries to discover RhodeCode User based on the autho string. Author string
791 is typically `FirstName LastName <email@address.com>`
791 is typically `FirstName LastName <email@address.com>`
792 """
792 """
793
793
794 # if author is already an instance use it for extraction
794 # if author is already an instance use it for extraction
795 if isinstance(author, User):
795 if isinstance(author, User):
796 return author
796 return author
797
797
798 # Valid email in the attribute passed, see if they're in the system
798 # Valid email in the attribute passed, see if they're in the system
799 _email = author_email(author)
799 _email = author_email(author)
800 if _email != '':
800 if _email != '':
801 user = User.get_by_email(_email, case_insensitive=True, cache=True)
801 user = User.get_by_email(_email, case_insensitive=True, cache=True)
802 if user is not None:
802 if user is not None:
803 return user
803 return user
804
804
805 # Maybe it's a username, we try to extract it and fetch by username ?
805 # Maybe it's a username, we try to extract it and fetch by username ?
806 _author = author_name(author)
806 _author = author_name(author)
807 user = User.get_by_username(_author, case_insensitive=True, cache=True)
807 user = User.get_by_username(_author, case_insensitive=True, cache=True)
808 if user is not None:
808 if user is not None:
809 return user
809 return user
810
810
811 return None
811 return None
812
812
813
813
814 def email_or_none(author):
814 def email_or_none(author):
815 # extract email from the commit string
815 # extract email from the commit string
816 _email = author_email(author)
816 _email = author_email(author)
817
817
818 # If we have an email, use it, otherwise
818 # If we have an email, use it, otherwise
819 # see if it contains a username we can get an email from
819 # see if it contains a username we can get an email from
820 if _email != '':
820 if _email != '':
821 return _email
821 return _email
822 else:
822 else:
823 user = User.get_by_username(
823 user = User.get_by_username(
824 author_name(author), case_insensitive=True, cache=True)
824 author_name(author), case_insensitive=True, cache=True)
825
825
826 if user is not None:
826 if user is not None:
827 return user.email
827 return user.email
828
828
829 # No valid email, not a valid user in the system, none!
829 # No valid email, not a valid user in the system, none!
830 return None
830 return None
831
831
832
832
833 def link_to_user(author, length=0, **kwargs):
833 def link_to_user(author, length=0, **kwargs):
834 user = discover_user(author)
834 user = discover_user(author)
835 # user can be None, but if we have it already it means we can re-use it
835 # user can be None, but if we have it already it means we can re-use it
836 # in the person() function, so we save 1 intensive-query
836 # in the person() function, so we save 1 intensive-query
837 if user:
837 if user:
838 author = user
838 author = user
839
839
840 display_person = person(author, 'username_or_name_or_email')
840 display_person = person(author, 'username_or_name_or_email')
841 if length:
841 if length:
842 display_person = shorter(display_person, length)
842 display_person = shorter(display_person, length)
843
843
844 if user:
844 if user:
845 return link_to(
845 return link_to(
846 escape(display_person),
846 escape(display_person),
847 route_path('user_profile', username=user.username),
847 route_path('user_profile', username=user.username),
848 **kwargs)
848 **kwargs)
849 else:
849 else:
850 return escape(display_person)
850 return escape(display_person)
851
851
852
852
853 def link_to_group(users_group_name, **kwargs):
853 def link_to_group(users_group_name, **kwargs):
854 return link_to(
854 return link_to(
855 escape(users_group_name),
855 escape(users_group_name),
856 route_path('user_group_profile', user_group_name=users_group_name),
856 route_path('user_group_profile', user_group_name=users_group_name),
857 **kwargs)
857 **kwargs)
858
858
859
859
860 def person(author, show_attr="username_and_name"):
860 def person(author, show_attr="username_and_name"):
861 user = discover_user(author)
861 user = discover_user(author)
862 if user:
862 if user:
863 return getattr(user, show_attr)
863 return getattr(user, show_attr)
864 else:
864 else:
865 _author = author_name(author)
865 _author = author_name(author)
866 _email = email(author)
866 _email = email(author)
867 return _author or _email
867 return _author or _email
868
868
869
869
870 def author_string(email):
870 def author_string(email):
871 if email:
871 if email:
872 user = User.get_by_email(email, case_insensitive=True, cache=True)
872 user = User.get_by_email(email, case_insensitive=True, cache=True)
873 if user:
873 if user:
874 if user.first_name or user.last_name:
874 if user.first_name or user.last_name:
875 return '%s %s &lt;%s&gt;' % (
875 return '%s %s &lt;%s&gt;' % (
876 user.first_name, user.last_name, email)
876 user.first_name, user.last_name, email)
877 else:
877 else:
878 return email
878 return email
879 else:
879 else:
880 return email
880 return email
881 else:
881 else:
882 return None
882 return None
883
883
884
884
885 def person_by_id(id_, show_attr="username_and_name"):
885 def person_by_id(id_, show_attr="username_and_name"):
886 # attr to return from fetched user
886 # attr to return from fetched user
887 person_getter = lambda usr: getattr(usr, show_attr)
887 person_getter = lambda usr: getattr(usr, show_attr)
888
888
889 #maybe it's an ID ?
889 #maybe it's an ID ?
890 if str(id_).isdigit() or isinstance(id_, int):
890 if str(id_).isdigit() or isinstance(id_, int):
891 id_ = int(id_)
891 id_ = int(id_)
892 user = User.get(id_)
892 user = User.get(id_)
893 if user is not None:
893 if user is not None:
894 return person_getter(user)
894 return person_getter(user)
895 return id_
895 return id_
896
896
897
897
898 def gravatar_with_user(request, author, show_disabled=False):
898 def gravatar_with_user(request, author, show_disabled=False):
899 _render = request.get_partial_renderer(
899 _render = request.get_partial_renderer(
900 'rhodecode:templates/base/base.mako')
900 'rhodecode:templates/base/base.mako')
901 return _render('gravatar_with_user', author, show_disabled=show_disabled)
901 return _render('gravatar_with_user', author, show_disabled=show_disabled)
902
902
903
903
904 tags_paterns = OrderedDict((
904 tags_paterns = OrderedDict((
905 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
905 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
906 '<div class="metatag" tag="lang">\\2</div>')),
906 '<div class="metatag" tag="lang">\\2</div>')),
907
907
908 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
908 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
909 '<div class="metatag" tag="see">see: \\1 </div>')),
909 '<div class="metatag" tag="see">see: \\1 </div>')),
910
910
911 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
911 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
912 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
912 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
913
913
914 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
914 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
915 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
915 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
916
916
917 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
917 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
918 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
918 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
919
919
920 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
920 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
921 '<div class="metatag" tag="state \\1">\\1</div>')),
921 '<div class="metatag" tag="state \\1">\\1</div>')),
922
922
923 # label in grey
923 # label in grey
924 ('label', (re.compile(r'\[([a-z]+)\]'),
924 ('label', (re.compile(r'\[([a-z]+)\]'),
925 '<div class="metatag" tag="label">\\1</div>')),
925 '<div class="metatag" tag="label">\\1</div>')),
926
926
927 # generic catch all in grey
927 # generic catch all in grey
928 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
928 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
929 '<div class="metatag" tag="generic">\\1</div>')),
929 '<div class="metatag" tag="generic">\\1</div>')),
930 ))
930 ))
931
931
932
932
933 def extract_metatags(value):
933 def extract_metatags(value):
934 """
934 """
935 Extract supported meta-tags from given text value
935 Extract supported meta-tags from given text value
936 """
936 """
937 tags = []
937 tags = []
938 if not value:
938 if not value:
939 return tags, ''
939 return tags, ''
940
940
941 for key, val in tags_paterns.items():
941 for key, val in tags_paterns.items():
942 pat, replace_html = val
942 pat, replace_html = val
943 tags.extend([(key, x.group()) for x in pat.finditer(value)])
943 tags.extend([(key, x.group()) for x in pat.finditer(value)])
944 value = pat.sub('', value)
944 value = pat.sub('', value)
945
945
946 return tags, value
946 return tags, value
947
947
948
948
949 def style_metatag(tag_type, value):
949 def style_metatag(tag_type, value):
950 """
950 """
951 converts tags from value into html equivalent
951 converts tags from value into html equivalent
952 """
952 """
953 if not value:
953 if not value:
954 return ''
954 return ''
955
955
956 html_value = value
956 html_value = value
957 tag_data = tags_paterns.get(tag_type)
957 tag_data = tags_paterns.get(tag_type)
958 if tag_data:
958 if tag_data:
959 pat, replace_html = tag_data
959 pat, replace_html = tag_data
960 # convert to plain `unicode` instead of a markup tag to be used in
960 # convert to plain `unicode` instead of a markup tag to be used in
961 # regex expressions. safe_unicode doesn't work here
961 # regex expressions. safe_unicode doesn't work here
962 html_value = pat.sub(replace_html, unicode(value))
962 html_value = pat.sub(replace_html, unicode(value))
963
963
964 return html_value
964 return html_value
965
965
966
966
967 def bool2icon(value, show_at_false=True):
967 def bool2icon(value, show_at_false=True):
968 """
968 """
969 Returns boolean value of a given value, represented as html element with
969 Returns boolean value of a given value, represented as html element with
970 classes that will represent icons
970 classes that will represent icons
971
971
972 :param value: given value to convert to html node
972 :param value: given value to convert to html node
973 """
973 """
974
974
975 if value: # does bool conversion
975 if value: # does bool conversion
976 return HTML.tag('i', class_="icon-true")
976 return HTML.tag('i', class_="icon-true")
977 else: # not true as bool
977 else: # not true as bool
978 if show_at_false:
978 if show_at_false:
979 return HTML.tag('i', class_="icon-false")
979 return HTML.tag('i', class_="icon-false")
980 return HTML.tag('i')
980 return HTML.tag('i')
981
981
982 #==============================================================================
982 #==============================================================================
983 # PERMS
983 # PERMS
984 #==============================================================================
984 #==============================================================================
985 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
985 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
986 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
986 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
987 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
987 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
988 csrf_token_key
988 csrf_token_key
989
989
990
990
991 #==============================================================================
991 #==============================================================================
992 # GRAVATAR URL
992 # GRAVATAR URL
993 #==============================================================================
993 #==============================================================================
994 class InitialsGravatar(object):
994 class InitialsGravatar(object):
995 def __init__(self, email_address, first_name, last_name, size=30,
995 def __init__(self, email_address, first_name, last_name, size=30,
996 background=None, text_color='#fff'):
996 background=None, text_color='#fff'):
997 self.size = size
997 self.size = size
998 self.first_name = first_name
998 self.first_name = first_name
999 self.last_name = last_name
999 self.last_name = last_name
1000 self.email_address = email_address
1000 self.email_address = email_address
1001 self.background = background or self.str2color(email_address)
1001 self.background = background or self.str2color(email_address)
1002 self.text_color = text_color
1002 self.text_color = text_color
1003
1003
1004 def get_color_bank(self):
1004 def get_color_bank(self):
1005 """
1005 """
1006 returns a predefined list of colors that gravatars can use.
1006 returns a predefined list of colors that gravatars can use.
1007 Those are randomized distinct colors that guarantee readability and
1007 Those are randomized distinct colors that guarantee readability and
1008 uniqueness.
1008 uniqueness.
1009
1009
1010 generated with: http://phrogz.net/css/distinct-colors.html
1010 generated with: http://phrogz.net/css/distinct-colors.html
1011 """
1011 """
1012 return [
1012 return [
1013 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1013 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1014 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1014 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1015 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1015 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1016 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1016 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1017 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1017 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1018 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1018 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1019 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1019 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1020 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1020 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1021 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1021 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1022 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1022 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1023 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1023 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1024 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1024 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1025 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1025 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1026 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1026 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1027 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1027 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1028 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1028 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1029 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1029 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1030 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1030 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1031 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1031 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1032 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1032 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1033 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1033 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1034 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1034 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1035 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1035 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1036 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1036 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1037 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1037 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1038 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1038 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1039 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1039 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1040 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1040 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1041 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1041 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1042 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1042 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1043 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1043 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1044 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1044 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1045 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1045 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1046 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1046 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1047 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1047 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1048 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1048 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1049 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1049 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1050 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1050 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1051 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1051 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1052 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1052 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1053 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1053 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1054 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1054 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1055 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1055 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1056 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1056 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1057 '#4f8c46', '#368dd9', '#5c0073'
1057 '#4f8c46', '#368dd9', '#5c0073'
1058 ]
1058 ]
1059
1059
1060 def rgb_to_hex_color(self, rgb_tuple):
1060 def rgb_to_hex_color(self, rgb_tuple):
1061 """
1061 """
1062 Converts an rgb_tuple passed to an hex color.
1062 Converts an rgb_tuple passed to an hex color.
1063
1063
1064 :param rgb_tuple: tuple with 3 ints represents rgb color space
1064 :param rgb_tuple: tuple with 3 ints represents rgb color space
1065 """
1065 """
1066 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1066 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1067
1067
1068 def email_to_int_list(self, email_str):
1068 def email_to_int_list(self, email_str):
1069 """
1069 """
1070 Get every byte of the hex digest value of email and turn it to integer.
1070 Get every byte of the hex digest value of email and turn it to integer.
1071 It's going to be always between 0-255
1071 It's going to be always between 0-255
1072 """
1072 """
1073 digest = md5_safe(email_str.lower())
1073 digest = md5_safe(email_str.lower())
1074 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1074 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1075
1075
1076 def pick_color_bank_index(self, email_str, color_bank):
1076 def pick_color_bank_index(self, email_str, color_bank):
1077 return self.email_to_int_list(email_str)[0] % len(color_bank)
1077 return self.email_to_int_list(email_str)[0] % len(color_bank)
1078
1078
1079 def str2color(self, email_str):
1079 def str2color(self, email_str):
1080 """
1080 """
1081 Tries to map in a stable algorithm an email to color
1081 Tries to map in a stable algorithm an email to color
1082
1082
1083 :param email_str:
1083 :param email_str:
1084 """
1084 """
1085 color_bank = self.get_color_bank()
1085 color_bank = self.get_color_bank()
1086 # pick position (module it's length so we always find it in the
1086 # pick position (module it's length so we always find it in the
1087 # bank even if it's smaller than 256 values
1087 # bank even if it's smaller than 256 values
1088 pos = self.pick_color_bank_index(email_str, color_bank)
1088 pos = self.pick_color_bank_index(email_str, color_bank)
1089 return color_bank[pos]
1089 return color_bank[pos]
1090
1090
1091 def normalize_email(self, email_address):
1091 def normalize_email(self, email_address):
1092 import unicodedata
1092 import unicodedata
1093 # default host used to fill in the fake/missing email
1093 # default host used to fill in the fake/missing email
1094 default_host = u'localhost'
1094 default_host = u'localhost'
1095
1095
1096 if not email_address:
1096 if not email_address:
1097 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1097 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1098
1098
1099 email_address = safe_unicode(email_address)
1099 email_address = safe_unicode(email_address)
1100
1100
1101 if u'@' not in email_address:
1101 if u'@' not in email_address:
1102 email_address = u'%s@%s' % (email_address, default_host)
1102 email_address = u'%s@%s' % (email_address, default_host)
1103
1103
1104 if email_address.endswith(u'@'):
1104 if email_address.endswith(u'@'):
1105 email_address = u'%s%s' % (email_address, default_host)
1105 email_address = u'%s%s' % (email_address, default_host)
1106
1106
1107 email_address = unicodedata.normalize('NFKD', email_address)\
1107 email_address = unicodedata.normalize('NFKD', email_address)\
1108 .encode('ascii', 'ignore')
1108 .encode('ascii', 'ignore')
1109 return email_address
1109 return email_address
1110
1110
1111 def get_initials(self):
1111 def get_initials(self):
1112 """
1112 """
1113 Returns 2 letter initials calculated based on the input.
1113 Returns 2 letter initials calculated based on the input.
1114 The algorithm picks first given email address, and takes first letter
1114 The algorithm picks first given email address, and takes first letter
1115 of part before @, and then the first letter of server name. In case
1115 of part before @, and then the first letter of server name. In case
1116 the part before @ is in a format of `somestring.somestring2` it replaces
1116 the part before @ is in a format of `somestring.somestring2` it replaces
1117 the server letter with first letter of somestring2
1117 the server letter with first letter of somestring2
1118
1118
1119 In case function was initialized with both first and lastname, this
1119 In case function was initialized with both first and lastname, this
1120 overrides the extraction from email by first letter of the first and
1120 overrides the extraction from email by first letter of the first and
1121 last name. We add special logic to that functionality, In case Full name
1121 last name. We add special logic to that functionality, In case Full name
1122 is compound, like Guido Von Rossum, we use last part of the last name
1122 is compound, like Guido Von Rossum, we use last part of the last name
1123 (Von Rossum) picking `R`.
1123 (Von Rossum) picking `R`.
1124
1124
1125 Function also normalizes the non-ascii characters to they ascii
1125 Function also normalizes the non-ascii characters to they ascii
1126 representation, eg Δ„ => A
1126 representation, eg Δ„ => A
1127 """
1127 """
1128 import unicodedata
1128 import unicodedata
1129 # replace non-ascii to ascii
1129 # replace non-ascii to ascii
1130 first_name = unicodedata.normalize(
1130 first_name = unicodedata.normalize(
1131 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1131 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1132 last_name = unicodedata.normalize(
1132 last_name = unicodedata.normalize(
1133 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1133 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1134
1134
1135 # do NFKD encoding, and also make sure email has proper format
1135 # do NFKD encoding, and also make sure email has proper format
1136 email_address = self.normalize_email(self.email_address)
1136 email_address = self.normalize_email(self.email_address)
1137
1137
1138 # first push the email initials
1138 # first push the email initials
1139 prefix, server = email_address.split('@', 1)
1139 prefix, server = email_address.split('@', 1)
1140
1140
1141 # check if prefix is maybe a 'first_name.last_name' syntax
1141 # check if prefix is maybe a 'first_name.last_name' syntax
1142 _dot_split = prefix.rsplit('.', 1)
1142 _dot_split = prefix.rsplit('.', 1)
1143 if len(_dot_split) == 2 and _dot_split[1]:
1143 if len(_dot_split) == 2 and _dot_split[1]:
1144 initials = [_dot_split[0][0], _dot_split[1][0]]
1144 initials = [_dot_split[0][0], _dot_split[1][0]]
1145 else:
1145 else:
1146 initials = [prefix[0], server[0]]
1146 initials = [prefix[0], server[0]]
1147
1147
1148 # then try to replace either first_name or last_name
1148 # then try to replace either first_name or last_name
1149 fn_letter = (first_name or " ")[0].strip()
1149 fn_letter = (first_name or " ")[0].strip()
1150 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1150 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1151
1151
1152 if fn_letter:
1152 if fn_letter:
1153 initials[0] = fn_letter
1153 initials[0] = fn_letter
1154
1154
1155 if ln_letter:
1155 if ln_letter:
1156 initials[1] = ln_letter
1156 initials[1] = ln_letter
1157
1157
1158 return ''.join(initials).upper()
1158 return ''.join(initials).upper()
1159
1159
1160 def get_img_data_by_type(self, font_family, img_type):
1160 def get_img_data_by_type(self, font_family, img_type):
1161 default_user = """
1161 default_user = """
1162 <svg xmlns="http://www.w3.org/2000/svg"
1162 <svg xmlns="http://www.w3.org/2000/svg"
1163 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1163 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1164 viewBox="-15 -10 439.165 429.164"
1164 viewBox="-15 -10 439.165 429.164"
1165
1165
1166 xml:space="preserve"
1166 xml:space="preserve"
1167 style="background:{background};" >
1167 style="background:{background};" >
1168
1168
1169 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1169 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1170 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1170 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1171 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1171 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1172 168.596,153.916,216.671,
1172 168.596,153.916,216.671,
1173 204.583,216.671z" fill="{text_color}"/>
1173 204.583,216.671z" fill="{text_color}"/>
1174 <path d="M407.164,374.717L360.88,
1174 <path d="M407.164,374.717L360.88,
1175 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1175 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1176 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1176 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1177 15.366-44.203,23.488-69.076,23.488c-24.877,
1177 15.366-44.203,23.488-69.076,23.488c-24.877,
1178 0-48.762-8.122-69.078-23.488
1178 0-48.762-8.122-69.078-23.488
1179 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1179 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1180 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1180 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1181 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1181 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1182 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1182 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1183 19.402-10.527 C409.699,390.129,
1183 19.402-10.527 C409.699,390.129,
1184 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1184 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1185 </svg>""".format(
1185 </svg>""".format(
1186 size=self.size,
1186 size=self.size,
1187 background='#979797', # @grey4
1187 background='#979797', # @grey4
1188 text_color=self.text_color,
1188 text_color=self.text_color,
1189 font_family=font_family)
1189 font_family=font_family)
1190
1190
1191 return {
1191 return {
1192 "default_user": default_user
1192 "default_user": default_user
1193 }[img_type]
1193 }[img_type]
1194
1194
1195 def get_img_data(self, svg_type=None):
1195 def get_img_data(self, svg_type=None):
1196 """
1196 """
1197 generates the svg metadata for image
1197 generates the svg metadata for image
1198 """
1198 """
1199 fonts = [
1199 fonts = [
1200 '-apple-system',
1200 '-apple-system',
1201 'BlinkMacSystemFont',
1201 'BlinkMacSystemFont',
1202 'Segoe UI',
1202 'Segoe UI',
1203 'Roboto',
1203 'Roboto',
1204 'Oxygen-Sans',
1204 'Oxygen-Sans',
1205 'Ubuntu',
1205 'Ubuntu',
1206 'Cantarell',
1206 'Cantarell',
1207 'Helvetica Neue',
1207 'Helvetica Neue',
1208 'sans-serif'
1208 'sans-serif'
1209 ]
1209 ]
1210 font_family = ','.join(fonts)
1210 font_family = ','.join(fonts)
1211 if svg_type:
1211 if svg_type:
1212 return self.get_img_data_by_type(font_family, svg_type)
1212 return self.get_img_data_by_type(font_family, svg_type)
1213
1213
1214 initials = self.get_initials()
1214 initials = self.get_initials()
1215 img_data = """
1215 img_data = """
1216 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1216 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1217 width="{size}" height="{size}"
1217 width="{size}" height="{size}"
1218 style="width: 100%; height: 100%; background-color: {background}"
1218 style="width: 100%; height: 100%; background-color: {background}"
1219 viewBox="0 0 {size} {size}">
1219 viewBox="0 0 {size} {size}">
1220 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1220 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1221 pointer-events="auto" fill="{text_color}"
1221 pointer-events="auto" fill="{text_color}"
1222 font-family="{font_family}"
1222 font-family="{font_family}"
1223 style="font-weight: 400; font-size: {f_size}px;">{text}
1223 style="font-weight: 400; font-size: {f_size}px;">{text}
1224 </text>
1224 </text>
1225 </svg>""".format(
1225 </svg>""".format(
1226 size=self.size,
1226 size=self.size,
1227 f_size=self.size/2.05, # scale the text inside the box nicely
1227 f_size=self.size/2.05, # scale the text inside the box nicely
1228 background=self.background,
1228 background=self.background,
1229 text_color=self.text_color,
1229 text_color=self.text_color,
1230 text=initials.upper(),
1230 text=initials.upper(),
1231 font_family=font_family)
1231 font_family=font_family)
1232
1232
1233 return img_data
1233 return img_data
1234
1234
1235 def generate_svg(self, svg_type=None):
1235 def generate_svg(self, svg_type=None):
1236 img_data = self.get_img_data(svg_type)
1236 img_data = self.get_img_data(svg_type)
1237 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1237 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1238
1238
1239
1239
1240 def initials_gravatar(email_address, first_name, last_name, size=30):
1240 def initials_gravatar(email_address, first_name, last_name, size=30):
1241 svg_type = None
1241 svg_type = None
1242 if email_address == User.DEFAULT_USER_EMAIL:
1242 if email_address == User.DEFAULT_USER_EMAIL:
1243 svg_type = 'default_user'
1243 svg_type = 'default_user'
1244 klass = InitialsGravatar(email_address, first_name, last_name, size)
1244 klass = InitialsGravatar(email_address, first_name, last_name, size)
1245 return klass.generate_svg(svg_type=svg_type)
1245 return klass.generate_svg(svg_type=svg_type)
1246
1246
1247
1247
1248 def gravatar_url(email_address, size=30, request=None):
1248 def gravatar_url(email_address, size=30, request=None):
1249 request = get_current_request()
1249 request = get_current_request()
1250 _use_gravatar = request.call_context.visual.use_gravatar
1250 _use_gravatar = request.call_context.visual.use_gravatar
1251 _gravatar_url = request.call_context.visual.gravatar_url
1251 _gravatar_url = request.call_context.visual.gravatar_url
1252
1252
1253 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1253 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1254
1254
1255 email_address = email_address or User.DEFAULT_USER_EMAIL
1255 email_address = email_address or User.DEFAULT_USER_EMAIL
1256 if isinstance(email_address, unicode):
1256 if isinstance(email_address, unicode):
1257 # hashlib crashes on unicode items
1257 # hashlib crashes on unicode items
1258 email_address = safe_str(email_address)
1258 email_address = safe_str(email_address)
1259
1259
1260 # empty email or default user
1260 # empty email or default user
1261 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1261 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1262 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1262 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1263
1263
1264 if _use_gravatar:
1264 if _use_gravatar:
1265 # TODO: Disuse pyramid thread locals. Think about another solution to
1265 # TODO: Disuse pyramid thread locals. Think about another solution to
1266 # get the host and schema here.
1266 # get the host and schema here.
1267 request = get_current_request()
1267 request = get_current_request()
1268 tmpl = safe_str(_gravatar_url)
1268 tmpl = safe_str(_gravatar_url)
1269 tmpl = tmpl.replace('{email}', email_address)\
1269 tmpl = tmpl.replace('{email}', email_address)\
1270 .replace('{md5email}', md5_safe(email_address.lower())) \
1270 .replace('{md5email}', md5_safe(email_address.lower())) \
1271 .replace('{netloc}', request.host)\
1271 .replace('{netloc}', request.host)\
1272 .replace('{scheme}', request.scheme)\
1272 .replace('{scheme}', request.scheme)\
1273 .replace('{size}', safe_str(size))
1273 .replace('{size}', safe_str(size))
1274 return tmpl
1274 return tmpl
1275 else:
1275 else:
1276 return initials_gravatar(email_address, '', '', size=size)
1276 return initials_gravatar(email_address, '', '', size=size)
1277
1277
1278
1278
1279 class Page(_Page):
1279 class Page(_Page):
1280 """
1280 """
1281 Custom pager to match rendering style with paginator
1281 Custom pager to match rendering style with paginator
1282 """
1282 """
1283
1283
1284 def _get_pos(self, cur_page, max_page, items):
1284 def _get_pos(self, cur_page, max_page, items):
1285 edge = (items / 2) + 1
1285 edge = (items / 2) + 1
1286 if (cur_page <= edge):
1286 if (cur_page <= edge):
1287 radius = max(items / 2, items - cur_page)
1287 radius = max(items / 2, items - cur_page)
1288 elif (max_page - cur_page) < edge:
1288 elif (max_page - cur_page) < edge:
1289 radius = (items - 1) - (max_page - cur_page)
1289 radius = (items - 1) - (max_page - cur_page)
1290 else:
1290 else:
1291 radius = items / 2
1291 radius = items / 2
1292
1292
1293 left = max(1, (cur_page - (radius)))
1293 left = max(1, (cur_page - (radius)))
1294 right = min(max_page, cur_page + (radius))
1294 right = min(max_page, cur_page + (radius))
1295 return left, cur_page, right
1295 return left, cur_page, right
1296
1296
1297 def _range(self, regexp_match):
1297 def _range(self, regexp_match):
1298 """
1298 """
1299 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1299 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1300
1300
1301 Arguments:
1301 Arguments:
1302
1302
1303 regexp_match
1303 regexp_match
1304 A "re" (regular expressions) match object containing the
1304 A "re" (regular expressions) match object containing the
1305 radius of linked pages around the current page in
1305 radius of linked pages around the current page in
1306 regexp_match.group(1) as a string
1306 regexp_match.group(1) as a string
1307
1307
1308 This function is supposed to be called as a callable in
1308 This function is supposed to be called as a callable in
1309 re.sub.
1309 re.sub.
1310
1310
1311 """
1311 """
1312 radius = int(regexp_match.group(1))
1312 radius = int(regexp_match.group(1))
1313
1313
1314 # Compute the first and last page number within the radius
1314 # Compute the first and last page number within the radius
1315 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1315 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1316 # -> leftmost_page = 5
1316 # -> leftmost_page = 5
1317 # -> rightmost_page = 9
1317 # -> rightmost_page = 9
1318 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1318 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1319 self.last_page,
1319 self.last_page,
1320 (radius * 2) + 1)
1320 (radius * 2) + 1)
1321 nav_items = []
1321 nav_items = []
1322
1322
1323 # Create a link to the first page (unless we are on the first page
1323 # Create a link to the first page (unless we are on the first page
1324 # or there would be no need to insert '..' spacers)
1324 # or there would be no need to insert '..' spacers)
1325 if self.page != self.first_page and self.first_page < leftmost_page:
1325 if self.page != self.first_page and self.first_page < leftmost_page:
1326 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1326 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1327
1327
1328 # Insert dots if there are pages between the first page
1328 # Insert dots if there are pages between the first page
1329 # and the currently displayed page range
1329 # and the currently displayed page range
1330 if leftmost_page - self.first_page > 1:
1330 if leftmost_page - self.first_page > 1:
1331 # Wrap in a SPAN tag if nolink_attr is set
1331 # Wrap in a SPAN tag if nolink_attr is set
1332 text = '..'
1332 text = '..'
1333 if self.dotdot_attr:
1333 if self.dotdot_attr:
1334 text = HTML.span(c=text, **self.dotdot_attr)
1334 text = HTML.span(c=text, **self.dotdot_attr)
1335 nav_items.append(text)
1335 nav_items.append(text)
1336
1336
1337 for thispage in xrange(leftmost_page, rightmost_page + 1):
1337 for thispage in xrange(leftmost_page, rightmost_page + 1):
1338 # Hilight the current page number and do not use a link
1338 # Hilight the current page number and do not use a link
1339 if thispage == self.page:
1339 if thispage == self.page:
1340 text = '%s' % (thispage,)
1340 text = '%s' % (thispage,)
1341 # Wrap in a SPAN tag if nolink_attr is set
1341 # Wrap in a SPAN tag if nolink_attr is set
1342 if self.curpage_attr:
1342 if self.curpage_attr:
1343 text = HTML.span(c=text, **self.curpage_attr)
1343 text = HTML.span(c=text, **self.curpage_attr)
1344 nav_items.append(text)
1344 nav_items.append(text)
1345 # Otherwise create just a link to that page
1345 # Otherwise create just a link to that page
1346 else:
1346 else:
1347 text = '%s' % (thispage,)
1347 text = '%s' % (thispage,)
1348 nav_items.append(self._pagerlink(thispage, text))
1348 nav_items.append(self._pagerlink(thispage, text))
1349
1349
1350 # Insert dots if there are pages between the displayed
1350 # Insert dots if there are pages between the displayed
1351 # page numbers and the end of the page range
1351 # page numbers and the end of the page range
1352 if self.last_page - rightmost_page > 1:
1352 if self.last_page - rightmost_page > 1:
1353 text = '..'
1353 text = '..'
1354 # Wrap in a SPAN tag if nolink_attr is set
1354 # Wrap in a SPAN tag if nolink_attr is set
1355 if self.dotdot_attr:
1355 if self.dotdot_attr:
1356 text = HTML.span(c=text, **self.dotdot_attr)
1356 text = HTML.span(c=text, **self.dotdot_attr)
1357 nav_items.append(text)
1357 nav_items.append(text)
1358
1358
1359 # Create a link to the very last page (unless we are on the last
1359 # Create a link to the very last page (unless we are on the last
1360 # page or there would be no need to insert '..' spacers)
1360 # page or there would be no need to insert '..' spacers)
1361 if self.page != self.last_page and rightmost_page < self.last_page:
1361 if self.page != self.last_page and rightmost_page < self.last_page:
1362 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1362 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1363
1363
1364 ## prerender links
1364 ## prerender links
1365 #_page_link = url.current()
1365 #_page_link = url.current()
1366 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1366 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1367 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1367 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1368 return self.separator.join(nav_items)
1368 return self.separator.join(nav_items)
1369
1369
1370 def pager(self, format='~2~', page_param='page', partial_param='partial',
1370 def pager(self, format='~2~', page_param='page', partial_param='partial',
1371 show_if_single_page=False, separator=' ', onclick=None,
1371 show_if_single_page=False, separator=' ', onclick=None,
1372 symbol_first='<<', symbol_last='>>',
1372 symbol_first='<<', symbol_last='>>',
1373 symbol_previous='<', symbol_next='>',
1373 symbol_previous='<', symbol_next='>',
1374 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1374 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1375 curpage_attr={'class': 'pager_curpage'},
1375 curpage_attr={'class': 'pager_curpage'},
1376 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1376 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1377
1377
1378 self.curpage_attr = curpage_attr
1378 self.curpage_attr = curpage_attr
1379 self.separator = separator
1379 self.separator = separator
1380 self.pager_kwargs = kwargs
1380 self.pager_kwargs = kwargs
1381 self.page_param = page_param
1381 self.page_param = page_param
1382 self.partial_param = partial_param
1382 self.partial_param = partial_param
1383 self.onclick = onclick
1383 self.onclick = onclick
1384 self.link_attr = link_attr
1384 self.link_attr = link_attr
1385 self.dotdot_attr = dotdot_attr
1385 self.dotdot_attr = dotdot_attr
1386
1386
1387 # Don't show navigator if there is no more than one page
1387 # Don't show navigator if there is no more than one page
1388 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1388 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1389 return ''
1389 return ''
1390
1390
1391 from string import Template
1391 from string import Template
1392 # Replace ~...~ in token format by range of pages
1392 # Replace ~...~ in token format by range of pages
1393 result = re.sub(r'~(\d+)~', self._range, format)
1393 result = re.sub(r'~(\d+)~', self._range, format)
1394
1394
1395 # Interpolate '%' variables
1395 # Interpolate '%' variables
1396 result = Template(result).safe_substitute({
1396 result = Template(result).safe_substitute({
1397 'first_page': self.first_page,
1397 'first_page': self.first_page,
1398 'last_page': self.last_page,
1398 'last_page': self.last_page,
1399 'page': self.page,
1399 'page': self.page,
1400 'page_count': self.page_count,
1400 'page_count': self.page_count,
1401 'items_per_page': self.items_per_page,
1401 'items_per_page': self.items_per_page,
1402 'first_item': self.first_item,
1402 'first_item': self.first_item,
1403 'last_item': self.last_item,
1403 'last_item': self.last_item,
1404 'item_count': self.item_count,
1404 'item_count': self.item_count,
1405 'link_first': self.page > self.first_page and \
1405 'link_first': self.page > self.first_page and \
1406 self._pagerlink(self.first_page, symbol_first) or '',
1406 self._pagerlink(self.first_page, symbol_first) or '',
1407 'link_last': self.page < self.last_page and \
1407 'link_last': self.page < self.last_page and \
1408 self._pagerlink(self.last_page, symbol_last) or '',
1408 self._pagerlink(self.last_page, symbol_last) or '',
1409 'link_previous': self.previous_page and \
1409 'link_previous': self.previous_page and \
1410 self._pagerlink(self.previous_page, symbol_previous) \
1410 self._pagerlink(self.previous_page, symbol_previous) \
1411 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1411 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1412 'link_next': self.next_page and \
1412 'link_next': self.next_page and \
1413 self._pagerlink(self.next_page, symbol_next) \
1413 self._pagerlink(self.next_page, symbol_next) \
1414 or HTML.span(symbol_next, class_="pg-next disabled")
1414 or HTML.span(symbol_next, class_="pg-next disabled")
1415 })
1415 })
1416
1416
1417 return literal(result)
1417 return literal(result)
1418
1418
1419
1419
1420 #==============================================================================
1420 #==============================================================================
1421 # REPO PAGER, PAGER FOR REPOSITORY
1421 # REPO PAGER, PAGER FOR REPOSITORY
1422 #==============================================================================
1422 #==============================================================================
1423 class RepoPage(Page):
1423 class RepoPage(Page):
1424
1424
1425 def __init__(self, collection, page=1, items_per_page=20,
1425 def __init__(self, collection, page=1, items_per_page=20,
1426 item_count=None, url=None, **kwargs):
1426 item_count=None, url=None, **kwargs):
1427
1427
1428 """Create a "RepoPage" instance. special pager for paging
1428 """Create a "RepoPage" instance. special pager for paging
1429 repository
1429 repository
1430 """
1430 """
1431 self._url_generator = url
1431 self._url_generator = url
1432
1432
1433 # Safe the kwargs class-wide so they can be used in the pager() method
1433 # Safe the kwargs class-wide so they can be used in the pager() method
1434 self.kwargs = kwargs
1434 self.kwargs = kwargs
1435
1435
1436 # Save a reference to the collection
1436 # Save a reference to the collection
1437 self.original_collection = collection
1437 self.original_collection = collection
1438
1438
1439 self.collection = collection
1439 self.collection = collection
1440
1440
1441 # The self.page is the number of the current page.
1441 # The self.page is the number of the current page.
1442 # The first page has the number 1!
1442 # The first page has the number 1!
1443 try:
1443 try:
1444 self.page = int(page) # make it int() if we get it as a string
1444 self.page = int(page) # make it int() if we get it as a string
1445 except (ValueError, TypeError):
1445 except (ValueError, TypeError):
1446 self.page = 1
1446 self.page = 1
1447
1447
1448 self.items_per_page = items_per_page
1448 self.items_per_page = items_per_page
1449
1449
1450 # Unless the user tells us how many items the collections has
1450 # Unless the user tells us how many items the collections has
1451 # we calculate that ourselves.
1451 # we calculate that ourselves.
1452 if item_count is not None:
1452 if item_count is not None:
1453 self.item_count = item_count
1453 self.item_count = item_count
1454 else:
1454 else:
1455 self.item_count = len(self.collection)
1455 self.item_count = len(self.collection)
1456
1456
1457 # Compute the number of the first and last available page
1457 # Compute the number of the first and last available page
1458 if self.item_count > 0:
1458 if self.item_count > 0:
1459 self.first_page = 1
1459 self.first_page = 1
1460 self.page_count = int(math.ceil(float(self.item_count) /
1460 self.page_count = int(math.ceil(float(self.item_count) /
1461 self.items_per_page))
1461 self.items_per_page))
1462 self.last_page = self.first_page + self.page_count - 1
1462 self.last_page = self.first_page + self.page_count - 1
1463
1463
1464 # Make sure that the requested page number is the range of
1464 # Make sure that the requested page number is the range of
1465 # valid pages
1465 # valid pages
1466 if self.page > self.last_page:
1466 if self.page > self.last_page:
1467 self.page = self.last_page
1467 self.page = self.last_page
1468 elif self.page < self.first_page:
1468 elif self.page < self.first_page:
1469 self.page = self.first_page
1469 self.page = self.first_page
1470
1470
1471 # Note: the number of items on this page can be less than
1471 # Note: the number of items on this page can be less than
1472 # items_per_page if the last page is not full
1472 # items_per_page if the last page is not full
1473 self.first_item = max(0, (self.item_count) - (self.page *
1473 self.first_item = max(0, (self.item_count) - (self.page *
1474 items_per_page))
1474 items_per_page))
1475 self.last_item = ((self.item_count - 1) - items_per_page *
1475 self.last_item = ((self.item_count - 1) - items_per_page *
1476 (self.page - 1))
1476 (self.page - 1))
1477
1477
1478 self.items = list(self.collection[self.first_item:self.last_item + 1])
1478 self.items = list(self.collection[self.first_item:self.last_item + 1])
1479
1479
1480 # Links to previous and next page
1480 # Links to previous and next page
1481 if self.page > self.first_page:
1481 if self.page > self.first_page:
1482 self.previous_page = self.page - 1
1482 self.previous_page = self.page - 1
1483 else:
1483 else:
1484 self.previous_page = None
1484 self.previous_page = None
1485
1485
1486 if self.page < self.last_page:
1486 if self.page < self.last_page:
1487 self.next_page = self.page + 1
1487 self.next_page = self.page + 1
1488 else:
1488 else:
1489 self.next_page = None
1489 self.next_page = None
1490
1490
1491 # No items available
1491 # No items available
1492 else:
1492 else:
1493 self.first_page = None
1493 self.first_page = None
1494 self.page_count = 0
1494 self.page_count = 0
1495 self.last_page = None
1495 self.last_page = None
1496 self.first_item = None
1496 self.first_item = None
1497 self.last_item = None
1497 self.last_item = None
1498 self.previous_page = None
1498 self.previous_page = None
1499 self.next_page = None
1499 self.next_page = None
1500 self.items = []
1500 self.items = []
1501
1501
1502 # This is a subclass of the 'list' type. Initialise the list now.
1502 # This is a subclass of the 'list' type. Initialise the list now.
1503 list.__init__(self, reversed(self.items))
1503 list.__init__(self, reversed(self.items))
1504
1504
1505
1505
1506 def breadcrumb_repo_link(repo):
1506 def breadcrumb_repo_link(repo):
1507 """
1507 """
1508 Makes a breadcrumbs path link to repo
1508 Makes a breadcrumbs path link to repo
1509
1509
1510 ex::
1510 ex::
1511 group >> subgroup >> repo
1511 group >> subgroup >> repo
1512
1512
1513 :param repo: a Repository instance
1513 :param repo: a Repository instance
1514 """
1514 """
1515
1515
1516 path = [
1516 path = [
1517 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1517 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1518 title='last change:{}'.format(format_date(group.last_commit_change)))
1518 title='last change:{}'.format(format_date(group.last_commit_change)))
1519 for group in repo.groups_with_parents
1519 for group in repo.groups_with_parents
1520 ] + [
1520 ] + [
1521 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1521 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1522 title='last change:{}'.format(format_date(repo.last_commit_change)))
1522 title='last change:{}'.format(format_date(repo.last_commit_change)))
1523 ]
1523 ]
1524
1524
1525 return literal(' &raquo; '.join(path))
1525 return literal(' &raquo; '.join(path))
1526
1526
1527
1527
1528 def breadcrumb_repo_group_link(repo_group):
1528 def breadcrumb_repo_group_link(repo_group):
1529 """
1529 """
1530 Makes a breadcrumbs path link to repo
1530 Makes a breadcrumbs path link to repo
1531
1531
1532 ex::
1532 ex::
1533 group >> subgroup
1533 group >> subgroup
1534
1534
1535 :param repo_group: a Repository Group instance
1535 :param repo_group: a Repository Group instance
1536 """
1536 """
1537
1537
1538 path = [
1538 path = [
1539 link_to(group.name,
1539 link_to(group.name,
1540 route_path('repo_group_home', repo_group_name=group.group_name),
1540 route_path('repo_group_home', repo_group_name=group.group_name),
1541 title='last change:{}'.format(format_date(group.last_commit_change)))
1541 title='last change:{}'.format(format_date(group.last_commit_change)))
1542 for group in repo_group.parents
1542 for group in repo_group.parents
1543 ] + [
1543 ] + [
1544 link_to(repo_group.name,
1544 link_to(repo_group.name,
1545 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1545 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1546 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1546 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1547 ]
1547 ]
1548
1548
1549 return literal(' &raquo; '.join(path))
1549 return literal(' &raquo; '.join(path))
1550
1550
1551
1551
1552 def format_byte_size_binary(file_size):
1552 def format_byte_size_binary(file_size):
1553 """
1553 """
1554 Formats file/folder sizes to standard.
1554 Formats file/folder sizes to standard.
1555 """
1555 """
1556 if file_size is None:
1556 if file_size is None:
1557 file_size = 0
1557 file_size = 0
1558
1558
1559 formatted_size = format_byte_size(file_size, binary=True)
1559 formatted_size = format_byte_size(file_size, binary=True)
1560 return formatted_size
1560 return formatted_size
1561
1561
1562
1562
1563 def urlify_text(text_, safe=True):
1563 def urlify_text(text_, safe=True):
1564 """
1564 """
1565 Extrac urls from text and make html links out of them
1565 Extrac urls from text and make html links out of them
1566
1566
1567 :param text_:
1567 :param text_:
1568 """
1568 """
1569
1569
1570 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1570 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1571 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1571 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1572
1572
1573 def url_func(match_obj):
1573 def url_func(match_obj):
1574 url_full = match_obj.groups()[0]
1574 url_full = match_obj.groups()[0]
1575 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1575 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1576 _newtext = url_pat.sub(url_func, text_)
1576 _newtext = url_pat.sub(url_func, text_)
1577 if safe:
1577 if safe:
1578 return literal(_newtext)
1578 return literal(_newtext)
1579 return _newtext
1579 return _newtext
1580
1580
1581
1581
1582 def urlify_commits(text_, repository):
1582 def urlify_commits(text_, repository):
1583 """
1583 """
1584 Extract commit ids from text and make link from them
1584 Extract commit ids from text and make link from them
1585
1585
1586 :param text_:
1586 :param text_:
1587 :param repository: repo name to build the URL with
1587 :param repository: repo name to build the URL with
1588 """
1588 """
1589
1589
1590 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1590 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1591
1591
1592 def url_func(match_obj):
1592 def url_func(match_obj):
1593 commit_id = match_obj.groups()[1]
1593 commit_id = match_obj.groups()[1]
1594 pref = match_obj.groups()[0]
1594 pref = match_obj.groups()[0]
1595 suf = match_obj.groups()[2]
1595 suf = match_obj.groups()[2]
1596
1596
1597 tmpl = (
1597 tmpl = (
1598 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1598 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1599 '%(commit_id)s</a>%(suf)s'
1599 '%(commit_id)s</a>%(suf)s'
1600 )
1600 )
1601 return tmpl % {
1601 return tmpl % {
1602 'pref': pref,
1602 'pref': pref,
1603 'cls': 'revision-link',
1603 'cls': 'revision-link',
1604 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1604 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1605 'commit_id': commit_id,
1605 'commit_id': commit_id,
1606 'suf': suf
1606 'suf': suf
1607 }
1607 }
1608
1608
1609 newtext = URL_PAT.sub(url_func, text_)
1609 newtext = URL_PAT.sub(url_func, text_)
1610
1610
1611 return newtext
1611 return newtext
1612
1612
1613
1613
1614 def _process_url_func(match_obj, repo_name, uid, entry,
1614 def _process_url_func(match_obj, repo_name, uid, entry,
1615 return_raw_data=False, link_format='html'):
1615 return_raw_data=False, link_format='html'):
1616 pref = ''
1616 pref = ''
1617 if match_obj.group().startswith(' '):
1617 if match_obj.group().startswith(' '):
1618 pref = ' '
1618 pref = ' '
1619
1619
1620 issue_id = ''.join(match_obj.groups())
1620 issue_id = ''.join(match_obj.groups())
1621
1621
1622 if link_format == 'html':
1622 if link_format == 'html':
1623 tmpl = (
1623 tmpl = (
1624 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1624 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1625 '%(issue-prefix)s%(id-repr)s'
1625 '%(issue-prefix)s%(id-repr)s'
1626 '</a>')
1626 '</a>')
1627 elif link_format == 'rst':
1627 elif link_format == 'rst':
1628 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1628 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1629 elif link_format == 'markdown':
1629 elif link_format == 'markdown':
1630 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1630 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1631 else:
1631 else:
1632 raise ValueError('Bad link_format:{}'.format(link_format))
1632 raise ValueError('Bad link_format:{}'.format(link_format))
1633
1633
1634 (repo_name_cleaned,
1634 (repo_name_cleaned,
1635 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1635 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1636
1636
1637 # variables replacement
1637 # variables replacement
1638 named_vars = {
1638 named_vars = {
1639 'id': issue_id,
1639 'id': issue_id,
1640 'repo': repo_name,
1640 'repo': repo_name,
1641 'repo_name': repo_name_cleaned,
1641 'repo_name': repo_name_cleaned,
1642 'group_name': parent_group_name
1642 'group_name': parent_group_name
1643 }
1643 }
1644 # named regex variables
1644 # named regex variables
1645 named_vars.update(match_obj.groupdict())
1645 named_vars.update(match_obj.groupdict())
1646 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1646 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1647
1647
1648 def quote_cleaner(input_str):
1648 def quote_cleaner(input_str):
1649 """Remove quotes as it's HTML"""
1649 """Remove quotes as it's HTML"""
1650 return input_str.replace('"', '')
1650 return input_str.replace('"', '')
1651
1651
1652 data = {
1652 data = {
1653 'pref': pref,
1653 'pref': pref,
1654 'cls': quote_cleaner('issue-tracker-link'),
1654 'cls': quote_cleaner('issue-tracker-link'),
1655 'url': quote_cleaner(_url),
1655 'url': quote_cleaner(_url),
1656 'id-repr': issue_id,
1656 'id-repr': issue_id,
1657 'issue-prefix': entry['pref'],
1657 'issue-prefix': entry['pref'],
1658 'serv': entry['url'],
1658 'serv': entry['url'],
1659 }
1659 }
1660 if return_raw_data:
1660 if return_raw_data:
1661 return {
1661 return {
1662 'id': issue_id,
1662 'id': issue_id,
1663 'url': _url
1663 'url': _url
1664 }
1664 }
1665 return tmpl % data
1665 return tmpl % data
1666
1666
1667
1667
1668 def get_active_pattern_entries(repo_name):
1668 def get_active_pattern_entries(repo_name):
1669 repo = None
1669 repo = None
1670 if repo_name:
1670 if repo_name:
1671 # Retrieving repo_name to avoid invalid repo_name to explode on
1671 # Retrieving repo_name to avoid invalid repo_name to explode on
1672 # IssueTrackerSettingsModel but still passing invalid name further down
1672 # IssueTrackerSettingsModel but still passing invalid name further down
1673 repo = Repository.get_by_repo_name(repo_name, cache=True)
1673 repo = Repository.get_by_repo_name(repo_name, cache=True)
1674
1674
1675 settings_model = IssueTrackerSettingsModel(repo=repo)
1675 settings_model = IssueTrackerSettingsModel(repo=repo)
1676 active_entries = settings_model.get_settings(cache=True)
1676 active_entries = settings_model.get_settings(cache=True)
1677 return active_entries
1677 return active_entries
1678
1678
1679
1679
1680 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1680 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1681
1681
1682 allowed_formats = ['html', 'rst', 'markdown']
1682 allowed_formats = ['html', 'rst', 'markdown']
1683 if link_format not in allowed_formats:
1683 if link_format not in allowed_formats:
1684 raise ValueError('Link format can be only one of:{} got {}'.format(
1684 raise ValueError('Link format can be only one of:{} got {}'.format(
1685 allowed_formats, link_format))
1685 allowed_formats, link_format))
1686
1686
1687 active_entries = active_entries or get_active_pattern_entries(repo_name)
1687 active_entries = active_entries or get_active_pattern_entries(repo_name)
1688 issues_data = []
1688 issues_data = []
1689 newtext = text_string
1689 newtext = text_string
1690
1690
1691 for uid, entry in active_entries.items():
1691 for uid, entry in active_entries.items():
1692 log.debug('found issue tracker entry with uid %s', uid)
1692 log.debug('found issue tracker entry with uid %s', uid)
1693
1693
1694 if not (entry['pat'] and entry['url']):
1694 if not (entry['pat'] and entry['url']):
1695 log.debug('skipping due to missing data')
1695 log.debug('skipping due to missing data')
1696 continue
1696 continue
1697
1697
1698 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1698 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1699 uid, entry['pat'], entry['url'], entry['pref'])
1699 uid, entry['pat'], entry['url'], entry['pref'])
1700
1700
1701 try:
1701 try:
1702 pattern = re.compile(r'%s' % entry['pat'])
1702 pattern = re.compile(r'%s' % entry['pat'])
1703 except re.error:
1703 except re.error:
1704 log.exception(
1704 log.exception(
1705 'issue tracker pattern: `%s` failed to compile',
1705 'issue tracker pattern: `%s` failed to compile',
1706 entry['pat'])
1706 entry['pat'])
1707 continue
1707 continue
1708
1708
1709 data_func = partial(
1709 data_func = partial(
1710 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1710 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1711 return_raw_data=True)
1711 return_raw_data=True)
1712
1712
1713 for match_obj in pattern.finditer(text_string):
1713 for match_obj in pattern.finditer(text_string):
1714 issues_data.append(data_func(match_obj))
1714 issues_data.append(data_func(match_obj))
1715
1715
1716 url_func = partial(
1716 url_func = partial(
1717 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1717 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1718 link_format=link_format)
1718 link_format=link_format)
1719
1719
1720 newtext = pattern.sub(url_func, newtext)
1720 newtext = pattern.sub(url_func, newtext)
1721 log.debug('processed prefix:uid `%s`', uid)
1721 log.debug('processed prefix:uid `%s`', uid)
1722
1722
1723 return newtext, issues_data
1723 return newtext, issues_data
1724
1724
1725
1725
1726 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1726 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1727 """
1727 """
1728 Parses given text message and makes proper links.
1728 Parses given text message and makes proper links.
1729 issues are linked to given issue-server, and rest is a commit link
1729 issues are linked to given issue-server, and rest is a commit link
1730
1730
1731 :param commit_text:
1731 :param commit_text:
1732 :param repository:
1732 :param repository:
1733 """
1733 """
1734 def escaper(string):
1734 def escaper(string):
1735 return string.replace('<', '&lt;').replace('>', '&gt;')
1735 return string.replace('<', '&lt;').replace('>', '&gt;')
1736
1736
1737 newtext = escaper(commit_text)
1737 newtext = escaper(commit_text)
1738
1738
1739 # extract http/https links and make them real urls
1739 # extract http/https links and make them real urls
1740 newtext = urlify_text(newtext, safe=False)
1740 newtext = urlify_text(newtext, safe=False)
1741
1741
1742 # urlify commits - extract commit ids and make link out of them, if we have
1742 # urlify commits - extract commit ids and make link out of them, if we have
1743 # the scope of repository present.
1743 # the scope of repository present.
1744 if repository:
1744 if repository:
1745 newtext = urlify_commits(newtext, repository)
1745 newtext = urlify_commits(newtext, repository)
1746
1746
1747 # process issue tracker patterns
1747 # process issue tracker patterns
1748 newtext, issues = process_patterns(newtext, repository or '',
1748 newtext, issues = process_patterns(newtext, repository or '',
1749 active_entries=active_pattern_entries)
1749 active_entries=active_pattern_entries)
1750
1750
1751 return literal(newtext)
1751 return literal(newtext)
1752
1752
1753
1753
1754 def render_binary(repo_name, file_obj):
1754 def render_binary(repo_name, file_obj):
1755 """
1755 """
1756 Choose how to render a binary file
1756 Choose how to render a binary file
1757 """
1757 """
1758
1758
1759 filename = file_obj.name
1759 filename = file_obj.name
1760
1760
1761 # images
1761 # images
1762 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1762 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1763 if fnmatch.fnmatch(filename, pat=ext):
1763 if fnmatch.fnmatch(filename, pat=ext):
1764 alt = escape(filename)
1764 alt = escape(filename)
1765 src = route_path(
1765 src = route_path(
1766 'repo_file_raw', repo_name=repo_name,
1766 'repo_file_raw', repo_name=repo_name,
1767 commit_id=file_obj.commit.raw_id,
1767 commit_id=file_obj.commit.raw_id,
1768 f_path=file_obj.path)
1768 f_path=file_obj.path)
1769 return literal(
1769 return literal(
1770 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1770 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1771
1771
1772
1772
1773 def renderer_from_filename(filename, exclude=None):
1773 def renderer_from_filename(filename, exclude=None):
1774 """
1774 """
1775 choose a renderer based on filename, this works only for text based files
1775 choose a renderer based on filename, this works only for text based files
1776 """
1776 """
1777
1777
1778 # ipython
1778 # ipython
1779 for ext in ['*.ipynb']:
1779 for ext in ['*.ipynb']:
1780 if fnmatch.fnmatch(filename, pat=ext):
1780 if fnmatch.fnmatch(filename, pat=ext):
1781 return 'jupyter'
1781 return 'jupyter'
1782
1782
1783 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1783 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1784 if is_markup:
1784 if is_markup:
1785 return is_markup
1785 return is_markup
1786 return None
1786 return None
1787
1787
1788
1788
1789 def render(source, renderer='rst', mentions=False, relative_urls=None,
1789 def render(source, renderer='rst', mentions=False, relative_urls=None,
1790 repo_name=None):
1790 repo_name=None):
1791
1791
1792 def maybe_convert_relative_links(html_source):
1792 def maybe_convert_relative_links(html_source):
1793 if relative_urls:
1793 if relative_urls:
1794 return relative_links(html_source, relative_urls)
1794 return relative_links(html_source, relative_urls)
1795 return html_source
1795 return html_source
1796
1796
1797 if renderer == 'plain':
1797 if renderer == 'plain':
1798 return literal(
1798 return literal(
1799 MarkupRenderer.plain(source, leading_newline=False))
1799 MarkupRenderer.plain(source, leading_newline=False))
1800
1800
1801 elif renderer == 'rst':
1801 elif renderer == 'rst':
1802 if repo_name:
1802 if repo_name:
1803 # process patterns on comments if we pass in repo name
1803 # process patterns on comments if we pass in repo name
1804 source, issues = process_patterns(
1804 source, issues = process_patterns(
1805 source, repo_name, link_format='rst')
1805 source, repo_name, link_format='rst')
1806
1806
1807 return literal(
1807 return literal(
1808 '<div class="rst-block">%s</div>' %
1808 '<div class="rst-block">%s</div>' %
1809 maybe_convert_relative_links(
1809 maybe_convert_relative_links(
1810 MarkupRenderer.rst(source, mentions=mentions)))
1810 MarkupRenderer.rst(source, mentions=mentions)))
1811
1811
1812 elif renderer == 'markdown':
1812 elif renderer == 'markdown':
1813 if repo_name:
1813 if repo_name:
1814 # process patterns on comments if we pass in repo name
1814 # process patterns on comments if we pass in repo name
1815 source, issues = process_patterns(
1815 source, issues = process_patterns(
1816 source, repo_name, link_format='markdown')
1816 source, repo_name, link_format='markdown')
1817
1817
1818 return literal(
1818 return literal(
1819 '<div class="markdown-block">%s</div>' %
1819 '<div class="markdown-block">%s</div>' %
1820 maybe_convert_relative_links(
1820 maybe_convert_relative_links(
1821 MarkupRenderer.markdown(source, flavored=True,
1821 MarkupRenderer.markdown(source, flavored=True,
1822 mentions=mentions)))
1822 mentions=mentions)))
1823
1823
1824 elif renderer == 'jupyter':
1824 elif renderer == 'jupyter':
1825 return literal(
1825 return literal(
1826 '<div class="ipynb">%s</div>' %
1826 '<div class="ipynb">%s</div>' %
1827 maybe_convert_relative_links(
1827 maybe_convert_relative_links(
1828 MarkupRenderer.jupyter(source)))
1828 MarkupRenderer.jupyter(source)))
1829
1829
1830 # None means just show the file-source
1830 # None means just show the file-source
1831 return None
1831 return None
1832
1832
1833
1833
1834 def commit_status(repo, commit_id):
1834 def commit_status(repo, commit_id):
1835 return ChangesetStatusModel().get_status(repo, commit_id)
1835 return ChangesetStatusModel().get_status(repo, commit_id)
1836
1836
1837
1837
1838 def commit_status_lbl(commit_status):
1838 def commit_status_lbl(commit_status):
1839 return dict(ChangesetStatus.STATUSES).get(commit_status)
1839 return dict(ChangesetStatus.STATUSES).get(commit_status)
1840
1840
1841
1841
1842 def commit_time(repo_name, commit_id):
1842 def commit_time(repo_name, commit_id):
1843 repo = Repository.get_by_repo_name(repo_name)
1843 repo = Repository.get_by_repo_name(repo_name)
1844 commit = repo.get_commit(commit_id=commit_id)
1844 commit = repo.get_commit(commit_id=commit_id)
1845 return commit.date
1845 return commit.date
1846
1846
1847
1847
1848 def get_permission_name(key):
1848 def get_permission_name(key):
1849 return dict(Permission.PERMS).get(key)
1849 return dict(Permission.PERMS).get(key)
1850
1850
1851
1851
1852 def journal_filter_help(request):
1852 def journal_filter_help(request):
1853 _ = request.translate
1853 _ = request.translate
1854 from rhodecode.lib.audit_logger import ACTIONS
1854 from rhodecode.lib.audit_logger import ACTIONS
1855 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1855 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1856
1856
1857 return _(
1857 return _(
1858 'Example filter terms:\n' +
1858 'Example filter terms:\n' +
1859 ' repository:vcs\n' +
1859 ' repository:vcs\n' +
1860 ' username:marcin\n' +
1860 ' username:marcin\n' +
1861 ' username:(NOT marcin)\n' +
1861 ' username:(NOT marcin)\n' +
1862 ' action:*push*\n' +
1862 ' action:*push*\n' +
1863 ' ip:127.0.0.1\n' +
1863 ' ip:127.0.0.1\n' +
1864 ' date:20120101\n' +
1864 ' date:20120101\n' +
1865 ' date:[20120101100000 TO 20120102]\n' +
1865 ' date:[20120101100000 TO 20120102]\n' +
1866 '\n' +
1866 '\n' +
1867 'Actions: {actions}\n' +
1867 'Actions: {actions}\n' +
1868 '\n' +
1868 '\n' +
1869 'Generate wildcards using \'*\' character:\n' +
1869 'Generate wildcards using \'*\' character:\n' +
1870 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1870 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1871 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1871 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1872 '\n' +
1872 '\n' +
1873 'Optional AND / OR operators in queries\n' +
1873 'Optional AND / OR operators in queries\n' +
1874 ' "repository:vcs OR repository:test"\n' +
1874 ' "repository:vcs OR repository:test"\n' +
1875 ' "username:test AND repository:test*"\n'
1875 ' "username:test AND repository:test*"\n'
1876 ).format(actions=actions)
1876 ).format(actions=actions)
1877
1877
1878
1878
1879 def not_mapped_error(repo_name):
1879 def not_mapped_error(repo_name):
1880 from rhodecode.translation import _
1880 from rhodecode.translation import _
1881 flash(_('%s repository is not mapped to db perhaps'
1881 flash(_('%s repository is not mapped to db perhaps'
1882 ' it was created or renamed from the filesystem'
1882 ' it was created or renamed from the filesystem'
1883 ' please run the application again'
1883 ' please run the application again'
1884 ' in order to rescan repositories') % repo_name, category='error')
1884 ' in order to rescan repositories') % repo_name, category='error')
1885
1885
1886
1886
1887 def ip_range(ip_addr):
1887 def ip_range(ip_addr):
1888 from rhodecode.model.db import UserIpMap
1888 from rhodecode.model.db import UserIpMap
1889 s, e = UserIpMap._get_ip_range(ip_addr)
1889 s, e = UserIpMap._get_ip_range(ip_addr)
1890 return '%s - %s' % (s, e)
1890 return '%s - %s' % (s, e)
1891
1891
1892
1892
1893 def form(url, method='post', needs_csrf_token=True, **attrs):
1893 def form(url, method='post', needs_csrf_token=True, **attrs):
1894 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1894 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1895 if method.lower() != 'get' and needs_csrf_token:
1895 if method.lower() != 'get' and needs_csrf_token:
1896 raise Exception(
1896 raise Exception(
1897 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1897 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1898 'CSRF token. If the endpoint does not require such token you can ' +
1898 'CSRF token. If the endpoint does not require such token you can ' +
1899 'explicitly set the parameter needs_csrf_token to false.')
1899 'explicitly set the parameter needs_csrf_token to false.')
1900
1900
1901 return wh_form(url, method=method, **attrs)
1901 return wh_form(url, method=method, **attrs)
1902
1902
1903
1903
1904 def secure_form(form_url, method="POST", multipart=False, **attrs):
1904 def secure_form(form_url, method="POST", multipart=False, **attrs):
1905 """Start a form tag that points the action to an url. This
1905 """Start a form tag that points the action to an url. This
1906 form tag will also include the hidden field containing
1906 form tag will also include the hidden field containing
1907 the auth token.
1907 the auth token.
1908
1908
1909 The url options should be given either as a string, or as a
1909 The url options should be given either as a string, or as a
1910 ``url()`` function. The method for the form defaults to POST.
1910 ``url()`` function. The method for the form defaults to POST.
1911
1911
1912 Options:
1912 Options:
1913
1913
1914 ``multipart``
1914 ``multipart``
1915 If set to True, the enctype is set to "multipart/form-data".
1915 If set to True, the enctype is set to "multipart/form-data".
1916 ``method``
1916 ``method``
1917 The method to use when submitting the form, usually either
1917 The method to use when submitting the form, usually either
1918 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1918 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1919 hidden input with name _method is added to simulate the verb
1919 hidden input with name _method is added to simulate the verb
1920 over POST.
1920 over POST.
1921
1921
1922 """
1922 """
1923 from webhelpers.pylonslib.secure_form import insecure_form
1923 from webhelpers.pylonslib.secure_form import insecure_form
1924
1924
1925 if 'request' in attrs:
1925 if 'request' in attrs:
1926 session = attrs['request'].session
1926 session = attrs['request'].session
1927 del attrs['request']
1927 del attrs['request']
1928 else:
1928 else:
1929 raise ValueError(
1929 raise ValueError(
1930 'Calling this form requires request= to be passed as argument')
1930 'Calling this form requires request= to be passed as argument')
1931
1931
1932 form = insecure_form(form_url, method, multipart, **attrs)
1932 form = insecure_form(form_url, method, multipart, **attrs)
1933 token = literal(
1933 token = literal(
1934 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1934 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1935 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1935 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1936
1936
1937 return literal("%s\n%s" % (form, token))
1937 return literal("%s\n%s" % (form, token))
1938
1938
1939
1939
1940 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1940 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1941 select_html = select(name, selected, options, **attrs)
1941 select_html = select(name, selected, options, **attrs)
1942
1942 select2 = """
1943 select2 = """
1943 <script>
1944 <script>
1944 $(document).ready(function() {
1945 $(document).ready(function() {
1945 $('#%s').select2({
1946 $('#%s').select2({
1946 containerCssClass: 'drop-menu',
1947 containerCssClass: 'drop-menu %s',
1947 dropdownCssClass: 'drop-menu-dropdown',
1948 dropdownCssClass: 'drop-menu-dropdown',
1948 dropdownAutoWidth: true%s
1949 dropdownAutoWidth: true%s
1949 });
1950 });
1950 });
1951 });
1951 </script>
1952 </script>
1952 """
1953 """
1954
1953 filter_option = """,
1955 filter_option = """,
1954 minimumResultsForSearch: -1
1956 minimumResultsForSearch: -1
1955 """
1957 """
1956 input_id = attrs.get('id') or name
1958 input_id = attrs.get('id') or name
1959 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1957 filter_enabled = "" if enable_filter else filter_option
1960 filter_enabled = "" if enable_filter else filter_option
1958 select_script = literal(select2 % (input_id, filter_enabled))
1961 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1959
1962
1960 return literal(select_html+select_script)
1963 return literal(select_html+select_script)
1961
1964
1962
1965
1963 def get_visual_attr(tmpl_context_var, attr_name):
1966 def get_visual_attr(tmpl_context_var, attr_name):
1964 """
1967 """
1965 A safe way to get a variable from visual variable of template context
1968 A safe way to get a variable from visual variable of template context
1966
1969
1967 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1970 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1968 :param attr_name: name of the attribute we fetch from the c.visual
1971 :param attr_name: name of the attribute we fetch from the c.visual
1969 """
1972 """
1970 visual = getattr(tmpl_context_var, 'visual', None)
1973 visual = getattr(tmpl_context_var, 'visual', None)
1971 if not visual:
1974 if not visual:
1972 return
1975 return
1973 else:
1976 else:
1974 return getattr(visual, attr_name, None)
1977 return getattr(visual, attr_name, None)
1975
1978
1976
1979
1977 def get_last_path_part(file_node):
1980 def get_last_path_part(file_node):
1978 if not file_node.path:
1981 if not file_node.path:
1979 return u'/'
1982 return u'/'
1980
1983
1981 path = safe_unicode(file_node.path.split('/')[-1])
1984 path = safe_unicode(file_node.path.split('/')[-1])
1982 return u'../' + path
1985 return u'../' + path
1983
1986
1984
1987
1985 def route_url(*args, **kwargs):
1988 def route_url(*args, **kwargs):
1986 """
1989 """
1987 Wrapper around pyramids `route_url` (fully qualified url) function.
1990 Wrapper around pyramids `route_url` (fully qualified url) function.
1988 """
1991 """
1989 req = get_current_request()
1992 req = get_current_request()
1990 return req.route_url(*args, **kwargs)
1993 return req.route_url(*args, **kwargs)
1991
1994
1992
1995
1993 def route_path(*args, **kwargs):
1996 def route_path(*args, **kwargs):
1994 """
1997 """
1995 Wrapper around pyramids `route_path` function.
1998 Wrapper around pyramids `route_path` function.
1996 """
1999 """
1997 req = get_current_request()
2000 req = get_current_request()
1998 return req.route_path(*args, **kwargs)
2001 return req.route_path(*args, **kwargs)
1999
2002
2000
2003
2001 def route_path_or_none(*args, **kwargs):
2004 def route_path_or_none(*args, **kwargs):
2002 try:
2005 try:
2003 return route_path(*args, **kwargs)
2006 return route_path(*args, **kwargs)
2004 except KeyError:
2007 except KeyError:
2005 return None
2008 return None
2006
2009
2007
2010
2008 def current_route_path(request, **kw):
2011 def current_route_path(request, **kw):
2009 new_args = request.GET.mixed()
2012 new_args = request.GET.mixed()
2010 new_args.update(kw)
2013 new_args.update(kw)
2011 return request.current_route_path(_query=new_args)
2014 return request.current_route_path(_query=new_args)
2012
2015
2013
2016
2014 def api_call_example(method, args):
2017 def api_call_example(method, args):
2015 """
2018 """
2016 Generates an API call example via CURL
2019 Generates an API call example via CURL
2017 """
2020 """
2018 args_json = json.dumps(OrderedDict([
2021 args_json = json.dumps(OrderedDict([
2019 ('id', 1),
2022 ('id', 1),
2020 ('auth_token', 'SECRET'),
2023 ('auth_token', 'SECRET'),
2021 ('method', method),
2024 ('method', method),
2022 ('args', args)
2025 ('args', args)
2023 ]))
2026 ]))
2024 return literal(
2027 return literal(
2025 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2028 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2026 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2029 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2027 "and needs to be of `api calls` role."
2030 "and needs to be of `api calls` role."
2028 .format(
2031 .format(
2029 api_url=route_url('apiv2'),
2032 api_url=route_url('apiv2'),
2030 token_url=route_url('my_account_auth_tokens'),
2033 token_url=route_url('my_account_auth_tokens'),
2031 data=args_json))
2034 data=args_json))
2032
2035
2033
2036
2034 def notification_description(notification, request):
2037 def notification_description(notification, request):
2035 """
2038 """
2036 Generate notification human readable description based on notification type
2039 Generate notification human readable description based on notification type
2037 """
2040 """
2038 from rhodecode.model.notification import NotificationModel
2041 from rhodecode.model.notification import NotificationModel
2039 return NotificationModel().make_description(
2042 return NotificationModel().make_description(
2040 notification, translate=request.translate)
2043 notification, translate=request.translate)
2041
2044
2042
2045
2043 def go_import_header(request, db_repo=None):
2046 def go_import_header(request, db_repo=None):
2044 """
2047 """
2045 Creates a header for go-import functionality in Go Lang
2048 Creates a header for go-import functionality in Go Lang
2046 """
2049 """
2047
2050
2048 if not db_repo:
2051 if not db_repo:
2049 return
2052 return
2050 if 'go-get' not in request.GET:
2053 if 'go-get' not in request.GET:
2051 return
2054 return
2052
2055
2053 clone_url = db_repo.clone_url()
2056 clone_url = db_repo.clone_url()
2054 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2057 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2055 # we have a repo and go-get flag,
2058 # we have a repo and go-get flag,
2056 return literal('<meta name="go-import" content="{} {} {}">'.format(
2059 return literal('<meta name="go-import" content="{} {} {}">'.format(
2057 prefix, db_repo.repo_type, clone_url))
2060 prefix, db_repo.repo_type, clone_url))
2058
2061
2059
2062
2060 def reviewer_as_json(*args, **kwargs):
2063 def reviewer_as_json(*args, **kwargs):
2061 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2064 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2062 return _reviewer_as_json(*args, **kwargs)
2065 return _reviewer_as_json(*args, **kwargs)
2063
2066
2064
2067
2065 def get_repo_view_type(request):
2068 def get_repo_view_type(request):
2066 route_name = request.matched_route.name
2069 route_name = request.matched_route.name
2067 route_to_view_type = {
2070 route_to_view_type = {
2068 'repo_changelog': 'commits',
2071 'repo_changelog': 'commits',
2069 'repo_commits': 'commits',
2072 'repo_commits': 'commits',
2070 'repo_files': 'files',
2073 'repo_files': 'files',
2071 'repo_summary': 'summary',
2074 'repo_summary': 'summary',
2072 'repo_commit': 'commit'
2075 'repo_commit': 'commit'
2073 }
2076 }
2074
2077
2075 return route_to_view_type.get(route_name)
2078 return route_to_view_type.get(route_name)
@@ -1,939 +1,941 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Scm model for RhodeCode
22 Scm model for RhodeCode
23 """
23 """
24
24
25 import os.path
25 import os.path
26 import traceback
26 import traceback
27 import logging
27 import logging
28 import cStringIO
28 import cStringIO
29
29
30 from sqlalchemy import func
30 from sqlalchemy import func
31 from zope.cachedescriptors.property import Lazy as LazyProperty
31 from zope.cachedescriptors.property import Lazy as LazyProperty
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib.vcs import get_backend
34 from rhodecode.lib.vcs import get_backend
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 from rhodecode.lib import helpers as h, rc_cache
38 from rhodecode.lib import helpers as h, rc_cache
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
41 HasUserGroupPermissionAny)
41 HasUserGroupPermissionAny)
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
43 from rhodecode.lib import hooks_utils
43 from rhodecode.lib import hooks_utils
44 from rhodecode.lib.utils import (
44 from rhodecode.lib.utils import (
45 get_filesystem_repos, make_db_config)
45 get_filesystem_repos, make_db_config)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
47 from rhodecode.lib.system_info import get_system_info
47 from rhodecode.lib.system_info import get_system_info
48 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
50 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
51 PullRequest)
51 PullRequest)
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
53 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
64
65
65
66 class RepoTemp(object):
66 class RepoTemp(object):
67 def __init__(self, repo_id):
67 def __init__(self, repo_id):
68 self.repo_id = repo_id
68 self.repo_id = repo_id
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
72
73
73
74 class SimpleCachedRepoList(object):
74 class SimpleCachedRepoList(object):
75 """
75 """
76 Lighter version of of iteration of repos without the scm initialisation,
76 Lighter version of of iteration of repos without the scm initialisation,
77 and with cache usage
77 and with cache usage
78 """
78 """
79 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
79 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
80 self.db_repo_list = db_repo_list
80 self.db_repo_list = db_repo_list
81 self.repos_path = repos_path
81 self.repos_path = repos_path
82 self.order_by = order_by
82 self.order_by = order_by
83 self.reversed = (order_by or '').startswith('-')
83 self.reversed = (order_by or '').startswith('-')
84 if not perm_set:
84 if not perm_set:
85 perm_set = ['repository.read', 'repository.write',
85 perm_set = ['repository.read', 'repository.write',
86 'repository.admin']
86 'repository.admin']
87 self.perm_set = perm_set
87 self.perm_set = perm_set
88
88
89 def __len__(self):
89 def __len__(self):
90 return len(self.db_repo_list)
90 return len(self.db_repo_list)
91
91
92 def __repr__(self):
92 def __repr__(self):
93 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
93 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
94
94
95 def __iter__(self):
95 def __iter__(self):
96 for dbr in self.db_repo_list:
96 for dbr in self.db_repo_list:
97 # check permission at this level
97 # check permission at this level
98 has_perm = HasRepoPermissionAny(*self.perm_set)(
98 has_perm = HasRepoPermissionAny(*self.perm_set)(
99 dbr.repo_name, 'SimpleCachedRepoList check')
99 dbr.repo_name, 'SimpleCachedRepoList check')
100 if not has_perm:
100 if not has_perm:
101 continue
101 continue
102
102
103 tmp_d = {
103 tmp_d = {
104 'name': dbr.repo_name,
104 'name': dbr.repo_name,
105 'dbrepo': dbr.get_dict(),
105 'dbrepo': dbr.get_dict(),
106 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
106 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
107 }
107 }
108 yield tmp_d
108 yield tmp_d
109
109
110
110
111 class _PermCheckIterator(object):
111 class _PermCheckIterator(object):
112
112
113 def __init__(
113 def __init__(
114 self, obj_list, obj_attr, perm_set, perm_checker,
114 self, obj_list, obj_attr, perm_set, perm_checker,
115 extra_kwargs=None):
115 extra_kwargs=None):
116 """
116 """
117 Creates iterator from given list of objects, additionally
117 Creates iterator from given list of objects, additionally
118 checking permission for them from perm_set var
118 checking permission for them from perm_set var
119
119
120 :param obj_list: list of db objects
120 :param obj_list: list of db objects
121 :param obj_attr: attribute of object to pass into perm_checker
121 :param obj_attr: attribute of object to pass into perm_checker
122 :param perm_set: list of permissions to check
122 :param perm_set: list of permissions to check
123 :param perm_checker: callable to check permissions against
123 :param perm_checker: callable to check permissions against
124 """
124 """
125 self.obj_list = obj_list
125 self.obj_list = obj_list
126 self.obj_attr = obj_attr
126 self.obj_attr = obj_attr
127 self.perm_set = perm_set
127 self.perm_set = perm_set
128 self.perm_checker = perm_checker
128 self.perm_checker = perm_checker
129 self.extra_kwargs = extra_kwargs or {}
129 self.extra_kwargs = extra_kwargs or {}
130
130
131 def __len__(self):
131 def __len__(self):
132 return len(self.obj_list)
132 return len(self.obj_list)
133
133
134 def __repr__(self):
134 def __repr__(self):
135 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
135 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
136
136
137 def __iter__(self):
137 def __iter__(self):
138 checker = self.perm_checker(*self.perm_set)
138 checker = self.perm_checker(*self.perm_set)
139 for db_obj in self.obj_list:
139 for db_obj in self.obj_list:
140 # check permission at this level
140 # check permission at this level
141 name = getattr(db_obj, self.obj_attr, None)
141 name = getattr(db_obj, self.obj_attr, None)
142 if not checker(name, self.__class__.__name__, **self.extra_kwargs):
142 if not checker(name, self.__class__.__name__, **self.extra_kwargs):
143 continue
143 continue
144
144
145 yield db_obj
145 yield db_obj
146
146
147
147
148 class RepoList(_PermCheckIterator):
148 class RepoList(_PermCheckIterator):
149
149
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
151 if not perm_set:
151 if not perm_set:
152 perm_set = [
152 perm_set = [
153 'repository.read', 'repository.write', 'repository.admin']
153 'repository.read', 'repository.write', 'repository.admin']
154
154
155 super(RepoList, self).__init__(
155 super(RepoList, self).__init__(
156 obj_list=db_repo_list,
156 obj_list=db_repo_list,
157 obj_attr='repo_name', perm_set=perm_set,
157 obj_attr='repo_name', perm_set=perm_set,
158 perm_checker=HasRepoPermissionAny,
158 perm_checker=HasRepoPermissionAny,
159 extra_kwargs=extra_kwargs)
159 extra_kwargs=extra_kwargs)
160
160
161
161
162 class RepoGroupList(_PermCheckIterator):
162 class RepoGroupList(_PermCheckIterator):
163
163
164 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
164 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
165 if not perm_set:
165 if not perm_set:
166 perm_set = ['group.read', 'group.write', 'group.admin']
166 perm_set = ['group.read', 'group.write', 'group.admin']
167
167
168 super(RepoGroupList, self).__init__(
168 super(RepoGroupList, self).__init__(
169 obj_list=db_repo_group_list,
169 obj_list=db_repo_group_list,
170 obj_attr='group_name', perm_set=perm_set,
170 obj_attr='group_name', perm_set=perm_set,
171 perm_checker=HasRepoGroupPermissionAny,
171 perm_checker=HasRepoGroupPermissionAny,
172 extra_kwargs=extra_kwargs)
172 extra_kwargs=extra_kwargs)
173
173
174
174
175 class UserGroupList(_PermCheckIterator):
175 class UserGroupList(_PermCheckIterator):
176
176
177 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
177 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
178 if not perm_set:
178 if not perm_set:
179 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
179 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
180
180
181 super(UserGroupList, self).__init__(
181 super(UserGroupList, self).__init__(
182 obj_list=db_user_group_list,
182 obj_list=db_user_group_list,
183 obj_attr='users_group_name', perm_set=perm_set,
183 obj_attr='users_group_name', perm_set=perm_set,
184 perm_checker=HasUserGroupPermissionAny,
184 perm_checker=HasUserGroupPermissionAny,
185 extra_kwargs=extra_kwargs)
185 extra_kwargs=extra_kwargs)
186
186
187
187
188 class ScmModel(BaseModel):
188 class ScmModel(BaseModel):
189 """
189 """
190 Generic Scm Model
190 Generic Scm Model
191 """
191 """
192
192
193 @LazyProperty
193 @LazyProperty
194 def repos_path(self):
194 def repos_path(self):
195 """
195 """
196 Gets the repositories root path from database
196 Gets the repositories root path from database
197 """
197 """
198
198
199 settings_model = VcsSettingsModel(sa=self.sa)
199 settings_model = VcsSettingsModel(sa=self.sa)
200 return settings_model.get_repos_location()
200 return settings_model.get_repos_location()
201
201
202 def repo_scan(self, repos_path=None):
202 def repo_scan(self, repos_path=None):
203 """
203 """
204 Listing of repositories in given path. This path should not be a
204 Listing of repositories in given path. This path should not be a
205 repository itself. Return a dictionary of repository objects
205 repository itself. Return a dictionary of repository objects
206
206
207 :param repos_path: path to directory containing repositories
207 :param repos_path: path to directory containing repositories
208 """
208 """
209
209
210 if repos_path is None:
210 if repos_path is None:
211 repos_path = self.repos_path
211 repos_path = self.repos_path
212
212
213 log.info('scanning for repositories in %s', repos_path)
213 log.info('scanning for repositories in %s', repos_path)
214
214
215 config = make_db_config()
215 config = make_db_config()
216 config.set('extensions', 'largefiles', '')
216 config.set('extensions', 'largefiles', '')
217 repos = {}
217 repos = {}
218
218
219 for name, path in get_filesystem_repos(repos_path, recursive=True):
219 for name, path in get_filesystem_repos(repos_path, recursive=True):
220 # name need to be decomposed and put back together using the /
220 # name need to be decomposed and put back together using the /
221 # since this is internal storage separator for rhodecode
221 # since this is internal storage separator for rhodecode
222 name = Repository.normalize_repo_name(name)
222 name = Repository.normalize_repo_name(name)
223
223
224 try:
224 try:
225 if name in repos:
225 if name in repos:
226 raise RepositoryError('Duplicate repository name %s '
226 raise RepositoryError('Duplicate repository name %s '
227 'found in %s' % (name, path))
227 'found in %s' % (name, path))
228 elif path[0] in rhodecode.BACKENDS:
228 elif path[0] in rhodecode.BACKENDS:
229 klass = get_backend(path[0])
229 klass = get_backend(path[0])
230 repos[name] = klass(path[1], config=config)
230 repos[name] = klass(path[1], config=config)
231 except OSError:
231 except OSError:
232 continue
232 continue
233 log.debug('found %s paths with repositories', len(repos))
233 log.debug('found %s paths with repositories', len(repos))
234 return repos
234 return repos
235
235
236 def get_repos(self, all_repos=None, sort_key=None):
236 def get_repos(self, all_repos=None, sort_key=None):
237 """
237 """
238 Get all repositories from db and for each repo create it's
238 Get all repositories from db and for each repo create it's
239 backend instance and fill that backed with information from database
239 backend instance and fill that backed with information from database
240
240
241 :param all_repos: list of repository names as strings
241 :param all_repos: list of repository names as strings
242 give specific repositories list, good for filtering
242 give specific repositories list, good for filtering
243
243
244 :param sort_key: initial sorting of repositories
244 :param sort_key: initial sorting of repositories
245 """
245 """
246 if all_repos is None:
246 if all_repos is None:
247 all_repos = self.sa.query(Repository)\
247 all_repos = self.sa.query(Repository)\
248 .filter(Repository.group_id == None)\
248 .filter(Repository.group_id == None)\
249 .order_by(func.lower(Repository.repo_name)).all()
249 .order_by(func.lower(Repository.repo_name)).all()
250 repo_iter = SimpleCachedRepoList(
250 repo_iter = SimpleCachedRepoList(
251 all_repos, repos_path=self.repos_path, order_by=sort_key)
251 all_repos, repos_path=self.repos_path, order_by=sort_key)
252 return repo_iter
252 return repo_iter
253
253
254 def get_repo_groups(self, all_groups=None):
254 def get_repo_groups(self, all_groups=None):
255 if all_groups is None:
255 if all_groups is None:
256 all_groups = RepoGroup.query()\
256 all_groups = RepoGroup.query()\
257 .filter(RepoGroup.group_parent_id == None).all()
257 .filter(RepoGroup.group_parent_id == None).all()
258 return [x for x in RepoGroupList(all_groups)]
258 return [x for x in RepoGroupList(all_groups)]
259
259
260 def mark_for_invalidation(self, repo_name, delete=False):
260 def mark_for_invalidation(self, repo_name, delete=False):
261 """
261 """
262 Mark caches of this repo invalid in the database. `delete` flag
262 Mark caches of this repo invalid in the database. `delete` flag
263 removes the cache entries
263 removes the cache entries
264
264
265 :param repo_name: the repo_name for which caches should be marked
265 :param repo_name: the repo_name for which caches should be marked
266 invalid, or deleted
266 invalid, or deleted
267 :param delete: delete the entry keys instead of setting bool
267 :param delete: delete the entry keys instead of setting bool
268 flag on them, and also purge caches used by the dogpile
268 flag on them, and also purge caches used by the dogpile
269 """
269 """
270 repo = Repository.get_by_repo_name(repo_name)
270 repo = Repository.get_by_repo_name(repo_name)
271
271
272 if repo:
272 if repo:
273 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
273 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
274 repo_id=repo.repo_id)
274 repo_id=repo.repo_id)
275 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
275 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
276
276
277 repo_id = repo.repo_id
277 repo_id = repo.repo_id
278 config = repo._config
278 config = repo._config
279 config.set('extensions', 'largefiles', '')
279 config.set('extensions', 'largefiles', '')
280 repo.update_commit_cache(config=config, cs_cache=None)
280 repo.update_commit_cache(config=config, cs_cache=None)
281 if delete:
281 if delete:
282 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
282 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
283 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
283 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
284
284
285 def toggle_following_repo(self, follow_repo_id, user_id):
285 def toggle_following_repo(self, follow_repo_id, user_id):
286
286
287 f = self.sa.query(UserFollowing)\
287 f = self.sa.query(UserFollowing)\
288 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
288 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
289 .filter(UserFollowing.user_id == user_id).scalar()
289 .filter(UserFollowing.user_id == user_id).scalar()
290
290
291 if f is not None:
291 if f is not None:
292 try:
292 try:
293 self.sa.delete(f)
293 self.sa.delete(f)
294 return
294 return
295 except Exception:
295 except Exception:
296 log.error(traceback.format_exc())
296 log.error(traceback.format_exc())
297 raise
297 raise
298
298
299 try:
299 try:
300 f = UserFollowing()
300 f = UserFollowing()
301 f.user_id = user_id
301 f.user_id = user_id
302 f.follows_repo_id = follow_repo_id
302 f.follows_repo_id = follow_repo_id
303 self.sa.add(f)
303 self.sa.add(f)
304 except Exception:
304 except Exception:
305 log.error(traceback.format_exc())
305 log.error(traceback.format_exc())
306 raise
306 raise
307
307
308 def toggle_following_user(self, follow_user_id, user_id):
308 def toggle_following_user(self, follow_user_id, user_id):
309 f = self.sa.query(UserFollowing)\
309 f = self.sa.query(UserFollowing)\
310 .filter(UserFollowing.follows_user_id == follow_user_id)\
310 .filter(UserFollowing.follows_user_id == follow_user_id)\
311 .filter(UserFollowing.user_id == user_id).scalar()
311 .filter(UserFollowing.user_id == user_id).scalar()
312
312
313 if f is not None:
313 if f is not None:
314 try:
314 try:
315 self.sa.delete(f)
315 self.sa.delete(f)
316 return
316 return
317 except Exception:
317 except Exception:
318 log.error(traceback.format_exc())
318 log.error(traceback.format_exc())
319 raise
319 raise
320
320
321 try:
321 try:
322 f = UserFollowing()
322 f = UserFollowing()
323 f.user_id = user_id
323 f.user_id = user_id
324 f.follows_user_id = follow_user_id
324 f.follows_user_id = follow_user_id
325 self.sa.add(f)
325 self.sa.add(f)
326 except Exception:
326 except Exception:
327 log.error(traceback.format_exc())
327 log.error(traceback.format_exc())
328 raise
328 raise
329
329
330 def is_following_repo(self, repo_name, user_id, cache=False):
330 def is_following_repo(self, repo_name, user_id, cache=False):
331 r = self.sa.query(Repository)\
331 r = self.sa.query(Repository)\
332 .filter(Repository.repo_name == repo_name).scalar()
332 .filter(Repository.repo_name == repo_name).scalar()
333
333
334 f = self.sa.query(UserFollowing)\
334 f = self.sa.query(UserFollowing)\
335 .filter(UserFollowing.follows_repository == r)\
335 .filter(UserFollowing.follows_repository == r)\
336 .filter(UserFollowing.user_id == user_id).scalar()
336 .filter(UserFollowing.user_id == user_id).scalar()
337
337
338 return f is not None
338 return f is not None
339
339
340 def is_following_user(self, username, user_id, cache=False):
340 def is_following_user(self, username, user_id, cache=False):
341 u = User.get_by_username(username)
341 u = User.get_by_username(username)
342
342
343 f = self.sa.query(UserFollowing)\
343 f = self.sa.query(UserFollowing)\
344 .filter(UserFollowing.follows_user == u)\
344 .filter(UserFollowing.follows_user == u)\
345 .filter(UserFollowing.user_id == user_id).scalar()
345 .filter(UserFollowing.user_id == user_id).scalar()
346
346
347 return f is not None
347 return f is not None
348
348
349 def get_followers(self, repo):
349 def get_followers(self, repo):
350 repo = self._get_repo(repo)
350 repo = self._get_repo(repo)
351
351
352 return self.sa.query(UserFollowing)\
352 return self.sa.query(UserFollowing)\
353 .filter(UserFollowing.follows_repository == repo).count()
353 .filter(UserFollowing.follows_repository == repo).count()
354
354
355 def get_forks(self, repo):
355 def get_forks(self, repo):
356 repo = self._get_repo(repo)
356 repo = self._get_repo(repo)
357 return self.sa.query(Repository)\
357 return self.sa.query(Repository)\
358 .filter(Repository.fork == repo).count()
358 .filter(Repository.fork == repo).count()
359
359
360 def get_pull_requests(self, repo):
360 def get_pull_requests(self, repo):
361 repo = self._get_repo(repo)
361 repo = self._get_repo(repo)
362 return self.sa.query(PullRequest)\
362 return self.sa.query(PullRequest)\
363 .filter(PullRequest.target_repo == repo)\
363 .filter(PullRequest.target_repo == repo)\
364 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
364 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
365
365
366 def mark_as_fork(self, repo, fork, user):
366 def mark_as_fork(self, repo, fork, user):
367 repo = self._get_repo(repo)
367 repo = self._get_repo(repo)
368 fork = self._get_repo(fork)
368 fork = self._get_repo(fork)
369 if fork and repo.repo_id == fork.repo_id:
369 if fork and repo.repo_id == fork.repo_id:
370 raise Exception("Cannot set repository as fork of itself")
370 raise Exception("Cannot set repository as fork of itself")
371
371
372 if fork and repo.repo_type != fork.repo_type:
372 if fork and repo.repo_type != fork.repo_type:
373 raise RepositoryError(
373 raise RepositoryError(
374 "Cannot set repository as fork of repository with other type")
374 "Cannot set repository as fork of repository with other type")
375
375
376 repo.fork = fork
376 repo.fork = fork
377 self.sa.add(repo)
377 self.sa.add(repo)
378 return repo
378 return repo
379
379
380 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
380 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
381 dbrepo = self._get_repo(repo)
381 dbrepo = self._get_repo(repo)
382 remote_uri = remote_uri or dbrepo.clone_uri
382 remote_uri = remote_uri or dbrepo.clone_uri
383 if not remote_uri:
383 if not remote_uri:
384 raise Exception("This repository doesn't have a clone uri")
384 raise Exception("This repository doesn't have a clone uri")
385
385
386 repo = dbrepo.scm_instance(cache=False)
386 repo = dbrepo.scm_instance(cache=False)
387 repo.config.clear_section('hooks')
387 repo.config.clear_section('hooks')
388
388
389 try:
389 try:
390 # NOTE(marcink): add extra validation so we skip invalid urls
390 # NOTE(marcink): add extra validation so we skip invalid urls
391 # this is due this tasks can be executed via scheduler without
391 # this is due this tasks can be executed via scheduler without
392 # proper validation of remote_uri
392 # proper validation of remote_uri
393 if validate_uri:
393 if validate_uri:
394 config = make_db_config(clear_session=False)
394 config = make_db_config(clear_session=False)
395 url_validator(remote_uri, dbrepo.repo_type, config)
395 url_validator(remote_uri, dbrepo.repo_type, config)
396 except InvalidCloneUrl:
396 except InvalidCloneUrl:
397 raise
397 raise
398
398
399 repo_name = dbrepo.repo_name
399 repo_name = dbrepo.repo_name
400 try:
400 try:
401 # TODO: we need to make sure those operations call proper hooks !
401 # TODO: we need to make sure those operations call proper hooks !
402 repo.fetch(remote_uri)
402 repo.fetch(remote_uri)
403
403
404 self.mark_for_invalidation(repo_name)
404 self.mark_for_invalidation(repo_name)
405 except Exception:
405 except Exception:
406 log.error(traceback.format_exc())
406 log.error(traceback.format_exc())
407 raise
407 raise
408
408
409 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
409 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
410 dbrepo = self._get_repo(repo)
410 dbrepo = self._get_repo(repo)
411 remote_uri = remote_uri or dbrepo.push_uri
411 remote_uri = remote_uri or dbrepo.push_uri
412 if not remote_uri:
412 if not remote_uri:
413 raise Exception("This repository doesn't have a clone uri")
413 raise Exception("This repository doesn't have a clone uri")
414
414
415 repo = dbrepo.scm_instance(cache=False)
415 repo = dbrepo.scm_instance(cache=False)
416 repo.config.clear_section('hooks')
416 repo.config.clear_section('hooks')
417
417
418 try:
418 try:
419 # NOTE(marcink): add extra validation so we skip invalid urls
419 # NOTE(marcink): add extra validation so we skip invalid urls
420 # this is due this tasks can be executed via scheduler without
420 # this is due this tasks can be executed via scheduler without
421 # proper validation of remote_uri
421 # proper validation of remote_uri
422 if validate_uri:
422 if validate_uri:
423 config = make_db_config(clear_session=False)
423 config = make_db_config(clear_session=False)
424 url_validator(remote_uri, dbrepo.repo_type, config)
424 url_validator(remote_uri, dbrepo.repo_type, config)
425 except InvalidCloneUrl:
425 except InvalidCloneUrl:
426 raise
426 raise
427
427
428 try:
428 try:
429 repo.push(remote_uri)
429 repo.push(remote_uri)
430 except Exception:
430 except Exception:
431 log.error(traceback.format_exc())
431 log.error(traceback.format_exc())
432 raise
432 raise
433
433
434 def commit_change(self, repo, repo_name, commit, user, author, message,
434 def commit_change(self, repo, repo_name, commit, user, author, message,
435 content, f_path):
435 content, f_path):
436 """
436 """
437 Commits changes
437 Commits changes
438
438
439 :param repo: SCM instance
439 :param repo: SCM instance
440
440
441 """
441 """
442 user = self._get_user(user)
442 user = self._get_user(user)
443
443
444 # decoding here will force that we have proper encoded values
444 # decoding here will force that we have proper encoded values
445 # in any other case this will throw exceptions and deny commit
445 # in any other case this will throw exceptions and deny commit
446 content = safe_str(content)
446 content = safe_str(content)
447 path = safe_str(f_path)
447 path = safe_str(f_path)
448 # message and author needs to be unicode
448 # message and author needs to be unicode
449 # proper backend should then translate that into required type
449 # proper backend should then translate that into required type
450 message = safe_unicode(message)
450 message = safe_unicode(message)
451 author = safe_unicode(author)
451 author = safe_unicode(author)
452 imc = repo.in_memory_commit
452 imc = repo.in_memory_commit
453 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
453 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
454 try:
454 try:
455 # TODO: handle pre-push action !
455 # TODO: handle pre-push action !
456 tip = imc.commit(
456 tip = imc.commit(
457 message=message, author=author, parents=[commit],
457 message=message, author=author, parents=[commit],
458 branch=commit.branch)
458 branch=commit.branch)
459 except Exception as e:
459 except Exception as e:
460 log.error(traceback.format_exc())
460 log.error(traceback.format_exc())
461 raise IMCCommitError(str(e))
461 raise IMCCommitError(str(e))
462 finally:
462 finally:
463 # always clear caches, if commit fails we want fresh object also
463 # always clear caches, if commit fails we want fresh object also
464 self.mark_for_invalidation(repo_name)
464 self.mark_for_invalidation(repo_name)
465
465
466 # We trigger the post-push action
466 # We trigger the post-push action
467 hooks_utils.trigger_post_push_hook(
467 hooks_utils.trigger_post_push_hook(
468 username=user.username, action='push_local', hook_type='post_push',
468 username=user.username, action='push_local', hook_type='post_push',
469 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
469 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
470 return tip
470 return tip
471
471
472 def _sanitize_path(self, f_path):
472 def _sanitize_path(self, f_path):
473 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
473 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
474 raise NonRelativePathError('%s is not an relative path' % f_path)
474 raise NonRelativePathError('%s is not an relative path' % f_path)
475 if f_path:
475 if f_path:
476 f_path = os.path.normpath(f_path)
476 f_path = os.path.normpath(f_path)
477 return f_path
477 return f_path
478
478
479 def get_dirnode_metadata(self, request, commit, dir_node):
479 def get_dirnode_metadata(self, request, commit, dir_node):
480 if not dir_node.is_dir():
480 if not dir_node.is_dir():
481 return []
481 return []
482
482
483 data = []
483 data = []
484 for node in dir_node:
484 for node in dir_node:
485 if not node.is_file():
485 if not node.is_file():
486 # we skip file-nodes
486 # we skip file-nodes
487 continue
487 continue
488
488
489 last_commit = node.last_commit
489 last_commit = node.last_commit
490 last_commit_date = last_commit.date
490 last_commit_date = last_commit.date
491 data.append({
491 data.append({
492 'name': node.name,
492 'name': node.name,
493 'size': h.format_byte_size_binary(node.size),
493 'size': h.format_byte_size_binary(node.size),
494 'modified_at': h.format_date(last_commit_date),
494 'modified_at': h.format_date(last_commit_date),
495 'modified_ts': last_commit_date.isoformat(),
495 'modified_ts': last_commit_date.isoformat(),
496 'revision': last_commit.revision,
496 'revision': last_commit.revision,
497 'short_id': last_commit.short_id,
497 'short_id': last_commit.short_id,
498 'message': h.escape(last_commit.message),
498 'message': h.escape(last_commit.message),
499 'author': h.escape(last_commit.author),
499 'author': h.escape(last_commit.author),
500 'user_profile': h.gravatar_with_user(
500 'user_profile': h.gravatar_with_user(
501 request, last_commit.author),
501 request, last_commit.author),
502 })
502 })
503
503
504 return data
504 return data
505
505
506 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
506 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
507 extended_info=False, content=False, max_file_bytes=None):
507 extended_info=False, content=False, max_file_bytes=None):
508 """
508 """
509 recursive walk in root dir and return a set of all path in that dir
509 recursive walk in root dir and return a set of all path in that dir
510 based on repository walk function
510 based on repository walk function
511
511
512 :param repo_name: name of repository
512 :param repo_name: name of repository
513 :param commit_id: commit id for which to list nodes
513 :param commit_id: commit id for which to list nodes
514 :param root_path: root path to list
514 :param root_path: root path to list
515 :param flat: return as a list, if False returns a dict with description
515 :param flat: return as a list, if False returns a dict with description
516 :param extended_info: show additional info such as md5, binary, size etc
516 :param extended_info: show additional info such as md5, binary, size etc
517 :param content: add nodes content to the return data
517 :param content: add nodes content to the return data
518 :param max_file_bytes: will not return file contents over this limit
518 :param max_file_bytes: will not return file contents over this limit
519
519
520 """
520 """
521 _files = list()
521 _files = list()
522 _dirs = list()
522 _dirs = list()
523 try:
523 try:
524 _repo = self._get_repo(repo_name)
524 _repo = self._get_repo(repo_name)
525 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
525 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
526 root_path = root_path.lstrip('/')
526 root_path = root_path.lstrip('/')
527 for __, dirs, files in commit.walk(root_path):
527 for __, dirs, files in commit.walk(root_path):
528
528
529 for f in files:
529 for f in files:
530 _content = None
530 _content = None
531 _data = f_name = f.unicode_path
531 _data = f_name = f.unicode_path
532
532
533 if not flat:
533 if not flat:
534 _data = {
534 _data = {
535 "name": h.escape(f_name),
535 "name": h.escape(f_name),
536 "type": "file",
536 "type": "file",
537 }
537 }
538 if extended_info:
538 if extended_info:
539 _data.update({
539 _data.update({
540 "md5": f.md5,
540 "md5": f.md5,
541 "binary": f.is_binary,
541 "binary": f.is_binary,
542 "size": f.size,
542 "size": f.size,
543 "extension": f.extension,
543 "extension": f.extension,
544 "mimetype": f.mimetype,
544 "mimetype": f.mimetype,
545 "lines": f.lines()[0]
545 "lines": f.lines()[0]
546 })
546 })
547
547
548 if content:
548 if content:
549 over_size_limit = (max_file_bytes is not None
549 over_size_limit = (max_file_bytes is not None
550 and f.size > max_file_bytes)
550 and f.size > max_file_bytes)
551 full_content = None
551 full_content = None
552 if not f.is_binary and not over_size_limit:
552 if not f.is_binary and not over_size_limit:
553 full_content = safe_str(f.content)
553 full_content = safe_str(f.content)
554
554
555 _data.update({
555 _data.update({
556 "content": full_content,
556 "content": full_content,
557 })
557 })
558 _files.append(_data)
558 _files.append(_data)
559
559
560 for d in dirs:
560 for d in dirs:
561 _data = d_name = d.unicode_path
561 _data = d_name = d.unicode_path
562 if not flat:
562 if not flat:
563 _data = {
563 _data = {
564 "name": h.escape(d_name),
564 "name": h.escape(d_name),
565 "type": "dir",
565 "type": "dir",
566 }
566 }
567 if extended_info:
567 if extended_info:
568 _data.update({
568 _data.update({
569 "md5": None,
569 "md5": None,
570 "binary": None,
570 "binary": None,
571 "size": None,
571 "size": None,
572 "extension": None,
572 "extension": None,
573 })
573 })
574 if content:
574 if content:
575 _data.update({
575 _data.update({
576 "content": None
576 "content": None
577 })
577 })
578 _dirs.append(_data)
578 _dirs.append(_data)
579 except RepositoryError:
579 except RepositoryError:
580 log.exception("Exception in get_nodes")
580 log.exception("Exception in get_nodes")
581 raise
581 raise
582
582
583 return _dirs, _files
583 return _dirs, _files
584
584
585 def get_node(self, repo_name, commit_id, file_path,
585 def get_node(self, repo_name, commit_id, file_path,
586 extended_info=False, content=False, max_file_bytes=None, cache=True):
586 extended_info=False, content=False, max_file_bytes=None, cache=True):
587 """
587 """
588 retrieve single node from commit
588 retrieve single node from commit
589 """
589 """
590 try:
590 try:
591
591
592 _repo = self._get_repo(repo_name)
592 _repo = self._get_repo(repo_name)
593 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
593 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
594
594
595 file_node = commit.get_node(file_path)
595 file_node = commit.get_node(file_path)
596 if file_node.is_dir():
596 if file_node.is_dir():
597 raise RepositoryError('The given path is a directory')
597 raise RepositoryError('The given path is a directory')
598
598
599 _content = None
599 _content = None
600 f_name = file_node.unicode_path
600 f_name = file_node.unicode_path
601
601
602 file_data = {
602 file_data = {
603 "name": h.escape(f_name),
603 "name": h.escape(f_name),
604 "type": "file",
604 "type": "file",
605 }
605 }
606
606
607 if extended_info:
607 if extended_info:
608 file_data.update({
608 file_data.update({
609 "extension": file_node.extension,
609 "extension": file_node.extension,
610 "mimetype": file_node.mimetype,
610 "mimetype": file_node.mimetype,
611 })
611 })
612
612
613 if cache:
613 if cache:
614 md5 = file_node.md5
614 md5 = file_node.md5
615 is_binary = file_node.is_binary
615 is_binary = file_node.is_binary
616 size = file_node.size
616 size = file_node.size
617 else:
617 else:
618 is_binary, md5, size, _content = file_node.metadata_uncached()
618 is_binary, md5, size, _content = file_node.metadata_uncached()
619
619
620 file_data.update({
620 file_data.update({
621 "md5": md5,
621 "md5": md5,
622 "binary": is_binary,
622 "binary": is_binary,
623 "size": size,
623 "size": size,
624 })
624 })
625
625
626 if content and cache:
626 if content and cache:
627 # get content + cache
627 # get content + cache
628 size = file_node.size
628 size = file_node.size
629 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
629 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
630 full_content = None
630 full_content = None
631 if not file_node.is_binary and not over_size_limit:
631 if not file_node.is_binary and not over_size_limit:
632 full_content = safe_unicode(file_node.content)
632 full_content = safe_unicode(file_node.content)
633
633
634 file_data.update({
634 file_data.update({
635 "content": full_content,
635 "content": full_content,
636 })
636 })
637 elif content:
637 elif content:
638 # get content *without* cache
638 # get content *without* cache
639 if _content is None:
639 if _content is None:
640 is_binary, md5, size, _content = file_node.metadata_uncached()
640 is_binary, md5, size, _content = file_node.metadata_uncached()
641
641
642 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
642 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
643 full_content = None
643 full_content = None
644 if not is_binary and not over_size_limit:
644 if not is_binary and not over_size_limit:
645 full_content = safe_unicode(_content)
645 full_content = safe_unicode(_content)
646
646
647 file_data.update({
647 file_data.update({
648 "content": full_content,
648 "content": full_content,
649 })
649 })
650
650
651 except RepositoryError:
651 except RepositoryError:
652 log.exception("Exception in get_node")
652 log.exception("Exception in get_node")
653 raise
653 raise
654
654
655 return file_data
655 return file_data
656
656
657 def get_fts_data(self, repo_name, commit_id, root_path='/'):
657 def get_fts_data(self, repo_name, commit_id, root_path='/'):
658 """
658 """
659 Fetch node tree for usage in full text search
659 Fetch node tree for usage in full text search
660 """
660 """
661
661
662 tree_info = list()
662 tree_info = list()
663
663
664 try:
664 try:
665 _repo = self._get_repo(repo_name)
665 _repo = self._get_repo(repo_name)
666 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
666 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
667 root_path = root_path.lstrip('/')
667 root_path = root_path.lstrip('/')
668 for __, dirs, files in commit.walk(root_path):
668 for __, dirs, files in commit.walk(root_path):
669
669
670 for f in files:
670 for f in files:
671 is_binary, md5, size, _content = f.metadata_uncached()
671 is_binary, md5, size, _content = f.metadata_uncached()
672 _data = {
672 _data = {
673 "name": f.unicode_path,
673 "name": f.unicode_path,
674 "md5": md5,
674 "md5": md5,
675 "extension": f.extension,
675 "extension": f.extension,
676 "binary": is_binary,
676 "binary": is_binary,
677 "size": size
677 "size": size
678 }
678 }
679
679
680 tree_info.append(_data)
680 tree_info.append(_data)
681
681
682 except RepositoryError:
682 except RepositoryError:
683 log.exception("Exception in get_nodes")
683 log.exception("Exception in get_nodes")
684 raise
684 raise
685
685
686 return tree_info
686 return tree_info
687
687
688 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
688 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
689 author=None, trigger_push_hook=True):
689 author=None, trigger_push_hook=True):
690 """
690 """
691 Commits given multiple nodes into repo
691 Commits given multiple nodes into repo
692
692
693 :param user: RhodeCode User object or user_id, the commiter
693 :param user: RhodeCode User object or user_id, the commiter
694 :param repo: RhodeCode Repository object
694 :param repo: RhodeCode Repository object
695 :param message: commit message
695 :param message: commit message
696 :param nodes: mapping {filename:{'content':content},...}
696 :param nodes: mapping {filename:{'content':content},...}
697 :param parent_commit: parent commit, can be empty than it's
697 :param parent_commit: parent commit, can be empty than it's
698 initial commit
698 initial commit
699 :param author: author of commit, cna be different that commiter
699 :param author: author of commit, cna be different that commiter
700 only for git
700 only for git
701 :param trigger_push_hook: trigger push hooks
701 :param trigger_push_hook: trigger push hooks
702
702
703 :returns: new commited commit
703 :returns: new commited commit
704 """
704 """
705
705
706 user = self._get_user(user)
706 user = self._get_user(user)
707 scm_instance = repo.scm_instance(cache=False)
707 scm_instance = repo.scm_instance(cache=False)
708
708
709 processed_nodes = []
709 processed_nodes = []
710 for f_path in nodes:
710 for f_path in nodes:
711 f_path = self._sanitize_path(f_path)
711 f_path = self._sanitize_path(f_path)
712 content = nodes[f_path]['content']
712 content = nodes[f_path]['content']
713 f_path = safe_str(f_path)
713 f_path = safe_str(f_path)
714 # decoding here will force that we have proper encoded values
714 # decoding here will force that we have proper encoded values
715 # in any other case this will throw exceptions and deny commit
715 # in any other case this will throw exceptions and deny commit
716 if isinstance(content, (basestring,)):
716 if isinstance(content, (basestring,)):
717 content = safe_str(content)
717 content = safe_str(content)
718 elif isinstance(content, (file, cStringIO.OutputType,)):
718 elif isinstance(content, (file, cStringIO.OutputType,)):
719 content = content.read()
719 content = content.read()
720 else:
720 else:
721 raise Exception('Content is of unrecognized type %s' % (
721 raise Exception('Content is of unrecognized type %s' % (
722 type(content)
722 type(content)
723 ))
723 ))
724 processed_nodes.append((f_path, content))
724 processed_nodes.append((f_path, content))
725
725
726 message = safe_unicode(message)
726 message = safe_unicode(message)
727 commiter = user.full_contact
727 commiter = user.full_contact
728 author = safe_unicode(author) if author else commiter
728 author = safe_unicode(author) if author else commiter
729
729
730 imc = scm_instance.in_memory_commit
730 imc = scm_instance.in_memory_commit
731
731
732 if not parent_commit:
732 if not parent_commit:
733 parent_commit = EmptyCommit(alias=scm_instance.alias)
733 parent_commit = EmptyCommit(alias=scm_instance.alias)
734
734
735 if isinstance(parent_commit, EmptyCommit):
735 if isinstance(parent_commit, EmptyCommit):
736 # EmptyCommit means we we're editing empty repository
736 # EmptyCommit means we we're editing empty repository
737 parents = None
737 parents = None
738 else:
738 else:
739 parents = [parent_commit]
739 parents = [parent_commit]
740 # add multiple nodes
740 # add multiple nodes
741 for path, content in processed_nodes:
741 for path, content in processed_nodes:
742 imc.add(FileNode(path, content=content))
742 imc.add(FileNode(path, content=content))
743 # TODO: handle pre push scenario
743 # TODO: handle pre push scenario
744 tip = imc.commit(message=message,
744 tip = imc.commit(message=message,
745 author=author,
745 author=author,
746 parents=parents,
746 parents=parents,
747 branch=parent_commit.branch)
747 branch=parent_commit.branch)
748
748
749 self.mark_for_invalidation(repo.repo_name)
749 self.mark_for_invalidation(repo.repo_name)
750 if trigger_push_hook:
750 if trigger_push_hook:
751 hooks_utils.trigger_post_push_hook(
751 hooks_utils.trigger_post_push_hook(
752 username=user.username, action='push_local',
752 username=user.username, action='push_local',
753 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
753 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
754 hook_type='post_push',
754 hook_type='post_push',
755 commit_ids=[tip.raw_id])
755 commit_ids=[tip.raw_id])
756 return tip
756 return tip
757
757
758 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
758 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
759 author=None, trigger_push_hook=True):
759 author=None, trigger_push_hook=True):
760 user = self._get_user(user)
760 user = self._get_user(user)
761 scm_instance = repo.scm_instance(cache=False)
761 scm_instance = repo.scm_instance(cache=False)
762
762
763 message = safe_unicode(message)
763 message = safe_unicode(message)
764 commiter = user.full_contact
764 commiter = user.full_contact
765 author = safe_unicode(author) if author else commiter
765 author = safe_unicode(author) if author else commiter
766
766
767 imc = scm_instance.in_memory_commit
767 imc = scm_instance.in_memory_commit
768
768
769 if not parent_commit:
769 if not parent_commit:
770 parent_commit = EmptyCommit(alias=scm_instance.alias)
770 parent_commit = EmptyCommit(alias=scm_instance.alias)
771
771
772 if isinstance(parent_commit, EmptyCommit):
772 if isinstance(parent_commit, EmptyCommit):
773 # EmptyCommit means we we're editing empty repository
773 # EmptyCommit means we we're editing empty repository
774 parents = None
774 parents = None
775 else:
775 else:
776 parents = [parent_commit]
776 parents = [parent_commit]
777
777
778 # add multiple nodes
778 # add multiple nodes
779 for _filename, data in nodes.items():
779 for _filename, data in nodes.items():
780 # new filename, can be renamed from the old one, also sanitaze
780 # new filename, can be renamed from the old one, also sanitaze
781 # the path for any hack around relative paths like ../../ etc.
781 # the path for any hack around relative paths like ../../ etc.
782 filename = self._sanitize_path(data['filename'])
782 filename = self._sanitize_path(data['filename'])
783 old_filename = self._sanitize_path(_filename)
783 old_filename = self._sanitize_path(_filename)
784 content = data['content']
784 content = data['content']
785 file_mode = data.get('mode')
785 file_mode = data.get('mode')
786 filenode = FileNode(old_filename, content=content, mode=file_mode)
786 filenode = FileNode(old_filename, content=content, mode=file_mode)
787 op = data['op']
787 op = data['op']
788 if op == 'add':
788 if op == 'add':
789 imc.add(filenode)
789 imc.add(filenode)
790 elif op == 'del':
790 elif op == 'del':
791 imc.remove(filenode)
791 imc.remove(filenode)
792 elif op == 'mod':
792 elif op == 'mod':
793 if filename != old_filename:
793 if filename != old_filename:
794 # TODO: handle renames more efficient, needs vcs lib changes
794 # TODO: handle renames more efficient, needs vcs lib changes
795 imc.remove(filenode)
795 imc.remove(filenode)
796 imc.add(FileNode(filename, content=content, mode=file_mode))
796 imc.add(FileNode(filename, content=content, mode=file_mode))
797 else:
797 else:
798 imc.change(filenode)
798 imc.change(filenode)
799
799
800 try:
800 try:
801 # TODO: handle pre push scenario commit changes
801 # TODO: handle pre push scenario commit changes
802 tip = imc.commit(message=message,
802 tip = imc.commit(message=message,
803 author=author,
803 author=author,
804 parents=parents,
804 parents=parents,
805 branch=parent_commit.branch)
805 branch=parent_commit.branch)
806 except NodeNotChangedError:
806 except NodeNotChangedError:
807 raise
807 raise
808 except Exception as e:
808 except Exception as e:
809 log.exception("Unexpected exception during call to imc.commit")
809 log.exception("Unexpected exception during call to imc.commit")
810 raise IMCCommitError(str(e))
810 raise IMCCommitError(str(e))
811 finally:
811 finally:
812 # always clear caches, if commit fails we want fresh object also
812 # always clear caches, if commit fails we want fresh object also
813 self.mark_for_invalidation(repo.repo_name)
813 self.mark_for_invalidation(repo.repo_name)
814
814
815 if trigger_push_hook:
815 if trigger_push_hook:
816 hooks_utils.trigger_post_push_hook(
816 hooks_utils.trigger_post_push_hook(
817 username=user.username, action='push_local', hook_type='post_push',
817 username=user.username, action='push_local', hook_type='post_push',
818 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
818 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
819 commit_ids=[tip.raw_id])
819 commit_ids=[tip.raw_id])
820
820
821 return tip
822
821 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
823 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
822 author=None, trigger_push_hook=True):
824 author=None, trigger_push_hook=True):
823 """
825 """
824 Deletes given multiple nodes into `repo`
826 Deletes given multiple nodes into `repo`
825
827
826 :param user: RhodeCode User object or user_id, the committer
828 :param user: RhodeCode User object or user_id, the committer
827 :param repo: RhodeCode Repository object
829 :param repo: RhodeCode Repository object
828 :param message: commit message
830 :param message: commit message
829 :param nodes: mapping {filename:{'content':content},...}
831 :param nodes: mapping {filename:{'content':content},...}
830 :param parent_commit: parent commit, can be empty than it's initial
832 :param parent_commit: parent commit, can be empty than it's initial
831 commit
833 commit
832 :param author: author of commit, cna be different that commiter only
834 :param author: author of commit, cna be different that commiter only
833 for git
835 for git
834 :param trigger_push_hook: trigger push hooks
836 :param trigger_push_hook: trigger push hooks
835
837
836 :returns: new commit after deletion
838 :returns: new commit after deletion
837 """
839 """
838
840
839 user = self._get_user(user)
841 user = self._get_user(user)
840 scm_instance = repo.scm_instance(cache=False)
842 scm_instance = repo.scm_instance(cache=False)
841
843
842 processed_nodes = []
844 processed_nodes = []
843 for f_path in nodes:
845 for f_path in nodes:
844 f_path = self._sanitize_path(f_path)
846 f_path = self._sanitize_path(f_path)
845 # content can be empty but for compatabilty it allows same dicts
847 # content can be empty but for compatabilty it allows same dicts
846 # structure as add_nodes
848 # structure as add_nodes
847 content = nodes[f_path].get('content')
849 content = nodes[f_path].get('content')
848 processed_nodes.append((f_path, content))
850 processed_nodes.append((f_path, content))
849
851
850 message = safe_unicode(message)
852 message = safe_unicode(message)
851 commiter = user.full_contact
853 commiter = user.full_contact
852 author = safe_unicode(author) if author else commiter
854 author = safe_unicode(author) if author else commiter
853
855
854 imc = scm_instance.in_memory_commit
856 imc = scm_instance.in_memory_commit
855
857
856 if not parent_commit:
858 if not parent_commit:
857 parent_commit = EmptyCommit(alias=scm_instance.alias)
859 parent_commit = EmptyCommit(alias=scm_instance.alias)
858
860
859 if isinstance(parent_commit, EmptyCommit):
861 if isinstance(parent_commit, EmptyCommit):
860 # EmptyCommit means we we're editing empty repository
862 # EmptyCommit means we we're editing empty repository
861 parents = None
863 parents = None
862 else:
864 else:
863 parents = [parent_commit]
865 parents = [parent_commit]
864 # add multiple nodes
866 # add multiple nodes
865 for path, content in processed_nodes:
867 for path, content in processed_nodes:
866 imc.remove(FileNode(path, content=content))
868 imc.remove(FileNode(path, content=content))
867
869
868 # TODO: handle pre push scenario
870 # TODO: handle pre push scenario
869 tip = imc.commit(message=message,
871 tip = imc.commit(message=message,
870 author=author,
872 author=author,
871 parents=parents,
873 parents=parents,
872 branch=parent_commit.branch)
874 branch=parent_commit.branch)
873
875
874 self.mark_for_invalidation(repo.repo_name)
876 self.mark_for_invalidation(repo.repo_name)
875 if trigger_push_hook:
877 if trigger_push_hook:
876 hooks_utils.trigger_post_push_hook(
878 hooks_utils.trigger_post_push_hook(
877 username=user.username, action='push_local', hook_type='post_push',
879 username=user.username, action='push_local', hook_type='post_push',
878 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
880 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
879 commit_ids=[tip.raw_id])
881 commit_ids=[tip.raw_id])
880 return tip
882 return tip
881
883
882 def strip(self, repo, commit_id, branch):
884 def strip(self, repo, commit_id, branch):
883 scm_instance = repo.scm_instance(cache=False)
885 scm_instance = repo.scm_instance(cache=False)
884 scm_instance.config.clear_section('hooks')
886 scm_instance.config.clear_section('hooks')
885 scm_instance.strip(commit_id, branch)
887 scm_instance.strip(commit_id, branch)
886 self.mark_for_invalidation(repo.repo_name)
888 self.mark_for_invalidation(repo.repo_name)
887
889
888 def get_unread_journal(self):
890 def get_unread_journal(self):
889 return self.sa.query(UserLog).count()
891 return self.sa.query(UserLog).count()
890
892
891 def get_repo_landing_revs(self, translator, repo=None):
893 def get_repo_landing_revs(self, translator, repo=None):
892 """
894 """
893 Generates select option with tags branches and bookmarks (for hg only)
895 Generates select option with tags branches and bookmarks (for hg only)
894 grouped by type
896 grouped by type
895
897
896 :param repo:
898 :param repo:
897 """
899 """
898 _ = translator
900 _ = translator
899 repo = self._get_repo(repo)
901 repo = self._get_repo(repo)
900
902
901 hist_l = [
903 hist_l = [
902 ['rev:tip', _('latest tip')]
904 ['rev:tip', _('latest tip')]
903 ]
905 ]
904 choices = [
906 choices = [
905 'rev:tip'
907 'rev:tip'
906 ]
908 ]
907
909
908 if not repo:
910 if not repo:
909 return choices, hist_l
911 return choices, hist_l
910
912
911 repo = repo.scm_instance()
913 repo = repo.scm_instance()
912
914
913 branches_group = (
915 branches_group = (
914 [(u'branch:%s' % safe_unicode(b), safe_unicode(b))
916 [(u'branch:%s' % safe_unicode(b), safe_unicode(b))
915 for b in repo.branches],
917 for b in repo.branches],
916 _("Branches"))
918 _("Branches"))
917 hist_l.append(branches_group)
919 hist_l.append(branches_group)
918 choices.extend([x[0] for x in branches_group[0]])
920 choices.extend([x[0] for x in branches_group[0]])
919
921
920 if repo.alias == 'hg':
922 if repo.alias == 'hg':
921 bookmarks_group = (
923 bookmarks_group = (
922 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
924 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
923 for b in repo.bookmarks],
925 for b in repo.bookmarks],
924 _("Bookmarks"))
926 _("Bookmarks"))
925 hist_l.append(bookmarks_group)
927 hist_l.append(bookmarks_group)
926 choices.extend([x[0] for x in bookmarks_group[0]])
928 choices.extend([x[0] for x in bookmarks_group[0]])
927
929
928 tags_group = (
930 tags_group = (
929 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
931 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
930 for t in repo.tags],
932 for t in repo.tags],
931 _("Tags"))
933 _("Tags"))
932 hist_l.append(tags_group)
934 hist_l.append(tags_group)
933 choices.extend([x[0] for x in tags_group[0]])
935 choices.extend([x[0] for x in tags_group[0]])
934
936
935 return choices, hist_l
937 return choices, hist_l
936
938
937 def get_server_info(self, environ=None):
939 def get_server_info(self, environ=None):
938 server_info = get_system_info(environ)
940 server_info = get_system_info(environ)
939 return server_info
941 return server_info
@@ -1,418 +1,418 b''
1 /* BASICS */
1 /* BASICS */
2
2
3 .CodeMirror {
3 .CodeMirror {
4 /* Set height, width, borders, and global font properties here */
4 /* Set height, width, borders, and global font properties here */
5 font-family: monospace;
5 font-family: monospace;
6 height: 300px;
6 height: 300px;
7 color: black;
7 color: black;
8 border-radius: @border-radius;
8 border-radius: @border-radius;
9 border: @border-thickness solid @grey6;
9 border: @border-thickness solid @grey6;
10 margin: 0 0 @padding;
10 margin: 0 0 @padding;
11 }
11 }
12
12
13 /* PADDING */
13 /* PADDING */
14
14
15 .CodeMirror-lines {
15 .CodeMirror-lines {
16 padding: 4px 0; /* Vertical padding around content */
16 padding: 4px 0; /* Vertical padding around content */
17 }
17 }
18 .CodeMirror pre {
18 .CodeMirror pre {
19 padding: 0 4px; /* Horizontal padding of content */
19 padding: 0 4px; /* Horizontal padding of content */
20 }
20 }
21
21
22 .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
22 .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
23 background-color: white; /* The little square between H and V scrollbars */
23 background-color: white; /* The little square between H and V scrollbars */
24 }
24 }
25
25
26 /* GUTTER */
26 /* GUTTER */
27
27
28 .CodeMirror-gutters {
28 .CodeMirror-gutters {
29 border-right: 1px solid #ddd;
29 border-right: 1px solid #ddd;
30 background-color: @grey6;
30 background-color: white;
31 white-space: nowrap;
31 white-space: nowrap;
32 }
32 }
33 .CodeMirror-linenumbers {}
33 .CodeMirror-linenumbers {}
34 .CodeMirror-linenumber {
34 .CodeMirror-linenumber {
35 padding: 0 3px 0 5px;
35 padding: 0 3px 0 5px;
36 min-width: 20px;
36 min-width: 20px;
37 text-align: right;
37 text-align: right;
38 color: @grey4;
38 color: @grey4;
39 white-space: nowrap;
39 white-space: nowrap;
40 }
40 }
41
41
42 .CodeMirror-guttermarker { color: black; }
42 .CodeMirror-guttermarker { color: black; }
43 .CodeMirror-guttermarker-subtle { color: #999; }
43 .CodeMirror-guttermarker-subtle { color: #999; }
44
44
45 /* CURSOR */
45 /* CURSOR */
46
46
47 .CodeMirror div.CodeMirror-cursor {
47 .CodeMirror div.CodeMirror-cursor {
48 border-left: 1px solid black;
48 border-left: 1px solid black;
49 }
49 }
50 /* Shown when moving in bi-directional text */
50 /* Shown when moving in bi-directional text */
51 .CodeMirror div.CodeMirror-secondarycursor {
51 .CodeMirror div.CodeMirror-secondarycursor {
52 border-left: 1px solid silver;
52 border-left: 1px solid silver;
53 }
53 }
54 .CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
54 .CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
55 width: auto;
55 width: auto;
56 border: 0;
56 border: 0;
57 background: @grey6;
57 background: @grey6;
58 }
58 }
59 .CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
59 .CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
60 z-index: 1;
60 z-index: 1;
61 }
61 }
62
62
63 .cm-animate-fat-cursor {
63 .cm-animate-fat-cursor {
64 width: auto;
64 width: auto;
65 border: 0;
65 border: 0;
66 -webkit-animation: blink 1.06s steps(1) infinite;
66 -webkit-animation: blink 1.06s steps(1) infinite;
67 -moz-animation: blink 1.06s steps(1) infinite;
67 -moz-animation: blink 1.06s steps(1) infinite;
68 animation: blink 1.06s steps(1) infinite;
68 animation: blink 1.06s steps(1) infinite;
69 }
69 }
70 @-moz-keyframes blink {
70 @-moz-keyframes blink {
71 0% { background: #7e7; }
71 0% { background: #7e7; }
72 50% { background: none; }
72 50% { background: none; }
73 100% { background: #7e7; }
73 100% { background: #7e7; }
74 }
74 }
75 @-webkit-keyframes blink {
75 @-webkit-keyframes blink {
76 0% { background: #7e7; }
76 0% { background: #7e7; }
77 50% { background: none; }
77 50% { background: none; }
78 100% { background: #7e7; }
78 100% { background: #7e7; }
79 }
79 }
80 @keyframes blink {
80 @keyframes blink {
81 0% { background: #7e7; }
81 0% { background: #7e7; }
82 50% { background: none; }
82 50% { background: none; }
83 100% { background: #7e7; }
83 100% { background: #7e7; }
84 }
84 }
85
85
86 /* Can style cursor different in overwrite (non-insert) mode */
86 /* Can style cursor different in overwrite (non-insert) mode */
87 div.CodeMirror-overwrite div.CodeMirror-cursor {}
87 div.CodeMirror-overwrite div.CodeMirror-cursor {}
88
88
89 .cm-tab { display: inline-block; text-decoration: inherit; }
89 .cm-tab { display: inline-block; text-decoration: inherit; }
90
90
91 .CodeMirror-ruler {
91 .CodeMirror-ruler {
92 border-left: 1px solid #ccc;
92 border-left: 1px solid #ccc;
93 position: absolute;
93 position: absolute;
94 }
94 }
95
95
96 /* DEFAULT THEME */
96 /* DEFAULT THEME */
97
97
98 .cm-s-default .cm-header {color: blue;}
98 .cm-s-default .cm-header {color: blue;}
99 .cm-s-default .cm-quote {color: #090;}
99 .cm-s-default .cm-quote {color: #090;}
100 .cm-negative {color: #d44;}
100 .cm-negative {color: #d44;}
101 .cm-positive {color: #292;}
101 .cm-positive {color: #292;}
102 .cm-header, .cm-strong {font-weight: bold;}
102 .cm-header, .cm-strong {font-weight: bold;}
103 .cm-em {font-style: italic;}
103 .cm-em {font-style: italic;}
104 .cm-link {text-decoration: underline;}
104 .cm-link {text-decoration: underline;}
105 .cm-strikethrough {text-decoration: line-through;}
105 .cm-strikethrough {text-decoration: line-through;}
106
106
107 .cm-s-default .cm-keyword {color: #708;}
107 .cm-s-default .cm-keyword {color: #708;}
108 .cm-s-default .cm-atom {color: #219;}
108 .cm-s-default .cm-atom {color: #219;}
109 .cm-s-default .cm-number {color: #164;}
109 .cm-s-default .cm-number {color: #164;}
110 .cm-s-default .cm-def {color: #00f;}
110 .cm-s-default .cm-def {color: #00f;}
111 .cm-s-default .cm-variable,
111 .cm-s-default .cm-variable,
112 .cm-s-default .cm-punctuation,
112 .cm-s-default .cm-punctuation,
113 .cm-s-default .cm-property,
113 .cm-s-default .cm-property,
114 .cm-s-default .cm-operator {}
114 .cm-s-default .cm-operator {}
115 .cm-s-default .cm-variable-2 {color: #05a;}
115 .cm-s-default .cm-variable-2 {color: #05a;}
116 .cm-s-default .cm-variable-3 {color: #085;}
116 .cm-s-default .cm-variable-3 {color: #085;}
117 .cm-s-default .cm-comment {color: #a50;}
117 .cm-s-default .cm-comment {color: #a50;}
118 .cm-s-default .cm-string {color: #a11;}
118 .cm-s-default .cm-string {color: #a11;}
119 .cm-s-default .cm-string-2 {color: #f50;}
119 .cm-s-default .cm-string-2 {color: #f50;}
120 .cm-s-default .cm-meta {color: #555;}
120 .cm-s-default .cm-meta {color: #555;}
121 .cm-s-default .cm-qualifier {color: #555;}
121 .cm-s-default .cm-qualifier {color: #555;}
122 .cm-s-default .cm-builtin {color: #30a;}
122 .cm-s-default .cm-builtin {color: #30a;}
123 .cm-s-default .cm-bracket {color: #997;}
123 .cm-s-default .cm-bracket {color: #997;}
124 .cm-s-default .cm-tag {color: #170;}
124 .cm-s-default .cm-tag {color: #170;}
125 .cm-s-default .cm-attribute {color: #00c;}
125 .cm-s-default .cm-attribute {color: #00c;}
126 .cm-s-default .cm-hr {color: #999;}
126 .cm-s-default .cm-hr {color: #999;}
127 .cm-s-default .cm-link {color: #00c;}
127 .cm-s-default .cm-link {color: #00c;}
128
128
129 .cm-s-default .cm-error {color: #f00;}
129 .cm-s-default .cm-error {color: #f00;}
130 .cm-invalidchar {color: #f00;}
130 .cm-invalidchar {color: #f00;}
131
131
132 .CodeMirror-composing { border-bottom: 2px solid; }
132 .CodeMirror-composing { border-bottom: 2px solid; }
133
133
134 /* Default styles for common addons */
134 /* Default styles for common addons */
135
135
136 div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
136 div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
137 div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
137 div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
138 .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
138 .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
139 .CodeMirror-activeline-background {background: #e8f2ff;}
139 .CodeMirror-activeline-background {background: #e8f2ff;}
140
140
141 /* STOP */
141 /* STOP */
142
142
143 /* The rest of this file contains styles related to the mechanics of
143 /* The rest of this file contains styles related to the mechanics of
144 the editor. You probably shouldn't touch them. */
144 the editor. You probably shouldn't touch them. */
145
145
146 .CodeMirror {
146 .CodeMirror {
147 position: relative;
147 position: relative;
148 overflow: hidden;
148 overflow: hidden;
149 background: white;
149 background: white;
150 }
150 }
151
151
152 .CodeMirror-scroll {
152 .CodeMirror-scroll {
153 overflow: scroll !important; /* Things will break if this is overridden */
153 overflow: scroll !important; /* Things will break if this is overridden */
154 /* 30px is the magic margin used to hide the element's real scrollbars */
154 /* 30px is the magic margin used to hide the element's real scrollbars */
155 /* See overflow: hidden in .CodeMirror */
155 /* See overflow: hidden in .CodeMirror */
156 margin-bottom: -30px; margin-right: -30px;
156 margin-bottom: -30px; margin-right: -30px;
157 padding-bottom: 30px;
157 padding-bottom: 30px;
158 height: 100%;
158 height: 100%;
159 outline: none; /* Prevent dragging from highlighting the element */
159 outline: none; /* Prevent dragging from highlighting the element */
160 position: relative;
160 position: relative;
161 }
161 }
162 .CodeMirror-sizer {
162 .CodeMirror-sizer {
163 position: relative;
163 position: relative;
164 border-right: 30px solid transparent;
164 border-right: 30px solid transparent;
165 }
165 }
166
166
167 /* The fake, visible scrollbars. Used to force redraw during scrolling
167 /* The fake, visible scrollbars. Used to force redraw during scrolling
168 before actual scrolling happens, thus preventing shaking and
168 before actual scrolling happens, thus preventing shaking and
169 flickering artifacts. */
169 flickering artifacts. */
170 .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
170 .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
171 position: absolute;
171 position: absolute;
172 z-index: 6;
172 z-index: 6;
173 display: none;
173 display: none;
174 }
174 }
175 .CodeMirror-vscrollbar {
175 .CodeMirror-vscrollbar {
176 right: 0; top: 0;
176 right: 0; top: 0;
177 overflow-x: hidden;
177 overflow-x: hidden;
178 overflow-y: scroll;
178 overflow-y: scroll;
179 }
179 }
180 .CodeMirror-hscrollbar {
180 .CodeMirror-hscrollbar {
181 bottom: 0; left: 0;
181 bottom: 0; left: 0;
182 overflow-y: hidden;
182 overflow-y: hidden;
183 overflow-x: scroll;
183 overflow-x: scroll;
184 }
184 }
185 .CodeMirror-scrollbar-filler {
185 .CodeMirror-scrollbar-filler {
186 right: 0; bottom: 0;
186 right: 0; bottom: 0;
187 }
187 }
188 .CodeMirror-gutter-filler {
188 .CodeMirror-gutter-filler {
189 left: 0; bottom: 0;
189 left: 0; bottom: 0;
190 }
190 }
191
191
192 .CodeMirror-gutters {
192 .CodeMirror-gutters {
193 position: absolute; left: 0; top: 0;
193 position: absolute; left: 0; top: 0;
194 z-index: 3;
194 z-index: 3;
195 }
195 }
196 .CodeMirror-gutter {
196 .CodeMirror-gutter {
197 white-space: normal;
197 white-space: normal;
198 height: 100%;
198 height: 100%;
199 display: inline-block;
199 display: inline-block;
200 margin-bottom: -30px;
200 margin-bottom: -30px;
201 /* Hack to make IE7 behave */
201 /* Hack to make IE7 behave */
202 *zoom:1;
202 *zoom:1;
203 *display:inline;
203 *display:inline;
204 }
204 }
205 .CodeMirror-gutter-wrapper {
205 .CodeMirror-gutter-wrapper {
206 position: absolute;
206 position: absolute;
207 z-index: 4;
207 z-index: 4;
208 height: 100%;
208 height: 100%;
209 }
209 }
210 .CodeMirror-gutter-background {
210 .CodeMirror-gutter-background {
211 position: absolute;
211 position: absolute;
212 top: 0; bottom: 0;
212 top: 0; bottom: 0;
213 z-index: 4;
213 z-index: 4;
214 }
214 }
215 .CodeMirror-gutter-elt {
215 .CodeMirror-gutter-elt {
216 position: absolute;
216 position: absolute;
217 cursor: default;
217 cursor: default;
218 z-index: 4;
218 z-index: 4;
219 }
219 }
220 .CodeMirror-gutter-wrapper {
220 .CodeMirror-gutter-wrapper {
221 -webkit-user-select: none;
221 -webkit-user-select: none;
222 -moz-user-select: none;
222 -moz-user-select: none;
223 user-select: none;
223 user-select: none;
224 }
224 }
225
225
226 .CodeMirror-lines {
226 .CodeMirror-lines {
227 cursor: text;
227 cursor: text;
228 min-height: 1px; /* prevents collapsing before first draw */
228 min-height: 1px; /* prevents collapsing before first draw */
229 }
229 }
230 .CodeMirror pre {
230 .CodeMirror pre {
231 /* Reset some styles that the rest of the page might have set */
231 /* Reset some styles that the rest of the page might have set */
232 -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
232 -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
233 border-width: 0;
233 border-width: 0;
234 background: transparent;
234 background: transparent;
235 font-family: inherit;
235 font-family: inherit;
236 font-size: inherit;
236 font-size: inherit;
237 margin: 0;
237 margin: 0;
238 white-space: pre;
238 white-space: pre;
239 word-wrap: normal;
239 word-wrap: normal;
240 line-height: inherit;
240 line-height: inherit;
241 color: inherit;
241 color: inherit;
242 z-index: 2;
242 z-index: 2;
243 position: relative;
243 position: relative;
244 overflow: visible;
244 overflow: visible;
245 -webkit-tap-highlight-color: transparent;
245 -webkit-tap-highlight-color: transparent;
246 }
246 }
247 .CodeMirror-wrap pre {
247 .CodeMirror-wrap pre {
248 word-wrap: break-word;
248 word-wrap: break-word;
249 white-space: pre-wrap;
249 white-space: pre-wrap;
250 word-break: normal;
250 word-break: normal;
251 }
251 }
252
252
253 .CodeMirror-linebackground {
253 .CodeMirror-linebackground {
254 position: absolute;
254 position: absolute;
255 left: 0; right: 0; top: 0; bottom: 0;
255 left: 0; right: 0; top: 0; bottom: 0;
256 z-index: 0;
256 z-index: 0;
257 }
257 }
258
258
259 .CodeMirror-linewidget {
259 .CodeMirror-linewidget {
260 position: relative;
260 position: relative;
261 z-index: 2;
261 z-index: 2;
262 overflow: auto;
262 overflow: auto;
263 }
263 }
264
264
265 .CodeMirror-widget {}
265 .CodeMirror-widget {}
266
266
267 .CodeMirror-code {
267 .CodeMirror-code {
268 outline: none;
268 outline: none;
269 }
269 }
270
270
271 /* Force content-box sizing for the elements where we expect it */
271 /* Force content-box sizing for the elements where we expect it */
272 .CodeMirror-scroll,
272 .CodeMirror-scroll,
273 .CodeMirror-sizer,
273 .CodeMirror-sizer,
274 .CodeMirror-gutter,
274 .CodeMirror-gutter,
275 .CodeMirror-gutters,
275 .CodeMirror-gutters,
276 .CodeMirror-linenumber {
276 .CodeMirror-linenumber {
277 -moz-box-sizing: content-box;
277 -moz-box-sizing: content-box;
278 box-sizing: content-box;
278 box-sizing: content-box;
279 }
279 }
280
280
281 .CodeMirror-measure {
281 .CodeMirror-measure {
282 position: absolute;
282 position: absolute;
283 width: 100%;
283 width: 100%;
284 height: 0;
284 height: 0;
285 overflow: hidden;
285 overflow: hidden;
286 visibility: hidden;
286 visibility: hidden;
287 }
287 }
288
288
289
289
290 .CodeMirror div.CodeMirror-cursor {
290 .CodeMirror div.CodeMirror-cursor {
291 position: absolute;
291 position: absolute;
292 border-right: none;
292 border-right: none;
293 width: 0;
293 width: 0;
294 }
294 }
295
295
296 .CodeMirror-measure pre { position: static; }
296 .CodeMirror-measure pre { position: static; }
297
297
298 div.CodeMirror-cursors {
298 div.CodeMirror-cursors {
299 visibility: hidden;
299 visibility: hidden;
300 position: relative;
300 position: relative;
301 z-index: 3;
301 z-index: 3;
302 }
302 }
303 div.CodeMirror-dragcursors {
303 div.CodeMirror-dragcursors {
304 visibility: visible;
304 visibility: visible;
305 }
305 }
306
306
307 .CodeMirror-focused div.CodeMirror-cursors {
307 .CodeMirror-focused div.CodeMirror-cursors {
308 visibility: visible;
308 visibility: visible;
309 }
309 }
310
310
311 .CodeMirror-selected { background: #d9d9d9; }
311 .CodeMirror-selected { background: #d9d9d9; }
312 .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
312 .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
313 .CodeMirror-crosshair { cursor: crosshair; }
313 .CodeMirror-crosshair { cursor: crosshair; }
314 .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
314 .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
315 .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
315 .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
316
316
317 .cm-searching {
317 .cm-searching {
318 background: #ffa;
318 background: #ffa;
319 background: rgba(255, 255, 0, .4);
319 background: rgba(255, 255, 0, .4);
320 }
320 }
321
321
322 /* IE7 hack to prevent it from returning funny offsetTops on the spans */
322 /* IE7 hack to prevent it from returning funny offsetTops on the spans */
323 .CodeMirror span { *vertical-align: text-bottom; }
323 .CodeMirror span { *vertical-align: text-bottom; }
324
324
325 /* Used to force a border model for a node */
325 /* Used to force a border model for a node */
326 .cm-force-border { padding-right: .1px; }
326 .cm-force-border { padding-right: .1px; }
327
327
328 @media print {
328 @media print {
329 /* Hide the cursor when printing */
329 /* Hide the cursor when printing */
330 .CodeMirror div.CodeMirror-cursors {
330 .CodeMirror div.CodeMirror-cursors {
331 visibility: hidden;
331 visibility: hidden;
332 }
332 }
333 }
333 }
334
334
335 /* See issue #2901 */
335 /* See issue #2901 */
336 .cm-tab-wrap-hack:after { content: ''; }
336 .cm-tab-wrap-hack:after { content: ''; }
337
337
338 /* Help users use markselection to safely style text background */
338 /* Help users use markselection to safely style text background */
339 span.CodeMirror-selectedtext { background: none; }
339 span.CodeMirror-selectedtext { background: none; }
340
340
341 /* codemirror autocomplete widget */
341 /* codemirror autocomplete widget */
342 .CodeMirror-hints {
342 .CodeMirror-hints {
343 position: absolute;
343 position: absolute;
344 z-index: 10;
344 z-index: 10;
345 overflow: hidden;
345 overflow: hidden;
346 list-style: none;
346 list-style: none;
347
347
348 margin: 0;
348 margin: 0;
349 padding: 0;
349 padding: 0;
350
350
351 border-radius: @border-radius;
351 border-radius: @border-radius;
352 border: @border-thickness solid @rcblue;
352 border: @border-thickness solid @rcblue;
353
353
354 color: @rcblue;
354 color: @rcblue;
355 background-color: white;
355 background-color: white;
356 font-size: 95%;
356 font-size: 95%;
357
357
358 max-height: 20em;
358 max-height: 20em;
359 overflow-y: auto;
359 overflow-y: auto;
360 }
360 }
361
361
362 .CodeMirror-hint {
362 .CodeMirror-hint {
363 margin: 0;
363 margin: 0;
364 padding: 4px 8px;
364 padding: 4px 8px;
365 max-width: 40em;
365 max-width: 40em;
366 white-space: pre;
366 white-space: pre;
367 color: @rcblue;
367 color: @rcblue;
368 cursor: pointer;
368 cursor: pointer;
369 }
369 }
370
370
371 .CodeMirror-hint-active {
371 .CodeMirror-hint-active {
372 background: @rclightblue;
372 background: @rclightblue;
373 color: @rcblue;
373 color: @rcblue;
374 }
374 }
375
375
376 .CodeMirror-hint-entry {
376 .CodeMirror-hint-entry {
377 width: 38em;
377 width: 38em;
378 color: @rcblue;
378 color: @rcblue;
379 }
379 }
380
380
381 .CodeMirror-hint-entry .gravatar {
381 .CodeMirror-hint-entry .gravatar {
382 height: @gravatar-size;
382 height: @gravatar-size;
383 width: @gravatar-size;
383 width: @gravatar-size;
384 margin-right: 4px;
384 margin-right: 4px;
385 }
385 }
386
386
387 .CodeMirror-empty {
387 .CodeMirror-empty {
388 border: @border-thickness solid @grey5;
388 border: @border-thickness solid @grey5;
389 }
389 }
390
390
391 .CodeMirror-focused {
391 .CodeMirror-focused {
392 border: @border-thickness solid @grey5;
392 border: @border-thickness solid @grey5;
393 }
393 }
394
394
395 .CodeMirror-empty.CodeMirror-focused {
395 .CodeMirror-empty.CodeMirror-focused {
396 border: @border-thickness solid @grey5;
396 border: @border-thickness solid @grey5;
397 }
397 }
398
398
399 .CodeMirror pre.CodeMirror-placeholder {
399 .CodeMirror pre.CodeMirror-placeholder {
400 color: @grey4;
400 color: @grey4;
401 }
401 }
402
402
403 /** RhodeCode Customizations **/
403 /** RhodeCode Customizations **/
404
404
405 .CodeMirror.cm-s-rc-input {
405 .CodeMirror.cm-s-rc-input {
406 border: @border-thickness solid @grey4;
406 border: @border-thickness solid @grey4;
407 }
407 }
408
408
409 .CodeMirror-code pre {
409 .CodeMirror-code pre {
410 border-right: 30px solid transparent;
410 border-right: 30px solid transparent;
411 width: -webkit-fit-content;
411 width: -webkit-fit-content;
412 width: -moz-fit-content;
412 width: -moz-fit-content;
413 width: fit-content;
413 width: fit-content;
414 }
414 }
415 .CodeMirror-wrap .CodeMirror-code pre {
415 .CodeMirror-wrap .CodeMirror-code pre {
416 border-right: none;
416 border-right: none;
417 width: auto;
417 width: auto;
418 }
418 }
@@ -1,320 +1,325 b''
1 // forms.less
1 // forms.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 form.rcform {
5 form.rcform {
6
6
7 // reset for ie
7 // reset for ie
8 // using :not(#ie) prevents older browsers from applying these rules
8 // using :not(#ie) prevents older browsers from applying these rules
9 input[type="radio"],
9 input[type="radio"],
10 input[type="checkbox"] {
10 input[type="checkbox"] {
11 padding: 0;
11 padding: 0;
12 border: none;
12 border: none;
13 }
13 }
14 label { display: inline; border:none; padding:0; }
14 label { display: inline; border:none; padding:0; }
15 .label { display: none; }
15 .label { display: none; }
16
16
17 max-width: 940px;
17 max-width: 940px;
18 line-height: normal;
18 line-height: normal;
19 white-space: normal;
19 white-space: normal;
20 font-size: @basefontsize;
20 font-size: @basefontsize;
21 font-family: @text-light;
21 font-family: @text-light;
22 color: @form-textcolor;
22 color: @form-textcolor;
23
23
24 fieldset,
24 fieldset,
25 .buttons {
25 .buttons {
26 clear: both;
26 clear: both;
27 position: relative;
27 position: relative;
28 display:block;
28 display:block;
29 width: 100%;
29 width: 100%;
30 min-height: 3em;
30 min-height: 3em;
31 margin-bottom: @form-vertical-margin;
31 margin-bottom: @form-vertical-margin;
32 line-height: 1.2em;
32 line-height: 1.2em;
33
33
34 &:after { //clearfix
34 &:after { //clearfix
35 content: "";
35 content: "";
36 clear: both;
36 clear: both;
37 width: 100%;
37 width: 100%;
38 height: 1em;
38 height: 1em;
39 }
39 }
40
40
41 .label:not(#ie) {
41 .label:not(#ie) {
42 display: inline;
42 display: inline;
43 margin: 0 1em 0 .5em;
43 margin: 0 1em 0 .5em;
44 line-height: 1em;
44 line-height: 1em;
45 }
45 }
46 }
46 }
47
47
48 legend {
48 legend {
49 float: left;
49 float: left;
50 display: block;
50 display: block;
51 width: @legend-width;
51 width: @legend-width;
52 margin: 0;
52 margin: 0;
53 padding: 0 @padding 0 0;
53 padding: 0 @padding 0 0;
54 }
54 }
55
55
56 .fields {
56 .fields {
57 float: left;
57 float: left;
58 display: block;
58 display: block;
59 width: 100%;
59 width: 100%;
60 max-width: 500px;
60 max-width: 500px;
61 margin: 0 0 @padding -@legend-width;
61 margin: 0 0 @padding -@legend-width;
62 padding: 0 0 0 @legend-width;
62 padding: 0 0 0 @legend-width;
63
63
64 .btn {
64 .btn {
65 display: inline-block;
65 display: inline-block;
66 margin: 0 1em @padding 0;
66 margin: 0 1em @padding 0;
67 }
67 }
68 }
68 }
69
69
70 input,
70 input,
71 textarea {
71 textarea {
72 float: left;
72 float: left;
73 .box-sizing(content-box);
73 .box-sizing(content-box);
74 padding: @input-padding;
74 padding: @input-padding;
75 border: @border-thickness-inputs solid @grey4;
75 border: @border-thickness-inputs solid @grey4;
76 }
76 }
77
77
78 input {
78 input {
79 float: left;
79 float: left;
80 margin: 0 @input-padding 0 0;
80 margin: 0 @input-padding 0 0;
81 line-height: 1em;
81 line-height: 1em;
82 }
82 }
83
83
84 input[type="text"],
84 input[type="text"],
85 input[type="password"],
85 input[type="password"],
86 textarea {
86 textarea {
87 float: left;
87 float: left;
88 min-width: 200px;
88 min-width: 200px;
89 margin: 0 1em @padding 0;
89 margin: 0 1em @padding 0;
90 color: @form-textcolor;
90 color: @form-textcolor;
91 }
91 }
92
92
93 input[type="text"],
93 input[type="text"],
94 input[type="password"] {
94 input[type="password"] {
95 height: 1em;
95 height: 1em;
96 }
96 }
97
97
98 textarea {
98 textarea {
99 width: 100%;
99 width: 100%;
100 margin-top: -1em; //so it lines up with legend
100 margin-top: -1em; //so it lines up with legend
101 overflow: auto;
101 overflow: auto;
102 }
102 }
103
103
104 label:not(#ie) {
104 label:not(#ie) {
105 cursor: pointer;
105 cursor: pointer;
106 display: inline-block;
106 display: inline-block;
107 position: relative;
107 position: relative;
108 background: white;
108 background: white;
109 border-radius: 4px;
109 border-radius: 4px;
110 box-shadow: none;
110 box-shadow: none;
111
111
112 &:hover::after {
112 &:hover::after {
113 opacity: 0.5;
113 opacity: 0.5;
114 }
114 }
115 }
115 }
116
116
117 input[type="radio"]:not(#ie),
117 input[type="radio"]:not(#ie),
118 input[type="checkbox"]:not(#ie) {
118 input[type="checkbox"]:not(#ie) {
119 // Hide the input, but have it still be clickable
119 // Hide the input, but have it still be clickable
120 opacity: 0;
120 opacity: 0;
121 float: left;
121 float: left;
122 height: 0;
122 height: 0;
123 width: 0;
123 width: 0;
124 margin: 0;
124 margin: 0;
125 padding: 0;
125 padding: 0;
126 }
126 }
127 input[type='radio'] + label:not(#ie),
127 input[type='radio'] + label:not(#ie),
128 input[type='checkbox'] + label:not(#ie) {
128 input[type='checkbox'] + label:not(#ie) {
129 margin: 0;
129 margin: 0;
130 clear: none;
130 clear: none;
131 }
131 }
132
132
133 input[type='radio'] + label:not(#ie) {
133 input[type='radio'] + label:not(#ie) {
134 .circle (@form-radio-width,white);
134 .circle (@form-radio-width,white);
135 float: left;
135 float: left;
136 display: inline-block;
136 display: inline-block;
137 height: @form-radio-width;
137 height: @form-radio-width;
138 width: @form-radio-width;
138 width: @form-radio-width;
139 margin: 2px 6px 2px 0;
139 margin: 2px 6px 2px 0;
140 border: 1px solid @grey4;
140 border: 1px solid @grey4;
141 background-color: white;
141 background-color: white;
142 box-shadow: none;
142 box-shadow: none;
143 text-indent: -9999px;
143 text-indent: -9999px;
144 transition: none;
144 transition: none;
145
145
146 & + .label {
146 & + .label {
147 float: left;
147 float: left;
148 margin-top: 7px
148 margin-top: 7px
149 }
149 }
150 }
150 }
151
151
152 input[type='radio']:checked + label:not(#ie) {
152 input[type='radio']:checked + label:not(#ie) {
153 margin: 0 4px 0 -2px;
153 margin: 0 4px 0 -2px;
154 padding: 3px;
154 padding: 3px;
155 border-style: double;
155 border-style: double;
156 border-color: white;
156 border-color: white;
157 border-width: thick;
157 border-width: thick;
158 background-color: @rcblue;
158 background-color: @rcblue;
159 box-shadow: none;
159 box-shadow: none;
160 }
160 }
161
161
162 input[type='checkbox'] + label:not(#ie) {
162 input[type='checkbox'] + label:not(#ie) {
163 float: left;
163 float: left;
164 width: @form-check-width;
164 width: @form-check-width;
165 height: @form-check-width;
165 height: @form-check-width;
166 margin: 0 5px 1em 0;
166 margin: 0 5px 1em 0;
167 border: 1px solid @grey3;
167 border: 1px solid @grey3;
168 .border-radius(@border-radius);
168 .border-radius(@border-radius);
169 background-color: white;
169 background-color: white;
170 box-shadow: none;
170 box-shadow: none;
171 text-indent: -9999px;
171 text-indent: -9999px;
172 transition: none;
172 transition: none;
173
173
174 &:after {
174 &:after {
175 content: '';
175 content: '';
176 width: 9px;
176 width: 9px;
177 height: 5px;
177 height: 5px;
178 position: absolute;
178 position: absolute;
179 top: 4px;
179 top: 4px;
180 left: 4px;
180 left: 4px;
181 border: 3px solid @grey3;
181 border: 3px solid @grey3;
182 border-top: none;
182 border-top: none;
183 border-right: none;
183 border-right: none;
184 background: transparent;
184 background: transparent;
185 opacity: 0;
185 opacity: 0;
186 transform: rotate(-45deg);
186 transform: rotate(-45deg);
187 filter: progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476); /* IE6,IE7 */
187 filter: progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476); /* IE6,IE7 */
188
188
189 -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(SizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)"; /* IE8 */ }
189 -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(SizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)"; /* IE8 */ }
190
190
191 & + .label {
191 & + .label {
192 float: left;
192 float: left;
193 margin-top: 5px
193 margin-top: 5px
194 }
194 }
195 }
195 }
196
196
197 input[type=checkbox]:not(#ie) {
197 input[type=checkbox]:not(#ie) {
198 visibility: hidden;
198 visibility: hidden;
199 &:checked + label:after {
199 &:checked + label:after {
200 opacity: 1;
200 opacity: 1;
201 }
201 }
202 }
202 }
203
203
204 // center checkbox and label on a drop-down
204 // center checkbox and label on a drop-down
205 .drop-menu + select + input[type='checkbox'] + label:not(#ie) {
205 .drop-menu + select + input[type='checkbox'] + label:not(#ie) {
206 margin-top:10px;
206 margin-top:10px;
207
207
208 & + .label {
208 & + .label {
209 margin-top: 15px;
209 margin-top: 15px;
210 }
210 }
211 }
211 }
212
212
213 .formlist {
213 .formlist {
214 position: relative;
214 position: relative;
215 float: left;
215 float: left;
216 margin: 0;
216 margin: 0;
217 padding: 0;
217 padding: 0;
218
218
219 li {
219 li {
220 list-style-type: none;
220 list-style-type: none;
221
221
222 &:after {
222 &:after {
223 content: "";
223 content: "";
224 float: left;
224 float: left;
225 display: block;
225 display: block;
226 height: @padding;
226 height: @padding;
227 width: 100%;
227 width: 100%;
228 }
228 }
229 }
229 }
230 }
230 }
231
231
232 .drop-menu {
232 .drop-menu {
233 float: left;
233 float: left;
234
235 & + .last-item {
236 margin: 0;
237 }
238
234 margin: 0 @input-padding 0 0;
239 margin: 0 @input-padding 0 0;
235 }
240 }
236
241
237 .help-block,
242 .help-block,
238 .error-message {
243 .error-message {
239 display: block;
244 display: block;
240 clear: both;
245 clear: both;
241 margin: @textmargin 0;
246 margin: @textmargin 0;
242 }
247 }
243
248
244 .error-message {
249 .error-message {
245 margin-top: 5px;
250 margin-top: 5px;
246 }
251 }
247
252
248 input[type=submit] {
253 input[type=submit] {
249 &:extend(.btn-primary);
254 &:extend(.btn-primary);
250
255
251 &:hover {
256 &:hover {
252 &:extend(.btn-primary:hover);
257 &:extend(.btn-primary:hover);
253 }
258 }
254 }
259 }
255
260
256 input[type=reset] {
261 input[type=reset] {
257 &:extend(.btn-default);
262 &:extend(.btn-default);
258
263
259 &:hover {
264 &:hover {
260 &:extend(.btn-default:hover);
265 &:extend(.btn-default:hover);
261 }
266 }
262 }
267 }
263
268
264 select,
269 select,
265 option:checked {
270 option:checked {
266 background-color: @rclightblue;
271 background-color: @rclightblue;
267 }
272 }
268
273
269 }
274 }
270
275
271 .badged-field {
276 .badged-field {
272 .user-badge {
277 .user-badge {
273 line-height: 25px;
278 line-height: 25px;
274 padding: .4em;
279 padding: .4em;
275 border-radius: @border-radius;
280 border-radius: @border-radius;
276 border-top: 1px solid @grey4;
281 border-top: 1px solid @grey4;
277 border-left: 1px solid @grey4;
282 border-left: 1px solid @grey4;
278 border-bottom: 1px solid @grey4;
283 border-bottom: 1px solid @grey4;
279 font-size: 14px;
284 font-size: 14px;
280 font-style: normal;
285 font-style: normal;
281 color: @text-light;
286 color: @text-light;
282 background: @grey7;
287 background: @grey7;
283 display: inline-block;
288 display: inline-block;
284 vertical-align: top;
289 vertical-align: top;
285 cursor: default;
290 cursor: default;
286 margin-right: -2px;
291 margin-right: -2px;
287 }
292 }
288 .badge-input-container {
293 .badge-input-container {
289 display: flex;
294 display: flex;
290 position: relative;
295 position: relative;
291 }
296 }
292 .user-disabled {
297 .user-disabled {
293 text-decoration: line-through;
298 text-decoration: line-through;
294 }
299 }
295 .badge-input-wrap {
300 .badge-input-wrap {
296 display: inline-block;
301 display: inline-block;
297 }
302 }
298 }
303 }
299
304
300 // for situations where we wish to display the form value but not the form input
305 // for situations where we wish to display the form value but not the form input
301 input.input-valuedisplay {
306 input.input-valuedisplay {
302 border: none;
307 border: none;
303 }
308 }
304
309
305 // for forms which only display information
310 // for forms which only display information
306 .infoform {
311 .infoform {
307 .fields {
312 .fields {
308 .field {
313 .field {
309 label,
314 label,
310 .label,
315 .label,
311 input,
316 input,
312 .input {
317 .input {
313 margin-top: 0;
318 margin-top: 0;
314 margin-bottom: 0;
319 margin-bottom: 0;
315 padding-top: 0;
320 padding-top: 0;
316 padding-bottom: 0;
321 padding-bottom: 0;
317 }
322 }
318 }
323 }
319 }
324 }
320 }
325 }
@@ -1,2618 +1,2743 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'variables';
8 @import 'variables';
9 @import 'bootstrap-variables';
9 @import 'bootstrap-variables';
10 @import 'form-bootstrap';
10 @import 'form-bootstrap';
11 @import 'codemirror';
11 @import 'codemirror';
12 @import 'legacy_code_styles';
12 @import 'legacy_code_styles';
13 @import 'readme-box';
13 @import 'readme-box';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29
29
30 //--- BASE ------------------//
30 //--- BASE ------------------//
31 .noscript-error {
31 .noscript-error {
32 top: 0;
32 top: 0;
33 left: 0;
33 left: 0;
34 width: 100%;
34 width: 100%;
35 z-index: 101;
35 z-index: 101;
36 text-align: center;
36 text-align: center;
37 font-size: 120%;
37 font-size: 120%;
38 color: white;
38 color: white;
39 background-color: @alert2;
39 background-color: @alert2;
40 padding: 5px 0 5px 0;
40 padding: 5px 0 5px 0;
41 font-weight: @text-semibold-weight;
41 font-weight: @text-semibold-weight;
42 font-family: @text-semibold;
42 font-family: @text-semibold;
43 }
43 }
44
44
45 html {
45 html {
46 display: table;
46 display: table;
47 height: 100%;
47 height: 100%;
48 width: 100%;
48 width: 100%;
49 }
49 }
50
50
51 body {
51 body {
52 display: table-cell;
52 display: table-cell;
53 width: 100%;
53 width: 100%;
54 }
54 }
55
55
56 //--- LAYOUT ------------------//
56 //--- LAYOUT ------------------//
57
57
58 .hidden{
58 .hidden{
59 display: none !important;
59 display: none !important;
60 }
60 }
61
61
62 .box{
62 .box{
63 float: left;
63 float: left;
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .browser-header {
67 .browser-header {
68 clear: both;
68 clear: both;
69 }
69 }
70 .main {
70 .main {
71 clear: both;
71 clear: both;
72 padding:0 0 @pagepadding;
72 padding:0 0 @pagepadding;
73 height: auto;
73 height: auto;
74
74
75 &:after { //clearfix
75 &:after { //clearfix
76 content:"";
76 content:"";
77 clear:both;
77 clear:both;
78 width:100%;
78 width:100%;
79 display:block;
79 display:block;
80 }
80 }
81 }
81 }
82
82
83 .action-link{
83 .action-link{
84 margin-left: @padding;
84 margin-left: @padding;
85 padding-left: @padding;
85 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
86 border-left: @border-thickness solid @border-default-color;
87 }
87 }
88
88
89 input + .action-link, .action-link.first{
89 input + .action-link, .action-link.first{
90 border-left: none;
90 border-left: none;
91 }
91 }
92
92
93 .action-link.last{
93 .action-link.last{
94 margin-right: @padding;
94 margin-right: @padding;
95 padding-right: @padding;
95 padding-right: @padding;
96 }
96 }
97
97
98 .action-link.active,
98 .action-link.active,
99 .action-link.active a{
99 .action-link.active a{
100 color: @grey4;
100 color: @grey4;
101 }
101 }
102
102
103 .action-link.disabled {
103 .action-link.disabled {
104 color: @grey4;
104 color: @grey4;
105 cursor: inherit;
105 cursor: inherit;
106 }
106 }
107
107
108 .clipboard-action {
108 .clipboard-action {
109 cursor: pointer;
109 cursor: pointer;
110 color: @grey4;
110 color: @grey4;
111 margin-left: 5px;
111 margin-left: 5px;
112
112
113 &:hover {
113 &:hover {
114 color: @grey2;
114 color: @grey2;
115 }
115 }
116 }
116 }
117
117
118 ul.simple-list{
118 ul.simple-list{
119 list-style: none;
119 list-style: none;
120 margin: 0;
120 margin: 0;
121 padding: 0;
121 padding: 0;
122 }
122 }
123
123
124 .main-content {
124 .main-content {
125 padding-bottom: @pagepadding;
125 padding-bottom: @pagepadding;
126 }
126 }
127
127
128 .wide-mode-wrapper {
128 .wide-mode-wrapper {
129 max-width:4000px !important;
129 max-width:4000px !important;
130 }
130 }
131
131
132 .wrapper {
132 .wrapper {
133 position: relative;
133 position: relative;
134 max-width: @wrapper-maxwidth;
134 max-width: @wrapper-maxwidth;
135 margin: 0 auto;
135 margin: 0 auto;
136 }
136 }
137
137
138 #content {
138 #content {
139 clear: both;
139 clear: both;
140 padding: 0 @contentpadding;
140 padding: 0 @contentpadding;
141 }
141 }
142
142
143 .advanced-settings-fields{
143 .advanced-settings-fields{
144 input{
144 input{
145 margin-left: @textmargin;
145 margin-left: @textmargin;
146 margin-right: @padding/2;
146 margin-right: @padding/2;
147 }
147 }
148 }
148 }
149
149
150 .cs_files_title {
150 .cs_files_title {
151 margin: @pagepadding 0 0;
151 margin: @pagepadding 0 0;
152 }
152 }
153
153
154 input.inline[type="file"] {
154 input.inline[type="file"] {
155 display: inline;
155 display: inline;
156 }
156 }
157
157
158 .error_page {
158 .error_page {
159 margin: 10% auto;
159 margin: 10% auto;
160
160
161 h1 {
161 h1 {
162 color: @grey2;
162 color: @grey2;
163 }
163 }
164
164
165 .alert {
165 .alert {
166 margin: @padding 0;
166 margin: @padding 0;
167 }
167 }
168
168
169 .error-branding {
169 .error-branding {
170 color: @grey4;
170 color: @grey4;
171 font-weight: @text-semibold-weight;
171 font-weight: @text-semibold-weight;
172 font-family: @text-semibold;
172 font-family: @text-semibold;
173 }
173 }
174
174
175 .error_message {
175 .error_message {
176 font-family: @text-regular;
176 font-family: @text-regular;
177 }
177 }
178
178
179 .sidebar {
179 .sidebar {
180 min-height: 275px;
180 min-height: 275px;
181 margin: 0;
181 margin: 0;
182 padding: 0 0 @sidebarpadding @sidebarpadding;
182 padding: 0 0 @sidebarpadding @sidebarpadding;
183 border: none;
183 border: none;
184 }
184 }
185
185
186 .main-content {
186 .main-content {
187 position: relative;
187 position: relative;
188 margin: 0 @sidebarpadding @sidebarpadding;
188 margin: 0 @sidebarpadding @sidebarpadding;
189 padding: 0 0 0 @sidebarpadding;
189 padding: 0 0 0 @sidebarpadding;
190 border-left: @border-thickness solid @grey5;
190 border-left: @border-thickness solid @grey5;
191
191
192 @media (max-width:767px) {
192 @media (max-width:767px) {
193 clear: both;
193 clear: both;
194 width: 100%;
194 width: 100%;
195 margin: 0;
195 margin: 0;
196 border: none;
196 border: none;
197 }
197 }
198 }
198 }
199
199
200 .inner-column {
200 .inner-column {
201 float: left;
201 float: left;
202 width: 29.75%;
202 width: 29.75%;
203 min-height: 150px;
203 min-height: 150px;
204 margin: @sidebarpadding 2% 0 0;
204 margin: @sidebarpadding 2% 0 0;
205 padding: 0 2% 0 0;
205 padding: 0 2% 0 0;
206 border-right: @border-thickness solid @grey5;
206 border-right: @border-thickness solid @grey5;
207
207
208 @media (max-width:767px) {
208 @media (max-width:767px) {
209 clear: both;
209 clear: both;
210 width: 100%;
210 width: 100%;
211 border: none;
211 border: none;
212 }
212 }
213
213
214 ul {
214 ul {
215 padding-left: 1.25em;
215 padding-left: 1.25em;
216 }
216 }
217
217
218 &:last-child {
218 &:last-child {
219 margin: @sidebarpadding 0 0;
219 margin: @sidebarpadding 0 0;
220 border: none;
220 border: none;
221 }
221 }
222
222
223 h4 {
223 h4 {
224 margin: 0 0 @padding;
224 margin: 0 0 @padding;
225 font-weight: @text-semibold-weight;
225 font-weight: @text-semibold-weight;
226 font-family: @text-semibold;
226 font-family: @text-semibold;
227 }
227 }
228 }
228 }
229 }
229 }
230 .error-page-logo {
230 .error-page-logo {
231 width: 130px;
231 width: 130px;
232 height: 160px;
232 height: 160px;
233 }
233 }
234
234
235 // HEADER
235 // HEADER
236 .header {
236 .header {
237
237
238 // TODO: johbo: Fix login pages, so that they work without a min-height
238 // TODO: johbo: Fix login pages, so that they work without a min-height
239 // for the header and then remove the min-height. I chose a smaller value
239 // for the header and then remove the min-height. I chose a smaller value
240 // intentionally here to avoid rendering issues in the main navigation.
240 // intentionally here to avoid rendering issues in the main navigation.
241 min-height: 49px;
241 min-height: 49px;
242
242
243 position: relative;
243 position: relative;
244 vertical-align: bottom;
244 vertical-align: bottom;
245 padding: 0 @header-padding;
245 padding: 0 @header-padding;
246 background-color: @grey1;
246 background-color: @grey1;
247 color: @grey5;
247 color: @grey5;
248
248
249 .title {
249 .title {
250 overflow: visible;
250 overflow: visible;
251 }
251 }
252
252
253 &:before,
253 &:before,
254 &:after {
254 &:after {
255 content: "";
255 content: "";
256 clear: both;
256 clear: both;
257 width: 100%;
257 width: 100%;
258 }
258 }
259
259
260 // TODO: johbo: Avoids breaking "Repositories" chooser
260 // TODO: johbo: Avoids breaking "Repositories" chooser
261 .select2-container .select2-choice .select2-arrow {
261 .select2-container .select2-choice .select2-arrow {
262 display: none;
262 display: none;
263 }
263 }
264 }
264 }
265
265
266 #header-inner {
266 #header-inner {
267 &.title {
267 &.title {
268 margin: 0;
268 margin: 0;
269 }
269 }
270 &:before,
270 &:before,
271 &:after {
271 &:after {
272 content: "";
272 content: "";
273 clear: both;
273 clear: both;
274 }
274 }
275 }
275 }
276
276
277 // Gists
277 // Gists
278 #files_data {
278 #files_data {
279 clear: both; //for firefox
279 clear: both; //for firefox
280 padding-top: 10px;
280 }
281 }
282
281 #gistid {
283 #gistid {
282 margin-right: @padding;
284 margin-right: @padding;
283 }
285 }
284
286
285 // Global Settings Editor
287 // Global Settings Editor
286 .textarea.editor {
288 .textarea.editor {
287 float: left;
289 float: left;
288 position: relative;
290 position: relative;
289 max-width: @texteditor-width;
291 max-width: @texteditor-width;
290
292
291 select {
293 select {
292 position: absolute;
294 position: absolute;
293 top:10px;
295 top:10px;
294 right:0;
296 right:0;
295 }
297 }
296
298
297 .CodeMirror {
299 .CodeMirror {
298 margin: 0;
300 margin: 0;
299 }
301 }
300
302
301 .help-block {
303 .help-block {
302 margin: 0 0 @padding;
304 margin: 0 0 @padding;
303 padding:.5em;
305 padding:.5em;
304 background-color: @grey6;
306 background-color: @grey6;
305 &.pre-formatting {
307 &.pre-formatting {
306 white-space: pre;
308 white-space: pre;
307 }
309 }
308 }
310 }
309 }
311 }
310
312
311 ul.auth_plugins {
313 ul.auth_plugins {
312 margin: @padding 0 @padding @legend-width;
314 margin: @padding 0 @padding @legend-width;
313 padding: 0;
315 padding: 0;
314
316
315 li {
317 li {
316 margin-bottom: @padding;
318 margin-bottom: @padding;
317 line-height: 1em;
319 line-height: 1em;
318 list-style-type: none;
320 list-style-type: none;
319
321
320 .auth_buttons .btn {
322 .auth_buttons .btn {
321 margin-right: @padding;
323 margin-right: @padding;
322 }
324 }
323
325
324 }
326 }
325 }
327 }
326
328
327
329
328 // My Account PR list
330 // My Account PR list
329
331
330 #show_closed {
332 #show_closed {
331 margin: 0 1em 0 0;
333 margin: 0 1em 0 0;
332 }
334 }
333
335
334 .pullrequestlist {
336 .pullrequestlist {
335 .closed {
337 .closed {
336 background-color: @grey6;
338 background-color: @grey6;
337 }
339 }
338 .td-status {
340 .td-status {
339 padding-left: .5em;
341 padding-left: .5em;
340 }
342 }
341 .log-container .truncate {
343 .log-container .truncate {
342 height: 2.75em;
344 height: 2.75em;
343 white-space: pre-line;
345 white-space: pre-line;
344 }
346 }
345 table.rctable .user {
347 table.rctable .user {
346 padding-left: 0;
348 padding-left: 0;
347 }
349 }
348 table.rctable {
350 table.rctable {
349 td.td-description,
351 td.td-description,
350 .rc-user {
352 .rc-user {
351 min-width: auto;
353 min-width: auto;
352 }
354 }
353 }
355 }
354 }
356 }
355
357
356 // Pull Requests
358 // Pull Requests
357
359
358 .pullrequests_section_head {
360 .pullrequests_section_head {
359 display: block;
361 display: block;
360 clear: both;
362 clear: both;
361 margin: @padding 0;
363 margin: @padding 0;
362 font-weight: @text-bold-weight;
364 font-weight: @text-bold-weight;
363 font-family: @text-bold;
365 font-family: @text-bold;
364 }
366 }
365
367
366 .pr-origininfo, .pr-targetinfo {
368 .pr-origininfo, .pr-targetinfo {
367 position: relative;
369 position: relative;
368
370
369 .tag {
371 .tag {
370 display: inline-block;
372 display: inline-block;
371 margin: 0 1em .5em 0;
373 margin: 0 1em .5em 0;
372 }
374 }
373
375
374 .clone-url {
376 .clone-url {
375 display: inline-block;
377 display: inline-block;
376 margin: 0 0 .5em 0;
378 margin: 0 0 .5em 0;
377 padding: 0;
379 padding: 0;
378 line-height: 1.2em;
380 line-height: 1.2em;
379 }
381 }
380 }
382 }
381
383
382 .pr-mergeinfo {
384 .pr-mergeinfo {
383 min-width: 95% !important;
385 min-width: 95% !important;
384 padding: 0 !important;
386 padding: 0 !important;
385 border: 0;
387 border: 0;
386 }
388 }
387 .pr-mergeinfo-copy {
389 .pr-mergeinfo-copy {
388 padding: 0 0;
390 padding: 0 0;
389 }
391 }
390
392
391 .pr-pullinfo {
393 .pr-pullinfo {
392 min-width: 95% !important;
394 min-width: 95% !important;
393 padding: 0 !important;
395 padding: 0 !important;
394 border: 0;
396 border: 0;
395 }
397 }
396 .pr-pullinfo-copy {
398 .pr-pullinfo-copy {
397 padding: 0 0;
399 padding: 0 0;
398 }
400 }
399
401
400
402
401 #pr-title-input {
403 #pr-title-input {
402 width: 72%;
404 width: 72%;
403 font-size: 1em;
405 font-size: 1em;
404 margin: 0;
406 margin: 0;
405 padding: 0 0 0 @padding/4;
407 padding: 0 0 0 @padding/4;
406 line-height: 1.7em;
408 line-height: 1.7em;
407 color: @text-color;
409 color: @text-color;
408 letter-spacing: .02em;
410 letter-spacing: .02em;
409 font-weight: @text-bold-weight;
411 font-weight: @text-bold-weight;
410 font-family: @text-bold;
412 font-family: @text-bold;
411 }
413 }
412
414
413 #pullrequest_title {
415 #pullrequest_title {
414 width: 100%;
416 width: 100%;
415 box-sizing: border-box;
417 box-sizing: border-box;
416 }
418 }
417
419
418 #pr_open_message {
420 #pr_open_message {
419 border: @border-thickness solid #fff;
421 border: @border-thickness solid #fff;
420 border-radius: @border-radius;
422 border-radius: @border-radius;
421 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
423 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
422 text-align: left;
424 text-align: left;
423 overflow: hidden;
425 overflow: hidden;
424 }
426 }
425
427
426 .pr-submit-button {
428 .pr-submit-button {
427 float: right;
429 float: right;
428 margin: 0 0 0 5px;
430 margin: 0 0 0 5px;
429 }
431 }
430
432
431 .pr-spacing-container {
433 .pr-spacing-container {
432 padding: 20px;
434 padding: 20px;
433 clear: both
435 clear: both
434 }
436 }
435
437
436 #pr-description-input {
438 #pr-description-input {
437 margin-bottom: 0;
439 margin-bottom: 0;
438 }
440 }
439
441
440 .pr-description-label {
442 .pr-description-label {
441 vertical-align: top;
443 vertical-align: top;
442 }
444 }
443
445
444 .perms_section_head {
446 .perms_section_head {
445 min-width: 625px;
447 min-width: 625px;
446
448
447 h2 {
449 h2 {
448 margin-bottom: 0;
450 margin-bottom: 0;
449 }
451 }
450
452
451 .label-checkbox {
453 .label-checkbox {
452 float: left;
454 float: left;
453 }
455 }
454
456
455 &.field {
457 &.field {
456 margin: @space 0 @padding;
458 margin: @space 0 @padding;
457 }
459 }
458
460
459 &:first-child.field {
461 &:first-child.field {
460 margin-top: 0;
462 margin-top: 0;
461
463
462 .label {
464 .label {
463 margin-top: 0;
465 margin-top: 0;
464 padding-top: 0;
466 padding-top: 0;
465 }
467 }
466
468
467 .radios {
469 .radios {
468 padding-top: 0;
470 padding-top: 0;
469 }
471 }
470 }
472 }
471
473
472 .radios {
474 .radios {
473 position: relative;
475 position: relative;
474 width: 505px;
476 width: 505px;
475 }
477 }
476 }
478 }
477
479
478 //--- MODULES ------------------//
480 //--- MODULES ------------------//
479
481
480
482
481 // Server Announcement
483 // Server Announcement
482 #server-announcement {
484 #server-announcement {
483 width: 95%;
485 width: 95%;
484 margin: @padding auto;
486 margin: @padding auto;
485 padding: @padding;
487 padding: @padding;
486 border-width: 2px;
488 border-width: 2px;
487 border-style: solid;
489 border-style: solid;
488 .border-radius(2px);
490 .border-radius(2px);
489 font-weight: @text-bold-weight;
491 font-weight: @text-bold-weight;
490 font-family: @text-bold;
492 font-family: @text-bold;
491
493
492 &.info { border-color: @alert4; background-color: @alert4-inner; }
494 &.info { border-color: @alert4; background-color: @alert4-inner; }
493 &.warning { border-color: @alert3; background-color: @alert3-inner; }
495 &.warning { border-color: @alert3; background-color: @alert3-inner; }
494 &.error { border-color: @alert2; background-color: @alert2-inner; }
496 &.error { border-color: @alert2; background-color: @alert2-inner; }
495 &.success { border-color: @alert1; background-color: @alert1-inner; }
497 &.success { border-color: @alert1; background-color: @alert1-inner; }
496 &.neutral { border-color: @grey3; background-color: @grey6; }
498 &.neutral { border-color: @grey3; background-color: @grey6; }
497 }
499 }
498
500
499 // Fixed Sidebar Column
501 // Fixed Sidebar Column
500 .sidebar-col-wrapper {
502 .sidebar-col-wrapper {
501 padding-left: @sidebar-all-width;
503 padding-left: @sidebar-all-width;
502
504
503 .sidebar {
505 .sidebar {
504 width: @sidebar-width;
506 width: @sidebar-width;
505 margin-left: -@sidebar-all-width;
507 margin-left: -@sidebar-all-width;
506 }
508 }
507 }
509 }
508
510
509 .sidebar-col-wrapper.scw-small {
511 .sidebar-col-wrapper.scw-small {
510 padding-left: @sidebar-small-all-width;
512 padding-left: @sidebar-small-all-width;
511
513
512 .sidebar {
514 .sidebar {
513 width: @sidebar-small-width;
515 width: @sidebar-small-width;
514 margin-left: -@sidebar-small-all-width;
516 margin-left: -@sidebar-small-all-width;
515 }
517 }
516 }
518 }
517
519
518
520
519 // FOOTER
521 // FOOTER
520 #footer {
522 #footer {
521 padding: 0;
523 padding: 0;
522 text-align: center;
524 text-align: center;
523 vertical-align: middle;
525 vertical-align: middle;
524 color: @grey2;
526 color: @grey2;
525 font-size: 11px;
527 font-size: 11px;
526
528
527 p {
529 p {
528 margin: 0;
530 margin: 0;
529 padding: 1em;
531 padding: 1em;
530 line-height: 1em;
532 line-height: 1em;
531 }
533 }
532
534
533 .server-instance { //server instance
535 .server-instance { //server instance
534 display: none;
536 display: none;
535 }
537 }
536
538
537 .title {
539 .title {
538 float: none;
540 float: none;
539 margin: 0 auto;
541 margin: 0 auto;
540 }
542 }
541 }
543 }
542
544
543 button.close {
545 button.close {
544 padding: 0;
546 padding: 0;
545 cursor: pointer;
547 cursor: pointer;
546 background: transparent;
548 background: transparent;
547 border: 0;
549 border: 0;
548 .box-shadow(none);
550 .box-shadow(none);
549 -webkit-appearance: none;
551 -webkit-appearance: none;
550 }
552 }
551
553
552 .close {
554 .close {
553 float: right;
555 float: right;
554 font-size: 21px;
556 font-size: 21px;
555 font-family: @text-bootstrap;
557 font-family: @text-bootstrap;
556 line-height: 1em;
558 line-height: 1em;
557 font-weight: bold;
559 font-weight: bold;
558 color: @grey2;
560 color: @grey2;
559
561
560 &:hover,
562 &:hover,
561 &:focus {
563 &:focus {
562 color: @grey1;
564 color: @grey1;
563 text-decoration: none;
565 text-decoration: none;
564 cursor: pointer;
566 cursor: pointer;
565 }
567 }
566 }
568 }
567
569
568 // GRID
570 // GRID
569 .sorting,
571 .sorting,
570 .sorting_desc,
572 .sorting_desc,
571 .sorting_asc {
573 .sorting_asc {
572 cursor: pointer;
574 cursor: pointer;
573 }
575 }
574 .sorting_desc:after {
576 .sorting_desc:after {
575 content: "\00A0\25B2";
577 content: "\00A0\25B2";
576 font-size: .75em;
578 font-size: .75em;
577 }
579 }
578 .sorting_asc:after {
580 .sorting_asc:after {
579 content: "\00A0\25BC";
581 content: "\00A0\25BC";
580 font-size: .68em;
582 font-size: .68em;
581 }
583 }
582
584
583
585
584 .user_auth_tokens {
586 .user_auth_tokens {
585
587
586 &.truncate {
588 &.truncate {
587 white-space: nowrap;
589 white-space: nowrap;
588 overflow: hidden;
590 overflow: hidden;
589 text-overflow: ellipsis;
591 text-overflow: ellipsis;
590 }
592 }
591
593
592 .fields .field .input {
594 .fields .field .input {
593 margin: 0;
595 margin: 0;
594 }
596 }
595
597
596 input#description {
598 input#description {
597 width: 100px;
599 width: 100px;
598 margin: 0;
600 margin: 0;
599 }
601 }
600
602
601 .drop-menu {
603 .drop-menu {
602 // TODO: johbo: Remove this, should work out of the box when
604 // TODO: johbo: Remove this, should work out of the box when
603 // having multiple inputs inline
605 // having multiple inputs inline
604 margin: 0 0 0 5px;
606 margin: 0 0 0 5px;
605 }
607 }
606 }
608 }
607 #user_list_table {
609 #user_list_table {
608 .closed {
610 .closed {
609 background-color: @grey6;
611 background-color: @grey6;
610 }
612 }
611 }
613 }
612
614
613
615
614 input, textarea {
616 input, textarea {
615 &.disabled {
617 &.disabled {
616 opacity: .5;
618 opacity: .5;
617 }
619 }
618
620
619 &:hover {
621 &:hover {
620 border-color: @grey3;
622 border-color: @grey3;
621 box-shadow: @button-shadow;
623 box-shadow: @button-shadow;
622 }
624 }
623
625
624 &:focus {
626 &:focus {
625 border-color: @rcblue;
627 border-color: @rcblue;
626 box-shadow: @button-shadow;
628 box-shadow: @button-shadow;
627 }
629 }
628 }
630 }
629
631
630 // remove extra padding in firefox
632 // remove extra padding in firefox
631 input::-moz-focus-inner { border:0; padding:0 }
633 input::-moz-focus-inner { border:0; padding:0 }
632
634
633 .adjacent input {
635 .adjacent input {
634 margin-bottom: @padding;
636 margin-bottom: @padding;
635 }
637 }
636
638
637 .permissions_boxes {
639 .permissions_boxes {
638 display: block;
640 display: block;
639 }
641 }
640
642
641 //FORMS
643 //FORMS
642
644
643 .medium-inline,
645 .medium-inline,
644 input#description.medium-inline {
646 input#description.medium-inline {
645 display: inline;
647 display: inline;
646 width: @medium-inline-input-width;
648 width: @medium-inline-input-width;
647 min-width: 100px;
649 min-width: 100px;
648 }
650 }
649
651
650 select {
652 select {
651 //reset
653 //reset
652 -webkit-appearance: none;
654 -webkit-appearance: none;
653 -moz-appearance: none;
655 -moz-appearance: none;
654
656
655 display: inline-block;
657 display: inline-block;
656 height: 28px;
658 height: 28px;
657 width: auto;
659 width: auto;
658 margin: 0 @padding @padding 0;
660 margin: 0 @padding @padding 0;
659 padding: 0 18px 0 8px;
661 padding: 0 18px 0 8px;
660 line-height:1em;
662 line-height:1em;
661 font-size: @basefontsize;
663 font-size: @basefontsize;
662 border: @border-thickness solid @grey5;
664 border: @border-thickness solid @grey5;
663 border-radius: @border-radius;
665 border-radius: @border-radius;
664 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
666 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
665 color: @grey4;
667 color: @grey4;
666 box-shadow: @button-shadow;
668 box-shadow: @button-shadow;
667
669
668 &:after {
670 &:after {
669 content: "\00A0\25BE";
671 content: "\00A0\25BE";
670 }
672 }
671
673
672 &:focus, &:hover {
674 &:focus, &:hover {
673 outline: none;
675 outline: none;
674 border-color: @grey4;
676 border-color: @grey4;
675 color: @rcdarkblue;
677 color: @rcdarkblue;
676 }
678 }
677 }
679 }
678
680
679 option {
681 option {
680 &:focus {
682 &:focus {
681 outline: none;
683 outline: none;
682 }
684 }
683 }
685 }
684
686
685 input,
687 input,
686 textarea {
688 textarea {
687 padding: @input-padding;
689 padding: @input-padding;
688 border: @input-border-thickness solid @border-highlight-color;
690 border: @input-border-thickness solid @border-highlight-color;
689 .border-radius (@border-radius);
691 .border-radius (@border-radius);
690 font-family: @text-light;
692 font-family: @text-light;
691 font-size: @basefontsize;
693 font-size: @basefontsize;
692
694
693 &.input-sm {
695 &.input-sm {
694 padding: 5px;
696 padding: 5px;
695 }
697 }
696
698
697 &#description {
699 &#description {
698 min-width: @input-description-minwidth;
700 min-width: @input-description-minwidth;
699 min-height: 1em;
701 min-height: 1em;
700 padding: 10px;
702 padding: 10px;
701 }
703 }
702 }
704 }
703
705
704 .field-sm {
706 .field-sm {
705 input,
707 input,
706 textarea {
708 textarea {
707 padding: 5px;
709 padding: 5px;
708 }
710 }
709 }
711 }
710
712
711 textarea {
713 textarea {
712 display: block;
714 display: block;
713 clear: both;
715 clear: both;
714 width: 100%;
716 width: 100%;
715 min-height: 100px;
717 min-height: 100px;
716 margin-bottom: @padding;
718 margin-bottom: @padding;
717 .box-sizing(border-box);
719 .box-sizing(border-box);
718 overflow: auto;
720 overflow: auto;
719 }
721 }
720
722
721 label {
723 label {
722 font-family: @text-light;
724 font-family: @text-light;
723 }
725 }
724
726
725 // GRAVATARS
727 // GRAVATARS
726 // centers gravatar on username to the right
728 // centers gravatar on username to the right
727
729
728 .gravatar {
730 .gravatar {
729 display: inline;
731 display: inline;
730 min-width: 16px;
732 min-width: 16px;
731 min-height: 16px;
733 min-height: 16px;
732 margin: -5px 0;
734 margin: -5px 0;
733 padding: 0;
735 padding: 0;
734 line-height: 1em;
736 line-height: 1em;
735 box-sizing: content-box;
737 box-sizing: content-box;
736 border-radius: 50%;
738 border-radius: 50%;
737
739
738 &.gravatar-large {
740 &.gravatar-large {
739 margin: -0.5em .25em -0.5em 0;
741 margin: -0.5em .25em -0.5em 0;
740 }
742 }
741
743
742 & + .user {
744 & + .user {
743 display: inline;
745 display: inline;
744 margin: 0;
746 margin: 0;
745 padding: 0 0 0 .17em;
747 padding: 0 0 0 .17em;
746 line-height: 1em;
748 line-height: 1em;
747 }
749 }
748 }
750 }
749
751
750 .user-inline-data {
752 .user-inline-data {
751 display: inline-block;
753 display: inline-block;
752 float: left;
754 float: left;
753 padding-left: .5em;
755 padding-left: .5em;
754 line-height: 1.3em;
756 line-height: 1.3em;
755 }
757 }
756
758
757 .rc-user { // gravatar + user wrapper
759 .rc-user { // gravatar + user wrapper
758 float: left;
760 float: left;
759 position: relative;
761 position: relative;
760 min-width: 100px;
762 min-width: 100px;
761 max-width: 200px;
763 max-width: 200px;
762 min-height: (@gravatar-size + @border-thickness * 2); // account for border
764 min-height: (@gravatar-size + @border-thickness * 2); // account for border
763 display: block;
765 display: block;
764 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
766 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
765
767
766
768
767 .gravatar {
769 .gravatar {
768 display: block;
770 display: block;
769 position: absolute;
771 position: absolute;
770 top: 0;
772 top: 0;
771 left: 0;
773 left: 0;
772 min-width: @gravatar-size;
774 min-width: @gravatar-size;
773 min-height: @gravatar-size;
775 min-height: @gravatar-size;
774 margin: 0;
776 margin: 0;
775 }
777 }
776
778
777 .user {
779 .user {
778 display: block;
780 display: block;
779 max-width: 175px;
781 max-width: 175px;
780 padding-top: 2px;
782 padding-top: 2px;
781 overflow: hidden;
783 overflow: hidden;
782 text-overflow: ellipsis;
784 text-overflow: ellipsis;
783 }
785 }
784 }
786 }
785
787
786 .gist-gravatar,
788 .gist-gravatar,
787 .journal_container {
789 .journal_container {
788 .gravatar-large {
790 .gravatar-large {
789 margin: 0 .5em -10px 0;
791 margin: 0 .5em -10px 0;
790 }
792 }
791 }
793 }
792
794
793
795
794 // ADMIN SETTINGS
796 // ADMIN SETTINGS
795
797
796 // Tag Patterns
798 // Tag Patterns
797 .tag_patterns {
799 .tag_patterns {
798 .tag_input {
800 .tag_input {
799 margin-bottom: @padding;
801 margin-bottom: @padding;
800 }
802 }
801 }
803 }
802
804
803 .locked_input {
805 .locked_input {
804 position: relative;
806 position: relative;
805
807
806 input {
808 input {
807 display: inline;
809 display: inline;
808 margin: 3px 5px 0px 0px;
810 margin: 3px 5px 0px 0px;
809 }
811 }
810
812
811 br {
813 br {
812 display: none;
814 display: none;
813 }
815 }
814
816
815 .error-message {
817 .error-message {
816 float: left;
818 float: left;
817 width: 100%;
819 width: 100%;
818 }
820 }
819
821
820 .lock_input_button {
822 .lock_input_button {
821 display: inline;
823 display: inline;
822 }
824 }
823
825
824 .help-block {
826 .help-block {
825 clear: both;
827 clear: both;
826 }
828 }
827 }
829 }
828
830
829 // Notifications
831 // Notifications
830
832
831 .notifications_buttons {
833 .notifications_buttons {
832 margin: 0 0 @space 0;
834 margin: 0 0 @space 0;
833 padding: 0;
835 padding: 0;
834
836
835 .btn {
837 .btn {
836 display: inline-block;
838 display: inline-block;
837 }
839 }
838 }
840 }
839
841
840 .notification-list {
842 .notification-list {
841
843
842 div {
844 div {
843 display: inline-block;
845 display: inline-block;
844 vertical-align: middle;
846 vertical-align: middle;
845 }
847 }
846
848
847 .container {
849 .container {
848 display: block;
850 display: block;
849 margin: 0 0 @padding 0;
851 margin: 0 0 @padding 0;
850 }
852 }
851
853
852 .delete-notifications {
854 .delete-notifications {
853 margin-left: @padding;
855 margin-left: @padding;
854 text-align: right;
856 text-align: right;
855 cursor: pointer;
857 cursor: pointer;
856 }
858 }
857
859
858 .read-notifications {
860 .read-notifications {
859 margin-left: @padding/2;
861 margin-left: @padding/2;
860 text-align: right;
862 text-align: right;
861 width: 35px;
863 width: 35px;
862 cursor: pointer;
864 cursor: pointer;
863 }
865 }
864
866
865 .icon-minus-sign {
867 .icon-minus-sign {
866 color: @alert2;
868 color: @alert2;
867 }
869 }
868
870
869 .icon-ok-sign {
871 .icon-ok-sign {
870 color: @alert1;
872 color: @alert1;
871 }
873 }
872 }
874 }
873
875
874 .user_settings {
876 .user_settings {
875 float: left;
877 float: left;
876 clear: both;
878 clear: both;
877 display: block;
879 display: block;
878 width: 100%;
880 width: 100%;
879
881
880 .gravatar_box {
882 .gravatar_box {
881 margin-bottom: @padding;
883 margin-bottom: @padding;
882
884
883 &:after {
885 &:after {
884 content: " ";
886 content: " ";
885 clear: both;
887 clear: both;
886 width: 100%;
888 width: 100%;
887 }
889 }
888 }
890 }
889
891
890 .fields .field {
892 .fields .field {
891 clear: both;
893 clear: both;
892 }
894 }
893 }
895 }
894
896
895 .advanced_settings {
897 .advanced_settings {
896 margin-bottom: @space;
898 margin-bottom: @space;
897
899
898 .help-block {
900 .help-block {
899 margin-left: 0;
901 margin-left: 0;
900 }
902 }
901
903
902 button + .help-block {
904 button + .help-block {
903 margin-top: @padding;
905 margin-top: @padding;
904 }
906 }
905 }
907 }
906
908
907 // admin settings radio buttons and labels
909 // admin settings radio buttons and labels
908 .label-2 {
910 .label-2 {
909 float: left;
911 float: left;
910 width: @label2-width;
912 width: @label2-width;
911
913
912 label {
914 label {
913 color: @grey1;
915 color: @grey1;
914 }
916 }
915 }
917 }
916 .checkboxes {
918 .checkboxes {
917 float: left;
919 float: left;
918 width: @checkboxes-width;
920 width: @checkboxes-width;
919 margin-bottom: @padding;
921 margin-bottom: @padding;
920
922
921 .checkbox {
923 .checkbox {
922 width: 100%;
924 width: 100%;
923
925
924 label {
926 label {
925 margin: 0;
927 margin: 0;
926 padding: 0;
928 padding: 0;
927 }
929 }
928 }
930 }
929
931
930 .checkbox + .checkbox {
932 .checkbox + .checkbox {
931 display: inline-block;
933 display: inline-block;
932 }
934 }
933
935
934 label {
936 label {
935 margin-right: 1em;
937 margin-right: 1em;
936 }
938 }
937 }
939 }
938
940
939 // CHANGELOG
941 // CHANGELOG
940 .container_header {
942 .container_header {
941 float: left;
943 float: left;
942 display: block;
944 display: block;
943 width: 100%;
945 width: 100%;
944 margin: @padding 0 @padding;
946 margin: @padding 0 @padding;
945
947
946 #filter_changelog {
948 #filter_changelog {
947 float: left;
949 float: left;
948 margin-right: @padding;
950 margin-right: @padding;
949 }
951 }
950
952
951 .breadcrumbs_light {
953 .breadcrumbs_light {
952 display: inline-block;
954 display: inline-block;
953 }
955 }
954 }
956 }
955
957
956 .info_box {
958 .info_box {
957 float: right;
959 float: right;
958 }
960 }
959
961
960
962
961 #graph_nodes {
963 #graph_nodes {
962 padding-top: 43px;
964 padding-top: 43px;
963 }
965 }
964
966
965 #graph_content{
967 #graph_content{
966
968
967 // adjust for table headers so that graph renders properly
969 // adjust for table headers so that graph renders properly
968 // #graph_nodes padding - table cell padding
970 // #graph_nodes padding - table cell padding
969 padding-top: (@space - (@basefontsize * 2.4));
971 padding-top: (@space - (@basefontsize * 2.4));
970
972
971 &.graph_full_width {
973 &.graph_full_width {
972 width: 100%;
974 width: 100%;
973 max-width: 100%;
975 max-width: 100%;
974 }
976 }
975 }
977 }
976
978
977 #graph {
979 #graph {
978 .flag_status {
980 .flag_status {
979 margin: 0;
981 margin: 0;
980 }
982 }
981
983
982 .pagination-left {
984 .pagination-left {
983 float: left;
985 float: left;
984 clear: both;
986 clear: both;
985 }
987 }
986
988
987 .log-container {
989 .log-container {
988 max-width: 345px;
990 max-width: 345px;
989
991
990 .message{
992 .message{
991 max-width: 340px;
993 max-width: 340px;
992 }
994 }
993 }
995 }
994
996
995 .graph-col-wrapper {
997 .graph-col-wrapper {
996 padding-left: 110px;
998 padding-left: 110px;
997
999
998 #graph_nodes {
1000 #graph_nodes {
999 width: 100px;
1001 width: 100px;
1000 margin-left: -110px;
1002 margin-left: -110px;
1001 float: left;
1003 float: left;
1002 clear: left;
1004 clear: left;
1003 }
1005 }
1004 }
1006 }
1005
1007
1006 .load-more-commits {
1008 .load-more-commits {
1007 text-align: center;
1009 text-align: center;
1008 }
1010 }
1009 .load-more-commits:hover {
1011 .load-more-commits:hover {
1010 background-color: @grey7;
1012 background-color: @grey7;
1011 }
1013 }
1012 .load-more-commits {
1014 .load-more-commits {
1013 a {
1015 a {
1014 display: block;
1016 display: block;
1015 }
1017 }
1016 }
1018 }
1017 }
1019 }
1018
1020
1019 #filter_changelog {
1021 #filter_changelog {
1020 float: left;
1022 float: left;
1021 }
1023 }
1022
1024
1023
1025
1024 //--- THEME ------------------//
1026 //--- THEME ------------------//
1025
1027
1026 #logo {
1028 #logo {
1027 float: left;
1029 float: left;
1028 margin: 9px 0 0 0;
1030 margin: 9px 0 0 0;
1029
1031
1030 .header {
1032 .header {
1031 background-color: transparent;
1033 background-color: transparent;
1032 }
1034 }
1033
1035
1034 a {
1036 a {
1035 display: inline-block;
1037 display: inline-block;
1036 }
1038 }
1037
1039
1038 img {
1040 img {
1039 height:30px;
1041 height:30px;
1040 }
1042 }
1041 }
1043 }
1042
1044
1043 .logo-wrapper {
1045 .logo-wrapper {
1044 float:left;
1046 float:left;
1045 }
1047 }
1046
1048
1047 .branding {
1049 .branding {
1048 float: left;
1050 float: left;
1049 padding: 9px 2px;
1051 padding: 9px 2px;
1050 line-height: 1em;
1052 line-height: 1em;
1051 font-size: @navigation-fontsize;
1053 font-size: @navigation-fontsize;
1052
1054
1053 a {
1055 a {
1054 color: @grey5
1056 color: @grey5
1055 }
1057 }
1056 }
1058 }
1057
1059
1058 img {
1060 img {
1059 border: none;
1061 border: none;
1060 outline: none;
1062 outline: none;
1061 }
1063 }
1062 user-profile-header
1064 user-profile-header
1063 label {
1065 label {
1064
1066
1065 input[type="checkbox"] {
1067 input[type="checkbox"] {
1066 margin-right: 1em;
1068 margin-right: 1em;
1067 }
1069 }
1068 input[type="radio"] {
1070 input[type="radio"] {
1069 margin-right: 1em;
1071 margin-right: 1em;
1070 }
1072 }
1071 }
1073 }
1072
1074
1073 .flag_status {
1075 .flag_status {
1074 margin: 2px;
1076 margin: 2px;
1075 &.under_review {
1077 &.under_review {
1076 .circle(5px, @alert3);
1078 .circle(5px, @alert3);
1077 }
1079 }
1078 &.approved {
1080 &.approved {
1079 .circle(5px, @alert1);
1081 .circle(5px, @alert1);
1080 }
1082 }
1081 &.rejected,
1083 &.rejected,
1082 &.forced_closed{
1084 &.forced_closed{
1083 .circle(5px, @alert2);
1085 .circle(5px, @alert2);
1084 }
1086 }
1085 &.not_reviewed {
1087 &.not_reviewed {
1086 .circle(5px, @grey5);
1088 .circle(5px, @grey5);
1087 }
1089 }
1088 }
1090 }
1089
1091
1090 .flag_status_comment_box {
1092 .flag_status_comment_box {
1091 margin: 5px 6px 0px 2px;
1093 margin: 5px 6px 0px 2px;
1092 }
1094 }
1093 .test_pattern_preview {
1095 .test_pattern_preview {
1094 margin: @space 0;
1096 margin: @space 0;
1095
1097
1096 p {
1098 p {
1097 margin-bottom: 0;
1099 margin-bottom: 0;
1098 border-bottom: @border-thickness solid @border-default-color;
1100 border-bottom: @border-thickness solid @border-default-color;
1099 color: @grey3;
1101 color: @grey3;
1100 }
1102 }
1101
1103
1102 .btn {
1104 .btn {
1103 margin-bottom: @padding;
1105 margin-bottom: @padding;
1104 }
1106 }
1105 }
1107 }
1106 #test_pattern_result {
1108 #test_pattern_result {
1107 display: none;
1109 display: none;
1108 &:extend(pre);
1110 &:extend(pre);
1109 padding: .9em;
1111 padding: .9em;
1110 color: @grey3;
1112 color: @grey3;
1111 background-color: @grey7;
1113 background-color: @grey7;
1112 border-right: @border-thickness solid @border-default-color;
1114 border-right: @border-thickness solid @border-default-color;
1113 border-bottom: @border-thickness solid @border-default-color;
1115 border-bottom: @border-thickness solid @border-default-color;
1114 border-left: @border-thickness solid @border-default-color;
1116 border-left: @border-thickness solid @border-default-color;
1115 }
1117 }
1116
1118
1117 #repo_vcs_settings {
1119 #repo_vcs_settings {
1118 #inherit_overlay_vcs_default {
1120 #inherit_overlay_vcs_default {
1119 display: none;
1121 display: none;
1120 }
1122 }
1121 #inherit_overlay_vcs_custom {
1123 #inherit_overlay_vcs_custom {
1122 display: custom;
1124 display: custom;
1123 }
1125 }
1124 &.inherited {
1126 &.inherited {
1125 #inherit_overlay_vcs_default {
1127 #inherit_overlay_vcs_default {
1126 display: block;
1128 display: block;
1127 }
1129 }
1128 #inherit_overlay_vcs_custom {
1130 #inherit_overlay_vcs_custom {
1129 display: none;
1131 display: none;
1130 }
1132 }
1131 }
1133 }
1132 }
1134 }
1133
1135
1134 .issue-tracker-link {
1136 .issue-tracker-link {
1135 color: @rcblue;
1137 color: @rcblue;
1136 }
1138 }
1137
1139
1138 // Issue Tracker Table Show/Hide
1140 // Issue Tracker Table Show/Hide
1139 #repo_issue_tracker {
1141 #repo_issue_tracker {
1140 #inherit_overlay {
1142 #inherit_overlay {
1141 display: none;
1143 display: none;
1142 }
1144 }
1143 #custom_overlay {
1145 #custom_overlay {
1144 display: custom;
1146 display: custom;
1145 }
1147 }
1146 &.inherited {
1148 &.inherited {
1147 #inherit_overlay {
1149 #inherit_overlay {
1148 display: block;
1150 display: block;
1149 }
1151 }
1150 #custom_overlay {
1152 #custom_overlay {
1151 display: none;
1153 display: none;
1152 }
1154 }
1153 }
1155 }
1154 }
1156 }
1155 table.issuetracker {
1157 table.issuetracker {
1156 &.readonly {
1158 &.readonly {
1157 tr, td {
1159 tr, td {
1158 color: @grey3;
1160 color: @grey3;
1159 }
1161 }
1160 }
1162 }
1161 .edit {
1163 .edit {
1162 display: none;
1164 display: none;
1163 }
1165 }
1164 .editopen {
1166 .editopen {
1165 .edit {
1167 .edit {
1166 display: inline;
1168 display: inline;
1167 }
1169 }
1168 .entry {
1170 .entry {
1169 display: none;
1171 display: none;
1170 }
1172 }
1171 }
1173 }
1172 tr td.td-action {
1174 tr td.td-action {
1173 min-width: 117px;
1175 min-width: 117px;
1174 }
1176 }
1175 td input {
1177 td input {
1176 max-width: none;
1178 max-width: none;
1177 min-width: 30px;
1179 min-width: 30px;
1178 width: 80%;
1180 width: 80%;
1179 }
1181 }
1180 .issuetracker_pref input {
1182 .issuetracker_pref input {
1181 width: 40%;
1183 width: 40%;
1182 }
1184 }
1183 input.edit_issuetracker_update {
1185 input.edit_issuetracker_update {
1184 margin-right: 0;
1186 margin-right: 0;
1185 width: auto;
1187 width: auto;
1186 }
1188 }
1187 }
1189 }
1188
1190
1189 table.integrations {
1191 table.integrations {
1190 .td-icon {
1192 .td-icon {
1191 width: 20px;
1193 width: 20px;
1192 .integration-icon {
1194 .integration-icon {
1193 height: 20px;
1195 height: 20px;
1194 width: 20px;
1196 width: 20px;
1195 }
1197 }
1196 }
1198 }
1197 }
1199 }
1198
1200
1199 .integrations {
1201 .integrations {
1200 a.integration-box {
1202 a.integration-box {
1201 color: @text-color;
1203 color: @text-color;
1202 &:hover {
1204 &:hover {
1203 .panel {
1205 .panel {
1204 background: #fbfbfb;
1206 background: #fbfbfb;
1205 }
1207 }
1206 }
1208 }
1207 .integration-icon {
1209 .integration-icon {
1208 width: 30px;
1210 width: 30px;
1209 height: 30px;
1211 height: 30px;
1210 margin-right: 20px;
1212 margin-right: 20px;
1211 float: left;
1213 float: left;
1212 }
1214 }
1213
1215
1214 .panel-body {
1216 .panel-body {
1215 padding: 10px;
1217 padding: 10px;
1216 }
1218 }
1217 .panel {
1219 .panel {
1218 margin-bottom: 10px;
1220 margin-bottom: 10px;
1219 }
1221 }
1220 h2 {
1222 h2 {
1221 display: inline-block;
1223 display: inline-block;
1222 margin: 0;
1224 margin: 0;
1223 min-width: 140px;
1225 min-width: 140px;
1224 }
1226 }
1225 }
1227 }
1226 a.integration-box.dummy-integration {
1228 a.integration-box.dummy-integration {
1227 color: @grey4
1229 color: @grey4
1228 }
1230 }
1229 }
1231 }
1230
1232
1231 //Permissions Settings
1233 //Permissions Settings
1232 #add_perm {
1234 #add_perm {
1233 margin: 0 0 @padding;
1235 margin: 0 0 @padding;
1234 cursor: pointer;
1236 cursor: pointer;
1235 }
1237 }
1236
1238
1237 .perm_ac {
1239 .perm_ac {
1238 input {
1240 input {
1239 width: 95%;
1241 width: 95%;
1240 }
1242 }
1241 }
1243 }
1242
1244
1243 .autocomplete-suggestions {
1245 .autocomplete-suggestions {
1244 width: auto !important; // overrides autocomplete.js
1246 width: auto !important; // overrides autocomplete.js
1245 min-width: 278px;
1247 min-width: 278px;
1246 margin: 0;
1248 margin: 0;
1247 border: @border-thickness solid @grey5;
1249 border: @border-thickness solid @grey5;
1248 border-radius: @border-radius;
1250 border-radius: @border-radius;
1249 color: @grey2;
1251 color: @grey2;
1250 background-color: white;
1252 background-color: white;
1251 }
1253 }
1252
1254
1253 .autocomplete-qfilter-suggestions {
1255 .autocomplete-qfilter-suggestions {
1254 width: auto !important; // overrides autocomplete.js
1256 width: auto !important; // overrides autocomplete.js
1255 max-height: 100% !important;
1257 max-height: 100% !important;
1256 min-width: 376px;
1258 min-width: 376px;
1257 margin: 0;
1259 margin: 0;
1258 border: @border-thickness solid @grey5;
1260 border: @border-thickness solid @grey5;
1259 color: @grey2;
1261 color: @grey2;
1260 background-color: white;
1262 background-color: white;
1261 }
1263 }
1262
1264
1263 .autocomplete-selected {
1265 .autocomplete-selected {
1264 background: #F0F0F0;
1266 background: #F0F0F0;
1265 }
1267 }
1266
1268
1267 .ac-container-wrap {
1269 .ac-container-wrap {
1268 margin: 0;
1270 margin: 0;
1269 padding: 8px;
1271 padding: 8px;
1270 border-bottom: @border-thickness solid @grey5;
1272 border-bottom: @border-thickness solid @grey5;
1271 list-style-type: none;
1273 list-style-type: none;
1272 cursor: pointer;
1274 cursor: pointer;
1273
1275
1274 &:hover {
1276 &:hover {
1275 background-color: @grey7;
1277 background-color: @grey7;
1276 }
1278 }
1277
1279
1278 img {
1280 img {
1279 height: @gravatar-size;
1281 height: @gravatar-size;
1280 width: @gravatar-size;
1282 width: @gravatar-size;
1281 margin-right: 1em;
1283 margin-right: 1em;
1282 }
1284 }
1283
1285
1284 strong {
1286 strong {
1285 font-weight: normal;
1287 font-weight: normal;
1286 }
1288 }
1287 }
1289 }
1288
1290
1289 // Settings Dropdown
1291 // Settings Dropdown
1290 .user-menu .container {
1292 .user-menu .container {
1291 padding: 0 4px;
1293 padding: 0 4px;
1292 margin: 0;
1294 margin: 0;
1293 }
1295 }
1294
1296
1295 .user-menu .gravatar {
1297 .user-menu .gravatar {
1296 cursor: pointer;
1298 cursor: pointer;
1297 }
1299 }
1298
1300
1299 .codeblock {
1301 .codeblock {
1300 margin-bottom: @padding;
1302 margin-bottom: @padding;
1301 clear: both;
1303 clear: both;
1302
1304
1303 .stats {
1305 .stats {
1304 overflow: hidden;
1306 overflow: hidden;
1305 }
1307 }
1306
1308
1307 .message{
1309 .message{
1308 textarea{
1310 textarea{
1309 margin: 0;
1311 margin: 0;
1310 }
1312 }
1311 }
1313 }
1312
1314
1313 .code-header {
1315 .code-header {
1314 .stats {
1316 .stats {
1315 line-height: 2em;
1317 line-height: 2em;
1316
1318
1317 .revision_id {
1319 .revision_id {
1318 margin-left: 0;
1320 margin-left: 0;
1319 }
1321 }
1320 .buttons {
1322 .buttons {
1321 padding-right: 0;
1323 padding-right: 0;
1322 }
1324 }
1323 }
1325 }
1324
1326
1325 .item{
1327 .item{
1326 margin-right: 0.5em;
1328 margin-right: 0.5em;
1327 }
1329 }
1328 }
1330 }
1329
1331
1330 #editor_container{
1332 #editor_container {
1331 position: relative;
1333 position: relative;
1332 margin: @padding;
1334 margin: @padding 10px;
1333 }
1335 }
1334 }
1336 }
1335
1337
1336 #file_history_container {
1338 #file_history_container {
1337 display: none;
1339 display: none;
1338 }
1340 }
1339
1341
1340 .file-history-inner {
1342 .file-history-inner {
1341 margin-bottom: 10px;
1343 margin-bottom: 10px;
1342 }
1344 }
1343
1345
1344 // Pull Requests
1346 // Pull Requests
1345 .summary-details {
1347 .summary-details {
1346 width: 72%;
1348 width: 72%;
1347 }
1349 }
1348 .pr-summary {
1350 .pr-summary {
1349 border-bottom: @border-thickness solid @grey5;
1351 border-bottom: @border-thickness solid @grey5;
1350 margin-bottom: @space;
1352 margin-bottom: @space;
1351 }
1353 }
1352 .reviewers-title {
1354 .reviewers-title {
1353 width: 25%;
1355 width: 25%;
1354 min-width: 200px;
1356 min-width: 200px;
1355 }
1357 }
1356 .reviewers {
1358 .reviewers {
1357 width: 25%;
1359 width: 25%;
1358 min-width: 200px;
1360 min-width: 200px;
1359 }
1361 }
1360 .reviewers ul li {
1362 .reviewers ul li {
1361 position: relative;
1363 position: relative;
1362 width: 100%;
1364 width: 100%;
1363 padding-bottom: 8px;
1365 padding-bottom: 8px;
1364 list-style-type: none;
1366 list-style-type: none;
1365 }
1367 }
1366
1368
1367 .reviewer_entry {
1369 .reviewer_entry {
1368 min-height: 55px;
1370 min-height: 55px;
1369 }
1371 }
1370
1372
1371 .reviewers_member {
1373 .reviewers_member {
1372 width: 100%;
1374 width: 100%;
1373 overflow: auto;
1375 overflow: auto;
1374 }
1376 }
1375 .reviewer_reason {
1377 .reviewer_reason {
1376 padding-left: 20px;
1378 padding-left: 20px;
1377 line-height: 1.5em;
1379 line-height: 1.5em;
1378 }
1380 }
1379 .reviewer_status {
1381 .reviewer_status {
1380 display: inline-block;
1382 display: inline-block;
1381 vertical-align: top;
1383 vertical-align: top;
1382 width: 25px;
1384 width: 25px;
1383 min-width: 25px;
1385 min-width: 25px;
1384 height: 1.2em;
1386 height: 1.2em;
1385 margin-top: 3px;
1387 margin-top: 3px;
1386 line-height: 1em;
1388 line-height: 1em;
1387 }
1389 }
1388
1390
1389 .reviewer_name {
1391 .reviewer_name {
1390 display: inline-block;
1392 display: inline-block;
1391 max-width: 83%;
1393 max-width: 83%;
1392 padding-right: 20px;
1394 padding-right: 20px;
1393 vertical-align: middle;
1395 vertical-align: middle;
1394 line-height: 1;
1396 line-height: 1;
1395
1397
1396 .rc-user {
1398 .rc-user {
1397 min-width: 0;
1399 min-width: 0;
1398 margin: -2px 1em 0 0;
1400 margin: -2px 1em 0 0;
1399 }
1401 }
1400
1402
1401 .reviewer {
1403 .reviewer {
1402 float: left;
1404 float: left;
1403 }
1405 }
1404 }
1406 }
1405
1407
1406 .reviewer_member_mandatory {
1408 .reviewer_member_mandatory {
1407 position: absolute;
1409 position: absolute;
1408 left: 15px;
1410 left: 15px;
1409 top: 8px;
1411 top: 8px;
1410 width: 16px;
1412 width: 16px;
1411 font-size: 11px;
1413 font-size: 11px;
1412 margin: 0;
1414 margin: 0;
1413 padding: 0;
1415 padding: 0;
1414 color: black;
1416 color: black;
1415 }
1417 }
1416
1418
1417 .reviewer_member_mandatory_remove,
1419 .reviewer_member_mandatory_remove,
1418 .reviewer_member_remove {
1420 .reviewer_member_remove {
1419 position: absolute;
1421 position: absolute;
1420 right: 0;
1422 right: 0;
1421 top: 0;
1423 top: 0;
1422 width: 16px;
1424 width: 16px;
1423 margin-bottom: 10px;
1425 margin-bottom: 10px;
1424 padding: 0;
1426 padding: 0;
1425 color: black;
1427 color: black;
1426 }
1428 }
1427
1429
1428 .reviewer_member_mandatory_remove {
1430 .reviewer_member_mandatory_remove {
1429 color: @grey4;
1431 color: @grey4;
1430 }
1432 }
1431
1433
1432 .reviewer_member_status {
1434 .reviewer_member_status {
1433 margin-top: 5px;
1435 margin-top: 5px;
1434 }
1436 }
1435 .pr-summary #summary{
1437 .pr-summary #summary{
1436 width: 100%;
1438 width: 100%;
1437 }
1439 }
1438 .pr-summary .action_button:hover {
1440 .pr-summary .action_button:hover {
1439 border: 0;
1441 border: 0;
1440 cursor: pointer;
1442 cursor: pointer;
1441 }
1443 }
1442 .pr-details-title {
1444 .pr-details-title {
1443 padding-bottom: 8px;
1445 padding-bottom: 8px;
1444 border-bottom: @border-thickness solid @grey5;
1446 border-bottom: @border-thickness solid @grey5;
1445
1447
1446 .action_button.disabled {
1448 .action_button.disabled {
1447 color: @grey4;
1449 color: @grey4;
1448 cursor: inherit;
1450 cursor: inherit;
1449 }
1451 }
1450 .action_button {
1452 .action_button {
1451 color: @rcblue;
1453 color: @rcblue;
1452 }
1454 }
1453 }
1455 }
1454 .pr-details-content {
1456 .pr-details-content {
1455 margin-top: @textmargin;
1457 margin-top: @textmargin;
1456 margin-bottom: @textmargin;
1458 margin-bottom: @textmargin;
1457 }
1459 }
1458
1460
1459 .pr-reviewer-rules {
1461 .pr-reviewer-rules {
1460 padding: 10px 0px 20px 0px;
1462 padding: 10px 0px 20px 0px;
1461 }
1463 }
1462
1464
1463 .group_members {
1465 .group_members {
1464 margin-top: 0;
1466 margin-top: 0;
1465 padding: 0;
1467 padding: 0;
1466 list-style: outside none none;
1468 list-style: outside none none;
1467
1469
1468 img {
1470 img {
1469 height: @gravatar-size;
1471 height: @gravatar-size;
1470 width: @gravatar-size;
1472 width: @gravatar-size;
1471 margin-right: .5em;
1473 margin-right: .5em;
1472 margin-left: 3px;
1474 margin-left: 3px;
1473 }
1475 }
1474
1476
1475 .to-delete {
1477 .to-delete {
1476 .user {
1478 .user {
1477 text-decoration: line-through;
1479 text-decoration: line-through;
1478 }
1480 }
1479 }
1481 }
1480 }
1482 }
1481
1483
1482 .compare_view_commits_title {
1484 .compare_view_commits_title {
1483 .disabled {
1485 .disabled {
1484 cursor: inherit;
1486 cursor: inherit;
1485 &:hover{
1487 &:hover{
1486 background-color: inherit;
1488 background-color: inherit;
1487 color: inherit;
1489 color: inherit;
1488 }
1490 }
1489 }
1491 }
1490 }
1492 }
1491
1493
1492 .subtitle-compare {
1494 .subtitle-compare {
1493 margin: -15px 0px 0px 0px;
1495 margin: -15px 0px 0px 0px;
1494 }
1496 }
1495
1497
1496 .comments-summary-td {
1498 .comments-summary-td {
1497 border-top: 1px dashed @grey5;
1499 border-top: 1px dashed @grey5;
1498 }
1500 }
1499
1501
1500 // new entry in group_members
1502 // new entry in group_members
1501 .td-author-new-entry {
1503 .td-author-new-entry {
1502 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1504 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1503 }
1505 }
1504
1506
1505 .usergroup_member_remove {
1507 .usergroup_member_remove {
1506 width: 16px;
1508 width: 16px;
1507 margin-bottom: 10px;
1509 margin-bottom: 10px;
1508 padding: 0;
1510 padding: 0;
1509 color: black !important;
1511 color: black !important;
1510 cursor: pointer;
1512 cursor: pointer;
1511 }
1513 }
1512
1514
1513 .reviewer_ac .ac-input {
1515 .reviewer_ac .ac-input {
1514 width: 92%;
1516 width: 92%;
1515 margin-bottom: 1em;
1517 margin-bottom: 1em;
1516 }
1518 }
1517
1519
1518 .compare_view_commits tr{
1520 .compare_view_commits tr{
1519 height: 20px;
1521 height: 20px;
1520 }
1522 }
1521 .compare_view_commits td {
1523 .compare_view_commits td {
1522 vertical-align: top;
1524 vertical-align: top;
1523 padding-top: 10px;
1525 padding-top: 10px;
1524 }
1526 }
1525 .compare_view_commits .author {
1527 .compare_view_commits .author {
1526 margin-left: 5px;
1528 margin-left: 5px;
1527 }
1529 }
1528
1530
1529 .compare_view_commits {
1531 .compare_view_commits {
1530 .color-a {
1532 .color-a {
1531 color: @alert1;
1533 color: @alert1;
1532 }
1534 }
1533
1535
1534 .color-c {
1536 .color-c {
1535 color: @color3;
1537 color: @color3;
1536 }
1538 }
1537
1539
1538 .color-r {
1540 .color-r {
1539 color: @color5;
1541 color: @color5;
1540 }
1542 }
1541
1543
1542 .color-a-bg {
1544 .color-a-bg {
1543 background-color: @alert1;
1545 background-color: @alert1;
1544 }
1546 }
1545
1547
1546 .color-c-bg {
1548 .color-c-bg {
1547 background-color: @alert3;
1549 background-color: @alert3;
1548 }
1550 }
1549
1551
1550 .color-r-bg {
1552 .color-r-bg {
1551 background-color: @alert2;
1553 background-color: @alert2;
1552 }
1554 }
1553
1555
1554 .color-a-border {
1556 .color-a-border {
1555 border: 1px solid @alert1;
1557 border: 1px solid @alert1;
1556 }
1558 }
1557
1559
1558 .color-c-border {
1560 .color-c-border {
1559 border: 1px solid @alert3;
1561 border: 1px solid @alert3;
1560 }
1562 }
1561
1563
1562 .color-r-border {
1564 .color-r-border {
1563 border: 1px solid @alert2;
1565 border: 1px solid @alert2;
1564 }
1566 }
1565
1567
1566 .commit-change-indicator {
1568 .commit-change-indicator {
1567 width: 15px;
1569 width: 15px;
1568 height: 15px;
1570 height: 15px;
1569 position: relative;
1571 position: relative;
1570 left: 15px;
1572 left: 15px;
1571 }
1573 }
1572
1574
1573 .commit-change-content {
1575 .commit-change-content {
1574 text-align: center;
1576 text-align: center;
1575 vertical-align: middle;
1577 vertical-align: middle;
1576 line-height: 15px;
1578 line-height: 15px;
1577 }
1579 }
1578 }
1580 }
1579
1581
1580 .compare_view_filepath {
1582 .compare_view_filepath {
1581 color: @grey1;
1583 color: @grey1;
1582 }
1584 }
1583
1585
1584 .show_more {
1586 .show_more {
1585 display: inline-block;
1587 display: inline-block;
1586 width: 0;
1588 width: 0;
1587 height: 0;
1589 height: 0;
1588 vertical-align: middle;
1590 vertical-align: middle;
1589 content: "";
1591 content: "";
1590 border: 4px solid;
1592 border: 4px solid;
1591 border-right-color: transparent;
1593 border-right-color: transparent;
1592 border-bottom-color: transparent;
1594 border-bottom-color: transparent;
1593 border-left-color: transparent;
1595 border-left-color: transparent;
1594 font-size: 0;
1596 font-size: 0;
1595 }
1597 }
1596
1598
1597 .journal_more .show_more {
1599 .journal_more .show_more {
1598 display: inline;
1600 display: inline;
1599
1601
1600 &:after {
1602 &:after {
1601 content: none;
1603 content: none;
1602 }
1604 }
1603 }
1605 }
1604
1606
1605 .compare_view_commits .collapse_commit:after {
1607 .compare_view_commits .collapse_commit:after {
1606 cursor: pointer;
1608 cursor: pointer;
1607 content: "\00A0\25B4";
1609 content: "\00A0\25B4";
1608 margin-left: -3px;
1610 margin-left: -3px;
1609 font-size: 17px;
1611 font-size: 17px;
1610 color: @grey4;
1612 color: @grey4;
1611 }
1613 }
1612
1614
1613 .diff_links {
1615 .diff_links {
1614 margin-left: 8px;
1616 margin-left: 8px;
1615 }
1617 }
1616
1618
1617 div.ancestor {
1619 div.ancestor {
1618 margin: -30px 0px;
1620 margin: -30px 0px;
1619 }
1621 }
1620
1622
1621 .cs_icon_td input[type="checkbox"] {
1623 .cs_icon_td input[type="checkbox"] {
1622 display: none;
1624 display: none;
1623 }
1625 }
1624
1626
1625 .cs_icon_td .expand_file_icon:after {
1627 .cs_icon_td .expand_file_icon:after {
1626 cursor: pointer;
1628 cursor: pointer;
1627 content: "\00A0\25B6";
1629 content: "\00A0\25B6";
1628 font-size: 12px;
1630 font-size: 12px;
1629 color: @grey4;
1631 color: @grey4;
1630 }
1632 }
1631
1633
1632 .cs_icon_td .collapse_file_icon:after {
1634 .cs_icon_td .collapse_file_icon:after {
1633 cursor: pointer;
1635 cursor: pointer;
1634 content: "\00A0\25BC";
1636 content: "\00A0\25BC";
1635 font-size: 12px;
1637 font-size: 12px;
1636 color: @grey4;
1638 color: @grey4;
1637 }
1639 }
1638
1640
1639 /*new binary
1641 /*new binary
1640 NEW_FILENODE = 1
1642 NEW_FILENODE = 1
1641 DEL_FILENODE = 2
1643 DEL_FILENODE = 2
1642 MOD_FILENODE = 3
1644 MOD_FILENODE = 3
1643 RENAMED_FILENODE = 4
1645 RENAMED_FILENODE = 4
1644 COPIED_FILENODE = 5
1646 COPIED_FILENODE = 5
1645 CHMOD_FILENODE = 6
1647 CHMOD_FILENODE = 6
1646 BIN_FILENODE = 7
1648 BIN_FILENODE = 7
1647 */
1649 */
1648 .cs_files_expand {
1650 .cs_files_expand {
1649 font-size: @basefontsize + 5px;
1651 font-size: @basefontsize + 5px;
1650 line-height: 1.8em;
1652 line-height: 1.8em;
1651 float: right;
1653 float: right;
1652 }
1654 }
1653
1655
1654 .cs_files_expand span{
1656 .cs_files_expand span{
1655 color: @rcblue;
1657 color: @rcblue;
1656 cursor: pointer;
1658 cursor: pointer;
1657 }
1659 }
1658 .cs_files {
1660 .cs_files {
1659 clear: both;
1661 clear: both;
1660 padding-bottom: @padding;
1662 padding-bottom: @padding;
1661
1663
1662 .cur_cs {
1664 .cur_cs {
1663 margin: 10px 2px;
1665 margin: 10px 2px;
1664 font-weight: bold;
1666 font-weight: bold;
1665 }
1667 }
1666
1668
1667 .node {
1669 .node {
1668 float: left;
1670 float: left;
1669 }
1671 }
1670
1672
1671 .changes {
1673 .changes {
1672 float: right;
1674 float: right;
1673 color: white;
1675 color: white;
1674 font-size: @basefontsize - 4px;
1676 font-size: @basefontsize - 4px;
1675 margin-top: 4px;
1677 margin-top: 4px;
1676 opacity: 0.6;
1678 opacity: 0.6;
1677 filter: Alpha(opacity=60); /* IE8 and earlier */
1679 filter: Alpha(opacity=60); /* IE8 and earlier */
1678
1680
1679 .added {
1681 .added {
1680 background-color: @alert1;
1682 background-color: @alert1;
1681 float: left;
1683 float: left;
1682 text-align: center;
1684 text-align: center;
1683 }
1685 }
1684
1686
1685 .deleted {
1687 .deleted {
1686 background-color: @alert2;
1688 background-color: @alert2;
1687 float: left;
1689 float: left;
1688 text-align: center;
1690 text-align: center;
1689 }
1691 }
1690
1692
1691 .bin {
1693 .bin {
1692 background-color: @alert1;
1694 background-color: @alert1;
1693 text-align: center;
1695 text-align: center;
1694 }
1696 }
1695
1697
1696 /*new binary*/
1698 /*new binary*/
1697 .bin.bin1 {
1699 .bin.bin1 {
1698 background-color: @alert1;
1700 background-color: @alert1;
1699 text-align: center;
1701 text-align: center;
1700 }
1702 }
1701
1703
1702 /*deleted binary*/
1704 /*deleted binary*/
1703 .bin.bin2 {
1705 .bin.bin2 {
1704 background-color: @alert2;
1706 background-color: @alert2;
1705 text-align: center;
1707 text-align: center;
1706 }
1708 }
1707
1709
1708 /*mod binary*/
1710 /*mod binary*/
1709 .bin.bin3 {
1711 .bin.bin3 {
1710 background-color: @grey2;
1712 background-color: @grey2;
1711 text-align: center;
1713 text-align: center;
1712 }
1714 }
1713
1715
1714 /*rename file*/
1716 /*rename file*/
1715 .bin.bin4 {
1717 .bin.bin4 {
1716 background-color: @alert4;
1718 background-color: @alert4;
1717 text-align: center;
1719 text-align: center;
1718 }
1720 }
1719
1721
1720 /*copied file*/
1722 /*copied file*/
1721 .bin.bin5 {
1723 .bin.bin5 {
1722 background-color: @alert4;
1724 background-color: @alert4;
1723 text-align: center;
1725 text-align: center;
1724 }
1726 }
1725
1727
1726 /*chmod file*/
1728 /*chmod file*/
1727 .bin.bin6 {
1729 .bin.bin6 {
1728 background-color: @grey2;
1730 background-color: @grey2;
1729 text-align: center;
1731 text-align: center;
1730 }
1732 }
1731 }
1733 }
1732 }
1734 }
1733
1735
1734 .cs_files .cs_added, .cs_files .cs_A,
1736 .cs_files .cs_added, .cs_files .cs_A,
1735 .cs_files .cs_added, .cs_files .cs_M,
1737 .cs_files .cs_added, .cs_files .cs_M,
1736 .cs_files .cs_added, .cs_files .cs_D {
1738 .cs_files .cs_added, .cs_files .cs_D {
1737 height: 16px;
1739 height: 16px;
1738 padding-right: 10px;
1740 padding-right: 10px;
1739 margin-top: 7px;
1741 margin-top: 7px;
1740 text-align: left;
1742 text-align: left;
1741 }
1743 }
1742
1744
1743 .cs_icon_td {
1745 .cs_icon_td {
1744 min-width: 16px;
1746 min-width: 16px;
1745 width: 16px;
1747 width: 16px;
1746 }
1748 }
1747
1749
1748 .pull-request-merge {
1750 .pull-request-merge {
1749 border: 1px solid @grey5;
1751 border: 1px solid @grey5;
1750 padding: 10px 0px 20px;
1752 padding: 10px 0px 20px;
1751 margin-top: 10px;
1753 margin-top: 10px;
1752 margin-bottom: 20px;
1754 margin-bottom: 20px;
1753 }
1755 }
1754
1756
1755 .pull-request-merge ul {
1757 .pull-request-merge ul {
1756 padding: 0px 0px;
1758 padding: 0px 0px;
1757 }
1759 }
1758
1760
1759 .pull-request-merge li {
1761 .pull-request-merge li {
1760 list-style-type: none;
1762 list-style-type: none;
1761 }
1763 }
1762
1764
1763 .pull-request-merge .pull-request-wrap {
1765 .pull-request-merge .pull-request-wrap {
1764 height: auto;
1766 height: auto;
1765 padding: 0px 0px;
1767 padding: 0px 0px;
1766 text-align: right;
1768 text-align: right;
1767 }
1769 }
1768
1770
1769 .pull-request-merge span {
1771 .pull-request-merge span {
1770 margin-right: 5px;
1772 margin-right: 5px;
1771 }
1773 }
1772
1774
1773 .pull-request-merge-actions {
1775 .pull-request-merge-actions {
1774 min-height: 30px;
1776 min-height: 30px;
1775 padding: 0px 0px;
1777 padding: 0px 0px;
1776 }
1778 }
1777
1779
1778 .pull-request-merge-info {
1780 .pull-request-merge-info {
1779 padding: 0px 5px 5px 0px;
1781 padding: 0px 5px 5px 0px;
1780 }
1782 }
1781
1783
1782 .merge-status {
1784 .merge-status {
1783 margin-right: 5px;
1785 margin-right: 5px;
1784 }
1786 }
1785
1787
1786 .merge-message {
1788 .merge-message {
1787 font-size: 1.2em
1789 font-size: 1.2em
1788 }
1790 }
1789
1791
1790 .merge-message.success i,
1792 .merge-message.success i,
1791 .merge-icon.success i {
1793 .merge-icon.success i {
1792 color:@alert1;
1794 color:@alert1;
1793 }
1795 }
1794
1796
1795 .merge-message.warning i,
1797 .merge-message.warning i,
1796 .merge-icon.warning i {
1798 .merge-icon.warning i {
1797 color: @alert3;
1799 color: @alert3;
1798 }
1800 }
1799
1801
1800 .merge-message.error i,
1802 .merge-message.error i,
1801 .merge-icon.error i {
1803 .merge-icon.error i {
1802 color:@alert2;
1804 color:@alert2;
1803 }
1805 }
1804
1806
1805 .pr-versions {
1807 .pr-versions {
1806 font-size: 1.1em;
1808 font-size: 1.1em;
1807
1809
1808 table {
1810 table {
1809 padding: 0px 5px;
1811 padding: 0px 5px;
1810 }
1812 }
1811
1813
1812 td {
1814 td {
1813 line-height: 15px;
1815 line-height: 15px;
1814 }
1816 }
1815
1817
1816 .flag_status {
1818 .flag_status {
1817 margin: 0;
1819 margin: 0;
1818 }
1820 }
1819
1821
1820 .compare-radio-button {
1822 .compare-radio-button {
1821 position: relative;
1823 position: relative;
1822 top: -3px;
1824 top: -3px;
1823 }
1825 }
1824 }
1826 }
1825
1827
1826
1828
1827 #close_pull_request {
1829 #close_pull_request {
1828 margin-right: 0px;
1830 margin-right: 0px;
1829 }
1831 }
1830
1832
1831 .empty_data {
1833 .empty_data {
1832 color: @grey4;
1834 color: @grey4;
1833 }
1835 }
1834
1836
1835 #changeset_compare_view_content {
1837 #changeset_compare_view_content {
1836 clear: both;
1838 clear: both;
1837 width: 100%;
1839 width: 100%;
1838 box-sizing: border-box;
1840 box-sizing: border-box;
1839 .border-radius(@border-radius);
1841 .border-radius(@border-radius);
1840
1842
1841 .help-block {
1843 .help-block {
1842 margin: @padding 0;
1844 margin: @padding 0;
1843 color: @text-color;
1845 color: @text-color;
1844 &.pre-formatting {
1846 &.pre-formatting {
1845 white-space: pre;
1847 white-space: pre;
1846 }
1848 }
1847 }
1849 }
1848
1850
1849 .empty_data {
1851 .empty_data {
1850 margin: @padding 0;
1852 margin: @padding 0;
1851 }
1853 }
1852
1854
1853 .alert {
1855 .alert {
1854 margin-bottom: @space;
1856 margin-bottom: @space;
1855 }
1857 }
1856 }
1858 }
1857
1859
1858 .table_disp {
1860 .table_disp {
1859 .status {
1861 .status {
1860 width: auto;
1862 width: auto;
1861
1863
1862 .flag_status {
1864 .flag_status {
1863 float: left;
1865 float: left;
1864 }
1866 }
1865 }
1867 }
1866 }
1868 }
1867
1869
1868
1870
1869 .creation_in_progress {
1871 .creation_in_progress {
1870 color: @grey4
1872 color: @grey4
1871 }
1873 }
1872
1874
1873 .status_box_menu {
1875 .status_box_menu {
1874 margin: 0;
1876 margin: 0;
1875 }
1877 }
1876
1878
1877 .notification-table{
1879 .notification-table{
1878 margin-bottom: @space;
1880 margin-bottom: @space;
1879 display: table;
1881 display: table;
1880 width: 100%;
1882 width: 100%;
1881
1883
1882 .container{
1884 .container{
1883 display: table-row;
1885 display: table-row;
1884
1886
1885 .notification-header{
1887 .notification-header{
1886 border-bottom: @border-thickness solid @border-default-color;
1888 border-bottom: @border-thickness solid @border-default-color;
1887 }
1889 }
1888
1890
1889 .notification-subject{
1891 .notification-subject{
1890 display: table-cell;
1892 display: table-cell;
1891 }
1893 }
1892 }
1894 }
1893 }
1895 }
1894
1896
1895 // Notifications
1897 // Notifications
1896 .notification-header{
1898 .notification-header{
1897 display: table;
1899 display: table;
1898 width: 100%;
1900 width: 100%;
1899 padding: floor(@basefontsize/2) 0;
1901 padding: floor(@basefontsize/2) 0;
1900 line-height: 1em;
1902 line-height: 1em;
1901
1903
1902 .desc, .delete-notifications, .read-notifications{
1904 .desc, .delete-notifications, .read-notifications{
1903 display: table-cell;
1905 display: table-cell;
1904 text-align: left;
1906 text-align: left;
1905 }
1907 }
1906
1908
1907 .desc{
1909 .desc{
1908 width: 1163px;
1910 width: 1163px;
1909 }
1911 }
1910
1912
1911 .delete-notifications, .read-notifications{
1913 .delete-notifications, .read-notifications{
1912 width: 35px;
1914 width: 35px;
1913 min-width: 35px; //fixes when only one button is displayed
1915 min-width: 35px; //fixes when only one button is displayed
1914 }
1916 }
1915 }
1917 }
1916
1918
1917 .notification-body {
1919 .notification-body {
1918 .markdown-block,
1920 .markdown-block,
1919 .rst-block {
1921 .rst-block {
1920 padding: @padding 0;
1922 padding: @padding 0;
1921 }
1923 }
1922
1924
1923 .notification-subject {
1925 .notification-subject {
1924 padding: @textmargin 0;
1926 padding: @textmargin 0;
1925 border-bottom: @border-thickness solid @border-default-color;
1927 border-bottom: @border-thickness solid @border-default-color;
1926 }
1928 }
1927 }
1929 }
1928
1930
1929
1931
1930 .notifications_buttons{
1932 .notifications_buttons{
1931 float: right;
1933 float: right;
1932 }
1934 }
1933
1935
1934 #notification-status{
1936 #notification-status{
1935 display: inline;
1937 display: inline;
1936 }
1938 }
1937
1939
1938 // Repositories
1940 // Repositories
1939
1941
1940 #summary.fields{
1942 #summary.fields{
1941 display: table;
1943 display: table;
1942
1944
1943 .field{
1945 .field{
1944 display: table-row;
1946 display: table-row;
1945
1947
1946 .label-summary{
1948 .label-summary{
1947 display: table-cell;
1949 display: table-cell;
1948 min-width: @label-summary-minwidth;
1950 min-width: @label-summary-minwidth;
1949 padding-top: @padding/2;
1951 padding-top: @padding/2;
1950 padding-bottom: @padding/2;
1952 padding-bottom: @padding/2;
1951 padding-right: @padding/2;
1953 padding-right: @padding/2;
1952 }
1954 }
1953
1955
1954 .input{
1956 .input{
1955 display: table-cell;
1957 display: table-cell;
1956 padding: @padding/2;
1958 padding: @padding/2;
1957
1959
1958 input{
1960 input{
1959 min-width: 29em;
1961 min-width: 29em;
1960 padding: @padding/4;
1962 padding: @padding/4;
1961 }
1963 }
1962 }
1964 }
1963 .statistics, .downloads{
1965 .statistics, .downloads{
1964 .disabled{
1966 .disabled{
1965 color: @grey4;
1967 color: @grey4;
1966 }
1968 }
1967 }
1969 }
1968 }
1970 }
1969 }
1971 }
1970
1972
1971 #summary{
1973 #summary{
1972 width: 70%;
1974 width: 70%;
1973 }
1975 }
1974
1976
1975
1977
1976 // Journal
1978 // Journal
1977 .journal.title {
1979 .journal.title {
1978 h5 {
1980 h5 {
1979 float: left;
1981 float: left;
1980 margin: 0;
1982 margin: 0;
1981 width: 70%;
1983 width: 70%;
1982 }
1984 }
1983
1985
1984 ul {
1986 ul {
1985 float: right;
1987 float: right;
1986 display: inline-block;
1988 display: inline-block;
1987 margin: 0;
1989 margin: 0;
1988 width: 30%;
1990 width: 30%;
1989 text-align: right;
1991 text-align: right;
1990
1992
1991 li {
1993 li {
1992 display: inline;
1994 display: inline;
1993 font-size: @journal-fontsize;
1995 font-size: @journal-fontsize;
1994 line-height: 1em;
1996 line-height: 1em;
1995
1997
1996 list-style-type: none;
1998 list-style-type: none;
1997 }
1999 }
1998 }
2000 }
1999 }
2001 }
2000
2002
2001 .filterexample {
2003 .filterexample {
2002 position: absolute;
2004 position: absolute;
2003 top: 95px;
2005 top: 95px;
2004 left: @contentpadding;
2006 left: @contentpadding;
2005 color: @rcblue;
2007 color: @rcblue;
2006 font-size: 11px;
2008 font-size: 11px;
2007 font-family: @text-regular;
2009 font-family: @text-regular;
2008 cursor: help;
2010 cursor: help;
2009
2011
2010 &:hover {
2012 &:hover {
2011 color: @rcdarkblue;
2013 color: @rcdarkblue;
2012 }
2014 }
2013
2015
2014 @media (max-width:768px) {
2016 @media (max-width:768px) {
2015 position: relative;
2017 position: relative;
2016 top: auto;
2018 top: auto;
2017 left: auto;
2019 left: auto;
2018 display: block;
2020 display: block;
2019 }
2021 }
2020 }
2022 }
2021
2023
2022
2024
2023 #journal{
2025 #journal{
2024 margin-bottom: @space;
2026 margin-bottom: @space;
2025
2027
2026 .journal_day{
2028 .journal_day{
2027 margin-bottom: @textmargin/2;
2029 margin-bottom: @textmargin/2;
2028 padding-bottom: @textmargin/2;
2030 padding-bottom: @textmargin/2;
2029 font-size: @journal-fontsize;
2031 font-size: @journal-fontsize;
2030 border-bottom: @border-thickness solid @border-default-color;
2032 border-bottom: @border-thickness solid @border-default-color;
2031 }
2033 }
2032
2034
2033 .journal_container{
2035 .journal_container{
2034 margin-bottom: @space;
2036 margin-bottom: @space;
2035
2037
2036 .journal_user{
2038 .journal_user{
2037 display: inline-block;
2039 display: inline-block;
2038 }
2040 }
2039 .journal_action_container{
2041 .journal_action_container{
2040 display: block;
2042 display: block;
2041 margin-top: @textmargin;
2043 margin-top: @textmargin;
2042
2044
2043 div{
2045 div{
2044 display: inline;
2046 display: inline;
2045 }
2047 }
2046
2048
2047 div.journal_action_params{
2049 div.journal_action_params{
2048 display: block;
2050 display: block;
2049 }
2051 }
2050
2052
2051 div.journal_repo:after{
2053 div.journal_repo:after{
2052 content: "\A";
2054 content: "\A";
2053 white-space: pre;
2055 white-space: pre;
2054 }
2056 }
2055
2057
2056 div.date{
2058 div.date{
2057 display: block;
2059 display: block;
2058 margin-bottom: @textmargin;
2060 margin-bottom: @textmargin;
2059 }
2061 }
2060 }
2062 }
2061 }
2063 }
2062 }
2064 }
2063
2065
2064 // Files
2066 // Files
2065 .edit-file-title {
2067 .edit-file-title {
2066 border-bottom: @border-thickness solid @border-default-color;
2068 font-size: 16px;
2067
2069
2068 .breadcrumbs {
2070 .title-heading {
2069 margin-bottom: 0;
2071 padding: 2px;
2070 }
2072 }
2071 }
2073 }
2072
2074
2073 .edit-file-fieldset {
2075 .edit-file-fieldset {
2074 margin-top: @sidebarpadding;
2076 margin: @sidebarpadding 0;
2075
2077
2076 .fieldset {
2078 .fieldset {
2077 .left-label {
2079 .left-label {
2078 width: 13%;
2080 width: 13%;
2079 }
2081 }
2080 .right-content {
2082 .right-content {
2081 width: 87%;
2083 width: 87%;
2082 max-width: 100%;
2084 max-width: 100%;
2083 }
2085 }
2084 .filename-label {
2086 .filename-label {
2085 margin-top: 13px;
2087 margin-top: 13px;
2086 }
2088 }
2087 .commit-message-label {
2089 .commit-message-label {
2088 margin-top: 4px;
2090 margin-top: 4px;
2089 }
2091 }
2090 .file-upload-input {
2092 .file-upload-input {
2091 input {
2093 input {
2092 display: none;
2094 display: none;
2093 }
2095 }
2094 margin-top: 10px;
2096 margin-top: 10px;
2095 }
2097 }
2096 .file-upload-label {
2098 .file-upload-label {
2097 margin-top: 10px;
2099 margin-top: 10px;
2098 }
2100 }
2099 p {
2101 p {
2100 margin-top: 5px;
2102 margin-top: 5px;
2101 }
2103 }
2102
2104
2103 }
2105 }
2104 .custom-path-link {
2106 .custom-path-link {
2105 margin-left: 5px;
2107 margin-left: 5px;
2106 }
2108 }
2107 #commit {
2109 #commit {
2108 resize: vertical;
2110 resize: vertical;
2109 }
2111 }
2110 }
2112 }
2111
2113
2112 .delete-file-preview {
2114 .delete-file-preview {
2113 max-height: 250px;
2115 max-height: 250px;
2114 }
2116 }
2115
2117
2116 .new-file,
2118 .new-file,
2117 #filter_activate,
2119 #filter_activate,
2118 #filter_deactivate {
2120 #filter_deactivate {
2119 float: right;
2121 float: right;
2120 margin: 0 0 0 10px;
2122 margin: 0 0 0 10px;
2121 }
2123 }
2122
2124
2125 .file-upload-transaction-wrapper {
2126 margin-top: 57px;
2127 clear: both;
2128 }
2129
2130 .file-upload-transaction-wrapper .error {
2131 color: @color5;
2132 }
2133
2134 .file-upload-transaction {
2135 min-height: 200px;
2136 padding: 54px;
2137 border: 1px solid @grey5;
2138 text-align: center;
2139 clear: both;
2140 }
2141
2142 .file-upload-transaction i {
2143 font-size: 48px
2144 }
2145
2123 h3.files_location{
2146 h3.files_location{
2124 line-height: 2.4em;
2147 line-height: 2.4em;
2125 }
2148 }
2126
2149
2127 .browser-nav {
2150 .browser-nav {
2128 width: 100%;
2151 width: 100%;
2129 display: table;
2152 display: table;
2130 margin-bottom: 20px;
2153 margin-bottom: 20px;
2131
2154
2132 .info_box {
2155 .info_box {
2133 float: left;
2156 float: left;
2134 display: inline-table;
2157 display: inline-table;
2135 height: 2.5em;
2158 height: 2.5em;
2136
2159
2137 .browser-cur-rev, .info_box_elem {
2160 .browser-cur-rev, .info_box_elem {
2138 display: table-cell;
2161 display: table-cell;
2139 vertical-align: middle;
2162 vertical-align: middle;
2140 }
2163 }
2141
2164
2142 .drop-menu {
2165 .drop-menu {
2143 margin: 0 10px;
2166 margin: 0 10px;
2144 }
2167 }
2145
2168
2146 .info_box_elem {
2169 .info_box_elem {
2147 border-top: @border-thickness solid @grey5;
2170 border-top: @border-thickness solid @grey5;
2148 border-bottom: @border-thickness solid @grey5;
2171 border-bottom: @border-thickness solid @grey5;
2149 box-shadow: @button-shadow;
2172 box-shadow: @button-shadow;
2150
2173
2151 #at_rev, a {
2174 #at_rev, a {
2152 padding: 0.6em 0.4em;
2175 padding: 0.6em 0.4em;
2153 margin: 0;
2176 margin: 0;
2154 .box-shadow(none);
2177 .box-shadow(none);
2155 border: 0;
2178 border: 0;
2156 height: 12px;
2179 height: 12px;
2157 color: @grey2;
2180 color: @grey2;
2158 }
2181 }
2159
2182
2160 input#at_rev {
2183 input#at_rev {
2161 max-width: 50px;
2184 max-width: 50px;
2162 text-align: center;
2185 text-align: center;
2163 }
2186 }
2164
2187
2165 &.previous {
2188 &.previous {
2166 border: @border-thickness solid @grey5;
2189 border: @border-thickness solid @grey5;
2167 border-top-left-radius: @border-radius;
2190 border-top-left-radius: @border-radius;
2168 border-bottom-left-radius: @border-radius;
2191 border-bottom-left-radius: @border-radius;
2169
2192
2170 &:hover {
2193 &:hover {
2171 border-color: @grey4;
2194 border-color: @grey4;
2172 }
2195 }
2173
2196
2174 .disabled {
2197 .disabled {
2175 color: @grey5;
2198 color: @grey5;
2176 cursor: not-allowed;
2199 cursor: not-allowed;
2177 opacity: 0.5;
2200 opacity: 0.5;
2178 }
2201 }
2179 }
2202 }
2180
2203
2181 &.next {
2204 &.next {
2182 border: @border-thickness solid @grey5;
2205 border: @border-thickness solid @grey5;
2183 border-top-right-radius: @border-radius;
2206 border-top-right-radius: @border-radius;
2184 border-bottom-right-radius: @border-radius;
2207 border-bottom-right-radius: @border-radius;
2185
2208
2186 &:hover {
2209 &:hover {
2187 border-color: @grey4;
2210 border-color: @grey4;
2188 }
2211 }
2189
2212
2190 .disabled {
2213 .disabled {
2191 color: @grey5;
2214 color: @grey5;
2192 cursor: not-allowed;
2215 cursor: not-allowed;
2193 opacity: 0.5;
2216 opacity: 0.5;
2194 }
2217 }
2195 }
2218 }
2196 }
2219 }
2197
2220
2198 .browser-cur-rev {
2221 .browser-cur-rev {
2199
2222
2200 span{
2223 span{
2201 margin: 0;
2224 margin: 0;
2202 color: @rcblue;
2225 color: @rcblue;
2203 height: 12px;
2226 height: 12px;
2204 display: inline-block;
2227 display: inline-block;
2205 padding: 0.7em 1em ;
2228 padding: 0.7em 1em ;
2206 border: @border-thickness solid @rcblue;
2229 border: @border-thickness solid @rcblue;
2207 margin-right: @padding;
2230 margin-right: @padding;
2208 }
2231 }
2209 }
2232 }
2210
2233
2211 }
2234 }
2212
2235
2213 .select-index-number {
2236 .select-index-number {
2214 margin: 0 0 0 20px;
2237 margin: 0 0 0 20px;
2215 color: @grey3;
2238 color: @grey3;
2216 }
2239 }
2217
2240
2218 .search_activate {
2241 .search_activate {
2219 display: table-cell;
2242 display: table-cell;
2220 vertical-align: middle;
2243 vertical-align: middle;
2221
2244
2222 input, label{
2245 input, label{
2223 margin: 0;
2246 margin: 0;
2224 padding: 0;
2247 padding: 0;
2225 }
2248 }
2226
2249
2227 input{
2250 input{
2228 margin-left: @textmargin;
2251 margin-left: @textmargin;
2229 }
2252 }
2230
2253
2231 }
2254 }
2232 }
2255 }
2233
2256
2234 .browser-cur-rev{
2257 .browser-cur-rev{
2235 margin-bottom: @textmargin;
2258 margin-bottom: @textmargin;
2236 }
2259 }
2237
2260
2238 #node_filter_box_loading{
2261 #node_filter_box_loading{
2239 .info_text;
2262 .info_text;
2240 }
2263 }
2241
2264
2242 .browser-search {
2265 .browser-search {
2243 margin: -25px 0px 5px 0px;
2266 margin: -25px 0px 5px 0px;
2244 }
2267 }
2245
2268
2246 .files-quick-filter {
2269 .files-quick-filter {
2247 float: right;
2270 float: right;
2248 width: 180px;
2271 width: 180px;
2249 position: relative;
2272 position: relative;
2250 }
2273 }
2251
2274
2252 .files-filter-box {
2275 .files-filter-box {
2253 display: flex;
2276 display: flex;
2254 padding: 0px;
2277 padding: 0px;
2255 border-radius: 3px;
2278 border-radius: 3px;
2256 margin-bottom: 0;
2279 margin-bottom: 0;
2257
2280
2258 a {
2281 a {
2259 border: none !important;
2282 border: none !important;
2260 }
2283 }
2261
2284
2262 li {
2285 li {
2263 list-style-type: none
2286 list-style-type: none
2264 }
2287 }
2265 }
2288 }
2266
2289
2267 .files-filter-box-path {
2290 .files-filter-box-path {
2268 line-height: 33px;
2291 line-height: 33px;
2269 padding: 0;
2292 padding: 0;
2270 width: 20px;
2293 width: 20px;
2271 position: absolute;
2294 position: absolute;
2272 z-index: 11;
2295 z-index: 11;
2273 left: 5px;
2296 left: 5px;
2274 }
2297 }
2275
2298
2276 .files-filter-box-input {
2299 .files-filter-box-input {
2277 margin-right: 0;
2300 margin-right: 0;
2278
2301
2279 input {
2302 input {
2280 border: 1px solid @white;
2303 border: 1px solid @white;
2281 padding-left: 25px;
2304 padding-left: 25px;
2282 width: 145px;
2305 width: 145px;
2283
2306
2284 &:hover {
2307 &:hover {
2285 border-color: @grey6;
2308 border-color: @grey6;
2286 }
2309 }
2287
2310
2288 &:focus {
2311 &:focus {
2289 border-color: @grey5;
2312 border-color: @grey5;
2290 }
2313 }
2291 }
2314 }
2292 }
2315 }
2293
2316
2294 .browser-result{
2317 .browser-result{
2295 td a{
2318 td a{
2296 margin-left: 0.5em;
2319 margin-left: 0.5em;
2297 display: inline-block;
2320 display: inline-block;
2298
2321
2299 em {
2322 em {
2300 font-weight: @text-bold-weight;
2323 font-weight: @text-bold-weight;
2301 font-family: @text-bold;
2324 font-family: @text-bold;
2302 }
2325 }
2303 }
2326 }
2304 }
2327 }
2305
2328
2306 .browser-highlight{
2329 .browser-highlight{
2307 background-color: @grey5-alpha;
2330 background-color: @grey5-alpha;
2308 }
2331 }
2309
2332
2310
2333
2334 .edit-file-fieldset #location,
2335 .edit-file-fieldset #filename {
2336 display: flex;
2337 width: -moz-available; /* WebKit-based browsers will ignore this. */
2338 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2339 width: fill-available;
2340 border: 0;
2341 }
2342
2343 .path-items {
2344 display: flex;
2345 padding: 0;
2346 border: 1px solid #eeeeee;
2347 width: 100%;
2348 float: left;
2349
2350 .breadcrumb-path {
2351 line-height: 30px;
2352 padding: 0 4px;
2353 white-space: nowrap;
2354 }
2355
2356 .location-path {
2357 width: -moz-available; /* WebKit-based browsers will ignore this. */
2358 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2359 width: fill-available;
2360
2361 .file-name-input {
2362 padding: 0.5em 0;
2363 }
2364
2365 }
2366
2367 ul {
2368 display: flex;
2369 margin: 0;
2370 padding: 0;
2371 }
2372 li {
2373 list-style-type: none;
2374 }
2375 }
2376
2377 .editor-items {
2378 height: 40px;
2379 margin: 10px 0 -17px 10px;
2380
2381 .editor-action {
2382 cursor: pointer;
2383 }
2384
2385 .editor-action.active {
2386 border-bottom: 2px solid #5C5C5C;
2387 }
2388
2389 li {
2390 list-style-type: none;
2391 }
2392 }
2393
2394 .edit-file-fieldset .message textarea {
2395 border: 1px solid #eeeeee;
2396 }
2397
2398 #files_data .codeblock {
2399 background-color: #F5F5F5;
2400 }
2401
2402 #editor_preview {
2403 background: white;
2404 }
2405
2406 .show-editor {
2407 padding: 10px;
2408 background-color: white;
2409
2410 }
2411
2412 .show-preview {
2413 padding: 10px;
2414 background-color: white;
2415 border-left: 1px solid #eeeeee;
2416 }
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2311 // Search
2429 // Search
2312
2430
2313 .search-form{
2431 .search-form{
2314 #q {
2432 #q {
2315 width: @search-form-width;
2433 width: @search-form-width;
2316 }
2434 }
2317 .fields{
2435 .fields{
2318 margin: 0 0 @space;
2436 margin: 0 0 @space;
2319 }
2437 }
2320
2438
2321 label{
2439 label{
2322 display: inline-block;
2440 display: inline-block;
2323 margin-right: @textmargin;
2441 margin-right: @textmargin;
2324 padding-top: 0.25em;
2442 padding-top: 0.25em;
2325 }
2443 }
2326
2444
2327
2445
2328 .results{
2446 .results{
2329 clear: both;
2447 clear: both;
2330 margin: 0 0 @padding;
2448 margin: 0 0 @padding;
2331 }
2449 }
2332
2450
2333 .search-tags {
2451 .search-tags {
2334 padding: 5px 0;
2452 padding: 5px 0;
2335 }
2453 }
2336 }
2454 }
2337
2455
2338 div.search-feedback-items {
2456 div.search-feedback-items {
2339 display: inline-block;
2457 display: inline-block;
2340 }
2458 }
2341
2459
2342 div.search-code-body {
2460 div.search-code-body {
2343 background-color: #ffffff; padding: 5px 0 5px 10px;
2461 background-color: #ffffff; padding: 5px 0 5px 10px;
2344 pre {
2462 pre {
2345 .match { background-color: #faffa6;}
2463 .match { background-color: #faffa6;}
2346 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2464 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2347 }
2465 }
2348 }
2466 }
2349
2467
2350 .expand_commit.search {
2468 .expand_commit.search {
2351 .show_more.open {
2469 .show_more.open {
2352 height: auto;
2470 height: auto;
2353 max-height: none;
2471 max-height: none;
2354 }
2472 }
2355 }
2473 }
2356
2474
2357 .search-results {
2475 .search-results {
2358
2476
2359 h2 {
2477 h2 {
2360 margin-bottom: 0;
2478 margin-bottom: 0;
2361 }
2479 }
2362 .codeblock {
2480 .codeblock {
2363 border: none;
2481 border: none;
2364 background: transparent;
2482 background: transparent;
2365 }
2483 }
2366
2484
2367 .codeblock-header {
2485 .codeblock-header {
2368 border: none;
2486 border: none;
2369 background: transparent;
2487 background: transparent;
2370 }
2488 }
2371
2489
2372 .code-body {
2490 .code-body {
2373 border: @border-thickness solid @grey6;
2491 border: @border-thickness solid @grey6;
2374 .border-radius(@border-radius);
2492 .border-radius(@border-radius);
2375 }
2493 }
2376
2494
2377 .td-commit {
2495 .td-commit {
2378 &:extend(pre);
2496 &:extend(pre);
2379 border-bottom: @border-thickness solid @border-default-color;
2497 border-bottom: @border-thickness solid @border-default-color;
2380 }
2498 }
2381
2499
2382 .message {
2500 .message {
2383 height: auto;
2501 height: auto;
2384 max-width: 350px;
2502 max-width: 350px;
2385 white-space: normal;
2503 white-space: normal;
2386 text-overflow: initial;
2504 text-overflow: initial;
2387 overflow: visible;
2505 overflow: visible;
2388
2506
2389 .match { background-color: #faffa6;}
2507 .match { background-color: #faffa6;}
2390 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2508 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2391 }
2509 }
2392
2510
2393 .path {
2511 .path {
2394 border-bottom: none !important;
2512 border-bottom: none !important;
2395 border-left: 1px solid @grey6 !important;
2513 border-left: 1px solid @grey6 !important;
2396 border-right: 1px solid @grey6 !important;
2514 border-right: 1px solid @grey6 !important;
2397 }
2515 }
2398 }
2516 }
2399
2517
2400 table.rctable td.td-search-results div {
2518 table.rctable td.td-search-results div {
2401 max-width: 100%;
2519 max-width: 100%;
2402 }
2520 }
2403
2521
2404 #tip-box, .tip-box{
2522 #tip-box, .tip-box{
2405 padding: @menupadding/2;
2523 padding: @menupadding/2;
2406 display: block;
2524 display: block;
2407 border: @border-thickness solid @border-highlight-color;
2525 border: @border-thickness solid @border-highlight-color;
2408 .border-radius(@border-radius);
2526 .border-radius(@border-radius);
2409 background-color: white;
2527 background-color: white;
2410 z-index: 99;
2528 z-index: 99;
2411 white-space: pre-wrap;
2529 white-space: pre-wrap;
2412 }
2530 }
2413
2531
2414 #linktt {
2532 #linktt {
2415 width: 79px;
2533 width: 79px;
2416 }
2534 }
2417
2535
2418 #help_kb .modal-content{
2536 #help_kb .modal-content{
2419 max-width: 750px;
2537 max-width: 750px;
2420 margin: 10% auto;
2538 margin: 10% auto;
2421
2539
2422 table{
2540 table{
2423 td,th{
2541 td,th{
2424 border-bottom: none;
2542 border-bottom: none;
2425 line-height: 2.5em;
2543 line-height: 2.5em;
2426 }
2544 }
2427 th{
2545 th{
2428 padding-bottom: @textmargin/2;
2546 padding-bottom: @textmargin/2;
2429 }
2547 }
2430 td.keys{
2548 td.keys{
2431 text-align: center;
2549 text-align: center;
2432 }
2550 }
2433 }
2551 }
2434
2552
2435 .block-left{
2553 .block-left{
2436 width: 45%;
2554 width: 45%;
2437 margin-right: 5%;
2555 margin-right: 5%;
2438 }
2556 }
2439 .modal-footer{
2557 .modal-footer{
2440 clear: both;
2558 clear: both;
2441 }
2559 }
2442 .key.tag{
2560 .key.tag{
2443 padding: 0.5em;
2561 padding: 0.5em;
2444 background-color: @rcblue;
2562 background-color: @rcblue;
2445 color: white;
2563 color: white;
2446 border-color: @rcblue;
2564 border-color: @rcblue;
2447 .box-shadow(none);
2565 .box-shadow(none);
2448 }
2566 }
2449 }
2567 }
2450
2568
2451
2569
2452
2570
2453 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2571 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2454
2572
2455 @import 'statistics-graph';
2573 @import 'statistics-graph';
2456 @import 'tables';
2574 @import 'tables';
2457 @import 'forms';
2575 @import 'forms';
2458 @import 'diff';
2576 @import 'diff';
2459 @import 'summary';
2577 @import 'summary';
2460 @import 'navigation';
2578 @import 'navigation';
2461
2579
2462 //--- SHOW/HIDE SECTIONS --//
2580 //--- SHOW/HIDE SECTIONS --//
2463
2581
2464 .btn-collapse {
2582 .btn-collapse {
2465 float: right;
2583 float: right;
2466 text-align: right;
2584 text-align: right;
2467 font-family: @text-light;
2585 font-family: @text-light;
2468 font-size: @basefontsize;
2586 font-size: @basefontsize;
2469 cursor: pointer;
2587 cursor: pointer;
2470 border: none;
2588 border: none;
2471 color: @rcblue;
2589 color: @rcblue;
2472 }
2590 }
2473
2591
2474 table.rctable,
2592 table.rctable,
2475 table.dataTable {
2593 table.dataTable {
2476 .btn-collapse {
2594 .btn-collapse {
2477 float: right;
2595 float: right;
2478 text-align: right;
2596 text-align: right;
2479 }
2597 }
2480 }
2598 }
2481
2599
2482 table.rctable {
2600 table.rctable {
2483 &.permissions {
2601 &.permissions {
2484
2602
2485 th.td-owner {
2603 th.td-owner {
2486 padding: 0;
2604 padding: 0;
2487 }
2605 }
2488
2606
2489 th {
2607 th {
2490 font-weight: normal;
2608 font-weight: normal;
2491 padding: 0 5px;
2609 padding: 0 5px;
2492 }
2610 }
2493
2611
2494 }
2612 }
2495 }
2613 }
2496
2614
2497
2615
2498 // TODO: johbo: Fix for IE10, this avoids that we see a border
2616 // TODO: johbo: Fix for IE10, this avoids that we see a border
2499 // and padding around checkboxes and radio boxes. Move to the right place,
2617 // and padding around checkboxes and radio boxes. Move to the right place,
2500 // or better: Remove this once we did the form refactoring.
2618 // or better: Remove this once we did the form refactoring.
2501 input[type=checkbox],
2619 input[type=checkbox],
2502 input[type=radio] {
2620 input[type=radio] {
2503 padding: 0;
2621 padding: 0;
2504 border: none;
2622 border: none;
2505 }
2623 }
2506
2624
2507 .toggle-ajax-spinner{
2625 .toggle-ajax-spinner{
2508 height: 16px;
2626 height: 16px;
2509 width: 16px;
2627 width: 16px;
2510 }
2628 }
2511
2629
2512
2630
2513 .markup-form .clearfix {
2631 .markup-form .clearfix {
2514 .border-radius(@border-radius);
2632 .border-radius(@border-radius);
2515 margin: 0px;
2633 margin: 0px;
2516 }
2634 }
2517
2635
2518 .markup-form-area {
2636 .markup-form-area {
2519 padding: 8px 12px;
2637 padding: 8px 12px;
2520 border: 1px solid @grey4;
2638 border: 1px solid @grey4;
2521 .border-radius(@border-radius);
2639 .border-radius(@border-radius);
2522 }
2640 }
2523
2641
2524 .markup-form-area-header .nav-links {
2642 .markup-form-area-header .nav-links {
2525 display: flex;
2643 display: flex;
2526 flex-flow: row wrap;
2644 flex-flow: row wrap;
2527 -webkit-flex-flow: row wrap;
2645 -webkit-flex-flow: row wrap;
2528 width: 100%;
2646 width: 100%;
2529 }
2647 }
2530
2648
2531 .markup-form-area-footer {
2649 .markup-form-area-footer {
2532 display: flex;
2650 display: flex;
2533 }
2651 }
2534
2652
2535 .markup-form-area-footer .toolbar {
2653 .markup-form-area-footer .toolbar {
2536
2654
2537 }
2655 }
2538
2656
2539 // markup Form
2657 // markup Form
2540 div.markup-form {
2658 div.markup-form {
2541 margin-top: 20px;
2659 margin-top: 20px;
2542 }
2660 }
2543
2661
2544 .markup-form strong {
2662 .markup-form strong {
2545 display: block;
2663 display: block;
2546 margin-bottom: 15px;
2664 margin-bottom: 15px;
2547 }
2665 }
2548
2666
2549 .markup-form textarea {
2667 .markup-form textarea {
2550 width: 100%;
2668 width: 100%;
2551 height: 100px;
2669 height: 100px;
2552 font-family: @text-monospace;
2670 font-family: @text-monospace;
2553 }
2671 }
2554
2672
2555 form.markup-form {
2673 form.markup-form {
2556 margin-top: 10px;
2674 margin-top: 10px;
2557 margin-left: 10px;
2675 margin-left: 10px;
2558 }
2676 }
2559
2677
2560 .markup-form .comment-block-ta,
2678 .markup-form .comment-block-ta,
2561 .markup-form .preview-box {
2679 .markup-form .preview-box {
2562 .border-radius(@border-radius);
2680 .border-radius(@border-radius);
2563 .box-sizing(border-box);
2681 .box-sizing(border-box);
2564 background-color: white;
2682 background-color: white;
2565 }
2683 }
2566
2684
2567 .markup-form .preview-box.unloaded {
2685 .markup-form .preview-box.unloaded {
2568 height: 50px;
2686 height: 50px;
2569 text-align: center;
2687 text-align: center;
2570 padding: 20px;
2688 padding: 20px;
2571 background-color: white;
2689 background-color: white;
2572 }
2690 }
2573
2691
2574
2692
2575 .dropzone-wrapper {
2693 .dropzone-wrapper {
2576 border: 1px solid @grey5;
2694 border: 1px solid @grey5;
2577 padding: 20px;
2695 padding: 20px;
2578 }
2696 }
2579
2697
2580 .dropzone {
2698 .dropzone,
2699 .dropzone-pure {
2581 border: 2px dashed @grey5;
2700 border: 2px dashed @grey5;
2582 border-radius: 5px;
2701 border-radius: 5px;
2583 background: white;
2702 background: white;
2584 min-height: 200px;
2703 min-height: 200px;
2585 padding: 54px;
2704 padding: 54px;
2586 }
2705
2587 .dropzone .dz-message {
2706 .dz-message {
2588 font-weight: 700;
2707 font-weight: 700;
2589 }
2708 text-align: center;
2590
2709 margin: 2em 0;
2591 .dropzone .dz-message {
2710 }
2592 text-align: center;
2711
2593 margin: 2em 0;
2594 }
2712 }
2595
2713
2596 .dz-preview {
2714 .dz-preview {
2597 margin: 10px 0px !important;
2715 margin: 10px 0 !important;
2598 position: relative;
2716 position: relative;
2599 vertical-align: top;
2717 vertical-align: top;
2600 padding: 10px;
2718 padding: 10px;
2719 border-bottom: 1px solid @grey5;
2601 }
2720 }
2602
2721
2603 .dz-filename {
2722 .dz-filename {
2604 font-weight: 700;
2723 font-weight: 700;
2605 float:left;
2724 float:left;
2606 }
2725 }
2607
2726
2727 .dz-sending {
2728 float: right;
2729 }
2730
2608 .dz-response {
2731 .dz-response {
2609 clear:both
2732 clear:both
2610 }
2733 }
2611
2734
2612 .dz-filename-size {
2735 .dz-filename-size {
2613 float:right
2736 float:right
2614 }
2737 }
2615
2738
2616 .dz-error-message {
2739 .dz-error-message {
2617 color: @alert2;
2740 color: @alert2;
2618 } No newline at end of file
2741 padding-top: 10px;
2742 clear: both;
2743 }
@@ -1,374 +1,375 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
17 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
18 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
19 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
20 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
21 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
22 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
24 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
26 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
27 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
28 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
29 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
31 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
32 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
83 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
83 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
84 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
84 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
85 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
85 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
86 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
86 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
87 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
87 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
88 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
88 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
89 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
89 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
90 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
90 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
91 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
91 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
92 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
92 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
93 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
93 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
94 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
94 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
95 pyroutes.register('users', '/_admin/users', []);
95 pyroutes.register('users', '/_admin/users', []);
96 pyroutes.register('users_data', '/_admin/users_data', []);
96 pyroutes.register('users_data', '/_admin/users_data', []);
97 pyroutes.register('users_create', '/_admin/users/create', []);
97 pyroutes.register('users_create', '/_admin/users/create', []);
98 pyroutes.register('users_new', '/_admin/users/new', []);
98 pyroutes.register('users_new', '/_admin/users/new', []);
99 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
99 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
100 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
100 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
101 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
101 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
102 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
102 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
103 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
103 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
104 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
104 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
105 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
105 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
106 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
106 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
107 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
107 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
108 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
108 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
109 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
109 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
110 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
110 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
111 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
111 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
113 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
113 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
114 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
114 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
115 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
115 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
116 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
116 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
117 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
117 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
118 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
118 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
119 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
119 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
120 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
120 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
121 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
121 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
122 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
122 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
123 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
123 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
124 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
124 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
125 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
125 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
126 pyroutes.register('user_groups', '/_admin/user_groups', []);
126 pyroutes.register('user_groups', '/_admin/user_groups', []);
127 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
127 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
128 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
128 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
129 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
129 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
130 pyroutes.register('repos', '/_admin/repos', []);
130 pyroutes.register('repos', '/_admin/repos', []);
131 pyroutes.register('repo_new', '/_admin/repos/new', []);
131 pyroutes.register('repo_new', '/_admin/repos/new', []);
132 pyroutes.register('repo_create', '/_admin/repos/create', []);
132 pyroutes.register('repo_create', '/_admin/repos/create', []);
133 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
133 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
134 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
134 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
135 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
135 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
136 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
136 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
137 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
137 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
138 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
138 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
139 pyroutes.register('channelstream_proxy', '/_channelstream', []);
139 pyroutes.register('channelstream_proxy', '/_channelstream', []);
140 pyroutes.register('upload_file', '/_file_store/upload', []);
140 pyroutes.register('upload_file', '/_file_store/upload', []);
141 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
141 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
142 pyroutes.register('logout', '/_admin/logout', []);
142 pyroutes.register('logout', '/_admin/logout', []);
143 pyroutes.register('reset_password', '/_admin/password_reset', []);
143 pyroutes.register('reset_password', '/_admin/password_reset', []);
144 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
144 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
145 pyroutes.register('home', '/', []);
145 pyroutes.register('home', '/', []);
146 pyroutes.register('user_autocomplete_data', '/_users', []);
146 pyroutes.register('user_autocomplete_data', '/_users', []);
147 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
147 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
148 pyroutes.register('repo_list_data', '/_repos', []);
148 pyroutes.register('repo_list_data', '/_repos', []);
149 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
149 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
150 pyroutes.register('goto_switcher_data', '/_goto_data', []);
150 pyroutes.register('goto_switcher_data', '/_goto_data', []);
151 pyroutes.register('markup_preview', '/_markup_preview', []);
151 pyroutes.register('markup_preview', '/_markup_preview', []);
152 pyroutes.register('file_preview', '/_file_preview', []);
152 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
153 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
153 pyroutes.register('journal', '/_admin/journal', []);
154 pyroutes.register('journal', '/_admin/journal', []);
154 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
155 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
155 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
156 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
156 pyroutes.register('journal_public', '/_admin/public_journal', []);
157 pyroutes.register('journal_public', '/_admin/public_journal', []);
157 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
158 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
158 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
159 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
159 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
160 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
160 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
161 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
161 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
162 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
162 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
163 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
163 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
164 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
164 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
165 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
165 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
166 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
166 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
176 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
176 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
178 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
178 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
179 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
179 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
180 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
180 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
181 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
181 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
183 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
183 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
188 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
188 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
202 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
202 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
203 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
203 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
204 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
204 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
205 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
205 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
207 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
207 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
209 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
209 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
211 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
211 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
212 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
212 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
213 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
213 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
214 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
214 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
215 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
215 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
216 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
216 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
217 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
217 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
218 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
218 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
219 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
219 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
220 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
220 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
221 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
221 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
222 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
222 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
223 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
223 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
224 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
224 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
225 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
225 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
226 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
226 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
228 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
228 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
229 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
229 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
230 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
230 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
231 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
231 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
232 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
232 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
233 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
236 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
236 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
237 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
237 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
238 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
238 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
239 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
239 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
240 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
240 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
241 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
241 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
242 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
242 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
243 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
243 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
244 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
244 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
245 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
245 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
246 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
246 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
247 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
247 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
248 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
248 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
249 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
249 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
250 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
250 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
251 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
251 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
252 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
252 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
253 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
253 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
254 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
254 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
255 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
255 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
256 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
256 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
257 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
257 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
258 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
258 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
259 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
259 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
260 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
260 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
261 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
261 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
262 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
262 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
263 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
263 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
264 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
264 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
265 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
265 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
266 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
266 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
267 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
267 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
268 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
268 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
269 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
269 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
270 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
270 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
271 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
271 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
272 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
272 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
273 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
273 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
274 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
274 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
275 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
275 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
276 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
276 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
277 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
277 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
278 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
278 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
279 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
279 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
280 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
280 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
281 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
281 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
282 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
282 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
283 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
283 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
284 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
284 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
285 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
285 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
286 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
286 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
287 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
287 pyroutes.register('search', '/_admin/search', []);
288 pyroutes.register('search', '/_admin/search', []);
288 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
289 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
289 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
290 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
290 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
291 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
291 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
292 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
292 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
293 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
293 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
294 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
294 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
295 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
295 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
296 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
296 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
297 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
297 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
298 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
298 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
299 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
299 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
300 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
300 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
301 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
301 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
302 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
302 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
303 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
303 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
304 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
304 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
305 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
305 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
306 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
306 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
307 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
307 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
308 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
308 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
309 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
309 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
310 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
310 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
311 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
311 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
312 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
312 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
313 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
313 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
314 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
314 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
315 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
315 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
316 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
316 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
317 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
317 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
318 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
318 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
319 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
319 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
320 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
320 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
321 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
321 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
322 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
322 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
323 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
323 pyroutes.register('gists_show', '/_admin/gists', []);
324 pyroutes.register('gists_show', '/_admin/gists', []);
324 pyroutes.register('gists_new', '/_admin/gists/new', []);
325 pyroutes.register('gists_new', '/_admin/gists/new', []);
325 pyroutes.register('gists_create', '/_admin/gists/create', []);
326 pyroutes.register('gists_create', '/_admin/gists/create', []);
326 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
327 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
327 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
328 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
328 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
329 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
329 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
330 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
330 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
331 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
331 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
332 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
332 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
333 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
333 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
334 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
334 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
335 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
335 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
336 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
336 pyroutes.register('apiv2', '/_admin/api', []);
337 pyroutes.register('apiv2', '/_admin/api', []);
337 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
338 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
338 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
339 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
339 pyroutes.register('login', '/_admin/login', []);
340 pyroutes.register('login', '/_admin/login', []);
340 pyroutes.register('register', '/_admin/register', []);
341 pyroutes.register('register', '/_admin/register', []);
341 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
342 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
342 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
343 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
343 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
344 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
344 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
345 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
345 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
346 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
346 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
347 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
347 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
348 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
348 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
349 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
349 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
350 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
350 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
351 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
351 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
352 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
352 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
353 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
353 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
354 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
354 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
355 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
355 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
356 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
356 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
357 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
357 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
358 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
358 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
359 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
359 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
360 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
360 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
361 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
361 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
362 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
362 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
363 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
363 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
364 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
364 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
365 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
365 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
366 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
366 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
367 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
367 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
368 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
368 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
369 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
369 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
370 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
370 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
371 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
371 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
372 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
372 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
373 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
373 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
374 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
374 }
375 }
@@ -1,850 +1,843 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Code Mirror
20 * Code Mirror
21 */
21 */
22 // global code-mirror logger;, to enable run
22 // global code-mirror logger;, to enable run
23 // Logger.get('CodeMirror').setLevel(Logger.DEBUG)
23 // Logger.get('CodeMirror').setLevel(Logger.DEBUG)
24
24
25 cmLog = Logger.get('CodeMirror');
25 cmLog = Logger.get('CodeMirror');
26 cmLog.setLevel(Logger.OFF);
26 cmLog.setLevel(Logger.OFF);
27
27
28
28
29 //global cache for inline forms
29 //global cache for inline forms
30 var userHintsCache = {};
30 var userHintsCache = {};
31
31
32 // global timer, used to cancel async loading
32 // global timer, used to cancel async loading
33 var CodeMirrorLoadUserHintTimer;
33 var CodeMirrorLoadUserHintTimer;
34
34
35 var escapeRegExChars = function(value) {
35 var escapeRegExChars = function(value) {
36 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
36 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
37 };
37 };
38
38
39 /**
39 /**
40 * Load hints from external source returns an array of objects in a format
40 * Load hints from external source returns an array of objects in a format
41 * that hinting lib requires
41 * that hinting lib requires
42 * @returns {Array}
42 * @returns {Array}
43 */
43 */
44 var CodeMirrorLoadUserHints = function(query, triggerHints) {
44 var CodeMirrorLoadUserHints = function(query, triggerHints) {
45 cmLog.debug('Loading mentions users via AJAX');
45 cmLog.debug('Loading mentions users via AJAX');
46 var _users = [];
46 var _users = [];
47 $.ajax({
47 $.ajax({
48 type: 'GET',
48 type: 'GET',
49 data: {query: query},
49 data: {query: query},
50 url: pyroutes.url('user_autocomplete_data'),
50 url: pyroutes.url('user_autocomplete_data'),
51 headers: {'X-PARTIAL-XHR': true},
51 headers: {'X-PARTIAL-XHR': true},
52 async: true
52 async: true
53 })
53 })
54 .done(function(data) {
54 .done(function(data) {
55 var tmpl = '<img class="gravatar" src="{0}"/>{1}';
55 var tmpl = '<img class="gravatar" src="{0}"/>{1}';
56 $.each(data.suggestions, function(i) {
56 $.each(data.suggestions, function(i) {
57 var userObj = data.suggestions[i];
57 var userObj = data.suggestions[i];
58
58
59 if (userObj.username !== "default") {
59 if (userObj.username !== "default") {
60 _users.push({
60 _users.push({
61 text: userObj.username + " ",
61 text: userObj.username + " ",
62 org_text: userObj.username,
62 org_text: userObj.username,
63 displayText: userObj.value_display, // search that field
63 displayText: userObj.value_display, // search that field
64 // internal caches
64 // internal caches
65 _icon_link: userObj.icon_link,
65 _icon_link: userObj.icon_link,
66 _text: userObj.value_display,
66 _text: userObj.value_display,
67
67
68 render: function(elt, data, completion) {
68 render: function(elt, data, completion) {
69 var el = document.createElement('div');
69 var el = document.createElement('div');
70 el.className = "CodeMirror-hint-entry";
70 el.className = "CodeMirror-hint-entry";
71 el.innerHTML = tmpl.format(
71 el.innerHTML = tmpl.format(
72 completion._icon_link, completion._text);
72 completion._icon_link, completion._text);
73 elt.appendChild(el);
73 elt.appendChild(el);
74 }
74 }
75 });
75 });
76 }
76 }
77 });
77 });
78 cmLog.debug('Mention users loaded');
78 cmLog.debug('Mention users loaded');
79 // set to global cache
79 // set to global cache
80 userHintsCache[query] = _users;
80 userHintsCache[query] = _users;
81 triggerHints(userHintsCache[query]);
81 triggerHints(userHintsCache[query]);
82 })
82 })
83 .fail(function(data, textStatus, xhr) {
83 .fail(function(data, textStatus, xhr) {
84 alert("error processing request. \n" +
84 alert("error processing request. \n" +
85 "Error code {0} ({1}).".format(data.status, data.statusText));
85 "Error code {0} ({1}).".format(data.status, data.statusText));
86 });
86 });
87 };
87 };
88
88
89 /**
89 /**
90 * filters the results based on the current context
90 * filters the results based on the current context
91 * @param users
91 * @param users
92 * @param context
92 * @param context
93 * @returns {Array}
93 * @returns {Array}
94 */
94 */
95 var CodeMirrorFilterUsers = function(users, context) {
95 var CodeMirrorFilterUsers = function(users, context) {
96 var MAX_LIMIT = 10;
96 var MAX_LIMIT = 10;
97 var filtered_users = [];
97 var filtered_users = [];
98 var curWord = context.string;
98 var curWord = context.string;
99
99
100 cmLog.debug('Filtering users based on query:', curWord);
100 cmLog.debug('Filtering users based on query:', curWord);
101 $.each(users, function(i) {
101 $.each(users, function(i) {
102 var match = users[i];
102 var match = users[i];
103 var searchText = match.displayText;
103 var searchText = match.displayText;
104
104
105 if (!curWord ||
105 if (!curWord ||
106 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
106 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
107 // reset state
107 // reset state
108 match._text = match.displayText;
108 match._text = match.displayText;
109 if (curWord) {
109 if (curWord) {
110 // do highlighting
110 // do highlighting
111 var pattern = '(' + escapeRegExChars(curWord) + ')';
111 var pattern = '(' + escapeRegExChars(curWord) + ')';
112 match._text = searchText.replace(
112 match._text = searchText.replace(
113 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
113 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
114 }
114 }
115
115
116 filtered_users.push(match);
116 filtered_users.push(match);
117 }
117 }
118 // to not return to many results, use limit of filtered results
118 // to not return to many results, use limit of filtered results
119 if (filtered_users.length > MAX_LIMIT) {
119 if (filtered_users.length > MAX_LIMIT) {
120 return false;
120 return false;
121 }
121 }
122 });
122 });
123
123
124 return filtered_users;
124 return filtered_users;
125 };
125 };
126
126
127 var CodeMirrorMentionHint = function(editor, callback, options) {
127 var CodeMirrorMentionHint = function(editor, callback, options) {
128 var cur = editor.getCursor();
128 var cur = editor.getCursor();
129 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
129 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
130
130
131 // match on @ +1char
131 // match on @ +1char
132 var tokenMatch = new RegExp(
132 var tokenMatch = new RegExp(
133 '(^@| @)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*)$').exec(curLine);
133 '(^@| @)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*)$').exec(curLine);
134
134
135 var tokenStr = '';
135 var tokenStr = '';
136 if (tokenMatch !== null && tokenMatch.length > 0){
136 if (tokenMatch !== null && tokenMatch.length > 0){
137 tokenStr = tokenMatch[0].strip();
137 tokenStr = tokenMatch[0].strip();
138 } else {
138 } else {
139 // skip if we didn't match our token
139 // skip if we didn't match our token
140 return;
140 return;
141 }
141 }
142
142
143 var context = {
143 var context = {
144 start: (cur.ch - tokenStr.length) + 1,
144 start: (cur.ch - tokenStr.length) + 1,
145 end: cur.ch,
145 end: cur.ch,
146 string: tokenStr.slice(1),
146 string: tokenStr.slice(1),
147 type: null
147 type: null
148 };
148 };
149
149
150 // case when we put the @sign in fron of a string,
150 // case when we put the @sign in fron of a string,
151 // eg <@ we put it here>sometext then we need to prepend to text
151 // eg <@ we put it here>sometext then we need to prepend to text
152 if (context.end > cur.ch) {
152 if (context.end > cur.ch) {
153 context.start = context.start + 1; // we add to the @ sign
153 context.start = context.start + 1; // we add to the @ sign
154 context.end = cur.ch; // don't eat front part just append
154 context.end = cur.ch; // don't eat front part just append
155 context.string = context.string.slice(1, cur.ch - context.start);
155 context.string = context.string.slice(1, cur.ch - context.start);
156 }
156 }
157
157
158 cmLog.debug('Mention context', context);
158 cmLog.debug('Mention context', context);
159
159
160 var triggerHints = function(userHints){
160 var triggerHints = function(userHints){
161 return callback({
161 return callback({
162 list: CodeMirrorFilterUsers(userHints, context),
162 list: CodeMirrorFilterUsers(userHints, context),
163 from: CodeMirror.Pos(cur.line, context.start),
163 from: CodeMirror.Pos(cur.line, context.start),
164 to: CodeMirror.Pos(cur.line, context.end)
164 to: CodeMirror.Pos(cur.line, context.end)
165 });
165 });
166 };
166 };
167
167
168 var queryBasedHintsCache = undefined;
168 var queryBasedHintsCache = undefined;
169 // if we have something in the cache, try to fetch the query based cache
169 // if we have something in the cache, try to fetch the query based cache
170 if (userHintsCache !== {}){
170 if (userHintsCache !== {}){
171 queryBasedHintsCache = userHintsCache[context.string];
171 queryBasedHintsCache = userHintsCache[context.string];
172 }
172 }
173
173
174 if (queryBasedHintsCache !== undefined) {
174 if (queryBasedHintsCache !== undefined) {
175 cmLog.debug('Users loaded from cache');
175 cmLog.debug('Users loaded from cache');
176 triggerHints(queryBasedHintsCache);
176 triggerHints(queryBasedHintsCache);
177 } else {
177 } else {
178 // this takes care for async loading, and then displaying results
178 // this takes care for async loading, and then displaying results
179 // and also propagates the userHintsCache
179 // and also propagates the userHintsCache
180 window.clearTimeout(CodeMirrorLoadUserHintTimer);
180 window.clearTimeout(CodeMirrorLoadUserHintTimer);
181 CodeMirrorLoadUserHintTimer = setTimeout(function() {
181 CodeMirrorLoadUserHintTimer = setTimeout(function() {
182 CodeMirrorLoadUserHints(context.string, triggerHints);
182 CodeMirrorLoadUserHints(context.string, triggerHints);
183 }, 300);
183 }, 300);
184 }
184 }
185 };
185 };
186
186
187 var CodeMirrorCompleteAfter = function(cm, pred) {
187 var CodeMirrorCompleteAfter = function(cm, pred) {
188 var options = {
188 var options = {
189 completeSingle: false,
189 completeSingle: false,
190 async: true,
190 async: true,
191 closeOnUnfocus: true
191 closeOnUnfocus: true
192 };
192 };
193 var cur = cm.getCursor();
193 var cur = cm.getCursor();
194 setTimeout(function() {
194 setTimeout(function() {
195 if (!cm.state.completionActive) {
195 if (!cm.state.completionActive) {
196 cmLog.debug('Trigger mentions hinting');
196 cmLog.debug('Trigger mentions hinting');
197 CodeMirror.showHint(cm, CodeMirror.hint.mentions, options);
197 CodeMirror.showHint(cm, CodeMirror.hint.mentions, options);
198 }
198 }
199 }, 100);
199 }, 100);
200
200
201 // tell CodeMirror we didn't handle the key
201 // tell CodeMirror we didn't handle the key
202 // trick to trigger on a char but still complete it
202 // trick to trigger on a char but still complete it
203 return CodeMirror.Pass;
203 return CodeMirror.Pass;
204 };
204 };
205
205
206 var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
206 var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
207 var ta = $('#' + textAreadId).get(0);
207 if (textAreadId.substr(0,1) === "#"){
208 var ta = $(textAreadId).get(0);
209 }else {
210 var ta = $('#' + textAreadId).get(0);
211 }
212
208 if (focus === undefined) {
213 if (focus === undefined) {
209 focus = true;
214 focus = true;
210 }
215 }
211
216
212 // default options
217 // default options
213 var codeMirrorOptions = {
218 var codeMirrorOptions = {
214 mode: "null",
219 mode: "null",
215 lineNumbers: true,
220 lineNumbers: true,
216 indentUnit: 4,
221 indentUnit: 4,
217 autofocus: focus
222 autofocus: focus
218 };
223 };
219
224
220 if (options !== undefined) {
225 if (options !== undefined) {
221 // extend with custom options
226 // extend with custom options
222 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
227 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
223 }
228 }
224
229
225 var myCodeMirror = CodeMirror.fromTextArea(ta, codeMirrorOptions);
230 var myCodeMirror = CodeMirror.fromTextArea(ta, codeMirrorOptions);
226
231
227 $('#reset').on('click', function(e) {
232 $('#reset').on('click', function(e) {
228 window.location = resetUrl;
233 window.location = resetUrl;
229 });
234 });
230
235
231 return myCodeMirror;
236 return myCodeMirror;
232 };
237 };
233
238
234
239
235 var initMarkupCodeMirror = function(textAreadId, focus, options) {
240 var initMarkupCodeMirror = function(textAreadId, focus, options) {
236 var initialHeight = 100;
241 var initialHeight = 100;
237
242
238 var ta = $(textAreadId).get(0);
243 var ta = $(textAreadId).get(0);
239 if (focus === undefined) {
244 if (focus === undefined) {
240 focus = true;
245 focus = true;
241 }
246 }
242
247
243 // default options
248 // default options
244 var codeMirrorOptions = {
249 var codeMirrorOptions = {
245 lineNumbers: false,
250 lineNumbers: false,
246 indentUnit: 4,
251 indentUnit: 4,
247 viewportMargin: 30,
252 viewportMargin: 30,
248 // this is a trick to trigger some logic behind codemirror placeholder
253 // this is a trick to trigger some logic behind codemirror placeholder
249 // it influences styling and behaviour.
254 // it influences styling and behaviour.
250 placeholder: " ",
255 placeholder: " ",
251 lineWrapping: true,
256 lineWrapping: true,
252 autofocus: focus
257 autofocus: focus
253 };
258 };
254
259
255 if (options !== undefined) {
260 if (options !== undefined) {
256 // extend with custom options
261 // extend with custom options
257 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
262 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
258 }
263 }
259
264
260 var cm = CodeMirror.fromTextArea(ta, codeMirrorOptions);
265 var cm = CodeMirror.fromTextArea(ta, codeMirrorOptions);
261 cm.setSize(null, initialHeight);
266 cm.setSize(null, initialHeight);
262 cm.setOption("mode", DEFAULT_RENDERER);
267 cm.setOption("mode", DEFAULT_RENDERER);
263 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
268 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
264 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
269 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
265
270
266 // start listening on changes to make auto-expanded editor
271 // start listening on changes to make auto-expanded editor
267 cm.on("change", function(instance, changeObj) {
272 cm.on("change", function(instance, changeObj) {
268 var height = initialHeight;
273 var height = initialHeight;
269 var lines = instance.lineCount();
274 var lines = instance.lineCount();
270 if ( lines > 6 && lines < 20) {
275 if ( lines > 6 && lines < 20) {
271 height = "auto";
276 height = "auto";
272 }
277 }
273 else if (lines >= 20){
278 else if (lines >= 20){
274 zheight = 20*15;
279 zheight = 20*15;
275 }
280 }
276 instance.setSize(null, height);
281 instance.setSize(null, height);
277
282
278 // detect if the change was trigger by auto desc, or user input
283 // detect if the change was trigger by auto desc, or user input
279 var changeOrigin = changeObj.origin;
284 var changeOrigin = changeObj.origin;
280
285
281 if (changeOrigin === "setValue") {
286 if (changeOrigin === "setValue") {
282 cmLog.debug('Change triggered by setValue');
287 cmLog.debug('Change triggered by setValue');
283 }
288 }
284 else {
289 else {
285 cmLog.debug('user triggered change !');
290 cmLog.debug('user triggered change !');
286 // set special marker to indicate user has created an input.
291 // set special marker to indicate user has created an input.
287 instance._userDefinedValue = true;
292 instance._userDefinedValue = true;
288 }
293 }
289
294
290 });
295 });
291
296
292 return cm;
297 return cm;
293 };
298 };
294
299
295
300
296 var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions){
301 var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions){
297 var initialHeight = 100;
302 var initialHeight = 100;
298
303
299 if (typeof userHintsCache === "undefined") {
304 if (typeof userHintsCache === "undefined") {
300 userHintsCache = {};
305 userHintsCache = {};
301 cmLog.debug('Init empty cache for mentions');
306 cmLog.debug('Init empty cache for mentions');
302 }
307 }
303 if (!$(textAreaId).get(0)) {
308 if (!$(textAreaId).get(0)) {
304 cmLog.debug('Element for textarea not found', textAreaId);
309 cmLog.debug('Element for textarea not found', textAreaId);
305 return;
310 return;
306 }
311 }
307 /**
312 /**
308 * Filter action based on typed in text
313 * Filter action based on typed in text
309 * @param actions
314 * @param actions
310 * @param context
315 * @param context
311 * @returns {Array}
316 * @returns {Array}
312 */
317 */
313
318
314 var filterActions = function(actions, context){
319 var filterActions = function(actions, context){
315
320
316 var MAX_LIMIT = 10;
321 var MAX_LIMIT = 10;
317 var filtered_actions = [];
322 var filtered_actions = [];
318 var curWord = context.string;
323 var curWord = context.string;
319
324
320 cmLog.debug('Filtering actions based on query:', curWord);
325 cmLog.debug('Filtering actions based on query:', curWord);
321 $.each(actions, function(i) {
326 $.each(actions, function(i) {
322 var match = actions[i];
327 var match = actions[i];
323 var searchText = match.searchText;
328 var searchText = match.searchText;
324
329
325 if (!curWord ||
330 if (!curWord ||
326 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
331 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
327 // reset state
332 // reset state
328 match._text = match.displayText;
333 match._text = match.displayText;
329 if (curWord) {
334 if (curWord) {
330 // do highlighting
335 // do highlighting
331 var pattern = '(' + escapeRegExChars(curWord) + ')';
336 var pattern = '(' + escapeRegExChars(curWord) + ')';
332 match._text = searchText.replace(
337 match._text = searchText.replace(
333 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
338 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
334 }
339 }
335
340
336 filtered_actions.push(match);
341 filtered_actions.push(match);
337 }
342 }
338 // to not return to many results, use limit of filtered results
343 // to not return to many results, use limit of filtered results
339 if (filtered_actions.length > MAX_LIMIT) {
344 if (filtered_actions.length > MAX_LIMIT) {
340 return false;
345 return false;
341 }
346 }
342 });
347 });
343
348
344 return filtered_actions;
349 return filtered_actions;
345 };
350 };
346
351
347 var submitForm = function(cm, pred) {
352 var submitForm = function(cm, pred) {
348 $(cm.display.input.textarea.form).submit();
353 $(cm.display.input.textarea.form).submit();
349 return CodeMirror.Pass;
354 return CodeMirror.Pass;
350 };
355 };
351
356
352 var completeActions = function(actions){
357 var completeActions = function(actions){
353
358
354 var registeredActions = [];
359 var registeredActions = [];
355 var allActions = [
360 var allActions = [
356 {
361 {
357 text: "approve",
362 text: "approve",
358 searchText: "status approved",
363 searchText: "status approved",
359 displayText: _gettext('Set status to Approved'),
364 displayText: _gettext('Set status to Approved'),
360 hint: function(CodeMirror, data, completion) {
365 hint: function(CodeMirror, data, completion) {
361 CodeMirror.replaceRange("", completion.from || data.from,
366 CodeMirror.replaceRange("", completion.from || data.from,
362 completion.to || data.to, "complete");
367 completion.to || data.to, "complete");
363 $(CommentForm.statusChange).select2("val", 'approved').trigger('change');
368 $(CommentForm.statusChange).select2("val", 'approved').trigger('change');
364 },
369 },
365 render: function(elt, data, completion) {
370 render: function(elt, data, completion) {
366 var el = document.createElement('div');
371 var el = document.createElement('div');
367 el.className = "flag_status flag_status_comment_box approved pull-left";
372 el.className = "flag_status flag_status_comment_box approved pull-left";
368 elt.appendChild(el);
373 elt.appendChild(el);
369
374
370 el = document.createElement('span');
375 el = document.createElement('span');
371 el.innerHTML = completion.displayText;
376 el.innerHTML = completion.displayText;
372 elt.appendChild(el);
377 elt.appendChild(el);
373 }
378 }
374 },
379 },
375 {
380 {
376 text: "reject",
381 text: "reject",
377 searchText: "status rejected",
382 searchText: "status rejected",
378 displayText: _gettext('Set status to Rejected'),
383 displayText: _gettext('Set status to Rejected'),
379 hint: function(CodeMirror, data, completion) {
384 hint: function(CodeMirror, data, completion) {
380 CodeMirror.replaceRange("", completion.from || data.from,
385 CodeMirror.replaceRange("", completion.from || data.from,
381 completion.to || data.to, "complete");
386 completion.to || data.to, "complete");
382 $(CommentForm.statusChange).select2("val", 'rejected').trigger('change');
387 $(CommentForm.statusChange).select2("val", 'rejected').trigger('change');
383 },
388 },
384 render: function(elt, data, completion) {
389 render: function(elt, data, completion) {
385 var el = document.createElement('div');
390 var el = document.createElement('div');
386 el.className = "flag_status flag_status_comment_box rejected pull-left";
391 el.className = "flag_status flag_status_comment_box rejected pull-left";
387 elt.appendChild(el);
392 elt.appendChild(el);
388
393
389 el = document.createElement('span');
394 el = document.createElement('span');
390 el.innerHTML = completion.displayText;
395 el.innerHTML = completion.displayText;
391 elt.appendChild(el);
396 elt.appendChild(el);
392 }
397 }
393 },
398 },
394 {
399 {
395 text: "as_todo",
400 text: "as_todo",
396 searchText: "todo comment",
401 searchText: "todo comment",
397 displayText: _gettext('TODO comment'),
402 displayText: _gettext('TODO comment'),
398 hint: function(CodeMirror, data, completion) {
403 hint: function(CodeMirror, data, completion) {
399 CodeMirror.replaceRange("", completion.from || data.from,
404 CodeMirror.replaceRange("", completion.from || data.from,
400 completion.to || data.to, "complete");
405 completion.to || data.to, "complete");
401
406
402 $(CommentForm.commentType).val('todo');
407 $(CommentForm.commentType).val('todo');
403 },
408 },
404 render: function(elt, data, completion) {
409 render: function(elt, data, completion) {
405 var el = document.createElement('div');
410 var el = document.createElement('div');
406 el.className = "pull-left";
411 el.className = "pull-left";
407 elt.appendChild(el);
412 elt.appendChild(el);
408
413
409 el = document.createElement('span');
414 el = document.createElement('span');
410 el.innerHTML = completion.displayText;
415 el.innerHTML = completion.displayText;
411 elt.appendChild(el);
416 elt.appendChild(el);
412 }
417 }
413 },
418 },
414 {
419 {
415 text: "as_note",
420 text: "as_note",
416 searchText: "note comment",
421 searchText: "note comment",
417 displayText: _gettext('Note Comment'),
422 displayText: _gettext('Note Comment'),
418 hint: function(CodeMirror, data, completion) {
423 hint: function(CodeMirror, data, completion) {
419 CodeMirror.replaceRange("", completion.from || data.from,
424 CodeMirror.replaceRange("", completion.from || data.from,
420 completion.to || data.to, "complete");
425 completion.to || data.to, "complete");
421
426
422 $(CommentForm.commentType).val('note');
427 $(CommentForm.commentType).val('note');
423 },
428 },
424 render: function(elt, data, completion) {
429 render: function(elt, data, completion) {
425 var el = document.createElement('div');
430 var el = document.createElement('div');
426 el.className = "pull-left";
431 el.className = "pull-left";
427 elt.appendChild(el);
432 elt.appendChild(el);
428
433
429 el = document.createElement('span');
434 el = document.createElement('span');
430 el.innerHTML = completion.displayText;
435 el.innerHTML = completion.displayText;
431 elt.appendChild(el);
436 elt.appendChild(el);
432 }
437 }
433 }
438 }
434 ];
439 ];
435
440
436 $.each(allActions, function(index, value){
441 $.each(allActions, function(index, value){
437 var actionData = allActions[index];
442 var actionData = allActions[index];
438 if (actions.indexOf(actionData['text']) != -1) {
443 if (actions.indexOf(actionData['text']) != -1) {
439 registeredActions.push(actionData);
444 registeredActions.push(actionData);
440 }
445 }
441 });
446 });
442
447
443 return function(cm, pred) {
448 return function(cm, pred) {
444 var cur = cm.getCursor();
449 var cur = cm.getCursor();
445 var options = {
450 var options = {
446 closeOnUnfocus: true,
451 closeOnUnfocus: true,
447 registeredActions: registeredActions
452 registeredActions: registeredActions
448 };
453 };
449 setTimeout(function() {
454 setTimeout(function() {
450 if (!cm.state.completionActive) {
455 if (!cm.state.completionActive) {
451 cmLog.debug('Trigger actions hinting');
456 cmLog.debug('Trigger actions hinting');
452 CodeMirror.showHint(cm, CodeMirror.hint.actions, options);
457 CodeMirror.showHint(cm, CodeMirror.hint.actions, options);
453 }
458 }
454 }, 100);
459 }, 100);
455
460
456 // tell CodeMirror we didn't handle the key
461 // tell CodeMirror we didn't handle the key
457 // trick to trigger on a char but still complete it
462 // trick to trigger on a char but still complete it
458 return CodeMirror.Pass;
463 return CodeMirror.Pass;
459 }
464 }
460 };
465 };
461
466
462 var extraKeys = {
467 var extraKeys = {
463 "'@'": CodeMirrorCompleteAfter,
468 "'@'": CodeMirrorCompleteAfter,
464 Tab: function(cm) {
469 Tab: function(cm) {
465 // space indent instead of TABS
470 // space indent instead of TABS
466 var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
471 var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
467 cm.replaceSelection(spaces);
472 cm.replaceSelection(spaces);
468 }
473 }
469 };
474 };
470 // submit form on Meta-Enter
475 // submit form on Meta-Enter
471 if (OSType === "mac") {
476 if (OSType === "mac") {
472 extraKeys["Cmd-Enter"] = submitForm;
477 extraKeys["Cmd-Enter"] = submitForm;
473 }
478 }
474 else {
479 else {
475 extraKeys["Ctrl-Enter"] = submitForm;
480 extraKeys["Ctrl-Enter"] = submitForm;
476 }
481 }
477
482
478 if (triggerActions) {
483 if (triggerActions) {
479 // register triggerActions for this instance
484 // register triggerActions for this instance
480 extraKeys["'/'"] = completeActions(triggerActions);
485 extraKeys["'/'"] = completeActions(triggerActions);
481 }
486 }
482
487
483 var cm = CodeMirror.fromTextArea($(textAreaId).get(0), {
488 var cm = CodeMirror.fromTextArea($(textAreaId).get(0), {
484 lineNumbers: false,
489 lineNumbers: false,
485 indentUnit: 4,
490 indentUnit: 4,
486 viewportMargin: 30,
491 viewportMargin: 30,
487 // this is a trick to trigger some logic behind codemirror placeholder
492 // this is a trick to trigger some logic behind codemirror placeholder
488 // it influences styling and behaviour.
493 // it influences styling and behaviour.
489 placeholder: " ",
494 placeholder: " ",
490 extraKeys: extraKeys,
495 extraKeys: extraKeys,
491 lineWrapping: true
496 lineWrapping: true
492 });
497 });
493
498
494 cm.setSize(null, initialHeight);
499 cm.setSize(null, initialHeight);
495 cm.setOption("mode", DEFAULT_RENDERER);
500 cm.setOption("mode", DEFAULT_RENDERER);
496 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
501 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
497 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
502 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
498 // start listening on changes to make auto-expanded editor
503 // start listening on changes to make auto-expanded editor
499 cm.on("change", function(self) {
504 cm.on("change", function(self) {
500 var height = initialHeight;
505 var height = initialHeight;
501 var lines = self.lineCount();
506 var lines = self.lineCount();
502 if ( lines > 6 && lines < 20) {
507 if ( lines > 6 && lines < 20) {
503 height = "auto";
508 height = "auto";
504 }
509 }
505 else if (lines >= 20){
510 else if (lines >= 20){
506 zheight = 20*15;
511 zheight = 20*15;
507 }
512 }
508 self.setSize(null, height);
513 self.setSize(null, height);
509 });
514 });
510
515
511 var actionHint = function(editor, options) {
516 var actionHint = function(editor, options) {
512
517
513 var cur = editor.getCursor();
518 var cur = editor.getCursor();
514 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
519 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
515
520
516 // match only on /+1 character minimum
521 // match only on /+1 character minimum
517 var tokenMatch = new RegExp('(^/\|/\)([a-zA-Z]*)$').exec(curLine);
522 var tokenMatch = new RegExp('(^/\|/\)([a-zA-Z]*)$').exec(curLine);
518
523
519 var tokenStr = '';
524 var tokenStr = '';
520 if (tokenMatch !== null && tokenMatch.length > 0){
525 if (tokenMatch !== null && tokenMatch.length > 0){
521 tokenStr = tokenMatch[2].strip();
526 tokenStr = tokenMatch[2].strip();
522 }
527 }
523
528
524 var context = {
529 var context = {
525 start: (cur.ch - tokenStr.length) - 1,
530 start: (cur.ch - tokenStr.length) - 1,
526 end: cur.ch,
531 end: cur.ch,
527 string: tokenStr,
532 string: tokenStr,
528 type: null
533 type: null
529 };
534 };
530
535
531 return {
536 return {
532 list: filterActions(options.registeredActions, context),
537 list: filterActions(options.registeredActions, context),
533 from: CodeMirror.Pos(cur.line, context.start),
538 from: CodeMirror.Pos(cur.line, context.start),
534 to: CodeMirror.Pos(cur.line, context.end)
539 to: CodeMirror.Pos(cur.line, context.end)
535 };
540 };
536
541
537 };
542 };
538 CodeMirror.registerHelper("hint", "mentions", CodeMirrorMentionHint);
543 CodeMirror.registerHelper("hint", "mentions", CodeMirrorMentionHint);
539 CodeMirror.registerHelper("hint", "actions", actionHint);
544 CodeMirror.registerHelper("hint", "actions", actionHint);
540 return cm;
545 return cm;
541 };
546 };
542
547
543 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
548 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
544 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
549 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
545 codeMirrorInstance.setOption("mode", mode);
550 codeMirrorInstance.setOption("mode", mode);
546 };
551 };
547
552
548 var setCodeMirrorLineWrap = function(codeMirrorInstance, line_wrap) {
553 var setCodeMirrorLineWrap = function(codeMirrorInstance, line_wrap) {
549 codeMirrorInstance.setOption("lineWrapping", line_wrap);
554 codeMirrorInstance.setOption("lineWrapping", line_wrap);
550 };
555 };
551
556
552 var setCodeMirrorModeFromSelect = function(
557 var setCodeMirrorModeFromSelect = function(
553 targetSelect, targetFileInput, codeMirrorInstance, callback){
558 targetSelect, targetFileInput, codeMirrorInstance, callback){
554
559
555 $(targetSelect).on('change', function(e) {
560 $(targetSelect).on('change', function(e) {
556 cmLog.debug('codemirror select2 mode change event !');
561 cmLog.debug('codemirror select2 mode change event !');
557 var selected = e.currentTarget;
562 var selected = e.currentTarget;
558 var node = selected.options[selected.selectedIndex];
563 var node = selected.options[selected.selectedIndex];
559 var mimetype = node.value;
564 var mimetype = node.value;
560 cmLog.debug('picked mimetype', mimetype);
565 cmLog.debug('picked mimetype', mimetype);
561 var new_mode = $(node).attr('mode');
566 var new_mode = $(node).attr('mode');
562 setCodeMirrorMode(codeMirrorInstance, new_mode);
567 setCodeMirrorMode(codeMirrorInstance, new_mode);
563 cmLog.debug('set new mode', new_mode);
568 cmLog.debug('set new mode', new_mode);
564
569
565 //propose filename from picked mode
570 //propose filename from picked mode
566 cmLog.debug('setting mimetype', mimetype);
571 cmLog.debug('setting mimetype', mimetype);
567 var proposed_ext = getExtFromMimeType(mimetype);
572 var proposed_ext = getExtFromMimeType(mimetype);
568 cmLog.debug('file input', $(targetFileInput).val());
573 cmLog.debug('file input', $(targetFileInput).val());
569 var file_data = getFilenameAndExt($(targetFileInput).val());
574 var file_data = getFilenameAndExt($(targetFileInput).val());
570 var filename = file_data.filename || 'filename1';
575 var filename = file_data.filename || 'filename1';
571 $(targetFileInput).val(filename + proposed_ext);
576 $(targetFileInput).val(filename + proposed_ext);
572 cmLog.debug('proposed file', filename + proposed_ext);
577 cmLog.debug('proposed file', filename + proposed_ext);
573
578
574
579
575 if (typeof(callback) === 'function') {
580 if (typeof(callback) === 'function') {
576 try {
581 try {
577 cmLog.debug('running callback', callback);
582 cmLog.debug('running callback', callback);
578 callback(filename, mimetype, new_mode);
583 callback(filename, mimetype, new_mode);
579 } catch (err) {
584 } catch (err) {
580 console.log('failed to run callback', callback, err);
585 console.log('failed to run callback', callback, err);
581 }
586 }
582 }
587 }
583 cmLog.debug('finish iteration...');
588 cmLog.debug('finish iteration...');
584 });
589 });
585 };
590 };
586
591
587 var setCodeMirrorModeFromInput = function(
592 var setCodeMirrorModeFromInput = function(
588 targetSelect, targetFileInput, codeMirrorInstance, callback) {
593 targetSelect, targetFileInput, codeMirrorInstance, callback) {
589
594
590 // on type the new filename set mode
595 // on type the new filename set mode
591 $(targetFileInput).on('keyup', function(e) {
596 $(targetFileInput).on('keyup', function(e) {
592 var file_data = getFilenameAndExt(this.value);
597 var file_data = getFilenameAndExt(this.value);
593 if (file_data.ext === null) {
598 if (file_data.ext === null) {
594 return;
599 return;
595 }
600 }
596
601
597 var mimetypes = getMimeTypeFromExt(file_data.ext, true);
602 var mimetypes = getMimeTypeFromExt(file_data.ext, true);
598 cmLog.debug('mimetype from file', file_data, mimetypes);
603 cmLog.debug('mimetype from file', file_data, mimetypes);
599 var detected_mode;
604 var detected_mode;
600 var detected_option;
605 var detected_option;
601 for (var i in mimetypes) {
606 for (var i in mimetypes) {
602 var mt = mimetypes[i];
607 var mt = mimetypes[i];
603 if (!detected_mode) {
608 if (!detected_mode) {
604 detected_mode = detectCodeMirrorMode(this.value, mt);
609 detected_mode = detectCodeMirrorMode(this.value, mt);
605 }
610 }
606
611
607 if (!detected_option) {
612 if (!detected_option) {
608 cmLog.debug('#mimetype option[value="{0}"]'.format(mt));
613 cmLog.debug('#mimetype option[value="{0}"]'.format(mt));
609 if ($(targetSelect).find('option[value="{0}"]'.format(mt)).length) {
614 if ($(targetSelect).find('option[value="{0}"]'.format(mt)).length) {
610 detected_option = mt;
615 detected_option = mt;
611 }
616 }
612 }
617 }
613 }
618 }
614
619
615 cmLog.debug('detected mode', detected_mode);
620 cmLog.debug('detected mode', detected_mode);
616 cmLog.debug('detected option', detected_option);
621 cmLog.debug('detected option', detected_option);
617 if (detected_mode && detected_option){
622 if (detected_mode && detected_option){
618
623
619 $(targetSelect).select2("val", detected_option);
624 $(targetSelect).select2("val", detected_option);
620 setCodeMirrorMode(codeMirrorInstance, detected_mode);
625 setCodeMirrorMode(codeMirrorInstance, detected_mode);
621
626
622 if(typeof(callback) === 'function'){
627 if(typeof(callback) === 'function'){
623 try{
628 try{
624 cmLog.debug('running callback', callback);
629 cmLog.debug('running callback', callback);
625 var filename = file_data.filename + "." + file_data.ext;
630 var filename = file_data.filename + "." + file_data.ext;
626 callback(filename, detected_option, detected_mode);
631 callback(filename, detected_option, detected_mode);
627 }catch (err){
632 }catch (err){
628 console.log('failed to run callback', callback, err);
633 console.log('failed to run callback', callback, err);
629 }
634 }
630 }
635 }
631 }
636 }
632
637
633 });
638 });
634 };
639 };
635
640
636 var fillCodeMirrorOptions = function(targetSelect) {
641 var fillCodeMirrorOptions = function(targetSelect) {
637 //inject new modes, based on codeMirrors modeInfo object
642 //inject new modes, based on codeMirrors modeInfo object
638 var modes_select = $(targetSelect);
643 var modes_select = $(targetSelect);
639 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
644 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
640 var m = CodeMirror.modeInfo[i];
645 var m = CodeMirror.modeInfo[i];
641 var opt = new Option(m.name, m.mime);
646 var opt = new Option(m.name, m.mime);
642 $(opt).attr('mode', m.mode);
647 $(opt).attr('mode', m.mode);
643 modes_select.append(opt);
648 modes_select.append(opt);
644 }
649 }
645 };
650 };
646
651
647 var CodeMirrorPreviewEnable = function(edit_mode) {
648 // in case it a preview enabled mode enable the button
649 if (['markdown', 'rst', 'gfm'].indexOf(edit_mode) !== -1) {
650 $('#render_preview').removeClass('hidden');
651 }
652 else {
653 if (!$('#render_preview').hasClass('hidden')) {
654 $('#render_preview').addClass('hidden');
655 }
656 }
657 };
658
659
652
660 /* markup form */
653 /* markup form */
661 (function(mod) {
654 (function(mod) {
662
655
663 if (typeof exports == "object" && typeof module == "object") {
656 if (typeof exports == "object" && typeof module == "object") {
664 // CommonJS
657 // CommonJS
665 module.exports = mod();
658 module.exports = mod();
666 }
659 }
667 else {
660 else {
668 // Plain browser env
661 // Plain browser env
669 (this || window).MarkupForm = mod();
662 (this || window).MarkupForm = mod();
670 }
663 }
671
664
672 })(function() {
665 })(function() {
673 "use strict";
666 "use strict";
674
667
675 function MarkupForm(textareaId) {
668 function MarkupForm(textareaId) {
676 if (!(this instanceof MarkupForm)) {
669 if (!(this instanceof MarkupForm)) {
677 return new MarkupForm(textareaId);
670 return new MarkupForm(textareaId);
678 }
671 }
679
672
680 // bind the element instance to our Form
673 // bind the element instance to our Form
681 $('#' + textareaId).get(0).MarkupForm = this;
674 $('#' + textareaId).get(0).MarkupForm = this;
682
675
683 this.withSelectorId = function(selector) {
676 this.withSelectorId = function(selector) {
684 var selectorId = textareaId;
677 var selectorId = textareaId;
685 return selector + '_' + selectorId;
678 return selector + '_' + selectorId;
686 };
679 };
687
680
688 this.previewButton = this.withSelectorId('#preview-btn');
681 this.previewButton = this.withSelectorId('#preview-btn');
689 this.previewContainer = this.withSelectorId('#preview-container');
682 this.previewContainer = this.withSelectorId('#preview-container');
690
683
691 this.previewBoxSelector = this.withSelectorId('#preview-box');
684 this.previewBoxSelector = this.withSelectorId('#preview-box');
692
685
693 this.editButton = this.withSelectorId('#edit-btn');
686 this.editButton = this.withSelectorId('#edit-btn');
694 this.editContainer = this.withSelectorId('#edit-container');
687 this.editContainer = this.withSelectorId('#edit-container');
695
688
696 this.cmBox = textareaId;
689 this.cmBox = textareaId;
697 this.cm = initMarkupCodeMirror('#' + textareaId);
690 this.cm = initMarkupCodeMirror('#' + textareaId);
698
691
699 this.previewUrl = pyroutes.url('markup_preview');
692 this.previewUrl = pyroutes.url('markup_preview');
700
693
701 // FUNCTIONS and helpers
694 // FUNCTIONS and helpers
702 var self = this;
695 var self = this;
703
696
704 this.getCmInstance = function(){
697 this.getCmInstance = function(){
705 return this.cm
698 return this.cm
706 };
699 };
707
700
708 this.setPlaceholder = function(placeholder) {
701 this.setPlaceholder = function(placeholder) {
709 var cm = this.getCmInstance();
702 var cm = this.getCmInstance();
710 if (cm){
703 if (cm){
711 cm.setOption('placeholder', placeholder);
704 cm.setOption('placeholder', placeholder);
712 }
705 }
713 };
706 };
714
707
715 this.initStatusChangeSelector = function(){
708 this.initStatusChangeSelector = function(){
716 var formatChangeStatus = function(state, escapeMarkup) {
709 var formatChangeStatus = function(state, escapeMarkup) {
717 var originalOption = state.element;
710 var originalOption = state.element;
718 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
711 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
719 '<span>' + escapeMarkup(state.text) + '</span>';
712 '<span>' + escapeMarkup(state.text) + '</span>';
720 };
713 };
721 var formatResult = function(result, container, query, escapeMarkup) {
714 var formatResult = function(result, container, query, escapeMarkup) {
722 return formatChangeStatus(result, escapeMarkup);
715 return formatChangeStatus(result, escapeMarkup);
723 };
716 };
724
717
725 var formatSelection = function(data, container, escapeMarkup) {
718 var formatSelection = function(data, container, escapeMarkup) {
726 return formatChangeStatus(data, escapeMarkup);
719 return formatChangeStatus(data, escapeMarkup);
727 };
720 };
728
721
729 $(this.submitForm).find(this.statusChange).select2({
722 $(this.submitForm).find(this.statusChange).select2({
730 placeholder: _gettext('Status Review'),
723 placeholder: _gettext('Status Review'),
731 formatResult: formatResult,
724 formatResult: formatResult,
732 formatSelection: formatSelection,
725 formatSelection: formatSelection,
733 containerCssClass: "drop-menu status_box_menu",
726 containerCssClass: "drop-menu status_box_menu",
734 dropdownCssClass: "drop-menu-dropdown",
727 dropdownCssClass: "drop-menu-dropdown",
735 dropdownAutoWidth: true,
728 dropdownAutoWidth: true,
736 minimumResultsForSearch: -1
729 minimumResultsForSearch: -1
737 });
730 });
738 $(this.submitForm).find(this.statusChange).on('change', function() {
731 $(this.submitForm).find(this.statusChange).on('change', function() {
739 var status = self.getCommentStatus();
732 var status = self.getCommentStatus();
740
733
741 if (status && !self.isInline()) {
734 if (status && !self.isInline()) {
742 $(self.submitButton).prop('disabled', false);
735 $(self.submitButton).prop('disabled', false);
743 }
736 }
744
737
745 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
738 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
746 self.setPlaceholder(placeholderText)
739 self.setPlaceholder(placeholderText)
747 })
740 })
748 };
741 };
749
742
750 // reset the text area into it's original state
743 // reset the text area into it's original state
751 this.resetMarkupFormState = function(content) {
744 this.resetMarkupFormState = function(content) {
752 content = content || '';
745 content = content || '';
753
746
754 $(this.editContainer).show();
747 $(this.editContainer).show();
755 $(this.editButton).parent().addClass('active');
748 $(this.editButton).parent().addClass('active');
756
749
757 $(this.previewContainer).hide();
750 $(this.previewContainer).hide();
758 $(this.previewButton).parent().removeClass('active');
751 $(this.previewButton).parent().removeClass('active');
759
752
760 this.setActionButtonsDisabled(true);
753 this.setActionButtonsDisabled(true);
761 self.cm.setValue(content);
754 self.cm.setValue(content);
762 self.cm.setOption("readOnly", false);
755 self.cm.setOption("readOnly", false);
763 };
756 };
764
757
765 this.previewSuccessCallback = function(o) {
758 this.previewSuccessCallback = function(o) {
766 $(self.previewBoxSelector).html(o);
759 $(self.previewBoxSelector).html(o);
767 $(self.previewBoxSelector).removeClass('unloaded');
760 $(self.previewBoxSelector).removeClass('unloaded');
768
761
769 // swap buttons, making preview active
762 // swap buttons, making preview active
770 $(self.previewButton).parent().addClass('active');
763 $(self.previewButton).parent().addClass('active');
771 $(self.editButton).parent().removeClass('active');
764 $(self.editButton).parent().removeClass('active');
772
765
773 // unlock buttons
766 // unlock buttons
774 self.setActionButtonsDisabled(false);
767 self.setActionButtonsDisabled(false);
775 };
768 };
776
769
777 this.setActionButtonsDisabled = function(state) {
770 this.setActionButtonsDisabled = function(state) {
778 $(this.editButton).prop('disabled', state);
771 $(this.editButton).prop('disabled', state);
779 $(this.previewButton).prop('disabled', state);
772 $(this.previewButton).prop('disabled', state);
780 };
773 };
781
774
782 // lock preview/edit/submit buttons on load, but exclude cancel button
775 // lock preview/edit/submit buttons on load, but exclude cancel button
783 var excludeCancelBtn = true;
776 var excludeCancelBtn = true;
784 this.setActionButtonsDisabled(true);
777 this.setActionButtonsDisabled(true);
785
778
786 // anonymous users don't have access to initialized CM instance
779 // anonymous users don't have access to initialized CM instance
787 if (this.cm !== undefined){
780 if (this.cm !== undefined){
788 this.cm.on('change', function(cMirror) {
781 this.cm.on('change', function(cMirror) {
789 if (cMirror.getValue() === "") {
782 if (cMirror.getValue() === "") {
790 self.setActionButtonsDisabled(true)
783 self.setActionButtonsDisabled(true)
791 } else {
784 } else {
792 self.setActionButtonsDisabled(false)
785 self.setActionButtonsDisabled(false)
793 }
786 }
794 });
787 });
795 }
788 }
796
789
797 $(this.editButton).on('click', function(e) {
790 $(this.editButton).on('click', function(e) {
798 e.preventDefault();
791 e.preventDefault();
799
792
800 $(self.previewButton).parent().removeClass('active');
793 $(self.previewButton).parent().removeClass('active');
801 $(self.previewContainer).hide();
794 $(self.previewContainer).hide();
802
795
803 $(self.editButton).parent().addClass('active');
796 $(self.editButton).parent().addClass('active');
804 $(self.editContainer).show();
797 $(self.editContainer).show();
805
798
806 });
799 });
807
800
808 $(this.previewButton).on('click', function(e) {
801 $(this.previewButton).on('click', function(e) {
809 e.preventDefault();
802 e.preventDefault();
810 var text = self.cm.getValue();
803 var text = self.cm.getValue();
811
804
812 if (text === "") {
805 if (text === "") {
813 return;
806 return;
814 }
807 }
815
808
816 var postData = {
809 var postData = {
817 'text': text,
810 'text': text,
818 'renderer': templateContext.visual.default_renderer,
811 'renderer': templateContext.visual.default_renderer,
819 'csrf_token': CSRF_TOKEN
812 'csrf_token': CSRF_TOKEN
820 };
813 };
821
814
822 // lock ALL buttons on preview
815 // lock ALL buttons on preview
823 self.setActionButtonsDisabled(true);
816 self.setActionButtonsDisabled(true);
824
817
825 $(self.previewBoxSelector).addClass('unloaded');
818 $(self.previewBoxSelector).addClass('unloaded');
826 $(self.previewBoxSelector).html(_gettext('Loading ...'));
819 $(self.previewBoxSelector).html(_gettext('Loading ...'));
827
820
828 $(self.editContainer).hide();
821 $(self.editContainer).hide();
829 $(self.previewContainer).show();
822 $(self.previewContainer).show();
830
823
831 // by default we reset state of comment preserving the text
824 // by default we reset state of comment preserving the text
832 var previewFailCallback = function(data){
825 var previewFailCallback = function(data){
833 alert(
826 alert(
834 "Error while submitting preview.\n" +
827 "Error while submitting preview.\n" +
835 "Error code {0} ({1}).".format(data.status, data.statusText)
828 "Error code {0} ({1}).".format(data.status, data.statusText)
836 );
829 );
837 self.resetMarkupFormState(text)
830 self.resetMarkupFormState(text)
838 };
831 };
839 _submitAjaxPOST(
832 _submitAjaxPOST(
840 self.previewUrl, postData, self.previewSuccessCallback,
833 self.previewUrl, postData, self.previewSuccessCallback,
841 previewFailCallback);
834 previewFailCallback);
842
835
843 $(self.previewButton).parent().addClass('active');
836 $(self.previewButton).parent().addClass('active');
844 $(self.editButton).parent().removeClass('active');
837 $(self.editButton).parent().removeClass('active');
845 });
838 });
846
839
847 }
840 }
848
841
849 return MarkupForm;
842 return MarkupForm;
850 });
843 });
@@ -1,411 +1,517 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Search file list
20 * Search file list
21 */
21 */
22
22
23 var NodeFilter = {};
23 var NodeFilter = {};
24
24
25 var fileBrowserListeners = function (node_list_url, url_base) {
25 var fileBrowserListeners = function (node_list_url, url_base) {
26 var $filterInput = $('#node_filter');
26 var $filterInput = $('#node_filter');
27 var n_filter = $filterInput.get(0);
27 var n_filter = $filterInput.get(0);
28
28
29 NodeFilter.filterTimeout = null;
29 NodeFilter.filterTimeout = null;
30 var nodes = null;
30 var nodes = null;
31
31
32 NodeFilter.focus = function () {
32 NodeFilter.focus = function () {
33 $filterInput.focus()
33 $filterInput.focus()
34 };
34 };
35
35
36 NodeFilter.fetchNodes = function (callback) {
36 NodeFilter.fetchNodes = function (callback) {
37 $.ajax(
37 $.ajax(
38 {url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
38 {url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
39 .done(function (data) {
39 .done(function (data) {
40 nodes = data.nodes;
40 nodes = data.nodes;
41 if (callback) {
41 if (callback) {
42 callback();
42 callback();
43 }
43 }
44 })
44 })
45 .fail(function (data) {
45 .fail(function (data) {
46 console.log('failed to load');
46 console.log('failed to load');
47 });
47 });
48 };
48 };
49
49
50 NodeFilter.initFilter = function (e) {
50 NodeFilter.initFilter = function (e) {
51 if ($filterInput.hasClass('loading')) {
51 if ($filterInput.hasClass('loading')) {
52 return
52 return
53 }
53 }
54
54
55 // in case we are already loaded, do nothing
55 // in case we are already loaded, do nothing
56 if (!$filterInput.hasClass('init')) {
56 if (!$filterInput.hasClass('init')) {
57 return NodeFilter.handleKey(e);
57 return NodeFilter.handleKey(e);
58 }
58 }
59 var iconLoading = 'icon-spin animate-spin';
59 var iconLoading = 'icon-spin animate-spin';
60 var iconSearch = 'icon-search';
60 var iconSearch = 'icon-search';
61 $('.files-filter-box-path i').removeClass(iconSearch).addClass(iconLoading);
61 $('.files-filter-box-path i').removeClass(iconSearch).addClass(iconLoading);
62 $filterInput.addClass('loading');
62 $filterInput.addClass('loading');
63
63
64 var callback = function (org) {
64 var callback = function (org) {
65 return function () {
65 return function () {
66 if ($filterInput.hasClass('init')) {
66 if ($filterInput.hasClass('init')) {
67 $filterInput.removeClass('init');
67 $filterInput.removeClass('init');
68 $filterInput.removeClass('loading');
68 $filterInput.removeClass('loading');
69 }
69 }
70 $('.files-filter-box-path i').removeClass(iconLoading).addClass(iconSearch);
70 $('.files-filter-box-path i').removeClass(iconLoading).addClass(iconSearch);
71
71
72 // auto re-filter if we filled in the input
72 // auto re-filter if we filled in the input
73 if (n_filter.value !== "") {
73 if (n_filter.value !== "") {
74 NodeFilter.updateFilter(n_filter, e)()
74 NodeFilter.updateFilter(n_filter, e)()
75 }
75 }
76
76
77 }
77 }
78 };
78 };
79 // load node data
79 // load node data
80 NodeFilter.fetchNodes(callback());
80 NodeFilter.fetchNodes(callback());
81
81
82 };
82 };
83
83
84 NodeFilter.resetFilter = function () {
84 NodeFilter.resetFilter = function () {
85 $('#tbody').show();
85 $('#tbody').show();
86 $('#tbody_filtered').hide();
86 $('#tbody_filtered').hide();
87 $filterInput.val('');
87 $filterInput.val('');
88 };
88 };
89
89
90 NodeFilter.handleKey = function (e) {
90 NodeFilter.handleKey = function (e) {
91 var scrollDown = function (element) {
91 var scrollDown = function (element) {
92 var elementBottom = element.offset().top + $(element).outerHeight();
92 var elementBottom = element.offset().top + $(element).outerHeight();
93 var windowBottom = window.innerHeight + $(window).scrollTop();
93 var windowBottom = window.innerHeight + $(window).scrollTop();
94 if (elementBottom > windowBottom) {
94 if (elementBottom > windowBottom) {
95 var offset = elementBottom - window.innerHeight;
95 var offset = elementBottom - window.innerHeight;
96 $('html,body').scrollTop(offset);
96 $('html,body').scrollTop(offset);
97 return false;
97 return false;
98 }
98 }
99 return true;
99 return true;
100 };
100 };
101
101
102 var scrollUp = function (element) {
102 var scrollUp = function (element) {
103 if (element.offset().top < $(window).scrollTop()) {
103 if (element.offset().top < $(window).scrollTop()) {
104 $('html,body').scrollTop(element.offset().top);
104 $('html,body').scrollTop(element.offset().top);
105 return false;
105 return false;
106 }
106 }
107 return true;
107 return true;
108 };
108 };
109 var $hlElem = $('.browser-highlight');
109 var $hlElem = $('.browser-highlight');
110
110
111 if (e.keyCode === 40) { // Down
111 if (e.keyCode === 40) { // Down
112 if ($hlElem.length === 0) {
112 if ($hlElem.length === 0) {
113 $('.browser-result').first().addClass('browser-highlight');
113 $('.browser-result').first().addClass('browser-highlight');
114 } else {
114 } else {
115 var next = $hlElem.next();
115 var next = $hlElem.next();
116 if (next.length !== 0) {
116 if (next.length !== 0) {
117 $hlElem.removeClass('browser-highlight');
117 $hlElem.removeClass('browser-highlight');
118 next.addClass('browser-highlight');
118 next.addClass('browser-highlight');
119 }
119 }
120 }
120 }
121
121
122 if ($hlElem.get(0) !== undefined){
122 if ($hlElem.get(0) !== undefined){
123 scrollDown($hlElem);
123 scrollDown($hlElem);
124 }
124 }
125 }
125 }
126 if (e.keyCode === 38) { // Up
126 if (e.keyCode === 38) { // Up
127 e.preventDefault();
127 e.preventDefault();
128 if ($hlElem.length !== 0) {
128 if ($hlElem.length !== 0) {
129 var next = $hlElem.prev();
129 var next = $hlElem.prev();
130 if (next.length !== 0) {
130 if (next.length !== 0) {
131 $('.browser-highlight').removeClass('browser-highlight');
131 $('.browser-highlight').removeClass('browser-highlight');
132 next.addClass('browser-highlight');
132 next.addClass('browser-highlight');
133 }
133 }
134 }
134 }
135
135
136 if ($hlElem.get(0) !== undefined){
136 if ($hlElem.get(0) !== undefined){
137 scrollUp($hlElem);
137 scrollUp($hlElem);
138 }
138 }
139
139
140 }
140 }
141 if (e.keyCode === 13) { // Enter
141 if (e.keyCode === 13) { // Enter
142 if ($('.browser-highlight').length !== 0) {
142 if ($('.browser-highlight').length !== 0) {
143 var url = $('.browser-highlight').find('.match-link').attr('href');
143 var url = $('.browser-highlight').find('.match-link').attr('href');
144 window.location = url;
144 window.location = url;
145 }
145 }
146 }
146 }
147 if (e.keyCode === 27) { // Esc
147 if (e.keyCode === 27) { // Esc
148 NodeFilter.resetFilter();
148 NodeFilter.resetFilter();
149 $('html,body').scrollTop(0);
149 $('html,body').scrollTop(0);
150 }
150 }
151
151
152 var capture_keys = [
152 var capture_keys = [
153 40, // ArrowDown
153 40, // ArrowDown
154 38, // ArrowUp
154 38, // ArrowUp
155 39, // ArrowRight
155 39, // ArrowRight
156 37, // ArrowLeft
156 37, // ArrowLeft
157 13, // Enter
157 13, // Enter
158 27 // Esc
158 27 // Esc
159 ];
159 ];
160
160
161 if ($.inArray(e.keyCode, capture_keys) === -1) {
161 if ($.inArray(e.keyCode, capture_keys) === -1) {
162 clearTimeout(NodeFilter.filterTimeout);
162 clearTimeout(NodeFilter.filterTimeout);
163 NodeFilter.filterTimeout = setTimeout(NodeFilter.updateFilter(n_filter, e), 200);
163 NodeFilter.filterTimeout = setTimeout(NodeFilter.updateFilter(n_filter, e), 200);
164 }
164 }
165
165
166 };
166 };
167
167
168 NodeFilter.fuzzy_match = function (filepath, query) {
168 NodeFilter.fuzzy_match = function (filepath, query) {
169 var highlight = [];
169 var highlight = [];
170 var order = 0;
170 var order = 0;
171 for (var i = 0; i < query.length; i++) {
171 for (var i = 0; i < query.length; i++) {
172 var match_position = filepath.indexOf(query[i]);
172 var match_position = filepath.indexOf(query[i]);
173 if (match_position !== -1) {
173 if (match_position !== -1) {
174 var prev_match_position = highlight[highlight.length - 1];
174 var prev_match_position = highlight[highlight.length - 1];
175 if (prev_match_position === undefined) {
175 if (prev_match_position === undefined) {
176 highlight.push(match_position);
176 highlight.push(match_position);
177 } else {
177 } else {
178 var current_match_position = prev_match_position + match_position + 1;
178 var current_match_position = prev_match_position + match_position + 1;
179 highlight.push(current_match_position);
179 highlight.push(current_match_position);
180 order = order + current_match_position - prev_match_position;
180 order = order + current_match_position - prev_match_position;
181 }
181 }
182 filepath = filepath.substring(match_position + 1);
182 filepath = filepath.substring(match_position + 1);
183 } else {
183 } else {
184 return false;
184 return false;
185 }
185 }
186 }
186 }
187 return {
187 return {
188 'order': order,
188 'order': order,
189 'highlight': highlight
189 'highlight': highlight
190 };
190 };
191 };
191 };
192
192
193 NodeFilter.sortPredicate = function (a, b) {
193 NodeFilter.sortPredicate = function (a, b) {
194 if (a.order < b.order) return -1;
194 if (a.order < b.order) return -1;
195 if (a.order > b.order) return 1;
195 if (a.order > b.order) return 1;
196 if (a.filepath < b.filepath) return -1;
196 if (a.filepath < b.filepath) return -1;
197 if (a.filepath > b.filepath) return 1;
197 if (a.filepath > b.filepath) return 1;
198 return 0;
198 return 0;
199 };
199 };
200
200
201 NodeFilter.updateFilter = function (elem, e) {
201 NodeFilter.updateFilter = function (elem, e) {
202 return function () {
202 return function () {
203 // Reset timeout
203 // Reset timeout
204 NodeFilter.filterTimeout = null;
204 NodeFilter.filterTimeout = null;
205 var query = elem.value.toLowerCase();
205 var query = elem.value.toLowerCase();
206 var match = [];
206 var match = [];
207 var matches_max = 20;
207 var matches_max = 20;
208 if (query !== "") {
208 if (query !== "") {
209 var results = [];
209 var results = [];
210 for (var k = 0; k < nodes.length; k++) {
210 for (var k = 0; k < nodes.length; k++) {
211 var result = NodeFilter.fuzzy_match(
211 var result = NodeFilter.fuzzy_match(
212 nodes[k].name.toLowerCase(), query);
212 nodes[k].name.toLowerCase(), query);
213 if (result) {
213 if (result) {
214 result.type = nodes[k].type;
214 result.type = nodes[k].type;
215 result.filepath = nodes[k].name;
215 result.filepath = nodes[k].name;
216 results.push(result);
216 results.push(result);
217 }
217 }
218 }
218 }
219 results = results.sort(NodeFilter.sortPredicate);
219 results = results.sort(NodeFilter.sortPredicate);
220 var limit = matches_max;
220 var limit = matches_max;
221 if (results.length < matches_max) {
221 if (results.length < matches_max) {
222 limit = results.length;
222 limit = results.length;
223 }
223 }
224 for (var i = 0; i < limit; i++) {
224 for (var i = 0; i < limit; i++) {
225 if (query && results.length > 0) {
225 if (query && results.length > 0) {
226 var n = results[i].filepath;
226 var n = results[i].filepath;
227 var t = results[i].type;
227 var t = results[i].type;
228 var n_hl = n.split("");
228 var n_hl = n.split("");
229 var pos = results[i].highlight;
229 var pos = results[i].highlight;
230 for (var j = 0; j < pos.length; j++) {
230 for (var j = 0; j < pos.length; j++) {
231 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
231 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
232 }
232 }
233 n_hl = n_hl.join("");
233 n_hl = n_hl.join("");
234 var new_url = url_base.replace('__FPATH__', n);
234 var new_url = url_base.replace('__FPATH__', n);
235
235
236 var typeObj = {
236 var typeObj = {
237 dir: 'icon-directory browser-dir',
237 dir: 'icon-directory browser-dir',
238 file: 'icon-file-text browser-file'
238 file: 'icon-file-text browser-file'
239 };
239 };
240
240
241 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
241 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
242 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url, typeIcon, n_hl));
242 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url, typeIcon, n_hl));
243 }
243 }
244 }
244 }
245 if (results.length > limit) {
245 if (results.length > limit) {
246 var truncated_count = results.length - matches_max;
246 var truncated_count = results.length - matches_max;
247 if (truncated_count === 1) {
247 if (truncated_count === 1) {
248 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
248 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
249 } else {
249 } else {
250 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
250 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
251 }
251 }
252 }
252 }
253 }
253 }
254 if (query !== "") {
254 if (query !== "") {
255 $('#tbody').hide();
255 $('#tbody').hide();
256 $('#tbody_filtered').show();
256 $('#tbody_filtered').show();
257
257
258 if (match.length === 0) {
258 if (match.length === 0) {
259 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
259 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
260 }
260 }
261 $('#tbody_filtered').html(match.join(""));
261 $('#tbody_filtered').html(match.join(""));
262 } else {
262 } else {
263 $('#tbody').show();
263 $('#tbody').show();
264 $('#tbody_filtered').hide();
264 $('#tbody_filtered').hide();
265 }
265 }
266
266
267 };
267 };
268 };
268 };
269
269
270 };
270 };
271
271
272 var getIdentNode = function(n){
272 var getIdentNode = function(n){
273 // iterate through nodes until matched interesting node
273 // iterate through nodes until matched interesting node
274 if (typeof n === 'undefined'){
274 if (typeof n === 'undefined'){
275 return -1;
275 return -1;
276 }
276 }
277 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
277 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
278 return n;
278 return n;
279 }
279 }
280 else{
280 else{
281 return getIdentNode(n.parentNode);
281 return getIdentNode(n.parentNode);
282 }
282 }
283 };
283 };
284
284
285 var getSelectionLink = function(e) {
285 var getSelectionLink = function(e) {
286 // get selection from start/to nodes
286 // get selection from start/to nodes
287 if (typeof window.getSelection !== "undefined") {
287 if (typeof window.getSelection !== "undefined") {
288 s = window.getSelection();
288 s = window.getSelection();
289
289
290 from = getIdentNode(s.anchorNode);
290 from = getIdentNode(s.anchorNode);
291 till = getIdentNode(s.focusNode);
291 till = getIdentNode(s.focusNode);
292
292
293 f_int = parseInt(from.id.replace('L',''));
293 f_int = parseInt(from.id.replace('L',''));
294 t_int = parseInt(till.id.replace('L',''));
294 t_int = parseInt(till.id.replace('L',''));
295
295
296 if (f_int > t_int){
296 if (f_int > t_int){
297 // highlight from bottom
297 // highlight from bottom
298 offset = -35;
298 offset = -35;
299 ranges = [t_int,f_int];
299 ranges = [t_int,f_int];
300 }
300 }
301 else{
301 else{
302 // highligth from top
302 // highligth from top
303 offset = 35;
303 offset = 35;
304 ranges = [f_int,t_int];
304 ranges = [f_int,t_int];
305 }
305 }
306 // if we select more than 2 lines
306 // if we select more than 2 lines
307 if (ranges[0] !== ranges[1]){
307 if (ranges[0] !== ranges[1]){
308 if($('#linktt').length === 0){
308 if($('#linktt').length === 0){
309 hl_div = document.createElement('div');
309 hl_div = document.createElement('div');
310 hl_div.id = 'linktt';
310 hl_div.id = 'linktt';
311 }
311 }
312 hl_div.innerHTML = '';
312 hl_div.innerHTML = '';
313
313
314 anchor = '#L'+ranges[0]+'-'+ranges[1];
314 anchor = '#L'+ranges[0]+'-'+ranges[1];
315 var link = document.createElement('a');
315 var link = document.createElement('a');
316 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
316 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
317 link.innerHTML = _gettext('Selection link');
317 link.innerHTML = _gettext('Selection link');
318 hl_div.appendChild(link);
318 hl_div.appendChild(link);
319 $('#codeblock').append(hl_div);
319 $('#codeblock').append(hl_div);
320
320
321 var xy = $(till).offset();
321 var xy = $(till).offset();
322 $('#linktt').addClass('hl-tip-box tip-box');
322 $('#linktt').addClass('hl-tip-box tip-box');
323 $('#linktt').offset({top: xy.top + offset, left: xy.left});
323 $('#linktt').offset({top: xy.top + offset, left: xy.left});
324 $('#linktt').css('visibility','visible');
324 $('#linktt').css('visibility','visible');
325 }
325 }
326 else{
326 else{
327 $('#linktt').css('visibility','hidden');
327 $('#linktt').css('visibility','hidden');
328 }
328 }
329 }
329 }
330 };
330 };
331
331
332 var getFileState = function() {
332 var getFileState = function() {
333 // relies on a global set filesUrlData
333 // relies on a global set filesUrlData
334 var f_path = filesUrlData['f_path'];
334 var f_path = filesUrlData['f_path'];
335 var commit_id = filesUrlData['commit_id'];
335 var commit_id = filesUrlData['commit_id'];
336
336
337 var url_params = {
337 var url_params = {
338 repo_name: templateContext.repo_name,
338 repo_name: templateContext.repo_name,
339 commit_id: commit_id,
339 commit_id: commit_id,
340 f_path:'__FPATH__'
340 f_path:'__FPATH__'
341 };
341 };
342 if (atRef !== '') {
342 if (atRef !== '') {
343 url_params['at'] = atRef
343 url_params['at'] = atRef
344 }
344 }
345
345
346 var _url_base = pyroutes.url('repo_files', url_params);
346 var _url_base = pyroutes.url('repo_files', url_params);
347 var _node_list_url = pyroutes.url('repo_files_nodelist',
347 var _node_list_url = pyroutes.url('repo_files_nodelist',
348 {repo_name: templateContext.repo_name,
348 {repo_name: templateContext.repo_name,
349 commit_id: commit_id, f_path: f_path});
349 commit_id: commit_id, f_path: f_path});
350
350
351 return {
351 return {
352 f_path: f_path,
352 f_path: f_path,
353 commit_id: commit_id,
353 commit_id: commit_id,
354 node_list_url: _node_list_url,
354 node_list_url: _node_list_url,
355 url_base: _url_base
355 url_base: _url_base
356 };
356 };
357 };
357 };
358
358
359 var getFilesMetadata = function() {
359 var getFilesMetadata = function() {
360 // relies on metadataRequest global state
360 // relies on metadataRequest global state
361 if (metadataRequest && metadataRequest.readyState != 4) {
361 if (metadataRequest && metadataRequest.readyState != 4) {
362 metadataRequest.abort();
362 metadataRequest.abort();
363 }
363 }
364
364
365 if ($('#file-tree-wrapper').hasClass('full-load')) {
365 if ($('#file-tree-wrapper').hasClass('full-load')) {
366 // in case our HTML wrapper has full-load class we don't
366 // in case our HTML wrapper has full-load class we don't
367 // trigger the async load of metadata
367 // trigger the async load of metadata
368 return false;
368 return false;
369 }
369 }
370
370
371 var state = getFileState();
371 var state = getFileState();
372 var url_data = {
372 var url_data = {
373 'repo_name': templateContext.repo_name,
373 'repo_name': templateContext.repo_name,
374 'commit_id': state.commit_id,
374 'commit_id': state.commit_id,
375 'f_path': state.f_path
375 'f_path': state.f_path
376 };
376 };
377
377
378 var url = pyroutes.url('repo_nodetree_full', url_data);
378 var url = pyroutes.url('repo_nodetree_full', url_data);
379
379
380 metadataRequest = $.ajax({url: url});
380 metadataRequest = $.ajax({url: url});
381
381
382 metadataRequest.done(function(data) {
382 metadataRequest.done(function(data) {
383 $('#file-tree').html(data);
383 $('#file-tree').html(data);
384 timeagoActivate();
384 timeagoActivate();
385 });
385 });
386 metadataRequest.fail(function (data, textStatus, errorThrown) {
386 metadataRequest.fail(function (data, textStatus, errorThrown) {
387 if (data.status != 0) {
387 if (data.status != 0) {
388 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
388 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
389 }
389 }
390 });
390 });
391 };
391 };
392
392
393 // show more authors
393 // show more authors
394 var showAuthors = function(elem, annotate) {
394 var showAuthors = function(elem, annotate) {
395 var state = getFileState('callbacks');
395 var state = getFileState('callbacks');
396
396
397 var url = pyroutes.url('repo_file_authors',
397 var url = pyroutes.url('repo_file_authors',
398 {'repo_name': templateContext.repo_name,
398 {'repo_name': templateContext.repo_name,
399 'commit_id': state.commit_id, 'f_path': state.f_path});
399 'commit_id': state.commit_id, 'f_path': state.f_path});
400
400
401 $.pjax({
401 $.pjax({
402 url: url,
402 url: url,
403 data: 'annotate={0}'.format(annotate),
403 data: 'annotate={0}'.format(annotate),
404 container: '#file_authors',
404 container: '#file_authors',
405 push: false,
405 push: false,
406 timeout: 5000
406 timeout: 5000
407 }).complete(function(){
407 }).complete(function(){
408 $(elem).hide();
408 $(elem).hide();
409 $('#file_authors_title').html(_gettext('All Authors'))
409 $('#file_authors_title').html(_gettext('All Authors'))
410 })
410 })
411 };
411 };
412
413
414 (function (mod) {
415
416 if (typeof exports == "object" && typeof module == "object") {
417 // CommonJS
418 module.exports = mod();
419 } else {
420 // Plain browser env
421 (this || window).FileEditor = mod();
422 }
423
424 })(function () {
425 "use strict";
426
427 function FileEditor(textAreaElement, options) {
428 if (!(this instanceof FileEditor)) {
429 return new FileEditor(textAreaElement, options);
430 }
431 // bind the element instance to our Form
432 var te = $(textAreaElement).get(0);
433 if (te !== undefined) {
434 te.FileEditor = this;
435 }
436
437 this.modes_select = '#set_mode';
438 this.filename_selector = '#filename';
439 this.commit_btn_selector = '#commit_btn';
440 this.line_wrap_selector = '#line_wrap';
441 this.editor_preview_selector = '#editor_preview';
442
443 if (te !== undefined) {
444 this.cm = initCodeMirror(textAreaElement, null, false);
445 }
446
447 // FUNCTIONS and helpers
448 var self = this;
449
450 this.submitHandler = function() {
451 $(self.commit_btn_selector).on('click', function(e) {
452
453 var filename = $(self.filename_selector).val();
454 if (filename === "") {
455 alert("Missing filename");
456 e.preventDefault();
457 }
458
459 var button = $(this);
460 if (button.hasClass('clicked')) {
461 button.attr('disabled', true);
462 } else {
463 button.addClass('clicked');
464 }
465 });
466 };
467 this.submitHandler();
468
469 // on select line wraps change the editor
470 this.lineWrapHandler = function () {
471 $(self.line_wrap_selector).on('change', function (e) {
472 var selected = e.currentTarget;
473 var line_wraps = {'on': true, 'off': false}[selected.value];
474 setCodeMirrorLineWrap(self.cm, line_wraps)
475 });
476 };
477 this.lineWrapHandler();
478
479
480 this.showPreview = function () {
481
482 var _text = self.cm.getValue();
483 var _file_path = $(self.filename_selector).val();
484 if (_text && _file_path) {
485 $('.show-preview').addClass('active');
486 $('.show-editor').removeClass('active');
487
488 $(self.editor_preview_selector).show();
489 $(self.cm.getWrapperElement()).hide();
490
491
492 var post_data = {'text': _text, 'file_path': _file_path, 'csrf_token': CSRF_TOKEN};
493 $(self.editor_preview_selector).html(_gettext('Loading ...'));
494
495 var url = pyroutes.url('file_preview');
496
497 ajaxPOST(url, post_data, function (o) {
498 $(self.editor_preview_selector).html(o);
499 })
500 }
501
502 };
503
504 this.showEditor = function () {
505 $(self.editor_preview_selector).hide();
506 $('.show-editor').addClass('active');
507 $('.show-preview').removeClass('active');
508
509 $(self.cm.getWrapperElement()).show();
510 };
511
512
513 }
514
515 return FileEditor;
516 });
517
@@ -1,182 +1,110 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Files Add') % c.repo_name}
4 ${_('{} Files Add').format(c.repo_name)}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()"></%def>
15 ${_('Add new file')} @ ${h.show_id(c.commit)} ${_('Branch')}: ${c.commit.branch}
16 </%def>
17
15
18 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
20 </%def>
18 </%def>
21
19
22 <%def name="main()">
20 <%def name="main()">
21
23 <div class="box">
22 <div class="box">
24
23
25 <div class="edit-file-title">
24 <div class="edit-file-title">
26 ${self.breadcrumbs()}
25 <span class="title-heading">${_('Add new file')} @ <code>${h.show_id(c.commit)}</code></span>
26 <span class="tag branchtag"><i class="icon-branch"></i> ${c.commit.branch}</span>
27 </div>
27 </div>
28
28
29 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', enctype="multipart/form-data", class_="form-horizontal", request=request)}
29 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', request=request)}
30 <div class="edit-file-fieldset">
30 <div class="edit-file-fieldset">
31 <div class="fieldset">
31 <div class="path-items">
32 <div id="destination-label" class="left-label">
32 <ul>
33 ${_('Path')}:
33 <li class="breadcrumb-path">
34 </div>
35 <div class="right-content">
36 <div>
34 <div>
37 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))} /
35 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path='')}"><i class="icon-home"></i></a> /
38 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
36 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">${c.f_path}</a> ${('/' if c.f_path else '')}
39 </div>
37 </div>
40 </div>
38 </li>
41 </div>
39 <li class="location-path">
42 <div id="filename_container" class="fieldset">
40 <input class="file-name-input input-small" type="text" value="" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
43 <div class="filename-label left-label">
41 </li>
44 ${_('Filename')}:
42 </ul>
45 </div>
46 <div class="right-content">
47 <input class="input-small" type="text" value="" size="46" name="filename" id="filename">
48
49 </div>
50 </div>
43 </div>
51
44
52 </div>
45 </div>
53
46
54 <div class="table">
47 <div class="table">
55 <div id="files_data">
48 <div id="files_data">
49
56 <div id="codeblock" class="codeblock">
50 <div id="codeblock" class="codeblock">
57 <div class="code-header form" id="set_mode_header">
51 <div class="editor-items">
58 <div class="fields">
52 <div class="editor-action active show-editor pull-left" onclick="fileEditor.showEditor(); return false">
59 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
53 ${_('Edit')}
60 <label for="line_wrap">${_('line wraps')}</label>
54 </div>
61 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
55
56 <div class="editor-action show-preview pull-left" onclick="fileEditor.showPreview(); return false">
57 ${_('Preview')}
58 </div>
62
59
63 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
60 <div class="pull-right">
61 ${h.dropdownmenu('line_wrap', 'off', [('on', _('Line wraps on')), ('off', _('line wraps off'))], extra_classes=['last-item'])}
62 </div>
63 <div class="pull-right">
64 ${h.dropdownmenu('set_mode','plain',[('plain', _('plain'))], enable_filter=True)}
65 </div>
64 </div>
66 </div>
65 </div>
67
66 <div id="editor_container">
68 <div id="editor_container">
67 <pre id="editor_pre"></pre>
69 <pre id="editor_pre"></pre>
68 <textarea id="editor" name="content" ></textarea>
70 <textarea id="editor" name="content" ></textarea>
69 <div id="editor_preview"></div>
71 <div id="editor_preview"></div>
70 </div>
72 </div>
71 </div>
73 </div>
72 </div>
74 </div>
73 </div>
75 </div>
74
76
75 <div class="edit-file-fieldset">
77 <div class="edit-file-fieldset">
76 <div class="fieldset">
78 <div class="fieldset">
77 <div id="commit-message-label" class="commit-message-label left-label">
79 <div class="message">
78 ${_('Commit Message')}:
80 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
79 </div>
80 <div class="right-content">
81 <div class="message">
82 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
83 </div>
84 </div>
81 </div>
85 </div>
82 </div>
86 <div class="pull-right">
83 <div class="pull-left">
87 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
84 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
88 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
89 </div>
85 </div>
90 </div>
86 </div>
91 ${h.end_form()}
87 ${h.end_form()}
92 </div>
88 </div>
89
93 <script type="text/javascript">
90 <script type="text/javascript">
94
91
95 $('#commit_btn').on('click', function() {
92 $(document).ready(function () {
96 var button = $(this);
93 var modes_select = $('#set_mode');
97 if (button.hasClass('clicked')) {
94 var filename_selector = '#filename';
98 button.attr('disabled', true);
95 fillCodeMirrorOptions(modes_select);
99 } else {
100 button.addClass('clicked');
101 }
102 });
103
104 var hide_upload = function(){
105 $('#files_data').show();
106 $('#upload_file_container').hide();
107 $('#filename_container').show();
108 };
109
96
110 $('#file_enable').on('click', function(e){
97 fileEditor = new FileEditor('#editor');
111 e.preventDefault();
112 hide_upload();
113 });
114
115 var renderer = "";
116 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}";
117 var myCodeMirror = initCodeMirror('editor', reset_url, false);
118
119 var modes_select = $('#set_mode');
120 fillCodeMirrorOptions(modes_select);
121
122 var filename_selector = '#filename';
123 var callback = function(filename, mimetype, mode){
124 CodeMirrorPreviewEnable(mode);
125 };
126 // on change of select field set mode
127 setCodeMirrorModeFromSelect(
128 modes_select, filename_selector, myCodeMirror, callback);
129
130 // on entering the new filename set mode, from given extension
131 setCodeMirrorModeFromInput(
132 modes_select, filename_selector, myCodeMirror, callback);
133
98
134 // if the file is renderable set line wraps automatically
99 // on change of select field set mode
135 if (renderer !== ""){
100 setCodeMirrorModeFromSelect(modes_select, filename_selector, fileEditor.cm, null);
136 var line_wrap = 'on';
137 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
138 setCodeMirrorLineWrap(myCodeMirror, true);
139 }
140
141 // on select line wraps change the editor
142 $('#line_wrap').on('change', function(e){
143 var selected = e.currentTarget;
144 var line_wraps = {'on': true, 'off': false}[selected.value];
145 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
146 });
147
148 // render preview/edit button
149 $('#render_preview').on('click', function(e){
150 if($(this).hasClass('preview')){
151 $(this).removeClass('preview');
152 $(this).html("${_('Edit')}");
153 $('#editor_preview').show();
154 $(myCodeMirror.getWrapperElement()).hide();
155
101
156 var possible_renderer = {
102 // on entering the new filename set mode, from given extension
157 'rst':'rst',
103 setCodeMirrorModeFromInput(modes_select, filename_selector, fileEditor.cm, null);
158 'markdown':'markdown',
159 'gfm': 'markdown'}[myCodeMirror.getMode().name];
160 var _text = myCodeMirror.getValue();
161 var _renderer = possible_renderer || DEFAULT_RENDERER;
162 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
163 $('#editor_preview').html(_gettext('Loading ...'));
164 var url = pyroutes.url('repo_commit_comment_preview',
165 {'repo_name': '${c.repo_name}',
166 'commit_id': '${c.commit.raw_id}'});
167
104
168 ajaxPOST(url, post_data, function(o){
105 $('#filename').focus();
169 $('#editor_preview').html(o);
106
170 })
171 }
172 else{
173 $(this).addClass('preview');
174 $(this).html("${_('Preview')}");
175 $('#editor_preview').hide();
176 $(myCodeMirror.getWrapperElement()).show();
177 }
178 });
107 });
179 $('#filename').focus();
180
108
181 </script>
109 </script>
182 </%def>
110 </%def>
@@ -1,72 +1,85 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Files Delete') % c.repo_name}
4 ${_('{} Files Delete').format(c.repo_name)}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()"></%def>
15 ${_('Delete file')} @ ${h.show_id(c.commit)}
16 </%def>
17
15
18 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
20 </%def>
18 </%def>
21
19
22 <%def name="main()">
20 <%def name="main()">
21
23 <div class="box">
22 <div class="box">
23
24 <div class="edit-file-title">
24 <div class="edit-file-title">
25 ${self.breadcrumbs()}
25 <span class="title-heading">${_('Delete file')} @ <code>${h.show_id(c.commit)}</code></span>
26 <span class="tag branchtag"><i class="icon-branch"></i> ${c.commit.branch}</span>
26 </div>
27 </div>
27 ${h.secure_form(h.route_path('repo_files_delete_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', class_="form-horizontal", request=request)}
28
29 ${h.secure_form(h.route_path('repo_files_delete_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', request=request)}
28 <div class="edit-file-fieldset">
30 <div class="edit-file-fieldset">
29 <div class="fieldset">
31 <div class="path-items">
30 <div id="destination-label" class="left-label">
32 <li class="breadcrumb-path">
31 ${_('Path')}:
33 <div>
32 </div>
34 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path='')}"><i class="icon-home"></i></a> /
33 <div class="right-content">
35 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.file.dir_path)}">${c.file.dir_path}</a> ${('/' if c.file.dir_path else '')}
34 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))}</span>
36 </div>
35 </div>
37 </li>
38 <li class="location-path">
39 <input type="hidden" value="${c.f_path}" name="root_path">
40 <input class="file-name-input input-small" type="text" value="${c.file.name}" name="filename" id="filename" disabled="disabled">
41 </li>
36 </div>
42 </div>
43
37 </div>
44 </div>
38
45
39 <div id="codeblock" class="codeblock delete-file-preview">
46 <div id="codeblock" class="codeblock delete-file-preview">
40 <div class="code-body">
47 <div class="code-body">
41 %if c.file.is_binary:
48 %if c.file.is_binary:
42 ${_('Binary file (%s)') % c.file.mimetype}
49 ${_('Binary file (%s)') % c.file.mimetype}
43 %else:
50 %else:
44 %if c.file.size < c.visual.cut_off_limit_file:
51 %if c.file.size < c.visual.cut_off_limit_file:
45 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
52 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
46 %else:
53 %else:
47 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
54 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
48 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
55 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
49 %endif
56 %endif
50 %endif
57 %endif
51 </div>
58 </div>
52 </div>
59 </div>
53
60
54 <div class="edit-file-fieldset">
61 <div class="edit-file-fieldset">
55 <div class="fieldset">
62 <div class="fieldset">
56 <div id="commit-message-label" class="commit-message-label left-label">
63 <div class="message">
57 ${_('Commit Message')}:
64 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
58 </div>
59 <div class="right-content">
60 <div class="message">
61 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
62 </div>
63 </div>
65 </div>
64 </div>
66 </div>
65 <div class="pull-right">
67 <div class="pull-left">
66 ${h.reset('reset',_('Cancel'),class_="btn btn-small btn-danger")}
68 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-danger-action")}
67 ${h.submit('commit',_('Delete File'),class_="btn btn-small btn-danger-action")}
68 </div>
69 </div>
69 </div>
70 </div>
70 ${h.end_form()}
71 ${h.end_form()}
71 </div>
72 </div>
73
74
75 <script type="text/javascript">
76
77 $(document).ready(function () {
78
79 fileEditor = new FileEditor('#editor');
80
81 });
82
83 </script>
84
72 </%def>
85 </%def>
@@ -1,194 +1,120 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s File Edit') % c.repo_name}
4 ${_('{} Files Edit').format(c.repo_name)}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()"></%def>
15 ${_('Edit file')} @ ${h.show_id(c.commit)}
16 </%def>
17
15
18 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
20 </%def>
18 </%def>
21
19
22 <%def name="main()">
20 <%def name="main()">
23 <% renderer = h.renderer_from_filename(c.f_path)%>
21
24 <div class="box">
22 <div class="box">
23
25 <div class="edit-file-title">
24 <div class="edit-file-title">
26 ${self.breadcrumbs()}
25 <span class="title-heading">${_('Edit file')} @ <code>${h.show_id(c.commit)}</code></span>
26 <span class="tag branchtag"><i class="icon-branch"></i> ${c.commit.branch}</span>
27 </div>
27 </div>
28
29 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', request=request)}
28 <div class="edit-file-fieldset">
30 <div class="edit-file-fieldset">
29 <div class="fieldset">
31 <div class="path-items">
30 <div id="destination-label" class="left-label">
32 <ul>
31 ${_('Path')}:
33 <li class="breadcrumb-path">
32 </div>
34 <div>
33 <div class="right-content">
35 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path='')}"><i class="icon-home"></i></a> /
34 <div id="specify-custom-path-container">
36 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.file.dir_path)}">${c.file.dir_path}</a> ${('/' if c.file.dir_path else '')}
35 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))}</span>
36 </div>
37 </div>
37 </div>
38 </li>
39 <li class="location-path">
40 <input type="hidden" value="${c.f_path}" name="root_path">
41 <input class="file-name-input input-small" type="text" value="${c.file.name}" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
42 </li>
43 </ul>
38 </div>
44 </div>
45
39 </div>
46 </div>
40
47
41 <div class="table">
48 <div class="table">
42 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', request=request)}
49 <div id="files_data">
43 <div id="codeblock" class="codeblock" >
44 <div class="code-header">
45 <div class="stats">
46 <i class="icon-file"></i>
47 <span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file.commit.raw_id))}</span>
48 <span class="item">${h.format_byte_size_binary(c.file.size)}</span>
49 <span class="item last">${c.file.mimetype}</span>
50 <div class="buttons">
51 <a class="btn btn-mini" href="${h.route_path('repo_commits_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">
52 <i class="icon-time"></i> ${_('history')}
53 </a>
54
50
55 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
51 <div id="codeblock" class="codeblock">
56 % if not c.file.is_binary:
52 <div class="editor-items">
57 %if True:
53 <div class="editor-action active show-editor pull-left" onclick="fileEditor.showEditor(); return false">
58 ${h.link_to(_('source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
54 ${_('Edit')}
59 %else:
55 </div>
60 ${h.link_to(_('annotation'),h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
56
61 %endif
57 <div class="editor-action show-preview pull-left" onclick="fileEditor.showPreview(); return false">
58 ${_('Preview')}
59 </div>
62
60
63 <a class="btn btn-mini" href="${h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
61 <div class="pull-right">
64 ${_('raw')}
62 ${h.dropdownmenu('line_wrap', 'off', [('on', _('Line wraps on')), ('off', _('line wraps off')),])}
65 </a>
63 </div>
66 <a class="btn btn-mini" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
64 <div class="pull-right">
67 <i class="icon-archive"></i> ${_('download')}
65 ${h.dropdownmenu('set_mode','plain',[('plain', _('plain'))],enable_filter=True)}
68 </a>
66 </div>
69 % endif
70 % endif
71 </div>
67 </div>
72 </div>
73 <div class="form">
74 <label for="set_mode">${_('Editing file')}:</label>
75 ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path}
76 <input id="filename" type="text" name="filename" value="${c.file.name}">
77
68
78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
69 <div id="editor_container">
79 <label for="line_wrap">${_('line wraps')}</label>
70 <pre id="editor_pre"></pre>
80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
71 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
81
72 <div id="editor_preview" ></div>
82 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
73 </div>
83 </div>
84 </div>
85 <div id="editor_container">
86 <pre id="editor_pre"></pre>
87 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
88 <div id="editor_preview" ></div>
89 </div>
74 </div>
90 </div>
75 </div>
91 </div>
76 </div>
92
77
93 <div class="edit-file-fieldset">
78 <div class="edit-file-fieldset">
94 <div class="fieldset">
79 <div class="fieldset">
95 <div id="commit-message-label" class="commit-message-label left-label">
80 <div class="message">
96 ${_('Commit Message')}:
81 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
97 </div>
98 <div class="right-content">
99 <div class="message">
100 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
101 </div>
102 </div>
82 </div>
103 </div>
83 </div>
104 <div class="pull-right">
84 <div class="pull-left">
105 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
85 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
106 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
107 </div>
86 </div>
108 </div>
87 </div>
109 ${h.end_form()}
88 ${h.end_form()}
110 </div>
89 </div>
111
90
112 <script type="text/javascript">
91 <script type="text/javascript">
113 $(document).ready(function(){
114 var renderer = "${renderer}";
115 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.path)}";
116 var myCodeMirror = initCodeMirror('editor', reset_url);
117
92
93 $(document).ready(function() {
118 var modes_select = $('#set_mode');
94 var modes_select = $('#set_mode');
95 var filename_selector = '#filename';
119 fillCodeMirrorOptions(modes_select);
96 fillCodeMirrorOptions(modes_select);
120
97
98 fileEditor = new FileEditor('#editor');
99
121 // try to detect the mode based on the file we edit
100 // try to detect the mode based on the file we edit
122 var mimetype = "${c.file.mimetype}";
101 var detected_mode = detectCodeMirrorMode("${c.file.name}", "${c.file.mimetype}");
123 var detected_mode = detectCodeMirrorMode(
124 "${c.file.name}", mimetype);
125
102
126 if(detected_mode){
103 if (detected_mode) {
127 setCodeMirrorMode(myCodeMirror, detected_mode);
104 setCodeMirrorMode(fileEditor.cm, detected_mode);
128 $(modes_select).select2("val", mimetype);
105
129 $(modes_select).change();
106 var mimetype = $(modes_select).find("option[mode={0}]".format(detected_mode)).val();
130 setCodeMirrorMode(myCodeMirror, detected_mode);
107 $(modes_select).select2("val", mimetype).trigger('change');
131 }
108 }
132
109
133 var filename_selector = '#filename';
134 var callback = function(filename, mimetype, mode){
135 CodeMirrorPreviewEnable(mode);
136 };
137 // on change of select field set mode
110 // on change of select field set mode
138 setCodeMirrorModeFromSelect(
111 setCodeMirrorModeFromSelect(modes_select, filename_selector, fileEditor.cm, null);
139 modes_select, filename_selector, myCodeMirror, callback);
140
112
141 // on entering the new filename set mode, from given extension
113 // on entering the new filename set mode, from given extension
142 setCodeMirrorModeFromInput(
114 setCodeMirrorModeFromInput(modes_select, filename_selector, fileEditor.cm, null);
143 modes_select, filename_selector, myCodeMirror, callback);
144
145 // if the file is renderable set line wraps automatically
146 if (renderer !== ""){
147 var line_wrap = 'on';
148 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
149 setCodeMirrorLineWrap(myCodeMirror, true);
150 }
151 // on select line wraps change the editor
152 $('#line_wrap').on('change', function(e){
153 var selected = e.currentTarget;
154 var line_wraps = {'on': true, 'off': false}[selected.value];
155 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
156 });
157
115
158 // render preview/edit button
116 });
159 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
160 $('#render_preview').removeClass('hidden');
161 }
162 $('#render_preview').on('click', function(e){
163 if($(this).hasClass('preview')){
164 $(this).removeClass('preview');
165 $(this).html("${_('Edit')}");
166 $('#editor_preview').show();
167 $(myCodeMirror.getWrapperElement()).hide();
168
117
169 var possible_renderer = {
170 'rst':'rst',
171 'markdown':'markdown',
172 'gfm': 'markdown'}[myCodeMirror.getMode().name];
173 var _text = myCodeMirror.getValue();
174 var _renderer = possible_renderer || DEFAULT_RENDERER;
175 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
176 $('#editor_preview').html(_gettext('Loading ...'));
177 var url = pyroutes.url('repo_commit_comment_preview',
178 {'repo_name': '${c.repo_name}',
179 'commit_id': '${c.commit.raw_id}'});
180 ajaxPOST(url, post_data, function(o){
181 $('#editor_preview').html(o);
182 })
183 }
184 else{
185 $(this).addClass('preview');
186 $(this).html("${_('Preview')}");
187 $('#editor_preview').hide();
188 $(myCodeMirror.getWrapperElement()).show();
189 }
190 });
191
118
192 })
193 </script>
119 </script>
194 </%def>
120 </%def>
@@ -1,160 +1,160 b''
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
2
2
3 <div id="codeblock" class="browserblock">
3 <div id="codeblock" class="browserblock">
4 <div class="browser-header">
4 <div class="browser-header">
5 <div class="browser-nav">
5 <div class="browser-nav">
6 <div class="pull-left">
6 <div class="pull-left">
7 ## loads the history for a file
7 ## loads the history for a file
8 ${h.hidden('file_refs_filter')}
8 ${h.hidden('file_refs_filter')}
9 </div>
9 </div>
10
10
11 <div class="pull-right">
11 <div class="pull-right">
12
12
13 ## Download
13 ## Download
14 % if c.lf_node:
14 % if c.lf_node:
15 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
15 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
16 ${_('Download largefile')}
16 ${_('Download largefile')}
17 </a>
17 </a>
18 % else:
18 % else:
19 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
19 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
20 ${_('Download file')}
20 ${_('Download file')}
21 </a>
21 </a>
22 % endif
22 % endif
23
23
24 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
24 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
25 ## on branch head, can edit files
25 ## on branch head, can edit files
26 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
26 %if c.on_branch_head and c.branch_or_raw_id:
27 ## binary files are delete only
27 ## binary files are delete only
28 % if c.file.is_binary:
28 % if c.file.is_binary:
29 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing binary files not allowed'))}
29 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing binary files not allowed'))}
30 ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit'),class_="btn btn-danger")}
30 ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path),class_="btn btn-danger")}
31 % else:
31 % else:
32 <a class="btn btn-default" href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">
32 <a class="btn btn-default" href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path)}">
33 ${_('Edit on branch: ')}<code>${c.branch_name}</code>
33 ${_('Edit on branch: ')}<code>${c.branch_name}</code>
34 </a>
34 </a>
35
35
36 <a class="btn btn-danger" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">
36 <a class="btn btn-danger" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path)}">
37 ${_('Delete')}
37 ${_('Delete')}
38 </a>
38 </a>
39 % endif
39 % endif
40 ## not on head, forbid all
40 ## not on head, forbid all
41 % else:
41 % else:
42 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
42 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
43 ${h.link_to(_('Delete'), '#Delete', class_="btn btn-default btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
43 ${h.link_to(_('Delete'), '#Delete', class_="btn btn-default btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
44 % endif
44 % endif
45 %endif
45 %endif
46
46
47 </div>
47 </div>
48 </div>
48 </div>
49 <div id="file_history_container"></div>
49 <div id="file_history_container"></div>
50
50
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="codeblock">
54 <div class="codeblock">
55 <div class=" codeblock-header">
55 <div class=" codeblock-header">
56 <div class="file-filename">
56 <div class="file-filename">
57 <i class="icon-file"></i> ${c.file}
57 <i class="icon-file"></i> ${c.file}
58 </div>
58 </div>
59
59
60 <div class="file-stats">
60 <div class="file-stats">
61
61
62 <div class="stats-info">
62 <div class="stats-info">
63 <span class="stats-first-item">${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span>
63 <span class="stats-first-item">${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span>
64
64
65 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
65 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
66 % if c.lf_node:
66 % if c.lf_node:
67 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
67 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
68 % endif
68 % endif
69 <span> | ${c.file.mimetype} </span>
69 <span> | ${c.file.mimetype} </span>
70 <span> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
70 <span> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
71 </div>
71 </div>
72 </div>
72 </div>
73 </div>
73 </div>
74
74
75 <div class="path clear-fix">
75 <div class="path clear-fix">
76 <div class="pull-left">
76 <div class="pull-left">
77 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'))}
77 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'))}
78 </div>
78 </div>
79
79
80 <div class="pull-right stats">
80 <div class="pull-right stats">
81 <a id="file_history_overview" href="#loadHistory">
81 <a id="file_history_overview" href="#loadHistory">
82 ${_('History')}
82 ${_('History')}
83 </a>
83 </a>
84 |
84 |
85 %if c.annotate:
85 %if c.annotate:
86 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
86 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
87 %else:
87 %else:
88 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
88 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
89 %endif
89 %endif
90 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
90 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
91
91
92 </div>
92 </div>
93 <div class="clear-fix"></div>
93 <div class="clear-fix"></div>
94 </div>
94 </div>
95
95
96 <div class="code-body clear-fix ">
96 <div class="code-body clear-fix ">
97
97
98 %if c.file.is_binary:
98 %if c.file.is_binary:
99 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
99 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
100 % if rendered_binary:
100 % if rendered_binary:
101 ${rendered_binary}
101 ${rendered_binary}
102 % else:
102 % else:
103 <div>
103 <div>
104 ${_('Binary file (%s)') % c.file.mimetype}
104 ${_('Binary file (%s)') % c.file.mimetype}
105 </div>
105 </div>
106 % endif
106 % endif
107 %else:
107 %else:
108 % if c.file.size < c.visual.cut_off_limit_file:
108 % if c.file.size < c.visual.cut_off_limit_file:
109 %if c.renderer and not c.annotate:
109 %if c.renderer and not c.annotate:
110 ## pick relative url based on renderer
110 ## pick relative url based on renderer
111 <%
111 <%
112 relative_urls = {
112 relative_urls = {
113 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
113 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
114 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
114 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
115 }
115 }
116 %>
116 %>
117 ${h.render(c.file.content, renderer=c.renderer, relative_urls=relative_urls)}
117 ${h.render(c.file.content, renderer=c.renderer, relative_urls=relative_urls)}
118 %else:
118 %else:
119 <table class="cb codehilite">
119 <table class="cb codehilite">
120 %if c.annotate:
120 %if c.annotate:
121 <% color_hasher = h.color_hasher() %>
121 <% color_hasher = h.color_hasher() %>
122 %for annotation, lines in c.annotated_lines:
122 %for annotation, lines in c.annotated_lines:
123 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
123 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
124 %endfor
124 %endfor
125 %else:
125 %else:
126 %for line_num, tokens in enumerate(c.lines, 1):
126 %for line_num, tokens in enumerate(c.lines, 1):
127 ${sourceblock.render_line(line_num, tokens)}
127 ${sourceblock.render_line(line_num, tokens)}
128 %endfor
128 %endfor
129 %endif
129 %endif
130 </table>
130 </table>
131 %endif
131 %endif
132 %else:
132 %else:
133 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
133 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
134 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
134 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
135 %endif
135 %endif
136 %endif
136 %endif
137 </div>
137 </div>
138
138
139 </div>
139 </div>
140
140
141 <script type="text/javascript">
141 <script type="text/javascript">
142 % if request.GET.get('mark'):
142 % if request.GET.get('mark'):
143
143
144 $(function(){
144 $(function(){
145 $(".codehilite").mark(
145 $(".codehilite").mark(
146 "${request.GET.get('mark')}",
146 "${request.GET.get('mark')}",
147 {
147 {
148 "className": 'match',
148 "className": 'match',
149 "accuracy": "complementary",
149 "accuracy": "complementary",
150 "ignorePunctuation": ":._(){}[]!'+=".split(""),
150 "ignorePunctuation": ":._(){}[]!'+=".split(""),
151 "each": function(el) {
151 "each": function(el) {
152 // and also highlight lines !
152 // and also highlight lines !
153 $($(el).closest('tr')).find('td.cb-lineno').addClass('cb-line-selected');
153 $($(el).closest('tr')).find('td.cb-lineno').addClass('cb-line-selected');
154 }
154 }
155 }
155 }
156 );
156 );
157
157
158 });
158 });
159 % endif
159 % endif
160 </script>
160 </script>
@@ -1,190 +1,207 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Files Add') % c.repo_name}
4 ${_('{} Files Upload').format(c.repo_name)}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()"></%def>
15 ${_('Add new file')} @ ${h.show_id(c.commit)} ${_('Branch')}: ${c.commit.branch}
16 </%def>
17
15
18 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
20 </%def>
18 </%def>
21
19
22 <%def name="main()">
20 <%def name="main()">
21
23 <div class="box">
22 <div class="box">
23 ## Template for uploads
24 <div style="display: none" id="tpl-dropzone">
25 <div class="dz-preview dz-file-preview">
26 <div class="dz-details">
27
28 <div class="dz-filename">
29 <span data-dz-name></span>
30 </div>
31 <div class="dz-filename-size">
32 <span class="dz-size" data-dz-size></span>
24
33
25 <div class="edit-file-title">
34 </div>
26 ${self.breadcrumbs()}
35
36 <div class="dz-sending" style="display: none">${_('Uploading...')}</div>
37 <div class="dz-response" style="display: none">
38 ${_('Uploaded')} 100%
39 </div>
40
41 </div>
42 <div class="dz-progress">
43 <span class="dz-upload" data-dz-uploadprogress></span>
44 </div>
45
46 <div class="dz-error-message">
47 </div>
48 </div>
27 </div>
49 </div>
28
50
29 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', enctype="multipart/form-data", class_="form-horizontal", request=request)}
51 <div class="edit-file-title">
52 <span class="title-heading">${_('Upload new file')} @ <code>${h.show_id(c.commit)}</code></span>
53 <span class="tag branchtag"><i class="icon-branch"></i> ${c.commit.branch}</span>
54 </div>
55
56 <% form_url = h.route_path('repo_files_upload_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path) %>
57 ##${h.secure_form(form_url, id='eform', enctype="multipart/form-data", request=request)}
30 <div class="edit-file-fieldset">
58 <div class="edit-file-fieldset">
31 <div class="fieldset">
59 <div class="path-items">
32 <div id="destination-label" class="left-label">
60 <ul>
33 ${_('Path')}:
61 <li class="breadcrumb-path">
34 </div>
35 <div class="right-content">
36 <div>
62 <div>
37 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))} /
63 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path='')}"><i class="icon-home"></i></a> /
38 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
64 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">${c.f_path}</a> ${('/' if c.f_path else '')}
39 </div>
65 </div>
40 </div>
66 </li>
67 <li class="location-path">
68
69 </li>
70 </ul>
41 </div>
71 </div>
42
72
43 <div id="upload_file_container" class="fieldset">
73 </div>
44 <div class="filename-label left-label">
74
45 ${_('Filename')}:
75 <div class="upload-form table">
46 </div>
76 <div id="files_data">
47 <div class="right-content">
48 <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}">
49 </div>
50 <div class="filename-label left-label file-upload-label">
51 ${_('Upload file')}:
52 </div>
53 <div class="right-content file-upload-input">
54 <label for="upload_file" class="btn btn-default">Browse</label>
55
77
56 <input type="file" name="upload_file" id="upload_file">
78 <div class="dropzone-wrapper" id="file-uploader">
79 <div class="dropzone-pure">
80 <div class="dz-message">
81 <i class="icon-upload" style="font-size:36px"></i></br>
82 ${_("Drag'n Drop files here or")} <span class="link">${_('Choose your files')}</span>.<br>
83 </div>
84 </div>
85
57 </div>
86 </div>
58 </div>
87 </div>
59
88
60 </div>
89 </div>
61
90
62 <div class="table">
91 <div class="upload-form edit-file-fieldset">
63 <div id="files_data">
92 <div class="fieldset">
64 <div id="codeblock" class="codeblock">
93 <div class="message">
65 <div class="code-header form" id="set_mode_header">
94 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
66 <div class="fields">
67 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
68 <label for="line_wrap">${_('line wraps')}</label>
69 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
70
71 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
72 </div>
73 </div>
95 </div>
74 <div id="editor_container">
96 </div>
75 <pre id="editor_pre"></pre>
97 <div class="pull-left">
76 <textarea id="editor" name="content" ></textarea>
98 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
77 <div id="editor_preview"></div>
78 </div>
79 </div>
80 </div>
99 </div>
81 </div>
100 </div>
101 ##${h.end_form()}
82
102
83 <div class="edit-file-fieldset">
103 <div class="file-upload-transaction-wrapper" style="display: none">
84 <div class="fieldset">
104 <div class="file-upload-transaction">
85 <div id="commit-message-label" class="commit-message-label left-label">
105 <h3>${_('Commiting...')}</h3>
86 ${_('Commit Message')}:
106 <p>${_('Please wait while the files are being uploaded')}</p>
87 </div>
107 <p class="error" style="display: none">
88 <div class="right-content">
108
89 <div class="message">
109 </p>
90 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
110 <i class="icon-spin animate-spin"></i>
91 </div>
111 <p></p>
92 </div>
93 </div>
94 <div class="pull-right">
95 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
96 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
97 </div>
98 </div>
112 </div>
99 ${h.end_form()}
113 </div>
114
100 </div>
115 </div>
116
101 <script type="text/javascript">
117 <script type="text/javascript">
102
118
103 $('#commit_btn').on('click', function() {
119 $(document).ready(function () {
104 var button = $(this);
105 if (button.hasClass('clicked')) {
106 button.attr('disabled', true);
107 } else {
108 button.addClass('clicked');
109 }
110 });
111
112 var hide_upload = function(){
113 $('#files_data').show();
114 $('#upload_file_container').hide();
115 $('#filename_container').show();
116 };
117
120
118 $('#file_enable').on('click', function(e){
121 //see: https://www.dropzonejs.com/#configuration
119 e.preventDefault();
122 myDropzone = new Dropzone("div#file-uploader", {
120 hide_upload();
123 url: "${form_url}",
121 });
124 headers: {"X-CSRF-Token": CSRF_TOKEN},
122
125 paramName: function () {
123 var renderer = "";
126 return "files_upload"
124 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}";
127 }, // The name that will be used to transfer the file
125 var myCodeMirror = initCodeMirror('editor', reset_url, false);
128 parallelUploads: 20,
126
129 maxFiles: 20,
127 var modes_select = $('#set_mode');
130 uploadMultiple: true,
128 fillCodeMirrorOptions(modes_select);
131 //chunking: true, // use chunking transfer, not supported at the moment
132 //maxFilesize: 2, // in MBs
133 autoProcessQueue: false, // if false queue will not be processed automatically.
134 createImageThumbnails: false,
135 previewTemplate: document.querySelector('#tpl-dropzone').innerHTML,
136 accept: function (file, done) {
137 done();
138 },
139 init: function () {
140 this.on("addedfile", function (file) {
129
141
130 var filename_selector = '#filename';
142 });
131 var callback = function(filename, mimetype, mode){
143
132 CodeMirrorPreviewEnable(mode);
144 this.on("sending", function (file, xhr, formData) {
133 };
145 formData.append("message", $('#commit').val());
134 // on change of select field set mode
146 $(file.previewElement).find('.dz-sending').show();
135 setCodeMirrorModeFromSelect(
147 });
136 modes_select, filename_selector, myCodeMirror, callback);
137
148
138 // on entering the new filename set mode, from given extension
149 this.on("success", function (file, response) {
139 setCodeMirrorModeFromInput(
150 $(file.previewElement).find('.dz-sending').hide();
140 modes_select, filename_selector, myCodeMirror, callback);
151 $(file.previewElement).find('.dz-response').show();
152
153 if (response.error !== null) {
154 $('.file-upload-transaction-wrapper .error').html('ERROR: {0}'.format(response.error));
155 $('.file-upload-transaction-wrapper .error').show();
156 $('.file-upload-transaction-wrapper i').hide()
157 }
158
159 var redirect_url = response.redirect_url || '/';
160 window.location = redirect_url
141
161
142 // if the file is renderable set line wraps automatically
162 });
143 if (renderer !== ""){
163
144 var line_wrap = 'on';
164 this.on("error", function (file, errorMessage, xhr) {
145 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
165 var error = null;
146 setCodeMirrorLineWrap(myCodeMirror, true);
147 }
148
166
149 // on select line wraps change the editor
167 if (xhr !== undefined){
150 $('#line_wrap').on('change', function(e){
168 var httpStatus = xhr.status + " " + xhr.statusText;
151 var selected = e.currentTarget;
169 if (xhr.status >= 500) {
152 var line_wraps = {'on': true, 'off': false}[selected.value];
170 error = httpStatus;
153 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
171 }
154 });
172 }
155
173
156 // render preview/edit button
174 if (error === null) {
157 $('#render_preview').on('click', function(e){
175 error = errorMessage.error || errorMessage || httpStatus;
158 if($(this).hasClass('preview')){
176 }
159 $(this).removeClass('preview');
177
160 $(this).html("${_('Edit')}");
178 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
161 $('#editor_preview').show();
179 });
162 $(myCodeMirror.getWrapperElement()).hide();
180 }
181 });
163
182
164 var possible_renderer = {
183 $('#commit_btn').on('click', function(e) {
165 'rst':'rst',
184 e.preventDefault();
166 'markdown':'markdown',
185 var button = $(this);
167 'gfm': 'markdown'}[myCodeMirror.getMode().name];
186 if (button.hasClass('clicked')) {
168 var _text = myCodeMirror.getValue();
187 button.attr('disabled', true);
169 var _renderer = possible_renderer || DEFAULT_RENDERER;
188 } else {
170 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
189 button.addClass('clicked');
171 $('#editor_preview').html(_gettext('Loading ...'));
190 }
172 var url = pyroutes.url('repo_commit_comment_preview',
173 {'repo_name': '${c.repo_name}',
174 'commit_id': '${c.commit.raw_id}'});
175
191
176 ajaxPOST(url, post_data, function(o){
192 var files = myDropzone.getQueuedFiles();
177 $('#editor_preview').html(o);
193 if (files.length === 0) {
178 })
194 alert("Missing files");
179 }
195 e.preventDefault();
180 else{
196 }
181 $(this).addClass('preview');
197
182 $(this).html("${_('Preview')}");
198 $('.upload-form').hide();
183 $('#editor_preview').hide();
199 $('.file-upload-transaction-wrapper').show();
184 $(myCodeMirror.getWrapperElement()).show();
200 myDropzone.processQueue();
185 }
201
202 });
203
186 });
204 });
187 $('#filename').focus();
188
205
189 </script>
206 </script>
190 </%def>
207 </%def>
General Comments 0
You need to be logged in to leave comments. Login now