##// END OF EJS Templates
changelog: rename changelog to commits pages
marcink -
r3742:0d621dfb new-ui
parent child Browse files
Show More
@@ -1,749 +1,749 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,
31 CSRFRequired)
31 CSRFRequired)
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.model.db import (
35 from rhodecode.model.db import (
36 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
36 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.scm import RepoGroupList, RepoList
39 from rhodecode.model.scm import RepoGroupList, RepoList
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.user_group import UserGroupModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class HomeView(BaseAppView):
46 class HomeView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context()
49 c = self._get_local_tmpl_context()
50 c.user = c.auth_user.get_instance()
50 c.user = c.auth_user.get_instance()
51
51
52 return c
52 return c
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @view_config(
55 @view_config(
56 route_name='user_autocomplete_data', request_method='GET',
56 route_name='user_autocomplete_data', request_method='GET',
57 renderer='json_ext', xhr=True)
57 renderer='json_ext', xhr=True)
58 def user_autocomplete_data(self):
58 def user_autocomplete_data(self):
59 self.load_default_context()
59 self.load_default_context()
60 query = self.request.GET.get('query')
60 query = self.request.GET.get('query')
61 active = str2bool(self.request.GET.get('active') or True)
61 active = str2bool(self.request.GET.get('active') or True)
62 include_groups = str2bool(self.request.GET.get('user_groups'))
62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65
65
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 query, active, include_groups)
67 query, active, include_groups)
68
68
69 _users = UserModel().get_users(
69 _users = UserModel().get_users(
70 name_contains=query, only_active=active)
70 name_contains=query, only_active=active)
71
71
72 def maybe_skip_default_user(usr):
72 def maybe_skip_default_user(usr):
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 return False
74 return False
75 return True
75 return True
76 _users = filter(maybe_skip_default_user, _users)
76 _users = filter(maybe_skip_default_user, _users)
77
77
78 if include_groups:
78 if include_groups:
79 # extend with user groups
79 # extend with user groups
80 _user_groups = UserGroupModel().get_user_groups(
80 _user_groups = UserGroupModel().get_user_groups(
81 name_contains=query, only_active=active,
81 name_contains=query, only_active=active,
82 expand_groups=expand_groups)
82 expand_groups=expand_groups)
83 _users = _users + _user_groups
83 _users = _users + _user_groups
84
84
85 return {'suggestions': _users}
85 return {'suggestions': _users}
86
86
87 @LoginRequired()
87 @LoginRequired()
88 @NotAnonymous()
88 @NotAnonymous()
89 @view_config(
89 @view_config(
90 route_name='user_group_autocomplete_data', request_method='GET',
90 route_name='user_group_autocomplete_data', request_method='GET',
91 renderer='json_ext', xhr=True)
91 renderer='json_ext', xhr=True)
92 def user_group_autocomplete_data(self):
92 def user_group_autocomplete_data(self):
93 self.load_default_context()
93 self.load_default_context()
94 query = self.request.GET.get('query')
94 query = self.request.GET.get('query')
95 active = str2bool(self.request.GET.get('active') or True)
95 active = str2bool(self.request.GET.get('active') or True)
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97
97
98 log.debug('generating user group list, query:%s, active:%s',
98 log.debug('generating user group list, query:%s, active:%s',
99 query, active)
99 query, active)
100
100
101 _user_groups = UserGroupModel().get_user_groups(
101 _user_groups = UserGroupModel().get_user_groups(
102 name_contains=query, only_active=active,
102 name_contains=query, only_active=active,
103 expand_groups=expand_groups)
103 expand_groups=expand_groups)
104 _user_groups = _user_groups
104 _user_groups = _user_groups
105
105
106 return {'suggestions': _user_groups}
106 return {'suggestions': _user_groups}
107
107
108 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
108 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
109 org_query = name_contains
109 org_query = name_contains
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 ['repository.read', 'repository.write', 'repository.admin'],
111 ['repository.read', 'repository.write', 'repository.admin'],
112 cache=False, name_filter=name_contains) or [-1]
112 cache=False, name_filter=name_contains) or [-1]
113
113
114 query = Repository.query()\
114 query = Repository.query()\
115 .filter(Repository.archived.isnot(true()))\
115 .filter(Repository.archived.isnot(true()))\
116 .filter(or_(
116 .filter(or_(
117 # generate multiple IN to fix limitation problems
117 # generate multiple IN to fix limitation problems
118 *in_filter_generator(Repository.repo_id, allowed_ids)
118 *in_filter_generator(Repository.repo_id, allowed_ids)
119 ))
119 ))
120
120
121 query = query.order_by(case(
121 query = query.order_by(case(
122 [
122 [
123 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
123 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
124 ],
124 ],
125 ))
125 ))
126 query = query.order_by(func.length(Repository.repo_name))
126 query = query.order_by(func.length(Repository.repo_name))
127 query = query.order_by(Repository.repo_name)
127 query = query.order_by(Repository.repo_name)
128
128
129 if repo_type:
129 if repo_type:
130 query = query.filter(Repository.repo_type == repo_type)
130 query = query.filter(Repository.repo_type == repo_type)
131
131
132 if name_contains:
132 if name_contains:
133 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
133 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
134 query = query.filter(
134 query = query.filter(
135 Repository.repo_name.ilike(ilike_expression))
135 Repository.repo_name.ilike(ilike_expression))
136 query = query.limit(limit)
136 query = query.limit(limit)
137
137
138 acl_iter = query
138 acl_iter = query
139
139
140 return [
140 return [
141 {
141 {
142 'id': obj.repo_name,
142 'id': obj.repo_name,
143 'value': org_query,
143 'value': org_query,
144 'value_display': obj.repo_name,
144 'value_display': obj.repo_name,
145 'text': obj.repo_name,
145 'text': obj.repo_name,
146 'type': 'repo',
146 'type': 'repo',
147 'repo_id': obj.repo_id,
147 'repo_id': obj.repo_id,
148 'repo_type': obj.repo_type,
148 'repo_type': obj.repo_type,
149 'private': obj.private,
149 'private': obj.private,
150 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
150 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
151 }
151 }
152 for obj in acl_iter]
152 for obj in acl_iter]
153
153
154 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
154 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
155 org_query = name_contains
155 org_query = name_contains
156 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
156 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
157 ['group.read', 'group.write', 'group.admin'],
157 ['group.read', 'group.write', 'group.admin'],
158 cache=False, name_filter=name_contains) or [-1]
158 cache=False, name_filter=name_contains) or [-1]
159
159
160 query = RepoGroup.query()\
160 query = RepoGroup.query()\
161 .filter(or_(
161 .filter(or_(
162 # generate multiple IN to fix limitation problems
162 # generate multiple IN to fix limitation problems
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
164 ))
164 ))
165
165
166 query = query.order_by(case(
166 query = query.order_by(case(
167 [
167 [
168 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
168 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
169 ],
169 ],
170 ))
170 ))
171 query = query.order_by(func.length(RepoGroup.group_name))
171 query = query.order_by(func.length(RepoGroup.group_name))
172 query = query.order_by(RepoGroup.group_name)
172 query = query.order_by(RepoGroup.group_name)
173
173
174 if name_contains:
174 if name_contains:
175 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
175 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
176 query = query.filter(
176 query = query.filter(
177 RepoGroup.group_name.ilike(ilike_expression))
177 RepoGroup.group_name.ilike(ilike_expression))
178 query = query.limit(limit)
178 query = query.limit(limit)
179
179
180 acl_iter = query
180 acl_iter = query
181
181
182 return [
182 return [
183 {
183 {
184 'id': obj.group_name,
184 'id': obj.group_name,
185 'value': org_query,
185 'value': org_query,
186 'value_display': obj.group_name,
186 'value_display': obj.group_name,
187 'text': obj.group_name,
187 'text': obj.group_name,
188 'type': 'repo_group',
188 'type': 'repo_group',
189 'repo_group_id': obj.group_id,
189 'repo_group_id': obj.group_id,
190 'url': h.route_path(
190 'url': h.route_path(
191 'repo_group_home', repo_group_name=obj.group_name)
191 'repo_group_home', repo_group_name=obj.group_name)
192 }
192 }
193 for obj in acl_iter]
193 for obj in acl_iter]
194
194
195 def _get_user_list(self, name_contains=None, limit=20):
195 def _get_user_list(self, name_contains=None, limit=20):
196 org_query = name_contains
196 org_query = name_contains
197 if not name_contains:
197 if not name_contains:
198 return [], False
198 return [], False
199
199
200 # TODO(marcink): should all logged in users be allowed to search others?
200 # TODO(marcink): should all logged in users be allowed to search others?
201 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
201 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
202 if not allowed_user_search:
202 if not allowed_user_search:
203 return [], False
203 return [], False
204
204
205 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
205 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
206 if len(name_contains) != 1:
206 if len(name_contains) != 1:
207 return [], False
207 return [], False
208
208
209 name_contains = name_contains[0]
209 name_contains = name_contains[0]
210
210
211 query = User.query()\
211 query = User.query()\
212 .order_by(func.length(User.username))\
212 .order_by(func.length(User.username))\
213 .order_by(User.username) \
213 .order_by(User.username) \
214 .filter(User.username != User.DEFAULT_USER)
214 .filter(User.username != User.DEFAULT_USER)
215
215
216 if name_contains:
216 if name_contains:
217 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
217 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
218 query = query.filter(
218 query = query.filter(
219 User.username.ilike(ilike_expression))
219 User.username.ilike(ilike_expression))
220 query = query.limit(limit)
220 query = query.limit(limit)
221
221
222 acl_iter = query
222 acl_iter = query
223
223
224 return [
224 return [
225 {
225 {
226 'id': obj.user_id,
226 'id': obj.user_id,
227 'value': org_query,
227 'value': org_query,
228 'value_display': 'user: `{}`'.format(obj.username),
228 'value_display': 'user: `{}`'.format(obj.username),
229 'type': 'user',
229 'type': 'user',
230 'icon_link': h.gravatar_url(obj.email, 30),
230 'icon_link': h.gravatar_url(obj.email, 30),
231 'url': h.route_path(
231 'url': h.route_path(
232 'user_profile', username=obj.username)
232 'user_profile', username=obj.username)
233 }
233 }
234 for obj in acl_iter], True
234 for obj in acl_iter], True
235
235
236 def _get_user_groups_list(self, name_contains=None, limit=20):
236 def _get_user_groups_list(self, name_contains=None, limit=20):
237 org_query = name_contains
237 org_query = name_contains
238 if not name_contains:
238 if not name_contains:
239 return [], False
239 return [], False
240
240
241 # TODO(marcink): should all logged in users be allowed to search others?
241 # TODO(marcink): should all logged in users be allowed to search others?
242 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
242 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
243 if not allowed_user_search:
243 if not allowed_user_search:
244 return [], False
244 return [], False
245
245
246 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
246 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
247 if len(name_contains) != 1:
247 if len(name_contains) != 1:
248 return [], False
248 return [], False
249
249
250 name_contains = name_contains[0]
250 name_contains = name_contains[0]
251
251
252 query = UserGroup.query()\
252 query = UserGroup.query()\
253 .order_by(func.length(UserGroup.users_group_name))\
253 .order_by(func.length(UserGroup.users_group_name))\
254 .order_by(UserGroup.users_group_name)
254 .order_by(UserGroup.users_group_name)
255
255
256 if name_contains:
256 if name_contains:
257 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
257 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
258 query = query.filter(
258 query = query.filter(
259 UserGroup.users_group_name.ilike(ilike_expression))
259 UserGroup.users_group_name.ilike(ilike_expression))
260 query = query.limit(limit)
260 query = query.limit(limit)
261
261
262 acl_iter = query
262 acl_iter = query
263
263
264 return [
264 return [
265 {
265 {
266 'id': obj.users_group_id,
266 'id': obj.users_group_id,
267 'value': org_query,
267 'value': org_query,
268 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
268 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
269 'type': 'user_group',
269 'type': 'user_group',
270 'url': h.route_path(
270 'url': h.route_path(
271 'user_group_profile', user_group_name=obj.users_group_name)
271 'user_group_profile', user_group_name=obj.users_group_name)
272 }
272 }
273 for obj in acl_iter], True
273 for obj in acl_iter], True
274
274
275 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
275 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
276 repo_name = repo_group_name = None
276 repo_name = repo_group_name = None
277 if repo:
277 if repo:
278 repo_name = repo.repo_name
278 repo_name = repo.repo_name
279 if repo_group:
279 if repo_group:
280 repo_group_name = repo_group.group_name
280 repo_group_name = repo_group.group_name
281
281
282 org_query = query
282 org_query = query
283 if not query or len(query) < 3 or not searcher:
283 if not query or len(query) < 3 or not searcher:
284 return [], False
284 return [], False
285
285
286 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
286 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
287
287
288 if len(commit_hashes) != 1:
288 if len(commit_hashes) != 1:
289 return [], False
289 return [], False
290
290
291 commit_hash = commit_hashes[0]
291 commit_hash = commit_hashes[0]
292
292
293 result = searcher.search(
293 result = searcher.search(
294 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
294 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
295 repo_name, repo_group_name, raise_on_exc=False)
295 repo_name, repo_group_name, raise_on_exc=False)
296
296
297 commits = []
297 commits = []
298 for entry in result['results']:
298 for entry in result['results']:
299 repo_data = {
299 repo_data = {
300 'repository_id': entry.get('repository_id'),
300 'repository_id': entry.get('repository_id'),
301 'repository_type': entry.get('repo_type'),
301 'repository_type': entry.get('repo_type'),
302 'repository_name': entry.get('repository'),
302 'repository_name': entry.get('repository'),
303 }
303 }
304
304
305 commit_entry = {
305 commit_entry = {
306 'id': entry['commit_id'],
306 'id': entry['commit_id'],
307 'value': org_query,
307 'value': org_query,
308 'value_display': '`{}` commit: {}'.format(
308 'value_display': '`{}` commit: {}'.format(
309 entry['repository'], entry['commit_id']),
309 entry['repository'], entry['commit_id']),
310 'type': 'commit',
310 'type': 'commit',
311 'repo': entry['repository'],
311 'repo': entry['repository'],
312 'repo_data': repo_data,
312 'repo_data': repo_data,
313
313
314 'url': h.route_path(
314 'url': h.route_path(
315 'repo_commit',
315 'repo_commit',
316 repo_name=entry['repository'], commit_id=entry['commit_id'])
316 repo_name=entry['repository'], commit_id=entry['commit_id'])
317 }
317 }
318
318
319 commits.append(commit_entry)
319 commits.append(commit_entry)
320 return commits, True
320 return commits, True
321
321
322 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
322 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
323 repo_name = repo_group_name = None
323 repo_name = repo_group_name = None
324 if repo:
324 if repo:
325 repo_name = repo.repo_name
325 repo_name = repo.repo_name
326 if repo_group:
326 if repo_group:
327 repo_group_name = repo_group.group_name
327 repo_group_name = repo_group.group_name
328
328
329 org_query = query
329 org_query = query
330 if not query or len(query) < 3 or not searcher:
330 if not query or len(query) < 3 or not searcher:
331 return [], False
331 return [], False
332
332
333 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
333 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
334 if len(paths_re) != 1:
334 if len(paths_re) != 1:
335 return [], False
335 return [], False
336
336
337 file_path = paths_re[0]
337 file_path = paths_re[0]
338
338
339 search_path = searcher.escape_specials(file_path)
339 search_path = searcher.escape_specials(file_path)
340 result = searcher.search(
340 result = searcher.search(
341 'file.raw:*{}*'.format(search_path), 'path', auth_user,
341 'file.raw:*{}*'.format(search_path), 'path', auth_user,
342 repo_name, repo_group_name, raise_on_exc=False)
342 repo_name, repo_group_name, raise_on_exc=False)
343
343
344 files = []
344 files = []
345 for entry in result['results']:
345 for entry in result['results']:
346 repo_data = {
346 repo_data = {
347 'repository_id': entry.get('repository_id'),
347 'repository_id': entry.get('repository_id'),
348 'repository_type': entry.get('repo_type'),
348 'repository_type': entry.get('repo_type'),
349 'repository_name': entry.get('repository'),
349 'repository_name': entry.get('repository'),
350 }
350 }
351
351
352 file_entry = {
352 file_entry = {
353 'id': entry['commit_id'],
353 'id': entry['commit_id'],
354 'value': org_query,
354 'value': org_query,
355 'value_display': '`{}` file: {}'.format(
355 'value_display': '`{}` file: {}'.format(
356 entry['repository'], entry['file']),
356 entry['repository'], entry['file']),
357 'type': 'file',
357 'type': 'file',
358 'repo': entry['repository'],
358 'repo': entry['repository'],
359 'repo_data': repo_data,
359 'repo_data': repo_data,
360
360
361 'url': h.route_path(
361 'url': h.route_path(
362 'repo_files',
362 'repo_files',
363 repo_name=entry['repository'], commit_id=entry['commit_id'],
363 repo_name=entry['repository'], commit_id=entry['commit_id'],
364 f_path=entry['file'])
364 f_path=entry['file'])
365 }
365 }
366
366
367 files.append(file_entry)
367 files.append(file_entry)
368 return files, True
368 return files, True
369
369
370 @LoginRequired()
370 @LoginRequired()
371 @view_config(
371 @view_config(
372 route_name='repo_list_data', request_method='GET',
372 route_name='repo_list_data', request_method='GET',
373 renderer='json_ext', xhr=True)
373 renderer='json_ext', xhr=True)
374 def repo_list_data(self):
374 def repo_list_data(self):
375 _ = self.request.translate
375 _ = self.request.translate
376 self.load_default_context()
376 self.load_default_context()
377
377
378 query = self.request.GET.get('query')
378 query = self.request.GET.get('query')
379 repo_type = self.request.GET.get('repo_type')
379 repo_type = self.request.GET.get('repo_type')
380 log.debug('generating repo list, query:%s, repo_type:%s',
380 log.debug('generating repo list, query:%s, repo_type:%s',
381 query, repo_type)
381 query, repo_type)
382
382
383 res = []
383 res = []
384 repos = self._get_repo_list(query, repo_type=repo_type)
384 repos = self._get_repo_list(query, repo_type=repo_type)
385 if repos:
385 if repos:
386 res.append({
386 res.append({
387 'text': _('Repositories'),
387 'text': _('Repositories'),
388 'children': repos
388 'children': repos
389 })
389 })
390
390
391 data = {
391 data = {
392 'more': False,
392 'more': False,
393 'results': res
393 'results': res
394 }
394 }
395 return data
395 return data
396
396
397 @LoginRequired()
397 @LoginRequired()
398 @view_config(
398 @view_config(
399 route_name='repo_group_list_data', request_method='GET',
399 route_name='repo_group_list_data', request_method='GET',
400 renderer='json_ext', xhr=True)
400 renderer='json_ext', xhr=True)
401 def repo_group_list_data(self):
401 def repo_group_list_data(self):
402 _ = self.request.translate
402 _ = self.request.translate
403 self.load_default_context()
403 self.load_default_context()
404
404
405 query = self.request.GET.get('query')
405 query = self.request.GET.get('query')
406
406
407 log.debug('generating repo group list, query:%s',
407 log.debug('generating repo group list, query:%s',
408 query)
408 query)
409
409
410 res = []
410 res = []
411 repo_groups = self._get_repo_group_list(query)
411 repo_groups = self._get_repo_group_list(query)
412 if repo_groups:
412 if repo_groups:
413 res.append({
413 res.append({
414 'text': _('Repository Groups'),
414 'text': _('Repository Groups'),
415 'children': repo_groups
415 'children': repo_groups
416 })
416 })
417
417
418 data = {
418 data = {
419 'more': False,
419 'more': False,
420 'results': res
420 'results': res
421 }
421 }
422 return data
422 return data
423
423
424 def _get_default_search_queries(self, search_context, searcher, query):
424 def _get_default_search_queries(self, search_context, searcher, query):
425 if not searcher:
425 if not searcher:
426 return []
426 return []
427
427
428 is_es_6 = searcher.is_es_6
428 is_es_6 = searcher.is_es_6
429
429
430 queries = []
430 queries = []
431 repo_group_name, repo_name, repo_context = None, None, None
431 repo_group_name, repo_name, repo_context = None, None, None
432
432
433 # repo group context
433 # repo group context
434 if search_context.get('search_context[repo_group_name]'):
434 if search_context.get('search_context[repo_group_name]'):
435 repo_group_name = search_context.get('search_context[repo_group_name]')
435 repo_group_name = search_context.get('search_context[repo_group_name]')
436 if search_context.get('search_context[repo_name]'):
436 if search_context.get('search_context[repo_name]'):
437 repo_name = search_context.get('search_context[repo_name]')
437 repo_name = search_context.get('search_context[repo_name]')
438 repo_context = search_context.get('search_context[repo_view_type]')
438 repo_context = search_context.get('search_context[repo_view_type]')
439
439
440 if is_es_6 and repo_name:
440 if is_es_6 and repo_name:
441 # files
441 # files
442 def query_modifier():
442 def query_modifier():
443 qry = query
443 qry = query
444 return {'q': qry, 'type': 'content'}
444 return {'q': qry, 'type': 'content'}
445 label = u'File search for `{}` in this repository.'.format(query)
445 label = u'File search for `{}` in this repository.'.format(query)
446 file_qry = {
446 file_qry = {
447 'id': -10,
447 'id': -10,
448 'value': query,
448 'value': query,
449 'value_display': label,
449 'value_display': label,
450 'type': 'search',
450 'type': 'search',
451 'url': h.route_path('search_repo',
451 'url': h.route_path('search_repo',
452 repo_name=repo_name,
452 repo_name=repo_name,
453 _query=query_modifier())
453 _query=query_modifier())
454 }
454 }
455
455
456 # commits
456 # commits
457 def query_modifier():
457 def query_modifier():
458 qry = query
458 qry = query
459 return {'q': qry, 'type': 'commit'}
459 return {'q': qry, 'type': 'commit'}
460
460
461 label = u'Commit search for `{}` in this repository.'.format(query)
461 label = u'Commit search for `{}` in this repository.'.format(query)
462 commit_qry = {
462 commit_qry = {
463 'id': -20,
463 'id': -20,
464 'value': query,
464 'value': query,
465 'value_display': label,
465 'value_display': label,
466 'type': 'search',
466 'type': 'search',
467 'url': h.route_path('search_repo',
467 'url': h.route_path('search_repo',
468 repo_name=repo_name,
468 repo_name=repo_name,
469 _query=query_modifier())
469 _query=query_modifier())
470 }
470 }
471
471
472 if repo_context in ['commit', 'changelog']:
472 if repo_context in ['commit', 'commits']:
473 queries.extend([commit_qry, file_qry])
473 queries.extend([commit_qry, file_qry])
474 elif repo_context in ['files', 'summary']:
474 elif repo_context in ['files', 'summary']:
475 queries.extend([file_qry, commit_qry])
475 queries.extend([file_qry, commit_qry])
476 else:
476 else:
477 queries.extend([commit_qry, file_qry])
477 queries.extend([commit_qry, file_qry])
478
478
479 elif is_es_6 and repo_group_name:
479 elif is_es_6 and repo_group_name:
480 # files
480 # files
481 def query_modifier():
481 def query_modifier():
482 qry = query
482 qry = query
483 return {'q': qry, 'type': 'content'}
483 return {'q': qry, 'type': 'content'}
484
484
485 label = u'File search for `{}` in this repository group'.format(query)
485 label = u'File search for `{}` in this repository group'.format(query)
486 file_qry = {
486 file_qry = {
487 'id': -30,
487 'id': -30,
488 'value': query,
488 'value': query,
489 'value_display': label,
489 'value_display': label,
490 'type': 'search',
490 'type': 'search',
491 'url': h.route_path('search_repo_group',
491 'url': h.route_path('search_repo_group',
492 repo_group_name=repo_group_name,
492 repo_group_name=repo_group_name,
493 _query=query_modifier())
493 _query=query_modifier())
494 }
494 }
495
495
496 # commits
496 # commits
497 def query_modifier():
497 def query_modifier():
498 qry = query
498 qry = query
499 return {'q': qry, 'type': 'commit'}
499 return {'q': qry, 'type': 'commit'}
500
500
501 label = u'Commit search for `{}` in this repository group'.format(query)
501 label = u'Commit search for `{}` in this repository group'.format(query)
502 commit_qry = {
502 commit_qry = {
503 'id': -40,
503 'id': -40,
504 'value': query,
504 'value': query,
505 'value_display': label,
505 'value_display': label,
506 'type': 'search',
506 'type': 'search',
507 'url': h.route_path('search_repo_group',
507 'url': h.route_path('search_repo_group',
508 repo_group_name=repo_group_name,
508 repo_group_name=repo_group_name,
509 _query=query_modifier())
509 _query=query_modifier())
510 }
510 }
511
511
512 if repo_context in ['commit', 'changelog']:
512 if repo_context in ['commit', 'commits']:
513 queries.extend([commit_qry, file_qry])
513 queries.extend([commit_qry, file_qry])
514 elif repo_context in ['files', 'summary']:
514 elif repo_context in ['files', 'summary']:
515 queries.extend([file_qry, commit_qry])
515 queries.extend([file_qry, commit_qry])
516 else:
516 else:
517 queries.extend([commit_qry, file_qry])
517 queries.extend([commit_qry, file_qry])
518
518
519 # Global, not scoped
519 # Global, not scoped
520 if not queries:
520 if not queries:
521 queries.append(
521 queries.append(
522 {
522 {
523 'id': -1,
523 'id': -1,
524 'value': query,
524 'value': query,
525 'value_display': u'File search for: `{}`'.format(query),
525 'value_display': u'File search for: `{}`'.format(query),
526 'type': 'search',
526 'type': 'search',
527 'url': h.route_path('search',
527 'url': h.route_path('search',
528 _query={'q': query, 'type': 'content'})
528 _query={'q': query, 'type': 'content'})
529 })
529 })
530 queries.append(
530 queries.append(
531 {
531 {
532 'id': -2,
532 'id': -2,
533 'value': query,
533 'value': query,
534 'value_display': u'Commit search for: `{}`'.format(query),
534 'value_display': u'Commit search for: `{}`'.format(query),
535 'type': 'search',
535 'type': 'search',
536 'url': h.route_path('search',
536 'url': h.route_path('search',
537 _query={'q': query, 'type': 'commit'})
537 _query={'q': query, 'type': 'commit'})
538 })
538 })
539
539
540 return queries
540 return queries
541
541
542 @LoginRequired()
542 @LoginRequired()
543 @view_config(
543 @view_config(
544 route_name='goto_switcher_data', request_method='GET',
544 route_name='goto_switcher_data', request_method='GET',
545 renderer='json_ext', xhr=True)
545 renderer='json_ext', xhr=True)
546 def goto_switcher_data(self):
546 def goto_switcher_data(self):
547 c = self.load_default_context()
547 c = self.load_default_context()
548
548
549 _ = self.request.translate
549 _ = self.request.translate
550
550
551 query = self.request.GET.get('query')
551 query = self.request.GET.get('query')
552 log.debug('generating main filter data, query %s', query)
552 log.debug('generating main filter data, query %s', query)
553
553
554 res = []
554 res = []
555 if not query:
555 if not query:
556 return {'suggestions': res}
556 return {'suggestions': res}
557
557
558 def no_match(name):
558 def no_match(name):
559 return {
559 return {
560 'id': -1,
560 'id': -1,
561 'value': "",
561 'value': "",
562 'value_display': name,
562 'value_display': name,
563 'type': 'text',
563 'type': 'text',
564 'url': ""
564 'url': ""
565 }
565 }
566 searcher = searcher_from_config(self.request.registry.settings)
566 searcher = searcher_from_config(self.request.registry.settings)
567 has_specialized_search = False
567 has_specialized_search = False
568
568
569 # set repo context
569 # set repo context
570 repo = None
570 repo = None
571 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
571 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
572 if repo_id:
572 if repo_id:
573 repo = Repository.get(repo_id)
573 repo = Repository.get(repo_id)
574
574
575 # set group context
575 # set group context
576 repo_group = None
576 repo_group = None
577 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
577 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
578 if repo_group_id:
578 if repo_group_id:
579 repo_group = RepoGroup.get(repo_group_id)
579 repo_group = RepoGroup.get(repo_group_id)
580 prefix_match = False
580 prefix_match = False
581
581
582 # user: type search
582 # user: type search
583 if not prefix_match:
583 if not prefix_match:
584 users, prefix_match = self._get_user_list(query)
584 users, prefix_match = self._get_user_list(query)
585 if users:
585 if users:
586 has_specialized_search = True
586 has_specialized_search = True
587 for serialized_user in users:
587 for serialized_user in users:
588 res.append(serialized_user)
588 res.append(serialized_user)
589 elif prefix_match:
589 elif prefix_match:
590 has_specialized_search = True
590 has_specialized_search = True
591 res.append(no_match('No matching users found'))
591 res.append(no_match('No matching users found'))
592
592
593 # user_group: type search
593 # user_group: type search
594 if not prefix_match:
594 if not prefix_match:
595 user_groups, prefix_match = self._get_user_groups_list(query)
595 user_groups, prefix_match = self._get_user_groups_list(query)
596 if user_groups:
596 if user_groups:
597 has_specialized_search = True
597 has_specialized_search = True
598 for serialized_user_group in user_groups:
598 for serialized_user_group in user_groups:
599 res.append(serialized_user_group)
599 res.append(serialized_user_group)
600 elif prefix_match:
600 elif prefix_match:
601 has_specialized_search = True
601 has_specialized_search = True
602 res.append(no_match('No matching user groups found'))
602 res.append(no_match('No matching user groups found'))
603
603
604 # FTS commit: type search
604 # FTS commit: type search
605 if not prefix_match:
605 if not prefix_match:
606 commits, prefix_match = self._get_hash_commit_list(
606 commits, prefix_match = self._get_hash_commit_list(
607 c.auth_user, searcher, query, repo, repo_group)
607 c.auth_user, searcher, query, repo, repo_group)
608 if commits:
608 if commits:
609 has_specialized_search = True
609 has_specialized_search = True
610 unique_repos = collections.OrderedDict()
610 unique_repos = collections.OrderedDict()
611 for commit in commits:
611 for commit in commits:
612 repo_name = commit['repo']
612 repo_name = commit['repo']
613 unique_repos.setdefault(repo_name, []).append(commit)
613 unique_repos.setdefault(repo_name, []).append(commit)
614
614
615 for _repo, commits in unique_repos.items():
615 for _repo, commits in unique_repos.items():
616 for commit in commits:
616 for commit in commits:
617 res.append(commit)
617 res.append(commit)
618 elif prefix_match:
618 elif prefix_match:
619 has_specialized_search = True
619 has_specialized_search = True
620 res.append(no_match('No matching commits found'))
620 res.append(no_match('No matching commits found'))
621
621
622 # FTS file: type search
622 # FTS file: type search
623 if not prefix_match:
623 if not prefix_match:
624 paths, prefix_match = self._get_path_list(
624 paths, prefix_match = self._get_path_list(
625 c.auth_user, searcher, query, repo, repo_group)
625 c.auth_user, searcher, query, repo, repo_group)
626 if paths:
626 if paths:
627 has_specialized_search = True
627 has_specialized_search = True
628 unique_repos = collections.OrderedDict()
628 unique_repos = collections.OrderedDict()
629 for path in paths:
629 for path in paths:
630 repo_name = path['repo']
630 repo_name = path['repo']
631 unique_repos.setdefault(repo_name, []).append(path)
631 unique_repos.setdefault(repo_name, []).append(path)
632
632
633 for repo, paths in unique_repos.items():
633 for repo, paths in unique_repos.items():
634 for path in paths:
634 for path in paths:
635 res.append(path)
635 res.append(path)
636 elif prefix_match:
636 elif prefix_match:
637 has_specialized_search = True
637 has_specialized_search = True
638 res.append(no_match('No matching files found'))
638 res.append(no_match('No matching files found'))
639
639
640 # main suggestions
640 # main suggestions
641 if not has_specialized_search:
641 if not has_specialized_search:
642 repo_group_name = ''
642 repo_group_name = ''
643 if repo_group:
643 if repo_group:
644 repo_group_name = repo_group.group_name
644 repo_group_name = repo_group.group_name
645
645
646 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
646 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
647 res.append(_q)
647 res.append(_q)
648
648
649 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
649 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
650 for serialized_repo_group in repo_groups:
650 for serialized_repo_group in repo_groups:
651 res.append(serialized_repo_group)
651 res.append(serialized_repo_group)
652
652
653 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
653 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
654 for serialized_repo in repos:
654 for serialized_repo in repos:
655 res.append(serialized_repo)
655 res.append(serialized_repo)
656
656
657 if not repos and not repo_groups:
657 if not repos and not repo_groups:
658 res.append(no_match('No matches found'))
658 res.append(no_match('No matches found'))
659
659
660 return {'suggestions': res}
660 return {'suggestions': res}
661
661
662 def _get_groups_and_repos(self, repo_group_id=None):
662 def _get_groups_and_repos(self, repo_group_id=None):
663 # repo groups groups
663 # repo groups groups
664 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
664 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
665 _perms = ['group.read', 'group.write', 'group.admin']
665 _perms = ['group.read', 'group.write', 'group.admin']
666 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
666 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
667 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
667 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
668 repo_group_list=repo_group_list_acl, admin=False)
668 repo_group_list=repo_group_list_acl, admin=False)
669
669
670 # repositories
670 # repositories
671 repo_list = Repository.get_all_repos(group_id=repo_group_id)
671 repo_list = Repository.get_all_repos(group_id=repo_group_id)
672 _perms = ['repository.read', 'repository.write', 'repository.admin']
672 _perms = ['repository.read', 'repository.write', 'repository.admin']
673 repo_list_acl = RepoList(repo_list, perm_set=_perms)
673 repo_list_acl = RepoList(repo_list, perm_set=_perms)
674 repo_data = RepoModel().get_repos_as_dict(
674 repo_data = RepoModel().get_repos_as_dict(
675 repo_list=repo_list_acl, admin=False)
675 repo_list=repo_list_acl, admin=False)
676
676
677 return repo_data, repo_group_data
677 return repo_data, repo_group_data
678
678
679 @LoginRequired()
679 @LoginRequired()
680 @view_config(
680 @view_config(
681 route_name='home', request_method='GET',
681 route_name='home', request_method='GET',
682 renderer='rhodecode:templates/index.mako')
682 renderer='rhodecode:templates/index.mako')
683 def main_page(self):
683 def main_page(self):
684 c = self.load_default_context()
684 c = self.load_default_context()
685 c.repo_group = None
685 c.repo_group = None
686
686
687 repo_data, repo_group_data = self._get_groups_and_repos()
687 repo_data, repo_group_data = self._get_groups_and_repos()
688 # json used to render the grids
688 # json used to render the grids
689 c.repos_data = json.dumps(repo_data)
689 c.repos_data = json.dumps(repo_data)
690 c.repo_groups_data = json.dumps(repo_group_data)
690 c.repo_groups_data = json.dumps(repo_group_data)
691
691
692 return self._get_template_context(c)
692 return self._get_template_context(c)
693
693
694 @LoginRequired()
694 @LoginRequired()
695 @HasRepoGroupPermissionAnyDecorator(
695 @HasRepoGroupPermissionAnyDecorator(
696 'group.read', 'group.write', 'group.admin')
696 'group.read', 'group.write', 'group.admin')
697 @view_config(
697 @view_config(
698 route_name='repo_group_home', request_method='GET',
698 route_name='repo_group_home', request_method='GET',
699 renderer='rhodecode:templates/index_repo_group.mako')
699 renderer='rhodecode:templates/index_repo_group.mako')
700 @view_config(
700 @view_config(
701 route_name='repo_group_home_slash', request_method='GET',
701 route_name='repo_group_home_slash', request_method='GET',
702 renderer='rhodecode:templates/index_repo_group.mako')
702 renderer='rhodecode:templates/index_repo_group.mako')
703 def repo_group_main_page(self):
703 def repo_group_main_page(self):
704 c = self.load_default_context()
704 c = self.load_default_context()
705 c.repo_group = self.request.db_repo_group
705 c.repo_group = self.request.db_repo_group
706 repo_data, repo_group_data = self._get_groups_and_repos(c.repo_group.group_id)
706 repo_data, repo_group_data = self._get_groups_and_repos(c.repo_group.group_id)
707
707
708 # update every 5 min
708 # update every 5 min
709 if self.request.db_repo_group.last_commit_cache_update_diff > 60 * 5:
709 if self.request.db_repo_group.last_commit_cache_update_diff > 60 * 5:
710 self.request.db_repo_group.update_commit_cache()
710 self.request.db_repo_group.update_commit_cache()
711
711
712 # json used to render the grids
712 # json used to render the grids
713 c.repos_data = json.dumps(repo_data)
713 c.repos_data = json.dumps(repo_data)
714 c.repo_groups_data = json.dumps(repo_group_data)
714 c.repo_groups_data = json.dumps(repo_group_data)
715
715
716 return self._get_template_context(c)
716 return self._get_template_context(c)
717
717
718 @LoginRequired()
718 @LoginRequired()
719 @CSRFRequired()
719 @CSRFRequired()
720 @view_config(
720 @view_config(
721 route_name='markup_preview', request_method='POST',
721 route_name='markup_preview', request_method='POST',
722 renderer='string', xhr=True)
722 renderer='string', xhr=True)
723 def markup_preview(self):
723 def markup_preview(self):
724 # Technically a CSRF token is not needed as no state changes with this
724 # Technically a CSRF token is not needed as no state changes with this
725 # call. However, as this is a POST is better to have it, so automated
725 # call. However, as this is a POST is better to have it, so automated
726 # tools don't flag it as potential CSRF.
726 # tools don't flag it as potential CSRF.
727 # Post is required because the payload could be bigger than the maximum
727 # Post is required because the payload could be bigger than the maximum
728 # allowed by GET.
728 # allowed by GET.
729
729
730 text = self.request.POST.get('text')
730 text = self.request.POST.get('text')
731 renderer = self.request.POST.get('renderer') or 'rst'
731 renderer = self.request.POST.get('renderer') or 'rst'
732 if text:
732 if text:
733 return h.render(text, renderer=renderer, mentions=True)
733 return h.render(text, renderer=renderer, mentions=True)
734 return ''
734 return ''
735
735
736 @LoginRequired()
736 @LoginRequired()
737 @CSRFRequired()
737 @CSRFRequired()
738 @view_config(
738 @view_config(
739 route_name='store_user_session_value', request_method='POST',
739 route_name='store_user_session_value', request_method='POST',
740 renderer='string', xhr=True)
740 renderer='string', xhr=True)
741 def store_user_session_attr(self):
741 def store_user_session_attr(self):
742 key = self.request.POST.get('key')
742 key = self.request.POST.get('key')
743 val = self.request.POST.get('val')
743 val = self.request.POST.get('val')
744
744
745 existing_value = self.request.session.get(key)
745 existing_value = self.request.session.get(key)
746 if existing_value != val:
746 if existing_value != val:
747 self.request.session[key] = val
747 self.request.session[key] = val
748
748
749 return 'stored:{}:{}'.format(key, val)
749 return 'stored:{}:{}'.format(key, val)
@@ -1,387 +1,387 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 import logging
22 import logging
23 import itertools
23 import itertools
24
24
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
26
26
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.httpexceptions import HTTPBadRequest
28 from pyramid.httpexceptions import HTTPBadRequest
29 from pyramid.response import Response
29 from pyramid.response import Response
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.model.db import (
33 from rhodecode.model.db import (
34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import Page
38 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.user_log_filter import user_log_filter
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class JournalView(BaseAppView):
46 class JournalView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50
50
51 self._load_defaults(c.rhodecode_name)
51 self._load_defaults(c.rhodecode_name)
52
52
53 # TODO(marcink): what is this, why we need a global register ?
53 # TODO(marcink): what is this, why we need a global register ?
54 c.search_term = self.request.GET.get('filter') or ''
54 c.search_term = self.request.GET.get('filter') or ''
55 return c
55 return c
56
56
57 def _get_config(self, rhodecode_name):
57 def _get_config(self, rhodecode_name):
58 import rhodecode
58 import rhodecode
59 config = rhodecode.CONFIG
59 config = rhodecode.CONFIG
60
60
61 return {
61 return {
62 'language': 'en-us',
62 'language': 'en-us',
63 'feed_ttl': '5', # TTL of feed,
63 'feed_ttl': '5', # TTL of feed,
64 'feed_items_per_page':
64 'feed_items_per_page':
65 safe_int(config.get('rss_items_per_page', 20)),
65 safe_int(config.get('rss_items_per_page', 20)),
66 'rhodecode_name': rhodecode_name
66 'rhodecode_name': rhodecode_name
67 }
67 }
68
68
69 def _load_defaults(self, rhodecode_name):
69 def _load_defaults(self, rhodecode_name):
70 config = self._get_config(rhodecode_name)
70 config = self._get_config(rhodecode_name)
71 # common values for feeds
71 # common values for feeds
72 self.language = config["language"]
72 self.language = config["language"]
73 self.ttl = config["feed_ttl"]
73 self.ttl = config["feed_ttl"]
74 self.feed_items_per_page = config['feed_items_per_page']
74 self.feed_items_per_page = config['feed_items_per_page']
75 self.rhodecode_name = config['rhodecode_name']
75 self.rhodecode_name = config['rhodecode_name']
76
76
77 def _get_daily_aggregate(self, journal):
77 def _get_daily_aggregate(self, journal):
78 groups = []
78 groups = []
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
80 user_group = []
80 user_group = []
81 # groupby username if it's a present value, else
81 # groupby username if it's a present value, else
82 # fallback to journal username
82 # fallback to journal username
83 for _, g2 in itertools.groupby(
83 for _, g2 in itertools.groupby(
84 list(g), lambda x: x.user.username if x.user else x.username):
84 list(g), lambda x: x.user.username if x.user else x.username):
85 l = list(g2)
85 l = list(g2)
86 user_group.append((l[0].user, l))
86 user_group.append((l[0].user, l))
87
87
88 groups.append((k, user_group,))
88 groups.append((k, user_group,))
89
89
90 return groups
90 return groups
91
91
92 def _get_journal_data(self, following_repos, search_term):
92 def _get_journal_data(self, following_repos, search_term):
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
94 if x.follows_repository is not None]
94 if x.follows_repository is not None]
95 user_ids = [x.follows_user.user_id for x in following_repos
95 user_ids = [x.follows_user.user_id for x in following_repos
96 if x.follows_user is not None]
96 if x.follows_user is not None]
97
97
98 filtering_criterion = None
98 filtering_criterion = None
99
99
100 if repo_ids and user_ids:
100 if repo_ids and user_ids:
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
102 UserLog.user_id.in_(user_ids))
102 UserLog.user_id.in_(user_ids))
103 if repo_ids and not user_ids:
103 if repo_ids and not user_ids:
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
105 if not repo_ids and user_ids:
105 if not repo_ids and user_ids:
106 filtering_criterion = UserLog.user_id.in_(user_ids)
106 filtering_criterion = UserLog.user_id.in_(user_ids)
107 if filtering_criterion is not None:
107 if filtering_criterion is not None:
108 journal = Session().query(UserLog)\
108 journal = Session().query(UserLog)\
109 .options(joinedload(UserLog.user))\
109 .options(joinedload(UserLog.user))\
110 .options(joinedload(UserLog.repository))
110 .options(joinedload(UserLog.repository))
111 # filter
111 # filter
112 try:
112 try:
113 journal = user_log_filter(journal, search_term)
113 journal = user_log_filter(journal, search_term)
114 except Exception:
114 except Exception:
115 # we want this to crash for now
115 # we want this to crash for now
116 raise
116 raise
117 journal = journal.filter(filtering_criterion)\
117 journal = journal.filter(filtering_criterion)\
118 .order_by(UserLog.action_date.desc())
118 .order_by(UserLog.action_date.desc())
119 else:
119 else:
120 journal = []
120 journal = []
121
121
122 return journal
122 return journal
123
123
124 def feed_uid(self, entry_id):
124 def feed_uid(self, entry_id):
125 return '{}:{}'.format('journal', md5_safe(entry_id))
125 return '{}:{}'.format('journal', md5_safe(entry_id))
126
126
127 def _atom_feed(self, repos, search_term, public=True):
127 def _atom_feed(self, repos, search_term, public=True):
128 _ = self.request.translate
128 _ = self.request.translate
129 journal = self._get_journal_data(repos, search_term)
129 journal = self._get_journal_data(repos, search_term)
130 if public:
130 if public:
131 _link = h.route_url('journal_public_atom')
131 _link = h.route_url('journal_public_atom')
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
133 'atom feed')
133 'atom feed')
134 else:
134 else:
135 _link = h.route_url('journal_atom')
135 _link = h.route_url('journal_atom')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
137
137
138 feed = Atom1Feed(
138 feed = Atom1Feed(
139 title=_desc, link=_link, description=_desc,
139 title=_desc, link=_link, description=_desc,
140 language=self.language, ttl=self.ttl)
140 language=self.language, ttl=self.ttl)
141
141
142 for entry in journal[:self.feed_items_per_page]:
142 for entry in journal[:self.feed_items_per_page]:
143 user = entry.user
143 user = entry.user
144 if user is None:
144 if user is None:
145 # fix deleted users
145 # fix deleted users
146 user = AttributeDict({'short_contact': entry.username,
146 user = AttributeDict({'short_contact': entry.username,
147 'email': '',
147 'email': '',
148 'full_contact': ''})
148 'full_contact': ''})
149 action, action_extra, ico = h.action_parser(
149 action, action_extra, ico = h.action_parser(
150 self.request, entry, feed=True)
150 self.request, entry, feed=True)
151 title = "%s - %s %s" % (user.short_contact, action(),
151 title = "%s - %s %s" % (user.short_contact, action(),
152 entry.repository.repo_name)
152 entry.repository.repo_name)
153 desc = action_extra()
153 desc = action_extra()
154 _url = h.route_url('home')
154 _url = h.route_url('home')
155 if entry.repository is not None:
155 if entry.repository is not None:
156 _url = h.route_url('repo_changelog',
156 _url = h.route_url('repo_commits',
157 repo_name=entry.repository.repo_name)
157 repo_name=entry.repository.repo_name)
158
158
159 feed.add_item(
159 feed.add_item(
160 unique_id=self.feed_uid(entry.user_log_id),
160 unique_id=self.feed_uid(entry.user_log_id),
161 title=title,
161 title=title,
162 pubdate=entry.action_date,
162 pubdate=entry.action_date,
163 link=_url,
163 link=_url,
164 author_email=user.email,
164 author_email=user.email,
165 author_name=user.full_contact,
165 author_name=user.full_contact,
166 description=desc)
166 description=desc)
167
167
168 response = Response(feed.writeString('utf-8'))
168 response = Response(feed.writeString('utf-8'))
169 response.content_type = feed.mime_type
169 response.content_type = feed.mime_type
170 return response
170 return response
171
171
172 def _rss_feed(self, repos, search_term, public=True):
172 def _rss_feed(self, repos, search_term, public=True):
173 _ = self.request.translate
173 _ = self.request.translate
174 journal = self._get_journal_data(repos, search_term)
174 journal = self._get_journal_data(repos, search_term)
175 if public:
175 if public:
176 _link = h.route_url('journal_public_atom')
176 _link = h.route_url('journal_public_atom')
177 _desc = '%s %s %s' % (
177 _desc = '%s %s %s' % (
178 self.rhodecode_name, _('public journal'), 'rss feed')
178 self.rhodecode_name, _('public journal'), 'rss feed')
179 else:
179 else:
180 _link = h.route_url('journal_atom')
180 _link = h.route_url('journal_atom')
181 _desc = '%s %s %s' % (
181 _desc = '%s %s %s' % (
182 self.rhodecode_name, _('journal'), 'rss feed')
182 self.rhodecode_name, _('journal'), 'rss feed')
183
183
184 feed = Rss201rev2Feed(
184 feed = Rss201rev2Feed(
185 title=_desc, link=_link, description=_desc,
185 title=_desc, link=_link, description=_desc,
186 language=self.language, ttl=self.ttl)
186 language=self.language, ttl=self.ttl)
187
187
188 for entry in journal[:self.feed_items_per_page]:
188 for entry in journal[:self.feed_items_per_page]:
189 user = entry.user
189 user = entry.user
190 if user is None:
190 if user is None:
191 # fix deleted users
191 # fix deleted users
192 user = AttributeDict({'short_contact': entry.username,
192 user = AttributeDict({'short_contact': entry.username,
193 'email': '',
193 'email': '',
194 'full_contact': ''})
194 'full_contact': ''})
195 action, action_extra, ico = h.action_parser(
195 action, action_extra, ico = h.action_parser(
196 self.request, entry, feed=True)
196 self.request, entry, feed=True)
197 title = "%s - %s %s" % (user.short_contact, action(),
197 title = "%s - %s %s" % (user.short_contact, action(),
198 entry.repository.repo_name)
198 entry.repository.repo_name)
199 desc = action_extra()
199 desc = action_extra()
200 _url = h.route_url('home')
200 _url = h.route_url('home')
201 if entry.repository is not None:
201 if entry.repository is not None:
202 _url = h.route_url('repo_changelog',
202 _url = h.route_url('repo_commits',
203 repo_name=entry.repository.repo_name)
203 repo_name=entry.repository.repo_name)
204
204
205 feed.add_item(
205 feed.add_item(
206 unique_id=self.feed_uid(entry.user_log_id),
206 unique_id=self.feed_uid(entry.user_log_id),
207 title=title,
207 title=title,
208 pubdate=entry.action_date,
208 pubdate=entry.action_date,
209 link=_url,
209 link=_url,
210 author_email=user.email,
210 author_email=user.email,
211 author_name=user.full_contact,
211 author_name=user.full_contact,
212 description=desc)
212 description=desc)
213
213
214 response = Response(feed.writeString('utf-8'))
214 response = Response(feed.writeString('utf-8'))
215 response.content_type = feed.mime_type
215 response.content_type = feed.mime_type
216 return response
216 return response
217
217
218 @LoginRequired()
218 @LoginRequired()
219 @NotAnonymous()
219 @NotAnonymous()
220 @view_config(
220 @view_config(
221 route_name='journal', request_method='GET',
221 route_name='journal', request_method='GET',
222 renderer=None)
222 renderer=None)
223 def journal(self):
223 def journal(self):
224 c = self.load_default_context()
224 c = self.load_default_context()
225
225
226 p = safe_int(self.request.GET.get('page', 1), 1)
226 p = safe_int(self.request.GET.get('page', 1), 1)
227 c.user = User.get(self._rhodecode_user.user_id)
227 c.user = User.get(self._rhodecode_user.user_id)
228 following = Session().query(UserFollowing)\
228 following = Session().query(UserFollowing)\
229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
230 .options(joinedload(UserFollowing.follows_repository))\
230 .options(joinedload(UserFollowing.follows_repository))\
231 .all()
231 .all()
232
232
233 journal = self._get_journal_data(following, c.search_term)
233 journal = self._get_journal_data(following, c.search_term)
234
234
235 def url_generator(**kw):
235 def url_generator(**kw):
236 query_params = {
236 query_params = {
237 'filter': c.search_term
237 'filter': c.search_term
238 }
238 }
239 query_params.update(kw)
239 query_params.update(kw)
240 return self.request.current_route_path(_query=query_params)
240 return self.request.current_route_path(_query=query_params)
241
241
242 c.journal_pager = Page(
242 c.journal_pager = Page(
243 journal, page=p, items_per_page=20, url=url_generator)
243 journal, page=p, items_per_page=20, url=url_generator)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
245
245
246 c.journal_data = render(
246 c.journal_data = render(
247 'rhodecode:templates/journal/journal_data.mako',
247 'rhodecode:templates/journal/journal_data.mako',
248 self._get_template_context(c), self.request)
248 self._get_template_context(c), self.request)
249
249
250 if self.request.is_xhr:
250 if self.request.is_xhr:
251 return Response(c.journal_data)
251 return Response(c.journal_data)
252
252
253 html = render(
253 html = render(
254 'rhodecode:templates/journal/journal.mako',
254 'rhodecode:templates/journal/journal.mako',
255 self._get_template_context(c), self.request)
255 self._get_template_context(c), self.request)
256 return Response(html)
256 return Response(html)
257
257
258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
259 @NotAnonymous()
259 @NotAnonymous()
260 @view_config(
260 @view_config(
261 route_name='journal_atom', request_method='GET',
261 route_name='journal_atom', request_method='GET',
262 renderer=None)
262 renderer=None)
263 def journal_atom(self):
263 def journal_atom(self):
264 """
264 """
265 Produce an atom-1.0 feed via feedgenerator module
265 Produce an atom-1.0 feed via feedgenerator module
266 """
266 """
267 c = self.load_default_context()
267 c = self.load_default_context()
268 following_repos = Session().query(UserFollowing)\
268 following_repos = Session().query(UserFollowing)\
269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
270 .options(joinedload(UserFollowing.follows_repository))\
270 .options(joinedload(UserFollowing.follows_repository))\
271 .all()
271 .all()
272 return self._atom_feed(following_repos, c.search_term, public=False)
272 return self._atom_feed(following_repos, c.search_term, public=False)
273
273
274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
275 @NotAnonymous()
275 @NotAnonymous()
276 @view_config(
276 @view_config(
277 route_name='journal_rss', request_method='GET',
277 route_name='journal_rss', request_method='GET',
278 renderer=None)
278 renderer=None)
279 def journal_rss(self):
279 def journal_rss(self):
280 """
280 """
281 Produce an rss feed via feedgenerator module
281 Produce an rss feed via feedgenerator module
282 """
282 """
283 c = self.load_default_context()
283 c = self.load_default_context()
284 following_repos = Session().query(UserFollowing)\
284 following_repos = Session().query(UserFollowing)\
285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
286 .options(joinedload(UserFollowing.follows_repository))\
286 .options(joinedload(UserFollowing.follows_repository))\
287 .all()
287 .all()
288 return self._rss_feed(following_repos, c.search_term, public=False)
288 return self._rss_feed(following_repos, c.search_term, public=False)
289
289
290 @LoginRequired()
290 @LoginRequired()
291 @NotAnonymous()
291 @NotAnonymous()
292 @CSRFRequired()
292 @CSRFRequired()
293 @view_config(
293 @view_config(
294 route_name='toggle_following', request_method='POST',
294 route_name='toggle_following', request_method='POST',
295 renderer='json_ext')
295 renderer='json_ext')
296 def toggle_following(self):
296 def toggle_following(self):
297 user_id = self.request.POST.get('follows_user_id')
297 user_id = self.request.POST.get('follows_user_id')
298 if user_id:
298 if user_id:
299 try:
299 try:
300 ScmModel().toggle_following_user(user_id, self._rhodecode_user.user_id)
300 ScmModel().toggle_following_user(user_id, self._rhodecode_user.user_id)
301 Session().commit()
301 Session().commit()
302 return 'ok'
302 return 'ok'
303 except Exception:
303 except Exception:
304 raise HTTPBadRequest()
304 raise HTTPBadRequest()
305
305
306 repo_id = self.request.POST.get('follows_repo_id')
306 repo_id = self.request.POST.get('follows_repo_id')
307 repo = Repository.get_or_404(repo_id)
307 repo = Repository.get_or_404(repo_id)
308 perm_set = ['repository.read', 'repository.write', 'repository.admin']
308 perm_set = ['repository.read', 'repository.write', 'repository.admin']
309 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'RepoWatch check')
309 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'RepoWatch check')
310 if repo and has_perm:
310 if repo and has_perm:
311 try:
311 try:
312 ScmModel().toggle_following_repo(repo_id, self._rhodecode_user.user_id)
312 ScmModel().toggle_following_repo(repo_id, self._rhodecode_user.user_id)
313 Session().commit()
313 Session().commit()
314 return 'ok'
314 return 'ok'
315 except Exception:
315 except Exception:
316 raise HTTPBadRequest()
316 raise HTTPBadRequest()
317
317
318 raise HTTPBadRequest()
318 raise HTTPBadRequest()
319
319
320 @LoginRequired()
320 @LoginRequired()
321 @view_config(
321 @view_config(
322 route_name='journal_public', request_method='GET',
322 route_name='journal_public', request_method='GET',
323 renderer=None)
323 renderer=None)
324 def journal_public(self):
324 def journal_public(self):
325 c = self.load_default_context()
325 c = self.load_default_context()
326 # Return a rendered template
326 # Return a rendered template
327 p = safe_int(self.request.GET.get('page', 1), 1)
327 p = safe_int(self.request.GET.get('page', 1), 1)
328
328
329 c.following = Session().query(UserFollowing)\
329 c.following = Session().query(UserFollowing)\
330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
331 .options(joinedload(UserFollowing.follows_repository))\
331 .options(joinedload(UserFollowing.follows_repository))\
332 .all()
332 .all()
333
333
334 journal = self._get_journal_data(c.following, c.search_term)
334 journal = self._get_journal_data(c.following, c.search_term)
335
335
336 def url_generator(**kw):
336 def url_generator(**kw):
337 query_params = {}
337 query_params = {}
338 query_params.update(kw)
338 query_params.update(kw)
339 return self.request.current_route_path(_query=query_params)
339 return self.request.current_route_path(_query=query_params)
340
340
341 c.journal_pager = Page(
341 c.journal_pager = Page(
342 journal, page=p, items_per_page=20, url=url_generator)
342 journal, page=p, items_per_page=20, url=url_generator)
343 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
343 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
344
344
345 c.journal_data = render(
345 c.journal_data = render(
346 'rhodecode:templates/journal/journal_data.mako',
346 'rhodecode:templates/journal/journal_data.mako',
347 self._get_template_context(c), self.request)
347 self._get_template_context(c), self.request)
348
348
349 if self.request.is_xhr:
349 if self.request.is_xhr:
350 return Response(c.journal_data)
350 return Response(c.journal_data)
351
351
352 html = render(
352 html = render(
353 'rhodecode:templates/journal/public_journal.mako',
353 'rhodecode:templates/journal/public_journal.mako',
354 self._get_template_context(c), self.request)
354 self._get_template_context(c), self.request)
355 return Response(html)
355 return Response(html)
356
356
357 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
357 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
358 @view_config(
358 @view_config(
359 route_name='journal_public_atom', request_method='GET',
359 route_name='journal_public_atom', request_method='GET',
360 renderer=None)
360 renderer=None)
361 def journal_public_atom(self):
361 def journal_public_atom(self):
362 """
362 """
363 Produce an atom-1.0 feed via feedgenerator module
363 Produce an atom-1.0 feed via feedgenerator module
364 """
364 """
365 c = self.load_default_context()
365 c = self.load_default_context()
366 following_repos = Session().query(UserFollowing)\
366 following_repos = Session().query(UserFollowing)\
367 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
367 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
368 .options(joinedload(UserFollowing.follows_repository))\
368 .options(joinedload(UserFollowing.follows_repository))\
369 .all()
369 .all()
370
370
371 return self._atom_feed(following_repos, c.search_term)
371 return self._atom_feed(following_repos, c.search_term)
372
372
373 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
373 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
374 @view_config(
374 @view_config(
375 route_name='journal_public_rss', request_method='GET',
375 route_name='journal_public_rss', request_method='GET',
376 renderer=None)
376 renderer=None)
377 def journal_public_rss(self):
377 def journal_public_rss(self):
378 """
378 """
379 Produce an rss2 feed via feedgenerator module
379 Produce an rss2 feed via feedgenerator module
380 """
380 """
381 c = self.load_default_context()
381 c = self.load_default_context()
382 following_repos = Session().query(UserFollowing)\
382 following_repos = Session().query(UserFollowing)\
383 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
383 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
384 .options(joinedload(UserFollowing.follows_repository))\
384 .options(joinedload(UserFollowing.follows_repository))\
385 .all()
385 .all()
386
386
387 return self._rss_feed(following_repos, c.search_term)
387 return self._rss_feed(following_repos, c.search_term)
@@ -1,492 +1,500 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.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # repo creating checks, special cases that aren't repo routes
25 # repo creating checks, special cases that aren't repo routes
26 config.add_route(
26 config.add_route(
27 name='repo_creating',
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
29
30 config.add_route(
30 config.add_route(
31 name='repo_creating_check',
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
33
34 # Summary
34 # Summary
35 # NOTE(marcink): one additional route is defined in very bottom, catch
35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 # all pattern
36 # all pattern
37 config.add_route(
37 config.add_route(
38 name='repo_summary_explicit',
38 name='repo_summary_explicit',
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 config.add_route(
40 config.add_route(
41 name='repo_summary_commits',
41 name='repo_summary_commits',
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43
43
44 # Commits
44 # Commits
45 config.add_route(
45 config.add_route(
46 name='repo_commit',
46 name='repo_commit',
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48
48
49 config.add_route(
49 config.add_route(
50 name='repo_commit_children',
50 name='repo_commit_children',
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52
52
53 config.add_route(
53 config.add_route(
54 name='repo_commit_parents',
54 name='repo_commit_parents',
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56
56
57 config.add_route(
57 config.add_route(
58 name='repo_commit_raw',
58 name='repo_commit_raw',
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60
60
61 config.add_route(
61 config.add_route(
62 name='repo_commit_patch',
62 name='repo_commit_patch',
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64
64
65 config.add_route(
65 config.add_route(
66 name='repo_commit_download',
66 name='repo_commit_download',
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68
68
69 config.add_route(
69 config.add_route(
70 name='repo_commit_data',
70 name='repo_commit_data',
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72
72
73 config.add_route(
73 config.add_route(
74 name='repo_commit_comment_create',
74 name='repo_commit_comment_create',
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76
76
77 config.add_route(
77 config.add_route(
78 name='repo_commit_comment_preview',
78 name='repo_commit_comment_preview',
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80
80
81 config.add_route(
81 config.add_route(
82 name='repo_commit_comment_delete',
82 name='repo_commit_comment_delete',
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84
84
85 # still working url for backward compat.
85 # still working url for backward compat.
86 config.add_route(
86 config.add_route(
87 name='repo_commit_raw_deprecated',
87 name='repo_commit_raw_deprecated',
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89
89
90 # Files
90 # Files
91 config.add_route(
91 config.add_route(
92 name='repo_archivefile',
92 name='repo_archivefile',
93 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
94
94
95 config.add_route(
95 config.add_route(
96 name='repo_files_diff',
96 name='repo_files_diff',
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 config.add_route( # legacy route to make old links work
98 config.add_route( # legacy route to make old links work
99 name='repo_files_diff_2way_redirect',
99 name='repo_files_diff_2way_redirect',
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101
101
102 config.add_route(
102 config.add_route(
103 name='repo_files',
103 name='repo_files',
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 config.add_route(
105 config.add_route(
106 name='repo_files:default_path',
106 name='repo_files:default_path',
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 config.add_route(
108 config.add_route(
109 name='repo_files:default_commit',
109 name='repo_files:default_commit',
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111
111
112 config.add_route(
112 config.add_route(
113 name='repo_files:rendered',
113 name='repo_files:rendered',
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115
115
116 config.add_route(
116 config.add_route(
117 name='repo_files:annotated',
117 name='repo_files:annotated',
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 config.add_route(
119 config.add_route(
120 name='repo_files:annotated_previous',
120 name='repo_files:annotated_previous',
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122
122
123 config.add_route(
123 config.add_route(
124 name='repo_nodetree_full',
124 name='repo_nodetree_full',
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 config.add_route(
126 config.add_route(
127 name='repo_nodetree_full:default_path',
127 name='repo_nodetree_full:default_path',
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129
129
130 config.add_route(
130 config.add_route(
131 name='repo_files_nodelist',
131 name='repo_files_nodelist',
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133
133
134 config.add_route(
134 config.add_route(
135 name='repo_file_raw',
135 name='repo_file_raw',
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137
137
138 config.add_route(
138 config.add_route(
139 name='repo_file_download',
139 name='repo_file_download',
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 config.add_route( # backward compat to keep old links working
141 config.add_route( # backward compat to keep old links working
142 name='repo_file_download:legacy',
142 name='repo_file_download:legacy',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 repo_route=True)
144 repo_route=True)
145
145
146 config.add_route(
146 config.add_route(
147 name='repo_file_history',
147 name='repo_file_history',
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149
149
150 config.add_route(
150 config.add_route(
151 name='repo_file_authors',
151 name='repo_file_authors',
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153
153
154 config.add_route(
154 config.add_route(
155 name='repo_files_remove_file',
155 name='repo_files_remove_file',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 repo_route=True)
157 repo_route=True)
158 config.add_route(
158 config.add_route(
159 name='repo_files_delete_file',
159 name='repo_files_delete_file',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 repo_route=True)
161 repo_route=True)
162 config.add_route(
162 config.add_route(
163 name='repo_files_edit_file',
163 name='repo_files_edit_file',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 repo_route=True)
165 repo_route=True)
166 config.add_route(
166 config.add_route(
167 name='repo_files_update_file',
167 name='repo_files_update_file',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 repo_route=True)
169 repo_route=True)
170 config.add_route(
170 config.add_route(
171 name='repo_files_add_file',
171 name='repo_files_add_file',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 repo_route=True)
173 repo_route=True)
174 config.add_route(
174 config.add_route(
175 name='repo_files_upload_file',
175 name='repo_files_upload_file',
176 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
176 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
177 repo_route=True)
177 repo_route=True)
178 config.add_route(
178 config.add_route(
179 name='repo_files_create_file',
179 name='repo_files_create_file',
180 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
180 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
181 repo_route=True)
181 repo_route=True)
182
182
183 # Refs data
183 # Refs data
184 config.add_route(
184 config.add_route(
185 name='repo_refs_data',
185 name='repo_refs_data',
186 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
186 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
187
187
188 config.add_route(
188 config.add_route(
189 name='repo_refs_changelog_data',
189 name='repo_refs_changelog_data',
190 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
190 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
191
191
192 config.add_route(
192 config.add_route(
193 name='repo_stats',
193 name='repo_stats',
194 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
194 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
195
195
196 # Changelog
196 # Commits
197 config.add_route(
198 name='repo_commits',
199 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
200 config.add_route(
201 name='repo_commits_file',
202 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
203 config.add_route(
204 name='repo_commits_elements',
205 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
206 config.add_route(
207 name='repo_commits_elements_file',
208 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
209
210 # Changelog (old deprecated name for commits page)
197 config.add_route(
211 config.add_route(
198 name='repo_changelog',
212 name='repo_changelog',
199 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
213 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
200 config.add_route(
214 config.add_route(
201 name='repo_changelog_file',
215 name='repo_changelog_file',
202 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
216 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
203 config.add_route(
204 name='repo_changelog_elements',
205 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
206 config.add_route(
207 name='repo_changelog_elements_file',
208 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
209
217
210 # Compare
218 # Compare
211 config.add_route(
219 config.add_route(
212 name='repo_compare_select',
220 name='repo_compare_select',
213 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
221 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
214
222
215 config.add_route(
223 config.add_route(
216 name='repo_compare',
224 name='repo_compare',
217 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
225 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
218
226
219 # Tags
227 # Tags
220 config.add_route(
228 config.add_route(
221 name='tags_home',
229 name='tags_home',
222 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
230 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
223
231
224 # Branches
232 # Branches
225 config.add_route(
233 config.add_route(
226 name='branches_home',
234 name='branches_home',
227 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
235 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
228
236
229 # Bookmarks
237 # Bookmarks
230 config.add_route(
238 config.add_route(
231 name='bookmarks_home',
239 name='bookmarks_home',
232 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
240 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
233
241
234 # Forks
242 # Forks
235 config.add_route(
243 config.add_route(
236 name='repo_fork_new',
244 name='repo_fork_new',
237 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
245 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
238 repo_forbid_when_archived=True,
246 repo_forbid_when_archived=True,
239 repo_accepted_types=['hg', 'git'])
247 repo_accepted_types=['hg', 'git'])
240
248
241 config.add_route(
249 config.add_route(
242 name='repo_fork_create',
250 name='repo_fork_create',
243 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
251 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
244 repo_forbid_when_archived=True,
252 repo_forbid_when_archived=True,
245 repo_accepted_types=['hg', 'git'])
253 repo_accepted_types=['hg', 'git'])
246
254
247 config.add_route(
255 config.add_route(
248 name='repo_forks_show_all',
256 name='repo_forks_show_all',
249 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
257 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
250 repo_accepted_types=['hg', 'git'])
258 repo_accepted_types=['hg', 'git'])
251 config.add_route(
259 config.add_route(
252 name='repo_forks_data',
260 name='repo_forks_data',
253 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
261 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
254 repo_accepted_types=['hg', 'git'])
262 repo_accepted_types=['hg', 'git'])
255
263
256 # Pull Requests
264 # Pull Requests
257 config.add_route(
265 config.add_route(
258 name='pullrequest_show',
266 name='pullrequest_show',
259 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
267 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
260 repo_route=True)
268 repo_route=True)
261
269
262 config.add_route(
270 config.add_route(
263 name='pullrequest_show_all',
271 name='pullrequest_show_all',
264 pattern='/{repo_name:.*?[^/]}/pull-request',
272 pattern='/{repo_name:.*?[^/]}/pull-request',
265 repo_route=True, repo_accepted_types=['hg', 'git'])
273 repo_route=True, repo_accepted_types=['hg', 'git'])
266
274
267 config.add_route(
275 config.add_route(
268 name='pullrequest_show_all_data',
276 name='pullrequest_show_all_data',
269 pattern='/{repo_name:.*?[^/]}/pull-request-data',
277 pattern='/{repo_name:.*?[^/]}/pull-request-data',
270 repo_route=True, repo_accepted_types=['hg', 'git'])
278 repo_route=True, repo_accepted_types=['hg', 'git'])
271
279
272 config.add_route(
280 config.add_route(
273 name='pullrequest_repo_refs',
281 name='pullrequest_repo_refs',
274 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
282 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
275 repo_route=True)
283 repo_route=True)
276
284
277 config.add_route(
285 config.add_route(
278 name='pullrequest_repo_targets',
286 name='pullrequest_repo_targets',
279 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
287 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
280 repo_route=True)
288 repo_route=True)
281
289
282 config.add_route(
290 config.add_route(
283 name='pullrequest_new',
291 name='pullrequest_new',
284 pattern='/{repo_name:.*?[^/]}/pull-request/new',
292 pattern='/{repo_name:.*?[^/]}/pull-request/new',
285 repo_route=True, repo_accepted_types=['hg', 'git'],
293 repo_route=True, repo_accepted_types=['hg', 'git'],
286 repo_forbid_when_archived=True)
294 repo_forbid_when_archived=True)
287
295
288 config.add_route(
296 config.add_route(
289 name='pullrequest_create',
297 name='pullrequest_create',
290 pattern='/{repo_name:.*?[^/]}/pull-request/create',
298 pattern='/{repo_name:.*?[^/]}/pull-request/create',
291 repo_route=True, repo_accepted_types=['hg', 'git'],
299 repo_route=True, repo_accepted_types=['hg', 'git'],
292 repo_forbid_when_archived=True)
300 repo_forbid_when_archived=True)
293
301
294 config.add_route(
302 config.add_route(
295 name='pullrequest_update',
303 name='pullrequest_update',
296 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
304 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
297 repo_route=True, repo_forbid_when_archived=True)
305 repo_route=True, repo_forbid_when_archived=True)
298
306
299 config.add_route(
307 config.add_route(
300 name='pullrequest_merge',
308 name='pullrequest_merge',
301 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
309 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
302 repo_route=True, repo_forbid_when_archived=True)
310 repo_route=True, repo_forbid_when_archived=True)
303
311
304 config.add_route(
312 config.add_route(
305 name='pullrequest_delete',
313 name='pullrequest_delete',
306 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
314 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
307 repo_route=True, repo_forbid_when_archived=True)
315 repo_route=True, repo_forbid_when_archived=True)
308
316
309 config.add_route(
317 config.add_route(
310 name='pullrequest_comment_create',
318 name='pullrequest_comment_create',
311 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
319 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
312 repo_route=True)
320 repo_route=True)
313
321
314 config.add_route(
322 config.add_route(
315 name='pullrequest_comment_delete',
323 name='pullrequest_comment_delete',
316 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
324 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
317 repo_route=True, repo_accepted_types=['hg', 'git'])
325 repo_route=True, repo_accepted_types=['hg', 'git'])
318
326
319 # Artifacts, (EE feature)
327 # Artifacts, (EE feature)
320 config.add_route(
328 config.add_route(
321 name='repo_artifacts_list',
329 name='repo_artifacts_list',
322 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
330 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
323
331
324 # Settings
332 # Settings
325 config.add_route(
333 config.add_route(
326 name='edit_repo',
334 name='edit_repo',
327 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
335 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
328 # update is POST on edit_repo
336 # update is POST on edit_repo
329
337
330 # Settings advanced
338 # Settings advanced
331 config.add_route(
339 config.add_route(
332 name='edit_repo_advanced',
340 name='edit_repo_advanced',
333 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
341 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
334 config.add_route(
342 config.add_route(
335 name='edit_repo_advanced_archive',
343 name='edit_repo_advanced_archive',
336 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
344 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
337 config.add_route(
345 config.add_route(
338 name='edit_repo_advanced_delete',
346 name='edit_repo_advanced_delete',
339 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
347 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
340 config.add_route(
348 config.add_route(
341 name='edit_repo_advanced_locking',
349 name='edit_repo_advanced_locking',
342 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
350 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
343 config.add_route(
351 config.add_route(
344 name='edit_repo_advanced_journal',
352 name='edit_repo_advanced_journal',
345 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
353 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
346 config.add_route(
354 config.add_route(
347 name='edit_repo_advanced_fork',
355 name='edit_repo_advanced_fork',
348 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
356 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
349
357
350 config.add_route(
358 config.add_route(
351 name='edit_repo_advanced_hooks',
359 name='edit_repo_advanced_hooks',
352 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
360 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
353
361
354 # Caches
362 # Caches
355 config.add_route(
363 config.add_route(
356 name='edit_repo_caches',
364 name='edit_repo_caches',
357 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
365 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
358
366
359 # Permissions
367 # Permissions
360 config.add_route(
368 config.add_route(
361 name='edit_repo_perms',
369 name='edit_repo_perms',
362 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
370 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
363
371
364 # Permissions Branch (EE feature)
372 # Permissions Branch (EE feature)
365 config.add_route(
373 config.add_route(
366 name='edit_repo_perms_branch',
374 name='edit_repo_perms_branch',
367 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
375 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
368 config.add_route(
376 config.add_route(
369 name='edit_repo_perms_branch_delete',
377 name='edit_repo_perms_branch_delete',
370 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
378 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
371 repo_route=True)
379 repo_route=True)
372
380
373 # Maintenance
381 # Maintenance
374 config.add_route(
382 config.add_route(
375 name='edit_repo_maintenance',
383 name='edit_repo_maintenance',
376 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
384 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
377
385
378 config.add_route(
386 config.add_route(
379 name='edit_repo_maintenance_execute',
387 name='edit_repo_maintenance_execute',
380 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
388 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
381
389
382 # Fields
390 # Fields
383 config.add_route(
391 config.add_route(
384 name='edit_repo_fields',
392 name='edit_repo_fields',
385 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
393 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
386 config.add_route(
394 config.add_route(
387 name='edit_repo_fields_create',
395 name='edit_repo_fields_create',
388 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
396 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
389 config.add_route(
397 config.add_route(
390 name='edit_repo_fields_delete',
398 name='edit_repo_fields_delete',
391 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
399 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
392
400
393 # Locking
401 # Locking
394 config.add_route(
402 config.add_route(
395 name='repo_edit_toggle_locking',
403 name='repo_edit_toggle_locking',
396 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
404 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
397
405
398 # Remote
406 # Remote
399 config.add_route(
407 config.add_route(
400 name='edit_repo_remote',
408 name='edit_repo_remote',
401 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
409 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
402 config.add_route(
410 config.add_route(
403 name='edit_repo_remote_pull',
411 name='edit_repo_remote_pull',
404 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
412 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
405 config.add_route(
413 config.add_route(
406 name='edit_repo_remote_push',
414 name='edit_repo_remote_push',
407 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
415 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
408
416
409 # Statistics
417 # Statistics
410 config.add_route(
418 config.add_route(
411 name='edit_repo_statistics',
419 name='edit_repo_statistics',
412 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
420 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
413 config.add_route(
421 config.add_route(
414 name='edit_repo_statistics_reset',
422 name='edit_repo_statistics_reset',
415 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
423 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
416
424
417 # Issue trackers
425 # Issue trackers
418 config.add_route(
426 config.add_route(
419 name='edit_repo_issuetracker',
427 name='edit_repo_issuetracker',
420 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
428 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
421 config.add_route(
429 config.add_route(
422 name='edit_repo_issuetracker_test',
430 name='edit_repo_issuetracker_test',
423 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
431 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
424 config.add_route(
432 config.add_route(
425 name='edit_repo_issuetracker_delete',
433 name='edit_repo_issuetracker_delete',
426 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
434 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
427 config.add_route(
435 config.add_route(
428 name='edit_repo_issuetracker_update',
436 name='edit_repo_issuetracker_update',
429 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
437 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
430
438
431 # VCS Settings
439 # VCS Settings
432 config.add_route(
440 config.add_route(
433 name='edit_repo_vcs',
441 name='edit_repo_vcs',
434 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
442 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
435 config.add_route(
443 config.add_route(
436 name='edit_repo_vcs_update',
444 name='edit_repo_vcs_update',
437 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
445 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
438
446
439 # svn pattern
447 # svn pattern
440 config.add_route(
448 config.add_route(
441 name='edit_repo_vcs_svn_pattern_delete',
449 name='edit_repo_vcs_svn_pattern_delete',
442 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
450 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
443
451
444 # Repo Review Rules (EE feature)
452 # Repo Review Rules (EE feature)
445 config.add_route(
453 config.add_route(
446 name='repo_reviewers',
454 name='repo_reviewers',
447 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
455 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
448
456
449 config.add_route(
457 config.add_route(
450 name='repo_default_reviewers_data',
458 name='repo_default_reviewers_data',
451 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
459 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
452
460
453 # Repo Automation (EE feature)
461 # Repo Automation (EE feature)
454 config.add_route(
462 config.add_route(
455 name='repo_automation',
463 name='repo_automation',
456 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
464 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
457
465
458 # Strip
466 # Strip
459 config.add_route(
467 config.add_route(
460 name='edit_repo_strip',
468 name='edit_repo_strip',
461 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
469 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
462
470
463 config.add_route(
471 config.add_route(
464 name='strip_check',
472 name='strip_check',
465 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
473 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
466
474
467 config.add_route(
475 config.add_route(
468 name='strip_execute',
476 name='strip_execute',
469 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
477 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
470
478
471 # Audit logs
479 # Audit logs
472 config.add_route(
480 config.add_route(
473 name='edit_repo_audit_logs',
481 name='edit_repo_audit_logs',
474 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
482 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
475
483
476 # ATOM/RSS Feed
484 # ATOM/RSS Feed
477 config.add_route(
485 config.add_route(
478 name='rss_feed_home',
486 name='rss_feed_home',
479 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
487 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
480
488
481 config.add_route(
489 config.add_route(
482 name='atom_feed_home',
490 name='atom_feed_home',
483 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
491 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
484
492
485 # NOTE(marcink): needs to be at the end for catch-all
493 # NOTE(marcink): needs to be at the end for catch-all
486 add_route_with_slash(
494 add_route_with_slash(
487 config,
495 config,
488 name='repo_summary',
496 name='repo_summary',
489 pattern='/{repo_name:.*?[^/]}', repo_route=True)
497 pattern='/{repo_name:.*?[^/]}', repo_route=True)
490
498
491 # Scan module for configuration decorators.
499 # Scan module for configuration decorators.
492 config.scan('.views', ignore='.tests')
500 config.scan('.views', ignore='.tests')
@@ -1,195 +1,213 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 import re
21 import re
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.apps.repository.views.repo_changelog import DEFAULT_CHANGELOG_SIZE
25 from rhodecode.apps.repository.views.repo_changelog import DEFAULT_CHANGELOG_SIZE
26 from rhodecode.tests import TestController
26 from rhodecode.tests import TestController
27
27
28 MATCH_HASH = re.compile(r'<span class="commit_hash">r(\d+):[\da-f]+</span>')
28 MATCH_HASH = re.compile(r'<span class="commit_hash">r(\d+):[\da-f]+</span>')
29
29
30
30
31 def route_path(name, params=None, **kwargs):
31 def route_path(name, params=None, **kwargs):
32 import urllib
32 import urllib
33
33
34 base_url = {
34 base_url = {
35 'repo_changelog':'/{repo_name}/changelog',
35 'repo_changelog': '/{repo_name}/changelog',
36 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}',
36 'repo_commits': '/{repo_name}/commits',
37 'repo_changelog_elements':'/{repo_name}/changelog_elements',
37 'repo_commits_file': '/{repo_name}/commits/{commit_id}/{f_path}',
38 'repo_commits_elements': '/{repo_name}/commits_elements',
38 }[name].format(**kwargs)
39 }[name].format(**kwargs)
39
40
40 if params:
41 if params:
41 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 return base_url
43 return base_url
43
44
44
45
46 def assert_commits_on_page(response, indexes):
47 found_indexes = [int(idx) for idx in MATCH_HASH.findall(response.body)]
48 assert found_indexes == indexes
49
50
45 class TestChangelogController(TestController):
51 class TestChangelogController(TestController):
46
52
53 def test_commits_page(self, backend):
54 self.log_user()
55 response = self.app.get(
56 route_path('repo_commits', repo_name=backend.repo_name))
57
58 first_idx = -1
59 last_idx = -DEFAULT_CHANGELOG_SIZE
60 self.assert_commit_range_on_page(
61 response, first_idx, last_idx, backend)
62
47 def test_changelog(self, backend):
63 def test_changelog(self, backend):
48 self.log_user()
64 self.log_user()
49 response = self.app.get(
65 response = self.app.get(
50 route_path('repo_changelog', repo_name=backend.repo_name))
66 route_path('repo_changelog', repo_name=backend.repo_name))
51
67
52 first_idx = -1
68 first_idx = -1
53 last_idx = -DEFAULT_CHANGELOG_SIZE
69 last_idx = -DEFAULT_CHANGELOG_SIZE
54 self.assert_commit_range_on_page(
70 self.assert_commit_range_on_page(
55 response, first_idx, last_idx, backend)
71 response, first_idx, last_idx, backend)
56
72
57 @pytest.mark.backends("hg", "git")
73 @pytest.mark.backends("hg", "git")
58 def test_changelog_filtered_by_branch(self, backend):
74 def test_changelog_filtered_by_branch(self, backend):
59 self.log_user()
75 self.log_user()
60 self.app.get(
76 self.app.get(
61 route_path('repo_changelog', repo_name=backend.repo_name,
77 route_path('repo_changelog', repo_name=backend.repo_name,
62 params=dict(branch=backend.default_branch_name)),
78 params=dict(branch=backend.default_branch_name)),
63 status=200)
79 status=200)
64
80
81 @pytest.mark.backends("hg", "git")
82 def test_commits_filtered_by_branch(self, backend):
83 self.log_user()
84 self.app.get(
85 route_path('repo_commits', repo_name=backend.repo_name,
86 params=dict(branch=backend.default_branch_name)),
87 status=200)
88
65 @pytest.mark.backends("svn")
89 @pytest.mark.backends("svn")
66 def test_changelog_filtered_by_branch_svn(self, autologin_user, backend):
90 def test_changelog_filtered_by_branch_svn(self, autologin_user, backend):
67 repo = backend['svn-simple-layout']
91 repo = backend['svn-simple-layout']
68 response = self.app.get(
92 response = self.app.get(
69 route_path('repo_changelog', repo_name=repo.repo_name,
93 route_path('repo_changelog', repo_name=repo.repo_name,
70 params=dict(branch='trunk')),
94 params=dict(branch='trunk')),
71 status=200)
95 status=200)
72
96
73 self.assert_commits_on_page(
97 assert_commits_on_page(response, indexes=[15, 12, 7, 3, 2, 1])
74 response, indexes=[15, 12, 7, 3, 2, 1])
75
98
76 def test_changelog_filtered_by_wrong_branch(self, backend):
99 def test_commits_filtered_by_wrong_branch(self, backend):
77 self.log_user()
100 self.log_user()
78 branch = 'wrong-branch-name'
101 branch = 'wrong-branch-name'
79 response = self.app.get(
102 response = self.app.get(
80 route_path('repo_changelog', repo_name=backend.repo_name,
103 route_path('repo_commits', repo_name=backend.repo_name,
81 params=dict(branch=branch)),
104 params=dict(branch=branch)),
82 status=302)
105 status=302)
83 expected_url = '/{repo}/changelog/{branch}'.format(
106 expected_url = '/{repo}/commits/{branch}'.format(
84 repo=backend.repo_name, branch=branch)
107 repo=backend.repo_name, branch=branch)
85 assert expected_url in response.location
108 assert expected_url in response.location
86 response = response.follow()
109 response = response.follow()
87 expected_warning = 'Branch {} is not found.'.format(branch)
110 expected_warning = 'Branch {} is not found.'.format(branch)
88 assert expected_warning in response.body
111 assert expected_warning in response.body
89
112
90 def assert_commits_on_page(self, response, indexes):
91 found_indexes = [int(idx) for idx in MATCH_HASH.findall(response.body)]
92 assert found_indexes == indexes
93
94 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
113 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
95 def test_changelog_filtered_by_branch_with_merges(
114 def test_changelog_filtered_by_branch_with_merges(
96 self, autologin_user, backend):
115 self, autologin_user, backend):
97
116
98 # Note: The changelog of branch "b" does not contain the commit "a1"
117 # Note: The changelog of branch "b" does not contain the commit "a1"
99 # although this is a parent of commit "b1". And branch "b" has commits
118 # although this is a parent of commit "b1". And branch "b" has commits
100 # which have a smaller index than commit "a1".
119 # which have a smaller index than commit "a1".
101 commits = [
120 commits = [
102 {'message': 'a'},
121 {'message': 'a'},
103 {'message': 'b', 'branch': 'b'},
122 {'message': 'b', 'branch': 'b'},
104 {'message': 'a1', 'parents': ['a']},
123 {'message': 'a1', 'parents': ['a']},
105 {'message': 'b1', 'branch': 'b', 'parents': ['b', 'a1']},
124 {'message': 'b1', 'branch': 'b', 'parents': ['b', 'a1']},
106 ]
125 ]
107 backend.create_repo(commits)
126 backend.create_repo(commits)
108
127
109 self.app.get(
128 self.app.get(
110 route_path('repo_changelog', repo_name=backend.repo_name,
129 route_path('repo_changelog', repo_name=backend.repo_name,
111 params=dict(branch='b')),
130 params=dict(branch='b')),
112 status=200)
131 status=200)
113
132
114 @pytest.mark.backends("hg")
133 @pytest.mark.backends("hg")
115 def test_changelog_closed_branches(self, autologin_user, backend):
134 def test_commits_closed_branches(self, autologin_user, backend):
116 repo = backend['closed_branch']
135 repo = backend['closed_branch']
117 response = self.app.get(
136 response = self.app.get(
118 route_path('repo_changelog', repo_name=repo.repo_name,
137 route_path('repo_commits', repo_name=repo.repo_name,
119 params=dict(branch='experimental')),
138 params=dict(branch='experimental')),
120 status=200)
139 status=200)
121
140
122 self.assert_commits_on_page(
141 assert_commits_on_page(response, indexes=[3, 1])
123 response, indexes=[3, 1])
124
142
125 def test_changelog_pagination(self, backend):
143 def test_changelog_pagination(self, backend):
126 self.log_user()
144 self.log_user()
127 # pagination, walk up to page 6
145 # pagination, walk up to page 6
128 changelog_url = route_path(
146 changelog_url = route_path(
129 'repo_changelog', repo_name=backend.repo_name)
147 'repo_commits', repo_name=backend.repo_name)
130
148
131 for page in range(1, 7):
149 for page in range(1, 7):
132 response = self.app.get(changelog_url, {'page': page})
150 response = self.app.get(changelog_url, {'page': page})
133
151
134 first_idx = -DEFAULT_CHANGELOG_SIZE * (page - 1) - 1
152 first_idx = -DEFAULT_CHANGELOG_SIZE * (page - 1) - 1
135 last_idx = -DEFAULT_CHANGELOG_SIZE * page
153 last_idx = -DEFAULT_CHANGELOG_SIZE * page
136 self.assert_commit_range_on_page(response, first_idx, last_idx, backend)
154 self.assert_commit_range_on_page(response, first_idx, last_idx, backend)
137
155
138 def assert_commit_range_on_page(
156 def assert_commit_range_on_page(
139 self, response, first_idx, last_idx, backend):
157 self, response, first_idx, last_idx, backend):
140 input_template = (
158 input_template = (
141 """<input class="commit-range" id="%(raw_id)s" """
159 """<input class="commit-range" id="%(raw_id)s" """
142 """name="%(raw_id)s" type="checkbox" value="1" />"""
160 """name="%(raw_id)s" type="checkbox" value="1" />"""
143 )
161 )
144 commit_span_template = """<span class="commit_hash">r%s:%s</span>"""
162 commit_span_template = """<span class="commit_hash">r%s:%s</span>"""
145 repo = backend.repo
163 repo = backend.repo
146
164
147 first_commit_on_page = repo.get_commit(commit_idx=first_idx)
165 first_commit_on_page = repo.get_commit(commit_idx=first_idx)
148 response.mustcontain(
166 response.mustcontain(
149 input_template % {'raw_id': first_commit_on_page.raw_id})
167 input_template % {'raw_id': first_commit_on_page.raw_id})
150 response.mustcontain(commit_span_template % (
168 response.mustcontain(commit_span_template % (
151 first_commit_on_page.idx, first_commit_on_page.short_id)
169 first_commit_on_page.idx, first_commit_on_page.short_id)
152 )
170 )
153
171
154 last_commit_on_page = repo.get_commit(commit_idx=last_idx)
172 last_commit_on_page = repo.get_commit(commit_idx=last_idx)
155 response.mustcontain(
173 response.mustcontain(
156 input_template % {'raw_id': last_commit_on_page.raw_id})
174 input_template % {'raw_id': last_commit_on_page.raw_id})
157 response.mustcontain(commit_span_template % (
175 response.mustcontain(commit_span_template % (
158 last_commit_on_page.idx, last_commit_on_page.short_id)
176 last_commit_on_page.idx, last_commit_on_page.short_id)
159 )
177 )
160
178
161 first_commit_of_next_page = repo.get_commit(commit_idx=last_idx - 1)
179 first_commit_of_next_page = repo.get_commit(commit_idx=last_idx - 1)
162 first_span_of_next_page = commit_span_template % (
180 first_span_of_next_page = commit_span_template % (
163 first_commit_of_next_page.idx, first_commit_of_next_page.short_id)
181 first_commit_of_next_page.idx, first_commit_of_next_page.short_id)
164 assert first_span_of_next_page not in response
182 assert first_span_of_next_page not in response
165
183
166 @pytest.mark.parametrize('test_path', [
184 @pytest.mark.parametrize('test_path', [
167 'vcs/exceptions.py',
185 'vcs/exceptions.py',
168 '/vcs/exceptions.py',
186 '/vcs/exceptions.py',
169 '//vcs/exceptions.py'
187 '//vcs/exceptions.py'
170 ])
188 ])
171 def test_changelog_with_filenode(self, backend, test_path):
189 def test_commits_with_filenode(self, backend, test_path):
172 self.log_user()
190 self.log_user()
173 response = self.app.get(
191 response = self.app.get(
174 route_path('repo_changelog_file', repo_name=backend.repo_name,
192 route_path('repo_commits_file', repo_name=backend.repo_name,
175 commit_id='tip', f_path=test_path),
193 commit_id='tip', f_path=test_path),
176 )
194 )
177
195
178 # history commits messages
196 # history commits messages
179 response.mustcontain('Added exceptions module, this time for real')
197 response.mustcontain('Added exceptions module, this time for real')
180 response.mustcontain('Added not implemented hg backend test case')
198 response.mustcontain('Added not implemented hg backend test case')
181 response.mustcontain('Added BaseChangeset class')
199 response.mustcontain('Added BaseChangeset class')
182
200
183 def test_changelog_with_filenode_that_is_dirnode(self, backend):
201 def test_commits_with_filenode_that_is_dirnode(self, backend):
184 self.log_user()
202 self.log_user()
185 self.app.get(
203 self.app.get(
186 route_path('repo_changelog_file', repo_name=backend.repo_name,
204 route_path('repo_commits_file', repo_name=backend.repo_name,
187 commit_id='tip', f_path='/tests'),
205 commit_id='tip', f_path='/tests'),
188 status=302)
206 status=302)
189
207
190 def test_changelog_with_filenode_not_existing(self, backend):
208 def test_commits_with_filenode_not_existing(self, backend):
191 self.log_user()
209 self.log_user()
192 self.app.get(
210 self.app.get(
193 route_path('repo_changelog_file', repo_name=backend.repo_name,
211 route_path('repo_commits_file', repo_name=backend.repo_name,
194 commit_id='tip', f_path='wrong_path'),
212 commit_id='tip', f_path='wrong_path'),
195 status=302)
213 status=302)
@@ -1,1216 +1,1218 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 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 import rhodecode
23 import rhodecode
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
25 from rhodecode.lib.vcs.nodes import FileNode
25 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.tests import (
33 from rhodecode.tests import (
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39
39
40 base_url = {
40 base_url = {
41 'repo_changelog': '/{repo_name}/changelog',
41 'repo_changelog': '/{repo_name}/changelog',
42 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
42 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
43 'repo_commits': '/{repo_name}/changelog',
44 'repo_commits_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
43 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
45 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
44 'pullrequest_show_all': '/{repo_name}/pull-request',
46 'pullrequest_show_all': '/{repo_name}/pull-request',
45 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
47 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
46 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
48 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
47 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
49 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
48 'pullrequest_new': '/{repo_name}/pull-request/new',
50 'pullrequest_new': '/{repo_name}/pull-request/new',
49 'pullrequest_create': '/{repo_name}/pull-request/create',
51 'pullrequest_create': '/{repo_name}/pull-request/create',
50 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
52 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
51 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
53 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
52 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
54 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
53 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
55 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
54 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
56 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
55 }[name].format(**kwargs)
57 }[name].format(**kwargs)
56
58
57 if params:
59 if params:
58 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
60 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
59 return base_url
61 return base_url
60
62
61
63
62 @pytest.mark.usefixtures('app', 'autologin_user')
64 @pytest.mark.usefixtures('app', 'autologin_user')
63 @pytest.mark.backends("git", "hg")
65 @pytest.mark.backends("git", "hg")
64 class TestPullrequestsView(object):
66 class TestPullrequestsView(object):
65
67
66 def test_index(self, backend):
68 def test_index(self, backend):
67 self.app.get(route_path(
69 self.app.get(route_path(
68 'pullrequest_new',
70 'pullrequest_new',
69 repo_name=backend.repo_name))
71 repo_name=backend.repo_name))
70
72
71 def test_option_menu_create_pull_request_exists(self, backend):
73 def test_option_menu_create_pull_request_exists(self, backend):
72 repo_name = backend.repo_name
74 repo_name = backend.repo_name
73 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
75 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
74
76
75 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
77 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
76 'pullrequest_new', repo_name=repo_name)
78 'pullrequest_new', repo_name=repo_name)
77 response.mustcontain(create_pr_link)
79 response.mustcontain(create_pr_link)
78
80
79 def test_create_pr_form_with_raw_commit_id(self, backend):
81 def test_create_pr_form_with_raw_commit_id(self, backend):
80 repo = backend.repo
82 repo = backend.repo
81
83
82 self.app.get(
84 self.app.get(
83 route_path('pullrequest_new', repo_name=repo.repo_name,
85 route_path('pullrequest_new', repo_name=repo.repo_name,
84 commit=repo.get_commit().raw_id),
86 commit=repo.get_commit().raw_id),
85 status=200)
87 status=200)
86
88
87 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
89 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
88 @pytest.mark.parametrize('range_diff', ["0", "1"])
90 @pytest.mark.parametrize('range_diff', ["0", "1"])
89 def test_show(self, pr_util, pr_merge_enabled, range_diff):
91 def test_show(self, pr_util, pr_merge_enabled, range_diff):
90 pull_request = pr_util.create_pull_request(
92 pull_request = pr_util.create_pull_request(
91 mergeable=pr_merge_enabled, enable_notifications=False)
93 mergeable=pr_merge_enabled, enable_notifications=False)
92
94
93 response = self.app.get(route_path(
95 response = self.app.get(route_path(
94 'pullrequest_show',
96 'pullrequest_show',
95 repo_name=pull_request.target_repo.scm_instance().name,
97 repo_name=pull_request.target_repo.scm_instance().name,
96 pull_request_id=pull_request.pull_request_id,
98 pull_request_id=pull_request.pull_request_id,
97 params={'range-diff': range_diff}))
99 params={'range-diff': range_diff}))
98
100
99 for commit_id in pull_request.revisions:
101 for commit_id in pull_request.revisions:
100 response.mustcontain(commit_id)
102 response.mustcontain(commit_id)
101
103
102 assert pull_request.target_ref_parts.type in response
104 assert pull_request.target_ref_parts.type in response
103 assert pull_request.target_ref_parts.name in response
105 assert pull_request.target_ref_parts.name in response
104 target_clone_url = pull_request.target_repo.clone_url()
106 target_clone_url = pull_request.target_repo.clone_url()
105 assert target_clone_url in response
107 assert target_clone_url in response
106
108
107 assert 'class="pull-request-merge"' in response
109 assert 'class="pull-request-merge"' in response
108 if pr_merge_enabled:
110 if pr_merge_enabled:
109 response.mustcontain('Pull request reviewer approval is pending')
111 response.mustcontain('Pull request reviewer approval is pending')
110 else:
112 else:
111 response.mustcontain('Server-side pull request merging is disabled.')
113 response.mustcontain('Server-side pull request merging is disabled.')
112
114
113 if range_diff == "1":
115 if range_diff == "1":
114 response.mustcontain('Turn off: Show the diff as commit range')
116 response.mustcontain('Turn off: Show the diff as commit range')
115
117
116 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
118 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
117 # Logout
119 # Logout
118 response = self.app.post(
120 response = self.app.post(
119 h.route_path('logout'),
121 h.route_path('logout'),
120 params={'csrf_token': csrf_token})
122 params={'csrf_token': csrf_token})
121 # Login as regular user
123 # Login as regular user
122 response = self.app.post(h.route_path('login'),
124 response = self.app.post(h.route_path('login'),
123 {'username': TEST_USER_REGULAR_LOGIN,
125 {'username': TEST_USER_REGULAR_LOGIN,
124 'password': 'test12'})
126 'password': 'test12'})
125
127
126 pull_request = pr_util.create_pull_request(
128 pull_request = pr_util.create_pull_request(
127 author=TEST_USER_REGULAR_LOGIN)
129 author=TEST_USER_REGULAR_LOGIN)
128
130
129 response = self.app.get(route_path(
131 response = self.app.get(route_path(
130 'pullrequest_show',
132 'pullrequest_show',
131 repo_name=pull_request.target_repo.scm_instance().name,
133 repo_name=pull_request.target_repo.scm_instance().name,
132 pull_request_id=pull_request.pull_request_id))
134 pull_request_id=pull_request.pull_request_id))
133
135
134 response.mustcontain('Server-side pull request merging is disabled.')
136 response.mustcontain('Server-side pull request merging is disabled.')
135
137
136 assert_response = response.assert_response()
138 assert_response = response.assert_response()
137 # for regular user without a merge permissions, we don't see it
139 # for regular user without a merge permissions, we don't see it
138 assert_response.no_element_exists('#close-pull-request-action')
140 assert_response.no_element_exists('#close-pull-request-action')
139
141
140 user_util.grant_user_permission_to_repo(
142 user_util.grant_user_permission_to_repo(
141 pull_request.target_repo,
143 pull_request.target_repo,
142 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
144 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
143 'repository.write')
145 'repository.write')
144 response = self.app.get(route_path(
146 response = self.app.get(route_path(
145 'pullrequest_show',
147 'pullrequest_show',
146 repo_name=pull_request.target_repo.scm_instance().name,
148 repo_name=pull_request.target_repo.scm_instance().name,
147 pull_request_id=pull_request.pull_request_id))
149 pull_request_id=pull_request.pull_request_id))
148
150
149 response.mustcontain('Server-side pull request merging is disabled.')
151 response.mustcontain('Server-side pull request merging is disabled.')
150
152
151 assert_response = response.assert_response()
153 assert_response = response.assert_response()
152 # now regular user has a merge permissions, we have CLOSE button
154 # now regular user has a merge permissions, we have CLOSE button
153 assert_response.one_element_exists('#close-pull-request-action')
155 assert_response.one_element_exists('#close-pull-request-action')
154
156
155 def test_show_invalid_commit_id(self, pr_util):
157 def test_show_invalid_commit_id(self, pr_util):
156 # Simulating invalid revisions which will cause a lookup error
158 # Simulating invalid revisions which will cause a lookup error
157 pull_request = pr_util.create_pull_request()
159 pull_request = pr_util.create_pull_request()
158 pull_request.revisions = ['invalid']
160 pull_request.revisions = ['invalid']
159 Session().add(pull_request)
161 Session().add(pull_request)
160 Session().commit()
162 Session().commit()
161
163
162 response = self.app.get(route_path(
164 response = self.app.get(route_path(
163 'pullrequest_show',
165 'pullrequest_show',
164 repo_name=pull_request.target_repo.scm_instance().name,
166 repo_name=pull_request.target_repo.scm_instance().name,
165 pull_request_id=pull_request.pull_request_id))
167 pull_request_id=pull_request.pull_request_id))
166
168
167 for commit_id in pull_request.revisions:
169 for commit_id in pull_request.revisions:
168 response.mustcontain(commit_id)
170 response.mustcontain(commit_id)
169
171
170 def test_show_invalid_source_reference(self, pr_util):
172 def test_show_invalid_source_reference(self, pr_util):
171 pull_request = pr_util.create_pull_request()
173 pull_request = pr_util.create_pull_request()
172 pull_request.source_ref = 'branch:b:invalid'
174 pull_request.source_ref = 'branch:b:invalid'
173 Session().add(pull_request)
175 Session().add(pull_request)
174 Session().commit()
176 Session().commit()
175
177
176 self.app.get(route_path(
178 self.app.get(route_path(
177 'pullrequest_show',
179 'pullrequest_show',
178 repo_name=pull_request.target_repo.scm_instance().name,
180 repo_name=pull_request.target_repo.scm_instance().name,
179 pull_request_id=pull_request.pull_request_id))
181 pull_request_id=pull_request.pull_request_id))
180
182
181 def test_edit_title_description(self, pr_util, csrf_token):
183 def test_edit_title_description(self, pr_util, csrf_token):
182 pull_request = pr_util.create_pull_request()
184 pull_request = pr_util.create_pull_request()
183 pull_request_id = pull_request.pull_request_id
185 pull_request_id = pull_request.pull_request_id
184
186
185 response = self.app.post(
187 response = self.app.post(
186 route_path('pullrequest_update',
188 route_path('pullrequest_update',
187 repo_name=pull_request.target_repo.repo_name,
189 repo_name=pull_request.target_repo.repo_name,
188 pull_request_id=pull_request_id),
190 pull_request_id=pull_request_id),
189 params={
191 params={
190 'edit_pull_request': 'true',
192 'edit_pull_request': 'true',
191 'title': 'New title',
193 'title': 'New title',
192 'description': 'New description',
194 'description': 'New description',
193 'csrf_token': csrf_token})
195 'csrf_token': csrf_token})
194
196
195 assert_session_flash(
197 assert_session_flash(
196 response, u'Pull request title & description updated.',
198 response, u'Pull request title & description updated.',
197 category='success')
199 category='success')
198
200
199 pull_request = PullRequest.get(pull_request_id)
201 pull_request = PullRequest.get(pull_request_id)
200 assert pull_request.title == 'New title'
202 assert pull_request.title == 'New title'
201 assert pull_request.description == 'New description'
203 assert pull_request.description == 'New description'
202
204
203 def test_edit_title_description_closed(self, pr_util, csrf_token):
205 def test_edit_title_description_closed(self, pr_util, csrf_token):
204 pull_request = pr_util.create_pull_request()
206 pull_request = pr_util.create_pull_request()
205 pull_request_id = pull_request.pull_request_id
207 pull_request_id = pull_request.pull_request_id
206 repo_name = pull_request.target_repo.repo_name
208 repo_name = pull_request.target_repo.repo_name
207 pr_util.close()
209 pr_util.close()
208
210
209 response = self.app.post(
211 response = self.app.post(
210 route_path('pullrequest_update',
212 route_path('pullrequest_update',
211 repo_name=repo_name, pull_request_id=pull_request_id),
213 repo_name=repo_name, pull_request_id=pull_request_id),
212 params={
214 params={
213 'edit_pull_request': 'true',
215 'edit_pull_request': 'true',
214 'title': 'New title',
216 'title': 'New title',
215 'description': 'New description',
217 'description': 'New description',
216 'csrf_token': csrf_token}, status=200)
218 'csrf_token': csrf_token}, status=200)
217 assert_session_flash(
219 assert_session_flash(
218 response, u'Cannot update closed pull requests.',
220 response, u'Cannot update closed pull requests.',
219 category='error')
221 category='error')
220
222
221 def test_update_invalid_source_reference(self, pr_util, csrf_token):
223 def test_update_invalid_source_reference(self, pr_util, csrf_token):
222 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
224 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
223
225
224 pull_request = pr_util.create_pull_request()
226 pull_request = pr_util.create_pull_request()
225 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
227 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
226 Session().add(pull_request)
228 Session().add(pull_request)
227 Session().commit()
229 Session().commit()
228
230
229 pull_request_id = pull_request.pull_request_id
231 pull_request_id = pull_request.pull_request_id
230
232
231 response = self.app.post(
233 response = self.app.post(
232 route_path('pullrequest_update',
234 route_path('pullrequest_update',
233 repo_name=pull_request.target_repo.repo_name,
235 repo_name=pull_request.target_repo.repo_name,
234 pull_request_id=pull_request_id),
236 pull_request_id=pull_request_id),
235 params={'update_commits': 'true', 'csrf_token': csrf_token})
237 params={'update_commits': 'true', 'csrf_token': csrf_token})
236
238
237 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
239 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
238 UpdateFailureReason.MISSING_SOURCE_REF])
240 UpdateFailureReason.MISSING_SOURCE_REF])
239 assert_session_flash(response, expected_msg, category='error')
241 assert_session_flash(response, expected_msg, category='error')
240
242
241 def test_missing_target_reference(self, pr_util, csrf_token):
243 def test_missing_target_reference(self, pr_util, csrf_token):
242 from rhodecode.lib.vcs.backends.base import MergeFailureReason
244 from rhodecode.lib.vcs.backends.base import MergeFailureReason
243 pull_request = pr_util.create_pull_request(
245 pull_request = pr_util.create_pull_request(
244 approved=True, mergeable=True)
246 approved=True, mergeable=True)
245 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
247 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
246 pull_request.target_ref = unicode_reference
248 pull_request.target_ref = unicode_reference
247 Session().add(pull_request)
249 Session().add(pull_request)
248 Session().commit()
250 Session().commit()
249
251
250 pull_request_id = pull_request.pull_request_id
252 pull_request_id = pull_request.pull_request_id
251 pull_request_url = route_path(
253 pull_request_url = route_path(
252 'pullrequest_show',
254 'pullrequest_show',
253 repo_name=pull_request.target_repo.repo_name,
255 repo_name=pull_request.target_repo.repo_name,
254 pull_request_id=pull_request_id)
256 pull_request_id=pull_request_id)
255
257
256 response = self.app.get(pull_request_url)
258 response = self.app.get(pull_request_url)
257 target_ref_id = 'invalid-branch'
259 target_ref_id = 'invalid-branch'
258 merge_resp = MergeResponse(
260 merge_resp = MergeResponse(
259 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
261 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
260 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
262 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
261 response.assert_response().element_contains(
263 response.assert_response().element_contains(
262 'span[data-role="merge-message"]', merge_resp.merge_status_message)
264 'span[data-role="merge-message"]', merge_resp.merge_status_message)
263
265
264 def test_comment_and_close_pull_request_custom_message_approved(
266 def test_comment_and_close_pull_request_custom_message_approved(
265 self, pr_util, csrf_token, xhr_header):
267 self, pr_util, csrf_token, xhr_header):
266
268
267 pull_request = pr_util.create_pull_request(approved=True)
269 pull_request = pr_util.create_pull_request(approved=True)
268 pull_request_id = pull_request.pull_request_id
270 pull_request_id = pull_request.pull_request_id
269 author = pull_request.user_id
271 author = pull_request.user_id
270 repo = pull_request.target_repo.repo_id
272 repo = pull_request.target_repo.repo_id
271
273
272 self.app.post(
274 self.app.post(
273 route_path('pullrequest_comment_create',
275 route_path('pullrequest_comment_create',
274 repo_name=pull_request.target_repo.scm_instance().name,
276 repo_name=pull_request.target_repo.scm_instance().name,
275 pull_request_id=pull_request_id),
277 pull_request_id=pull_request_id),
276 params={
278 params={
277 'close_pull_request': '1',
279 'close_pull_request': '1',
278 'text': 'Closing a PR',
280 'text': 'Closing a PR',
279 'csrf_token': csrf_token},
281 'csrf_token': csrf_token},
280 extra_environ=xhr_header,)
282 extra_environ=xhr_header,)
281
283
282 journal = UserLog.query()\
284 journal = UserLog.query()\
283 .filter(UserLog.user_id == author)\
285 .filter(UserLog.user_id == author)\
284 .filter(UserLog.repository_id == repo) \
286 .filter(UserLog.repository_id == repo) \
285 .order_by('user_log_id') \
287 .order_by('user_log_id') \
286 .all()
288 .all()
287 assert journal[-1].action == 'repo.pull_request.close'
289 assert journal[-1].action == 'repo.pull_request.close'
288
290
289 pull_request = PullRequest.get(pull_request_id)
291 pull_request = PullRequest.get(pull_request_id)
290 assert pull_request.is_closed()
292 assert pull_request.is_closed()
291
293
292 status = ChangesetStatusModel().get_status(
294 status = ChangesetStatusModel().get_status(
293 pull_request.source_repo, pull_request=pull_request)
295 pull_request.source_repo, pull_request=pull_request)
294 assert status == ChangesetStatus.STATUS_APPROVED
296 assert status == ChangesetStatus.STATUS_APPROVED
295 comments = ChangesetComment().query() \
297 comments = ChangesetComment().query() \
296 .filter(ChangesetComment.pull_request == pull_request) \
298 .filter(ChangesetComment.pull_request == pull_request) \
297 .order_by(ChangesetComment.comment_id.asc())\
299 .order_by(ChangesetComment.comment_id.asc())\
298 .all()
300 .all()
299 assert comments[-1].text == 'Closing a PR'
301 assert comments[-1].text == 'Closing a PR'
300
302
301 def test_comment_force_close_pull_request_rejected(
303 def test_comment_force_close_pull_request_rejected(
302 self, pr_util, csrf_token, xhr_header):
304 self, pr_util, csrf_token, xhr_header):
303 pull_request = pr_util.create_pull_request()
305 pull_request = pr_util.create_pull_request()
304 pull_request_id = pull_request.pull_request_id
306 pull_request_id = pull_request.pull_request_id
305 PullRequestModel().update_reviewers(
307 PullRequestModel().update_reviewers(
306 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
308 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
307 pull_request.author)
309 pull_request.author)
308 author = pull_request.user_id
310 author = pull_request.user_id
309 repo = pull_request.target_repo.repo_id
311 repo = pull_request.target_repo.repo_id
310
312
311 self.app.post(
313 self.app.post(
312 route_path('pullrequest_comment_create',
314 route_path('pullrequest_comment_create',
313 repo_name=pull_request.target_repo.scm_instance().name,
315 repo_name=pull_request.target_repo.scm_instance().name,
314 pull_request_id=pull_request_id),
316 pull_request_id=pull_request_id),
315 params={
317 params={
316 'close_pull_request': '1',
318 'close_pull_request': '1',
317 'csrf_token': csrf_token},
319 'csrf_token': csrf_token},
318 extra_environ=xhr_header)
320 extra_environ=xhr_header)
319
321
320 pull_request = PullRequest.get(pull_request_id)
322 pull_request = PullRequest.get(pull_request_id)
321
323
322 journal = UserLog.query()\
324 journal = UserLog.query()\
323 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
325 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
324 .order_by('user_log_id') \
326 .order_by('user_log_id') \
325 .all()
327 .all()
326 assert journal[-1].action == 'repo.pull_request.close'
328 assert journal[-1].action == 'repo.pull_request.close'
327
329
328 # check only the latest status, not the review status
330 # check only the latest status, not the review status
329 status = ChangesetStatusModel().get_status(
331 status = ChangesetStatusModel().get_status(
330 pull_request.source_repo, pull_request=pull_request)
332 pull_request.source_repo, pull_request=pull_request)
331 assert status == ChangesetStatus.STATUS_REJECTED
333 assert status == ChangesetStatus.STATUS_REJECTED
332
334
333 def test_comment_and_close_pull_request(
335 def test_comment_and_close_pull_request(
334 self, pr_util, csrf_token, xhr_header):
336 self, pr_util, csrf_token, xhr_header):
335 pull_request = pr_util.create_pull_request()
337 pull_request = pr_util.create_pull_request()
336 pull_request_id = pull_request.pull_request_id
338 pull_request_id = pull_request.pull_request_id
337
339
338 response = self.app.post(
340 response = self.app.post(
339 route_path('pullrequest_comment_create',
341 route_path('pullrequest_comment_create',
340 repo_name=pull_request.target_repo.scm_instance().name,
342 repo_name=pull_request.target_repo.scm_instance().name,
341 pull_request_id=pull_request.pull_request_id),
343 pull_request_id=pull_request.pull_request_id),
342 params={
344 params={
343 'close_pull_request': 'true',
345 'close_pull_request': 'true',
344 'csrf_token': csrf_token},
346 'csrf_token': csrf_token},
345 extra_environ=xhr_header)
347 extra_environ=xhr_header)
346
348
347 assert response.json
349 assert response.json
348
350
349 pull_request = PullRequest.get(pull_request_id)
351 pull_request = PullRequest.get(pull_request_id)
350 assert pull_request.is_closed()
352 assert pull_request.is_closed()
351
353
352 # check only the latest status, not the review status
354 # check only the latest status, not the review status
353 status = ChangesetStatusModel().get_status(
355 status = ChangesetStatusModel().get_status(
354 pull_request.source_repo, pull_request=pull_request)
356 pull_request.source_repo, pull_request=pull_request)
355 assert status == ChangesetStatus.STATUS_REJECTED
357 assert status == ChangesetStatus.STATUS_REJECTED
356
358
357 def test_create_pull_request(self, backend, csrf_token):
359 def test_create_pull_request(self, backend, csrf_token):
358 commits = [
360 commits = [
359 {'message': 'ancestor'},
361 {'message': 'ancestor'},
360 {'message': 'change'},
362 {'message': 'change'},
361 {'message': 'change2'},
363 {'message': 'change2'},
362 ]
364 ]
363 commit_ids = backend.create_master_repo(commits)
365 commit_ids = backend.create_master_repo(commits)
364 target = backend.create_repo(heads=['ancestor'])
366 target = backend.create_repo(heads=['ancestor'])
365 source = backend.create_repo(heads=['change2'])
367 source = backend.create_repo(heads=['change2'])
366
368
367 response = self.app.post(
369 response = self.app.post(
368 route_path('pullrequest_create', repo_name=source.repo_name),
370 route_path('pullrequest_create', repo_name=source.repo_name),
369 [
371 [
370 ('source_repo', source.repo_name),
372 ('source_repo', source.repo_name),
371 ('source_ref', 'branch:default:' + commit_ids['change2']),
373 ('source_ref', 'branch:default:' + commit_ids['change2']),
372 ('target_repo', target.repo_name),
374 ('target_repo', target.repo_name),
373 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
375 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
374 ('common_ancestor', commit_ids['ancestor']),
376 ('common_ancestor', commit_ids['ancestor']),
375 ('pullrequest_title', 'Title'),
377 ('pullrequest_title', 'Title'),
376 ('pullrequest_desc', 'Description'),
378 ('pullrequest_desc', 'Description'),
377 ('description_renderer', 'markdown'),
379 ('description_renderer', 'markdown'),
378 ('__start__', 'review_members:sequence'),
380 ('__start__', 'review_members:sequence'),
379 ('__start__', 'reviewer:mapping'),
381 ('__start__', 'reviewer:mapping'),
380 ('user_id', '1'),
382 ('user_id', '1'),
381 ('__start__', 'reasons:sequence'),
383 ('__start__', 'reasons:sequence'),
382 ('reason', 'Some reason'),
384 ('reason', 'Some reason'),
383 ('__end__', 'reasons:sequence'),
385 ('__end__', 'reasons:sequence'),
384 ('__start__', 'rules:sequence'),
386 ('__start__', 'rules:sequence'),
385 ('__end__', 'rules:sequence'),
387 ('__end__', 'rules:sequence'),
386 ('mandatory', 'False'),
388 ('mandatory', 'False'),
387 ('__end__', 'reviewer:mapping'),
389 ('__end__', 'reviewer:mapping'),
388 ('__end__', 'review_members:sequence'),
390 ('__end__', 'review_members:sequence'),
389 ('__start__', 'revisions:sequence'),
391 ('__start__', 'revisions:sequence'),
390 ('revisions', commit_ids['change']),
392 ('revisions', commit_ids['change']),
391 ('revisions', commit_ids['change2']),
393 ('revisions', commit_ids['change2']),
392 ('__end__', 'revisions:sequence'),
394 ('__end__', 'revisions:sequence'),
393 ('user', ''),
395 ('user', ''),
394 ('csrf_token', csrf_token),
396 ('csrf_token', csrf_token),
395 ],
397 ],
396 status=302)
398 status=302)
397
399
398 location = response.headers['Location']
400 location = response.headers['Location']
399 pull_request_id = location.rsplit('/', 1)[1]
401 pull_request_id = location.rsplit('/', 1)[1]
400 assert pull_request_id != 'new'
402 assert pull_request_id != 'new'
401 pull_request = PullRequest.get(int(pull_request_id))
403 pull_request = PullRequest.get(int(pull_request_id))
402
404
403 # check that we have now both revisions
405 # check that we have now both revisions
404 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
406 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
405 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
407 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
406 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
408 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
407 assert pull_request.target_ref == expected_target_ref
409 assert pull_request.target_ref == expected_target_ref
408
410
409 def test_reviewer_notifications(self, backend, csrf_token):
411 def test_reviewer_notifications(self, backend, csrf_token):
410 # We have to use the app.post for this test so it will create the
412 # We have to use the app.post for this test so it will create the
411 # notifications properly with the new PR
413 # notifications properly with the new PR
412 commits = [
414 commits = [
413 {'message': 'ancestor',
415 {'message': 'ancestor',
414 'added': [FileNode('file_A', content='content_of_ancestor')]},
416 'added': [FileNode('file_A', content='content_of_ancestor')]},
415 {'message': 'change',
417 {'message': 'change',
416 'added': [FileNode('file_a', content='content_of_change')]},
418 'added': [FileNode('file_a', content='content_of_change')]},
417 {'message': 'change-child'},
419 {'message': 'change-child'},
418 {'message': 'ancestor-child', 'parents': ['ancestor'],
420 {'message': 'ancestor-child', 'parents': ['ancestor'],
419 'added': [
421 'added': [
420 FileNode('file_B', content='content_of_ancestor_child')]},
422 FileNode('file_B', content='content_of_ancestor_child')]},
421 {'message': 'ancestor-child-2'},
423 {'message': 'ancestor-child-2'},
422 ]
424 ]
423 commit_ids = backend.create_master_repo(commits)
425 commit_ids = backend.create_master_repo(commits)
424 target = backend.create_repo(heads=['ancestor-child'])
426 target = backend.create_repo(heads=['ancestor-child'])
425 source = backend.create_repo(heads=['change'])
427 source = backend.create_repo(heads=['change'])
426
428
427 response = self.app.post(
429 response = self.app.post(
428 route_path('pullrequest_create', repo_name=source.repo_name),
430 route_path('pullrequest_create', repo_name=source.repo_name),
429 [
431 [
430 ('source_repo', source.repo_name),
432 ('source_repo', source.repo_name),
431 ('source_ref', 'branch:default:' + commit_ids['change']),
433 ('source_ref', 'branch:default:' + commit_ids['change']),
432 ('target_repo', target.repo_name),
434 ('target_repo', target.repo_name),
433 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
435 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
434 ('common_ancestor', commit_ids['ancestor']),
436 ('common_ancestor', commit_ids['ancestor']),
435 ('pullrequest_title', 'Title'),
437 ('pullrequest_title', 'Title'),
436 ('pullrequest_desc', 'Description'),
438 ('pullrequest_desc', 'Description'),
437 ('description_renderer', 'markdown'),
439 ('description_renderer', 'markdown'),
438 ('__start__', 'review_members:sequence'),
440 ('__start__', 'review_members:sequence'),
439 ('__start__', 'reviewer:mapping'),
441 ('__start__', 'reviewer:mapping'),
440 ('user_id', '2'),
442 ('user_id', '2'),
441 ('__start__', 'reasons:sequence'),
443 ('__start__', 'reasons:sequence'),
442 ('reason', 'Some reason'),
444 ('reason', 'Some reason'),
443 ('__end__', 'reasons:sequence'),
445 ('__end__', 'reasons:sequence'),
444 ('__start__', 'rules:sequence'),
446 ('__start__', 'rules:sequence'),
445 ('__end__', 'rules:sequence'),
447 ('__end__', 'rules:sequence'),
446 ('mandatory', 'False'),
448 ('mandatory', 'False'),
447 ('__end__', 'reviewer:mapping'),
449 ('__end__', 'reviewer:mapping'),
448 ('__end__', 'review_members:sequence'),
450 ('__end__', 'review_members:sequence'),
449 ('__start__', 'revisions:sequence'),
451 ('__start__', 'revisions:sequence'),
450 ('revisions', commit_ids['change']),
452 ('revisions', commit_ids['change']),
451 ('__end__', 'revisions:sequence'),
453 ('__end__', 'revisions:sequence'),
452 ('user', ''),
454 ('user', ''),
453 ('csrf_token', csrf_token),
455 ('csrf_token', csrf_token),
454 ],
456 ],
455 status=302)
457 status=302)
456
458
457 location = response.headers['Location']
459 location = response.headers['Location']
458
460
459 pull_request_id = location.rsplit('/', 1)[1]
461 pull_request_id = location.rsplit('/', 1)[1]
460 assert pull_request_id != 'new'
462 assert pull_request_id != 'new'
461 pull_request = PullRequest.get(int(pull_request_id))
463 pull_request = PullRequest.get(int(pull_request_id))
462
464
463 # Check that a notification was made
465 # Check that a notification was made
464 notifications = Notification.query()\
466 notifications = Notification.query()\
465 .filter(Notification.created_by == pull_request.author.user_id,
467 .filter(Notification.created_by == pull_request.author.user_id,
466 Notification.type_ == Notification.TYPE_PULL_REQUEST,
468 Notification.type_ == Notification.TYPE_PULL_REQUEST,
467 Notification.subject.contains(
469 Notification.subject.contains(
468 "wants you to review pull request #%s" % pull_request_id))
470 "wants you to review pull request #%s" % pull_request_id))
469 assert len(notifications.all()) == 1
471 assert len(notifications.all()) == 1
470
472
471 # Change reviewers and check that a notification was made
473 # Change reviewers and check that a notification was made
472 PullRequestModel().update_reviewers(
474 PullRequestModel().update_reviewers(
473 pull_request.pull_request_id, [(1, [], False, [])],
475 pull_request.pull_request_id, [(1, [], False, [])],
474 pull_request.author)
476 pull_request.author)
475 assert len(notifications.all()) == 2
477 assert len(notifications.all()) == 2
476
478
477 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
479 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
478 csrf_token):
480 csrf_token):
479 commits = [
481 commits = [
480 {'message': 'ancestor',
482 {'message': 'ancestor',
481 'added': [FileNode('file_A', content='content_of_ancestor')]},
483 'added': [FileNode('file_A', content='content_of_ancestor')]},
482 {'message': 'change',
484 {'message': 'change',
483 'added': [FileNode('file_a', content='content_of_change')]},
485 'added': [FileNode('file_a', content='content_of_change')]},
484 {'message': 'change-child'},
486 {'message': 'change-child'},
485 {'message': 'ancestor-child', 'parents': ['ancestor'],
487 {'message': 'ancestor-child', 'parents': ['ancestor'],
486 'added': [
488 'added': [
487 FileNode('file_B', content='content_of_ancestor_child')]},
489 FileNode('file_B', content='content_of_ancestor_child')]},
488 {'message': 'ancestor-child-2'},
490 {'message': 'ancestor-child-2'},
489 ]
491 ]
490 commit_ids = backend.create_master_repo(commits)
492 commit_ids = backend.create_master_repo(commits)
491 target = backend.create_repo(heads=['ancestor-child'])
493 target = backend.create_repo(heads=['ancestor-child'])
492 source = backend.create_repo(heads=['change'])
494 source = backend.create_repo(heads=['change'])
493
495
494 response = self.app.post(
496 response = self.app.post(
495 route_path('pullrequest_create', repo_name=source.repo_name),
497 route_path('pullrequest_create', repo_name=source.repo_name),
496 [
498 [
497 ('source_repo', source.repo_name),
499 ('source_repo', source.repo_name),
498 ('source_ref', 'branch:default:' + commit_ids['change']),
500 ('source_ref', 'branch:default:' + commit_ids['change']),
499 ('target_repo', target.repo_name),
501 ('target_repo', target.repo_name),
500 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
502 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
501 ('common_ancestor', commit_ids['ancestor']),
503 ('common_ancestor', commit_ids['ancestor']),
502 ('pullrequest_title', 'Title'),
504 ('pullrequest_title', 'Title'),
503 ('pullrequest_desc', 'Description'),
505 ('pullrequest_desc', 'Description'),
504 ('description_renderer', 'markdown'),
506 ('description_renderer', 'markdown'),
505 ('__start__', 'review_members:sequence'),
507 ('__start__', 'review_members:sequence'),
506 ('__start__', 'reviewer:mapping'),
508 ('__start__', 'reviewer:mapping'),
507 ('user_id', '1'),
509 ('user_id', '1'),
508 ('__start__', 'reasons:sequence'),
510 ('__start__', 'reasons:sequence'),
509 ('reason', 'Some reason'),
511 ('reason', 'Some reason'),
510 ('__end__', 'reasons:sequence'),
512 ('__end__', 'reasons:sequence'),
511 ('__start__', 'rules:sequence'),
513 ('__start__', 'rules:sequence'),
512 ('__end__', 'rules:sequence'),
514 ('__end__', 'rules:sequence'),
513 ('mandatory', 'False'),
515 ('mandatory', 'False'),
514 ('__end__', 'reviewer:mapping'),
516 ('__end__', 'reviewer:mapping'),
515 ('__end__', 'review_members:sequence'),
517 ('__end__', 'review_members:sequence'),
516 ('__start__', 'revisions:sequence'),
518 ('__start__', 'revisions:sequence'),
517 ('revisions', commit_ids['change']),
519 ('revisions', commit_ids['change']),
518 ('__end__', 'revisions:sequence'),
520 ('__end__', 'revisions:sequence'),
519 ('user', ''),
521 ('user', ''),
520 ('csrf_token', csrf_token),
522 ('csrf_token', csrf_token),
521 ],
523 ],
522 status=302)
524 status=302)
523
525
524 location = response.headers['Location']
526 location = response.headers['Location']
525
527
526 pull_request_id = location.rsplit('/', 1)[1]
528 pull_request_id = location.rsplit('/', 1)[1]
527 assert pull_request_id != 'new'
529 assert pull_request_id != 'new'
528 pull_request = PullRequest.get(int(pull_request_id))
530 pull_request = PullRequest.get(int(pull_request_id))
529
531
530 # target_ref has to point to the ancestor's commit_id in order to
532 # target_ref has to point to the ancestor's commit_id in order to
531 # show the correct diff
533 # show the correct diff
532 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
534 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
533 assert pull_request.target_ref == expected_target_ref
535 assert pull_request.target_ref == expected_target_ref
534
536
535 # Check generated diff contents
537 # Check generated diff contents
536 response = response.follow()
538 response = response.follow()
537 assert 'content_of_ancestor' not in response.body
539 assert 'content_of_ancestor' not in response.body
538 assert 'content_of_ancestor-child' not in response.body
540 assert 'content_of_ancestor-child' not in response.body
539 assert 'content_of_change' in response.body
541 assert 'content_of_change' in response.body
540
542
541 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
543 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
542 # Clear any previous calls to rcextensions
544 # Clear any previous calls to rcextensions
543 rhodecode.EXTENSIONS.calls.clear()
545 rhodecode.EXTENSIONS.calls.clear()
544
546
545 pull_request = pr_util.create_pull_request(
547 pull_request = pr_util.create_pull_request(
546 approved=True, mergeable=True)
548 approved=True, mergeable=True)
547 pull_request_id = pull_request.pull_request_id
549 pull_request_id = pull_request.pull_request_id
548 repo_name = pull_request.target_repo.scm_instance().name,
550 repo_name = pull_request.target_repo.scm_instance().name,
549
551
550 response = self.app.post(
552 response = self.app.post(
551 route_path('pullrequest_merge',
553 route_path('pullrequest_merge',
552 repo_name=str(repo_name[0]),
554 repo_name=str(repo_name[0]),
553 pull_request_id=pull_request_id),
555 pull_request_id=pull_request_id),
554 params={'csrf_token': csrf_token}).follow()
556 params={'csrf_token': csrf_token}).follow()
555
557
556 pull_request = PullRequest.get(pull_request_id)
558 pull_request = PullRequest.get(pull_request_id)
557
559
558 assert response.status_int == 200
560 assert response.status_int == 200
559 assert pull_request.is_closed()
561 assert pull_request.is_closed()
560 assert_pull_request_status(
562 assert_pull_request_status(
561 pull_request, ChangesetStatus.STATUS_APPROVED)
563 pull_request, ChangesetStatus.STATUS_APPROVED)
562
564
563 # Check the relevant log entries were added
565 # Check the relevant log entries were added
564 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
566 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
565 actions = [log.action for log in user_logs]
567 actions = [log.action for log in user_logs]
566 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
568 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
567 expected_actions = [
569 expected_actions = [
568 u'repo.pull_request.close',
570 u'repo.pull_request.close',
569 u'repo.pull_request.merge',
571 u'repo.pull_request.merge',
570 u'repo.pull_request.comment.create'
572 u'repo.pull_request.comment.create'
571 ]
573 ]
572 assert actions == expected_actions
574 assert actions == expected_actions
573
575
574 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
576 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
575 actions = [log for log in user_logs]
577 actions = [log for log in user_logs]
576 assert actions[-1].action == 'user.push'
578 assert actions[-1].action == 'user.push'
577 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
579 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
578
580
579 # Check post_push rcextension was really executed
581 # Check post_push rcextension was really executed
580 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
582 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
581 assert len(push_calls) == 1
583 assert len(push_calls) == 1
582 unused_last_call_args, last_call_kwargs = push_calls[0]
584 unused_last_call_args, last_call_kwargs = push_calls[0]
583 assert last_call_kwargs['action'] == 'push'
585 assert last_call_kwargs['action'] == 'push'
584 assert last_call_kwargs['commit_ids'] == pr_commit_ids
586 assert last_call_kwargs['commit_ids'] == pr_commit_ids
585
587
586 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
588 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
587 pull_request = pr_util.create_pull_request(mergeable=False)
589 pull_request = pr_util.create_pull_request(mergeable=False)
588 pull_request_id = pull_request.pull_request_id
590 pull_request_id = pull_request.pull_request_id
589 pull_request = PullRequest.get(pull_request_id)
591 pull_request = PullRequest.get(pull_request_id)
590
592
591 response = self.app.post(
593 response = self.app.post(
592 route_path('pullrequest_merge',
594 route_path('pullrequest_merge',
593 repo_name=pull_request.target_repo.scm_instance().name,
595 repo_name=pull_request.target_repo.scm_instance().name,
594 pull_request_id=pull_request.pull_request_id),
596 pull_request_id=pull_request.pull_request_id),
595 params={'csrf_token': csrf_token}).follow()
597 params={'csrf_token': csrf_token}).follow()
596
598
597 assert response.status_int == 200
599 assert response.status_int == 200
598 response.mustcontain(
600 response.mustcontain(
599 'Merge is not currently possible because of below failed checks.')
601 'Merge is not currently possible because of below failed checks.')
600 response.mustcontain('Server-side pull request merging is disabled.')
602 response.mustcontain('Server-side pull request merging is disabled.')
601
603
602 @pytest.mark.skip_backends('svn')
604 @pytest.mark.skip_backends('svn')
603 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
605 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
604 pull_request = pr_util.create_pull_request(mergeable=True)
606 pull_request = pr_util.create_pull_request(mergeable=True)
605 pull_request_id = pull_request.pull_request_id
607 pull_request_id = pull_request.pull_request_id
606 repo_name = pull_request.target_repo.scm_instance().name
608 repo_name = pull_request.target_repo.scm_instance().name
607
609
608 response = self.app.post(
610 response = self.app.post(
609 route_path('pullrequest_merge',
611 route_path('pullrequest_merge',
610 repo_name=repo_name, pull_request_id=pull_request_id),
612 repo_name=repo_name, pull_request_id=pull_request_id),
611 params={'csrf_token': csrf_token}).follow()
613 params={'csrf_token': csrf_token}).follow()
612
614
613 assert response.status_int == 200
615 assert response.status_int == 200
614
616
615 response.mustcontain(
617 response.mustcontain(
616 'Merge is not currently possible because of below failed checks.')
618 'Merge is not currently possible because of below failed checks.')
617 response.mustcontain('Pull request reviewer approval is pending.')
619 response.mustcontain('Pull request reviewer approval is pending.')
618
620
619 def test_merge_pull_request_renders_failure_reason(
621 def test_merge_pull_request_renders_failure_reason(
620 self, user_regular, csrf_token, pr_util):
622 self, user_regular, csrf_token, pr_util):
621 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
623 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
622 pull_request_id = pull_request.pull_request_id
624 pull_request_id = pull_request.pull_request_id
623 repo_name = pull_request.target_repo.scm_instance().name
625 repo_name = pull_request.target_repo.scm_instance().name
624
626
625 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
627 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
626 MergeFailureReason.PUSH_FAILED,
628 MergeFailureReason.PUSH_FAILED,
627 metadata={'target': 'shadow repo',
629 metadata={'target': 'shadow repo',
628 'merge_commit': 'xxx'})
630 'merge_commit': 'xxx'})
629 model_patcher = mock.patch.multiple(
631 model_patcher = mock.patch.multiple(
630 PullRequestModel,
632 PullRequestModel,
631 merge_repo=mock.Mock(return_value=merge_resp),
633 merge_repo=mock.Mock(return_value=merge_resp),
632 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
634 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
633
635
634 with model_patcher:
636 with model_patcher:
635 response = self.app.post(
637 response = self.app.post(
636 route_path('pullrequest_merge',
638 route_path('pullrequest_merge',
637 repo_name=repo_name,
639 repo_name=repo_name,
638 pull_request_id=pull_request_id),
640 pull_request_id=pull_request_id),
639 params={'csrf_token': csrf_token}, status=302)
641 params={'csrf_token': csrf_token}, status=302)
640
642
641 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
643 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
642 metadata={'target': 'shadow repo',
644 metadata={'target': 'shadow repo',
643 'merge_commit': 'xxx'})
645 'merge_commit': 'xxx'})
644 assert_session_flash(response, merge_resp.merge_status_message)
646 assert_session_flash(response, merge_resp.merge_status_message)
645
647
646 def test_update_source_revision(self, backend, csrf_token):
648 def test_update_source_revision(self, backend, csrf_token):
647 commits = [
649 commits = [
648 {'message': 'ancestor'},
650 {'message': 'ancestor'},
649 {'message': 'change'},
651 {'message': 'change'},
650 {'message': 'change-2'},
652 {'message': 'change-2'},
651 ]
653 ]
652 commit_ids = backend.create_master_repo(commits)
654 commit_ids = backend.create_master_repo(commits)
653 target = backend.create_repo(heads=['ancestor'])
655 target = backend.create_repo(heads=['ancestor'])
654 source = backend.create_repo(heads=['change'])
656 source = backend.create_repo(heads=['change'])
655
657
656 # create pr from a in source to A in target
658 # create pr from a in source to A in target
657 pull_request = PullRequest()
659 pull_request = PullRequest()
658
660
659 pull_request.source_repo = source
661 pull_request.source_repo = source
660 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
662 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
661 branch=backend.default_branch_name, commit_id=commit_ids['change'])
663 branch=backend.default_branch_name, commit_id=commit_ids['change'])
662
664
663 pull_request.target_repo = target
665 pull_request.target_repo = target
664 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
666 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
665 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
667 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
666
668
667 pull_request.revisions = [commit_ids['change']]
669 pull_request.revisions = [commit_ids['change']]
668 pull_request.title = u"Test"
670 pull_request.title = u"Test"
669 pull_request.description = u"Description"
671 pull_request.description = u"Description"
670 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
672 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
671 pull_request.pull_request_state = PullRequest.STATE_CREATED
673 pull_request.pull_request_state = PullRequest.STATE_CREATED
672 Session().add(pull_request)
674 Session().add(pull_request)
673 Session().commit()
675 Session().commit()
674 pull_request_id = pull_request.pull_request_id
676 pull_request_id = pull_request.pull_request_id
675
677
676 # source has ancestor - change - change-2
678 # source has ancestor - change - change-2
677 backend.pull_heads(source, heads=['change-2'])
679 backend.pull_heads(source, heads=['change-2'])
678
680
679 # update PR
681 # update PR
680 self.app.post(
682 self.app.post(
681 route_path('pullrequest_update',
683 route_path('pullrequest_update',
682 repo_name=target.repo_name, pull_request_id=pull_request_id),
684 repo_name=target.repo_name, pull_request_id=pull_request_id),
683 params={'update_commits': 'true', 'csrf_token': csrf_token})
685 params={'update_commits': 'true', 'csrf_token': csrf_token})
684
686
685 response = self.app.get(
687 response = self.app.get(
686 route_path('pullrequest_show',
688 route_path('pullrequest_show',
687 repo_name=target.repo_name,
689 repo_name=target.repo_name,
688 pull_request_id=pull_request.pull_request_id))
690 pull_request_id=pull_request.pull_request_id))
689
691
690 assert response.status_int == 200
692 assert response.status_int == 200
691 assert 'Pull request updated to' in response.body
693 assert 'Pull request updated to' in response.body
692 assert 'with 1 added, 0 removed commits.' in response.body
694 assert 'with 1 added, 0 removed commits.' in response.body
693
695
694 # check that we have now both revisions
696 # check that we have now both revisions
695 pull_request = PullRequest.get(pull_request_id)
697 pull_request = PullRequest.get(pull_request_id)
696 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
698 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
697
699
698 def test_update_target_revision(self, backend, csrf_token):
700 def test_update_target_revision(self, backend, csrf_token):
699 commits = [
701 commits = [
700 {'message': 'ancestor'},
702 {'message': 'ancestor'},
701 {'message': 'change'},
703 {'message': 'change'},
702 {'message': 'ancestor-new', 'parents': ['ancestor']},
704 {'message': 'ancestor-new', 'parents': ['ancestor']},
703 {'message': 'change-rebased'},
705 {'message': 'change-rebased'},
704 ]
706 ]
705 commit_ids = backend.create_master_repo(commits)
707 commit_ids = backend.create_master_repo(commits)
706 target = backend.create_repo(heads=['ancestor'])
708 target = backend.create_repo(heads=['ancestor'])
707 source = backend.create_repo(heads=['change'])
709 source = backend.create_repo(heads=['change'])
708
710
709 # create pr from a in source to A in target
711 # create pr from a in source to A in target
710 pull_request = PullRequest()
712 pull_request = PullRequest()
711
713
712 pull_request.source_repo = source
714 pull_request.source_repo = source
713 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
715 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
714 branch=backend.default_branch_name, commit_id=commit_ids['change'])
716 branch=backend.default_branch_name, commit_id=commit_ids['change'])
715
717
716 pull_request.target_repo = target
718 pull_request.target_repo = target
717 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
719 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
718 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
720 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
719
721
720 pull_request.revisions = [commit_ids['change']]
722 pull_request.revisions = [commit_ids['change']]
721 pull_request.title = u"Test"
723 pull_request.title = u"Test"
722 pull_request.description = u"Description"
724 pull_request.description = u"Description"
723 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
725 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
724 pull_request.pull_request_state = PullRequest.STATE_CREATED
726 pull_request.pull_request_state = PullRequest.STATE_CREATED
725
727
726 Session().add(pull_request)
728 Session().add(pull_request)
727 Session().commit()
729 Session().commit()
728 pull_request_id = pull_request.pull_request_id
730 pull_request_id = pull_request.pull_request_id
729
731
730 # target has ancestor - ancestor-new
732 # target has ancestor - ancestor-new
731 # source has ancestor - ancestor-new - change-rebased
733 # source has ancestor - ancestor-new - change-rebased
732 backend.pull_heads(target, heads=['ancestor-new'])
734 backend.pull_heads(target, heads=['ancestor-new'])
733 backend.pull_heads(source, heads=['change-rebased'])
735 backend.pull_heads(source, heads=['change-rebased'])
734
736
735 # update PR
737 # update PR
736 self.app.post(
738 self.app.post(
737 route_path('pullrequest_update',
739 route_path('pullrequest_update',
738 repo_name=target.repo_name,
740 repo_name=target.repo_name,
739 pull_request_id=pull_request_id),
741 pull_request_id=pull_request_id),
740 params={'update_commits': 'true', 'csrf_token': csrf_token},
742 params={'update_commits': 'true', 'csrf_token': csrf_token},
741 status=200)
743 status=200)
742
744
743 # check that we have now both revisions
745 # check that we have now both revisions
744 pull_request = PullRequest.get(pull_request_id)
746 pull_request = PullRequest.get(pull_request_id)
745 assert pull_request.revisions == [commit_ids['change-rebased']]
747 assert pull_request.revisions == [commit_ids['change-rebased']]
746 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
748 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
747 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
749 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
748
750
749 response = self.app.get(
751 response = self.app.get(
750 route_path('pullrequest_show',
752 route_path('pullrequest_show',
751 repo_name=target.repo_name,
753 repo_name=target.repo_name,
752 pull_request_id=pull_request.pull_request_id))
754 pull_request_id=pull_request.pull_request_id))
753 assert response.status_int == 200
755 assert response.status_int == 200
754 assert 'Pull request updated to' in response.body
756 assert 'Pull request updated to' in response.body
755 assert 'with 1 added, 1 removed commits.' in response.body
757 assert 'with 1 added, 1 removed commits.' in response.body
756
758
757 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
759 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
758 backend = backend_git
760 backend = backend_git
759 commits = [
761 commits = [
760 {'message': 'master-commit-1'},
762 {'message': 'master-commit-1'},
761 {'message': 'master-commit-2-change-1'},
763 {'message': 'master-commit-2-change-1'},
762 {'message': 'master-commit-3-change-2'},
764 {'message': 'master-commit-3-change-2'},
763
765
764 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
766 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
765 {'message': 'feat-commit-2'},
767 {'message': 'feat-commit-2'},
766 ]
768 ]
767 commit_ids = backend.create_master_repo(commits)
769 commit_ids = backend.create_master_repo(commits)
768 target = backend.create_repo(heads=['master-commit-3-change-2'])
770 target = backend.create_repo(heads=['master-commit-3-change-2'])
769 source = backend.create_repo(heads=['feat-commit-2'])
771 source = backend.create_repo(heads=['feat-commit-2'])
770
772
771 # create pr from a in source to A in target
773 # create pr from a in source to A in target
772 pull_request = PullRequest()
774 pull_request = PullRequest()
773 pull_request.source_repo = source
775 pull_request.source_repo = source
774
776
775 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
777 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
776 branch=backend.default_branch_name,
778 branch=backend.default_branch_name,
777 commit_id=commit_ids['master-commit-3-change-2'])
779 commit_id=commit_ids['master-commit-3-change-2'])
778
780
779 pull_request.target_repo = target
781 pull_request.target_repo = target
780 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
782 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
781 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
783 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
782
784
783 pull_request.revisions = [
785 pull_request.revisions = [
784 commit_ids['feat-commit-1'],
786 commit_ids['feat-commit-1'],
785 commit_ids['feat-commit-2']
787 commit_ids['feat-commit-2']
786 ]
788 ]
787 pull_request.title = u"Test"
789 pull_request.title = u"Test"
788 pull_request.description = u"Description"
790 pull_request.description = u"Description"
789 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
791 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
790 pull_request.pull_request_state = PullRequest.STATE_CREATED
792 pull_request.pull_request_state = PullRequest.STATE_CREATED
791 Session().add(pull_request)
793 Session().add(pull_request)
792 Session().commit()
794 Session().commit()
793 pull_request_id = pull_request.pull_request_id
795 pull_request_id = pull_request.pull_request_id
794
796
795 # PR is created, now we simulate a force-push into target,
797 # PR is created, now we simulate a force-push into target,
796 # that drops a 2 last commits
798 # that drops a 2 last commits
797 vcsrepo = target.scm_instance()
799 vcsrepo = target.scm_instance()
798 vcsrepo.config.clear_section('hooks')
800 vcsrepo.config.clear_section('hooks')
799 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
801 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
800
802
801 # update PR
803 # update PR
802 self.app.post(
804 self.app.post(
803 route_path('pullrequest_update',
805 route_path('pullrequest_update',
804 repo_name=target.repo_name,
806 repo_name=target.repo_name,
805 pull_request_id=pull_request_id),
807 pull_request_id=pull_request_id),
806 params={'update_commits': 'true', 'csrf_token': csrf_token},
808 params={'update_commits': 'true', 'csrf_token': csrf_token},
807 status=200)
809 status=200)
808
810
809 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
811 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
810 assert response.status_int == 200
812 assert response.status_int == 200
811 response.mustcontain('Pull request updated to')
813 response.mustcontain('Pull request updated to')
812 response.mustcontain('with 0 added, 0 removed commits.')
814 response.mustcontain('with 0 added, 0 removed commits.')
813
815
814 def test_update_of_ancestor_reference(self, backend, csrf_token):
816 def test_update_of_ancestor_reference(self, backend, csrf_token):
815 commits = [
817 commits = [
816 {'message': 'ancestor'},
818 {'message': 'ancestor'},
817 {'message': 'change'},
819 {'message': 'change'},
818 {'message': 'change-2'},
820 {'message': 'change-2'},
819 {'message': 'ancestor-new', 'parents': ['ancestor']},
821 {'message': 'ancestor-new', 'parents': ['ancestor']},
820 {'message': 'change-rebased'},
822 {'message': 'change-rebased'},
821 ]
823 ]
822 commit_ids = backend.create_master_repo(commits)
824 commit_ids = backend.create_master_repo(commits)
823 target = backend.create_repo(heads=['ancestor'])
825 target = backend.create_repo(heads=['ancestor'])
824 source = backend.create_repo(heads=['change'])
826 source = backend.create_repo(heads=['change'])
825
827
826 # create pr from a in source to A in target
828 # create pr from a in source to A in target
827 pull_request = PullRequest()
829 pull_request = PullRequest()
828 pull_request.source_repo = source
830 pull_request.source_repo = source
829
831
830 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
832 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
831 branch=backend.default_branch_name, commit_id=commit_ids['change'])
833 branch=backend.default_branch_name, commit_id=commit_ids['change'])
832 pull_request.target_repo = target
834 pull_request.target_repo = target
833 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
835 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
834 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
836 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
835 pull_request.revisions = [commit_ids['change']]
837 pull_request.revisions = [commit_ids['change']]
836 pull_request.title = u"Test"
838 pull_request.title = u"Test"
837 pull_request.description = u"Description"
839 pull_request.description = u"Description"
838 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
840 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
839 pull_request.pull_request_state = PullRequest.STATE_CREATED
841 pull_request.pull_request_state = PullRequest.STATE_CREATED
840 Session().add(pull_request)
842 Session().add(pull_request)
841 Session().commit()
843 Session().commit()
842 pull_request_id = pull_request.pull_request_id
844 pull_request_id = pull_request.pull_request_id
843
845
844 # target has ancestor - ancestor-new
846 # target has ancestor - ancestor-new
845 # source has ancestor - ancestor-new - change-rebased
847 # source has ancestor - ancestor-new - change-rebased
846 backend.pull_heads(target, heads=['ancestor-new'])
848 backend.pull_heads(target, heads=['ancestor-new'])
847 backend.pull_heads(source, heads=['change-rebased'])
849 backend.pull_heads(source, heads=['change-rebased'])
848
850
849 # update PR
851 # update PR
850 self.app.post(
852 self.app.post(
851 route_path('pullrequest_update',
853 route_path('pullrequest_update',
852 repo_name=target.repo_name, pull_request_id=pull_request_id),
854 repo_name=target.repo_name, pull_request_id=pull_request_id),
853 params={'update_commits': 'true', 'csrf_token': csrf_token},
855 params={'update_commits': 'true', 'csrf_token': csrf_token},
854 status=200)
856 status=200)
855
857
856 # Expect the target reference to be updated correctly
858 # Expect the target reference to be updated correctly
857 pull_request = PullRequest.get(pull_request_id)
859 pull_request = PullRequest.get(pull_request_id)
858 assert pull_request.revisions == [commit_ids['change-rebased']]
860 assert pull_request.revisions == [commit_ids['change-rebased']]
859 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
861 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
860 branch=backend.default_branch_name,
862 branch=backend.default_branch_name,
861 commit_id=commit_ids['ancestor-new'])
863 commit_id=commit_ids['ancestor-new'])
862 assert pull_request.target_ref == expected_target_ref
864 assert pull_request.target_ref == expected_target_ref
863
865
864 def test_remove_pull_request_branch(self, backend_git, csrf_token):
866 def test_remove_pull_request_branch(self, backend_git, csrf_token):
865 branch_name = 'development'
867 branch_name = 'development'
866 commits = [
868 commits = [
867 {'message': 'initial-commit'},
869 {'message': 'initial-commit'},
868 {'message': 'old-feature'},
870 {'message': 'old-feature'},
869 {'message': 'new-feature', 'branch': branch_name},
871 {'message': 'new-feature', 'branch': branch_name},
870 ]
872 ]
871 repo = backend_git.create_repo(commits)
873 repo = backend_git.create_repo(commits)
872 commit_ids = backend_git.commit_ids
874 commit_ids = backend_git.commit_ids
873
875
874 pull_request = PullRequest()
876 pull_request = PullRequest()
875 pull_request.source_repo = repo
877 pull_request.source_repo = repo
876 pull_request.target_repo = repo
878 pull_request.target_repo = repo
877 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
879 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
878 branch=branch_name, commit_id=commit_ids['new-feature'])
880 branch=branch_name, commit_id=commit_ids['new-feature'])
879 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
881 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
880 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
882 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
881 pull_request.revisions = [commit_ids['new-feature']]
883 pull_request.revisions = [commit_ids['new-feature']]
882 pull_request.title = u"Test"
884 pull_request.title = u"Test"
883 pull_request.description = u"Description"
885 pull_request.description = u"Description"
884 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
886 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
885 pull_request.pull_request_state = PullRequest.STATE_CREATED
887 pull_request.pull_request_state = PullRequest.STATE_CREATED
886 Session().add(pull_request)
888 Session().add(pull_request)
887 Session().commit()
889 Session().commit()
888
890
889 vcs = repo.scm_instance()
891 vcs = repo.scm_instance()
890 vcs.remove_ref('refs/heads/{}'.format(branch_name))
892 vcs.remove_ref('refs/heads/{}'.format(branch_name))
891
893
892 response = self.app.get(route_path(
894 response = self.app.get(route_path(
893 'pullrequest_show',
895 'pullrequest_show',
894 repo_name=repo.repo_name,
896 repo_name=repo.repo_name,
895 pull_request_id=pull_request.pull_request_id))
897 pull_request_id=pull_request.pull_request_id))
896
898
897 assert response.status_int == 200
899 assert response.status_int == 200
898
900
899 response.assert_response().element_contains(
901 response.assert_response().element_contains(
900 '#changeset_compare_view_content .alert strong',
902 '#changeset_compare_view_content .alert strong',
901 'Missing commits')
903 'Missing commits')
902 response.assert_response().element_contains(
904 response.assert_response().element_contains(
903 '#changeset_compare_view_content .alert',
905 '#changeset_compare_view_content .alert',
904 'This pull request cannot be displayed, because one or more'
906 'This pull request cannot be displayed, because one or more'
905 ' commits no longer exist in the source repository.')
907 ' commits no longer exist in the source repository.')
906
908
907 def test_strip_commits_from_pull_request(
909 def test_strip_commits_from_pull_request(
908 self, backend, pr_util, csrf_token):
910 self, backend, pr_util, csrf_token):
909 commits = [
911 commits = [
910 {'message': 'initial-commit'},
912 {'message': 'initial-commit'},
911 {'message': 'old-feature'},
913 {'message': 'old-feature'},
912 {'message': 'new-feature', 'parents': ['initial-commit']},
914 {'message': 'new-feature', 'parents': ['initial-commit']},
913 ]
915 ]
914 pull_request = pr_util.create_pull_request(
916 pull_request = pr_util.create_pull_request(
915 commits, target_head='initial-commit', source_head='new-feature',
917 commits, target_head='initial-commit', source_head='new-feature',
916 revisions=['new-feature'])
918 revisions=['new-feature'])
917
919
918 vcs = pr_util.source_repository.scm_instance()
920 vcs = pr_util.source_repository.scm_instance()
919 if backend.alias == 'git':
921 if backend.alias == 'git':
920 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
922 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
921 else:
923 else:
922 vcs.strip(pr_util.commit_ids['new-feature'])
924 vcs.strip(pr_util.commit_ids['new-feature'])
923
925
924 response = self.app.get(route_path(
926 response = self.app.get(route_path(
925 'pullrequest_show',
927 'pullrequest_show',
926 repo_name=pr_util.target_repository.repo_name,
928 repo_name=pr_util.target_repository.repo_name,
927 pull_request_id=pull_request.pull_request_id))
929 pull_request_id=pull_request.pull_request_id))
928
930
929 assert response.status_int == 200
931 assert response.status_int == 200
930
932
931 response.assert_response().element_contains(
933 response.assert_response().element_contains(
932 '#changeset_compare_view_content .alert strong',
934 '#changeset_compare_view_content .alert strong',
933 'Missing commits')
935 'Missing commits')
934 response.assert_response().element_contains(
936 response.assert_response().element_contains(
935 '#changeset_compare_view_content .alert',
937 '#changeset_compare_view_content .alert',
936 'This pull request cannot be displayed, because one or more'
938 'This pull request cannot be displayed, because one or more'
937 ' commits no longer exist in the source repository.')
939 ' commits no longer exist in the source repository.')
938 response.assert_response().element_contains(
940 response.assert_response().element_contains(
939 '#update_commits',
941 '#update_commits',
940 'Update commits')
942 'Update commits')
941
943
942 def test_strip_commits_and_update(
944 def test_strip_commits_and_update(
943 self, backend, pr_util, csrf_token):
945 self, backend, pr_util, csrf_token):
944 commits = [
946 commits = [
945 {'message': 'initial-commit'},
947 {'message': 'initial-commit'},
946 {'message': 'old-feature'},
948 {'message': 'old-feature'},
947 {'message': 'new-feature', 'parents': ['old-feature']},
949 {'message': 'new-feature', 'parents': ['old-feature']},
948 ]
950 ]
949 pull_request = pr_util.create_pull_request(
951 pull_request = pr_util.create_pull_request(
950 commits, target_head='old-feature', source_head='new-feature',
952 commits, target_head='old-feature', source_head='new-feature',
951 revisions=['new-feature'], mergeable=True)
953 revisions=['new-feature'], mergeable=True)
952
954
953 vcs = pr_util.source_repository.scm_instance()
955 vcs = pr_util.source_repository.scm_instance()
954 if backend.alias == 'git':
956 if backend.alias == 'git':
955 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
957 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
956 else:
958 else:
957 vcs.strip(pr_util.commit_ids['new-feature'])
959 vcs.strip(pr_util.commit_ids['new-feature'])
958
960
959 response = self.app.post(
961 response = self.app.post(
960 route_path('pullrequest_update',
962 route_path('pullrequest_update',
961 repo_name=pull_request.target_repo.repo_name,
963 repo_name=pull_request.target_repo.repo_name,
962 pull_request_id=pull_request.pull_request_id),
964 pull_request_id=pull_request.pull_request_id),
963 params={'update_commits': 'true',
965 params={'update_commits': 'true',
964 'csrf_token': csrf_token})
966 'csrf_token': csrf_token})
965
967
966 assert response.status_int == 200
968 assert response.status_int == 200
967 assert response.body == 'true'
969 assert response.body == 'true'
968
970
969 # Make sure that after update, it won't raise 500 errors
971 # Make sure that after update, it won't raise 500 errors
970 response = self.app.get(route_path(
972 response = self.app.get(route_path(
971 'pullrequest_show',
973 'pullrequest_show',
972 repo_name=pr_util.target_repository.repo_name,
974 repo_name=pr_util.target_repository.repo_name,
973 pull_request_id=pull_request.pull_request_id))
975 pull_request_id=pull_request.pull_request_id))
974
976
975 assert response.status_int == 200
977 assert response.status_int == 200
976 response.assert_response().element_contains(
978 response.assert_response().element_contains(
977 '#changeset_compare_view_content .alert strong',
979 '#changeset_compare_view_content .alert strong',
978 'Missing commits')
980 'Missing commits')
979
981
980 def test_branch_is_a_link(self, pr_util):
982 def test_branch_is_a_link(self, pr_util):
981 pull_request = pr_util.create_pull_request()
983 pull_request = pr_util.create_pull_request()
982 pull_request.source_ref = 'branch:origin:1234567890abcdef'
984 pull_request.source_ref = 'branch:origin:1234567890abcdef'
983 pull_request.target_ref = 'branch:target:abcdef1234567890'
985 pull_request.target_ref = 'branch:target:abcdef1234567890'
984 Session().add(pull_request)
986 Session().add(pull_request)
985 Session().commit()
987 Session().commit()
986
988
987 response = self.app.get(route_path(
989 response = self.app.get(route_path(
988 'pullrequest_show',
990 'pullrequest_show',
989 repo_name=pull_request.target_repo.scm_instance().name,
991 repo_name=pull_request.target_repo.scm_instance().name,
990 pull_request_id=pull_request.pull_request_id))
992 pull_request_id=pull_request.pull_request_id))
991 assert response.status_int == 200
993 assert response.status_int == 200
992
994
993 origin = response.assert_response().get_element('.pr-origininfo .tag')
995 origin = response.assert_response().get_element('.pr-origininfo .tag')
994 origin_children = origin.getchildren()
996 origin_children = origin.getchildren()
995 assert len(origin_children) == 1
997 assert len(origin_children) == 1
996 target = response.assert_response().get_element('.pr-targetinfo .tag')
998 target = response.assert_response().get_element('.pr-targetinfo .tag')
997 target_children = target.getchildren()
999 target_children = target.getchildren()
998 assert len(target_children) == 1
1000 assert len(target_children) == 1
999
1001
1000 expected_origin_link = route_path(
1002 expected_origin_link = route_path(
1001 'repo_changelog',
1003 'repo_commits',
1002 repo_name=pull_request.source_repo.scm_instance().name,
1004 repo_name=pull_request.source_repo.scm_instance().name,
1003 params=dict(branch='origin'))
1005 params=dict(branch='origin'))
1004 expected_target_link = route_path(
1006 expected_target_link = route_path(
1005 'repo_changelog',
1007 'repo_commits',
1006 repo_name=pull_request.target_repo.scm_instance().name,
1008 repo_name=pull_request.target_repo.scm_instance().name,
1007 params=dict(branch='target'))
1009 params=dict(branch='target'))
1008 assert origin_children[0].attrib['href'] == expected_origin_link
1010 assert origin_children[0].attrib['href'] == expected_origin_link
1009 assert origin_children[0].text == 'branch: origin'
1011 assert origin_children[0].text == 'branch: origin'
1010 assert target_children[0].attrib['href'] == expected_target_link
1012 assert target_children[0].attrib['href'] == expected_target_link
1011 assert target_children[0].text == 'branch: target'
1013 assert target_children[0].text == 'branch: target'
1012
1014
1013 def test_bookmark_is_not_a_link(self, pr_util):
1015 def test_bookmark_is_not_a_link(self, pr_util):
1014 pull_request = pr_util.create_pull_request()
1016 pull_request = pr_util.create_pull_request()
1015 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1017 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1016 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1018 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1017 Session().add(pull_request)
1019 Session().add(pull_request)
1018 Session().commit()
1020 Session().commit()
1019
1021
1020 response = self.app.get(route_path(
1022 response = self.app.get(route_path(
1021 'pullrequest_show',
1023 'pullrequest_show',
1022 repo_name=pull_request.target_repo.scm_instance().name,
1024 repo_name=pull_request.target_repo.scm_instance().name,
1023 pull_request_id=pull_request.pull_request_id))
1025 pull_request_id=pull_request.pull_request_id))
1024 assert response.status_int == 200
1026 assert response.status_int == 200
1025
1027
1026 origin = response.assert_response().get_element('.pr-origininfo .tag')
1028 origin = response.assert_response().get_element('.pr-origininfo .tag')
1027 assert origin.text.strip() == 'bookmark: origin'
1029 assert origin.text.strip() == 'bookmark: origin'
1028 assert origin.getchildren() == []
1030 assert origin.getchildren() == []
1029
1031
1030 target = response.assert_response().get_element('.pr-targetinfo .tag')
1032 target = response.assert_response().get_element('.pr-targetinfo .tag')
1031 assert target.text.strip() == 'bookmark: target'
1033 assert target.text.strip() == 'bookmark: target'
1032 assert target.getchildren() == []
1034 assert target.getchildren() == []
1033
1035
1034 def test_tag_is_not_a_link(self, pr_util):
1036 def test_tag_is_not_a_link(self, pr_util):
1035 pull_request = pr_util.create_pull_request()
1037 pull_request = pr_util.create_pull_request()
1036 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1038 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1037 pull_request.target_ref = 'tag:target:abcdef1234567890'
1039 pull_request.target_ref = 'tag:target:abcdef1234567890'
1038 Session().add(pull_request)
1040 Session().add(pull_request)
1039 Session().commit()
1041 Session().commit()
1040
1042
1041 response = self.app.get(route_path(
1043 response = self.app.get(route_path(
1042 'pullrequest_show',
1044 'pullrequest_show',
1043 repo_name=pull_request.target_repo.scm_instance().name,
1045 repo_name=pull_request.target_repo.scm_instance().name,
1044 pull_request_id=pull_request.pull_request_id))
1046 pull_request_id=pull_request.pull_request_id))
1045 assert response.status_int == 200
1047 assert response.status_int == 200
1046
1048
1047 origin = response.assert_response().get_element('.pr-origininfo .tag')
1049 origin = response.assert_response().get_element('.pr-origininfo .tag')
1048 assert origin.text.strip() == 'tag: origin'
1050 assert origin.text.strip() == 'tag: origin'
1049 assert origin.getchildren() == []
1051 assert origin.getchildren() == []
1050
1052
1051 target = response.assert_response().get_element('.pr-targetinfo .tag')
1053 target = response.assert_response().get_element('.pr-targetinfo .tag')
1052 assert target.text.strip() == 'tag: target'
1054 assert target.text.strip() == 'tag: target'
1053 assert target.getchildren() == []
1055 assert target.getchildren() == []
1054
1056
1055 @pytest.mark.parametrize('mergeable', [True, False])
1057 @pytest.mark.parametrize('mergeable', [True, False])
1056 def test_shadow_repository_link(
1058 def test_shadow_repository_link(
1057 self, mergeable, pr_util, http_host_only_stub):
1059 self, mergeable, pr_util, http_host_only_stub):
1058 """
1060 """
1059 Check that the pull request summary page displays a link to the shadow
1061 Check that the pull request summary page displays a link to the shadow
1060 repository if the pull request is mergeable. If it is not mergeable
1062 repository if the pull request is mergeable. If it is not mergeable
1061 the link should not be displayed.
1063 the link should not be displayed.
1062 """
1064 """
1063 pull_request = pr_util.create_pull_request(
1065 pull_request = pr_util.create_pull_request(
1064 mergeable=mergeable, enable_notifications=False)
1066 mergeable=mergeable, enable_notifications=False)
1065 target_repo = pull_request.target_repo.scm_instance()
1067 target_repo = pull_request.target_repo.scm_instance()
1066 pr_id = pull_request.pull_request_id
1068 pr_id = pull_request.pull_request_id
1067 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1069 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1068 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1070 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1069
1071
1070 response = self.app.get(route_path(
1072 response = self.app.get(route_path(
1071 'pullrequest_show',
1073 'pullrequest_show',
1072 repo_name=target_repo.name,
1074 repo_name=target_repo.name,
1073 pull_request_id=pr_id))
1075 pull_request_id=pr_id))
1074
1076
1075 if mergeable:
1077 if mergeable:
1076 response.assert_response().element_value_contains(
1078 response.assert_response().element_value_contains(
1077 'input.pr-mergeinfo', shadow_url)
1079 'input.pr-mergeinfo', shadow_url)
1078 response.assert_response().element_value_contains(
1080 response.assert_response().element_value_contains(
1079 'input.pr-mergeinfo ', 'pr-merge')
1081 'input.pr-mergeinfo ', 'pr-merge')
1080 else:
1082 else:
1081 response.assert_response().no_element_exists('.pr-mergeinfo')
1083 response.assert_response().no_element_exists('.pr-mergeinfo')
1082
1084
1083
1085
1084 @pytest.mark.usefixtures('app')
1086 @pytest.mark.usefixtures('app')
1085 @pytest.mark.backends("git", "hg")
1087 @pytest.mark.backends("git", "hg")
1086 class TestPullrequestsControllerDelete(object):
1088 class TestPullrequestsControllerDelete(object):
1087 def test_pull_request_delete_button_permissions_admin(
1089 def test_pull_request_delete_button_permissions_admin(
1088 self, autologin_user, user_admin, pr_util):
1090 self, autologin_user, user_admin, pr_util):
1089 pull_request = pr_util.create_pull_request(
1091 pull_request = pr_util.create_pull_request(
1090 author=user_admin.username, enable_notifications=False)
1092 author=user_admin.username, enable_notifications=False)
1091
1093
1092 response = self.app.get(route_path(
1094 response = self.app.get(route_path(
1093 'pullrequest_show',
1095 'pullrequest_show',
1094 repo_name=pull_request.target_repo.scm_instance().name,
1096 repo_name=pull_request.target_repo.scm_instance().name,
1095 pull_request_id=pull_request.pull_request_id))
1097 pull_request_id=pull_request.pull_request_id))
1096
1098
1097 response.mustcontain('id="delete_pullrequest"')
1099 response.mustcontain('id="delete_pullrequest"')
1098 response.mustcontain('Confirm to delete this pull request')
1100 response.mustcontain('Confirm to delete this pull request')
1099
1101
1100 def test_pull_request_delete_button_permissions_owner(
1102 def test_pull_request_delete_button_permissions_owner(
1101 self, autologin_regular_user, user_regular, pr_util):
1103 self, autologin_regular_user, user_regular, pr_util):
1102 pull_request = pr_util.create_pull_request(
1104 pull_request = pr_util.create_pull_request(
1103 author=user_regular.username, enable_notifications=False)
1105 author=user_regular.username, enable_notifications=False)
1104
1106
1105 response = self.app.get(route_path(
1107 response = self.app.get(route_path(
1106 'pullrequest_show',
1108 'pullrequest_show',
1107 repo_name=pull_request.target_repo.scm_instance().name,
1109 repo_name=pull_request.target_repo.scm_instance().name,
1108 pull_request_id=pull_request.pull_request_id))
1110 pull_request_id=pull_request.pull_request_id))
1109
1111
1110 response.mustcontain('id="delete_pullrequest"')
1112 response.mustcontain('id="delete_pullrequest"')
1111 response.mustcontain('Confirm to delete this pull request')
1113 response.mustcontain('Confirm to delete this pull request')
1112
1114
1113 def test_pull_request_delete_button_permissions_forbidden(
1115 def test_pull_request_delete_button_permissions_forbidden(
1114 self, autologin_regular_user, user_regular, user_admin, pr_util):
1116 self, autologin_regular_user, user_regular, user_admin, pr_util):
1115 pull_request = pr_util.create_pull_request(
1117 pull_request = pr_util.create_pull_request(
1116 author=user_admin.username, enable_notifications=False)
1118 author=user_admin.username, enable_notifications=False)
1117
1119
1118 response = self.app.get(route_path(
1120 response = self.app.get(route_path(
1119 'pullrequest_show',
1121 'pullrequest_show',
1120 repo_name=pull_request.target_repo.scm_instance().name,
1122 repo_name=pull_request.target_repo.scm_instance().name,
1121 pull_request_id=pull_request.pull_request_id))
1123 pull_request_id=pull_request.pull_request_id))
1122 response.mustcontain(no=['id="delete_pullrequest"'])
1124 response.mustcontain(no=['id="delete_pullrequest"'])
1123 response.mustcontain(no=['Confirm to delete this pull request'])
1125 response.mustcontain(no=['Confirm to delete this pull request'])
1124
1126
1125 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1127 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1126 self, autologin_regular_user, user_regular, user_admin, pr_util,
1128 self, autologin_regular_user, user_regular, user_admin, pr_util,
1127 user_util):
1129 user_util):
1128
1130
1129 pull_request = pr_util.create_pull_request(
1131 pull_request = pr_util.create_pull_request(
1130 author=user_admin.username, enable_notifications=False)
1132 author=user_admin.username, enable_notifications=False)
1131
1133
1132 user_util.grant_user_permission_to_repo(
1134 user_util.grant_user_permission_to_repo(
1133 pull_request.target_repo, user_regular,
1135 pull_request.target_repo, user_regular,
1134 'repository.write')
1136 'repository.write')
1135
1137
1136 response = self.app.get(route_path(
1138 response = self.app.get(route_path(
1137 'pullrequest_show',
1139 'pullrequest_show',
1138 repo_name=pull_request.target_repo.scm_instance().name,
1140 repo_name=pull_request.target_repo.scm_instance().name,
1139 pull_request_id=pull_request.pull_request_id))
1141 pull_request_id=pull_request.pull_request_id))
1140
1142
1141 response.mustcontain('id="open_edit_pullrequest"')
1143 response.mustcontain('id="open_edit_pullrequest"')
1142 response.mustcontain('id="delete_pullrequest"')
1144 response.mustcontain('id="delete_pullrequest"')
1143 response.mustcontain(no=['Confirm to delete this pull request'])
1145 response.mustcontain(no=['Confirm to delete this pull request'])
1144
1146
1145 def test_delete_comment_returns_404_if_comment_does_not_exist(
1147 def test_delete_comment_returns_404_if_comment_does_not_exist(
1146 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1148 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1147
1149
1148 pull_request = pr_util.create_pull_request(
1150 pull_request = pr_util.create_pull_request(
1149 author=user_admin.username, enable_notifications=False)
1151 author=user_admin.username, enable_notifications=False)
1150
1152
1151 self.app.post(
1153 self.app.post(
1152 route_path(
1154 route_path(
1153 'pullrequest_comment_delete',
1155 'pullrequest_comment_delete',
1154 repo_name=pull_request.target_repo.scm_instance().name,
1156 repo_name=pull_request.target_repo.scm_instance().name,
1155 pull_request_id=pull_request.pull_request_id,
1157 pull_request_id=pull_request.pull_request_id,
1156 comment_id=1024404),
1158 comment_id=1024404),
1157 extra_environ=xhr_header,
1159 extra_environ=xhr_header,
1158 params={'csrf_token': csrf_token},
1160 params={'csrf_token': csrf_token},
1159 status=404
1161 status=404
1160 )
1162 )
1161
1163
1162 def test_delete_comment(
1164 def test_delete_comment(
1163 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1165 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1164
1166
1165 pull_request = pr_util.create_pull_request(
1167 pull_request = pr_util.create_pull_request(
1166 author=user_admin.username, enable_notifications=False)
1168 author=user_admin.username, enable_notifications=False)
1167 comment = pr_util.create_comment()
1169 comment = pr_util.create_comment()
1168 comment_id = comment.comment_id
1170 comment_id = comment.comment_id
1169
1171
1170 response = self.app.post(
1172 response = self.app.post(
1171 route_path(
1173 route_path(
1172 'pullrequest_comment_delete',
1174 'pullrequest_comment_delete',
1173 repo_name=pull_request.target_repo.scm_instance().name,
1175 repo_name=pull_request.target_repo.scm_instance().name,
1174 pull_request_id=pull_request.pull_request_id,
1176 pull_request_id=pull_request.pull_request_id,
1175 comment_id=comment_id),
1177 comment_id=comment_id),
1176 extra_environ=xhr_header,
1178 extra_environ=xhr_header,
1177 params={'csrf_token': csrf_token},
1179 params={'csrf_token': csrf_token},
1178 status=200
1180 status=200
1179 )
1181 )
1180 assert response.body == 'true'
1182 assert response.body == 'true'
1181
1183
1182 @pytest.mark.parametrize('url_type', [
1184 @pytest.mark.parametrize('url_type', [
1183 'pullrequest_new',
1185 'pullrequest_new',
1184 'pullrequest_create',
1186 'pullrequest_create',
1185 'pullrequest_update',
1187 'pullrequest_update',
1186 'pullrequest_merge',
1188 'pullrequest_merge',
1187 ])
1189 ])
1188 def test_pull_request_is_forbidden_on_archived_repo(
1190 def test_pull_request_is_forbidden_on_archived_repo(
1189 self, autologin_user, backend, xhr_header, user_util, url_type):
1191 self, autologin_user, backend, xhr_header, user_util, url_type):
1190
1192
1191 # create a temporary repo
1193 # create a temporary repo
1192 source = user_util.create_repo(repo_type=backend.alias)
1194 source = user_util.create_repo(repo_type=backend.alias)
1193 repo_name = source.repo_name
1195 repo_name = source.repo_name
1194 repo = Repository.get_by_repo_name(repo_name)
1196 repo = Repository.get_by_repo_name(repo_name)
1195 repo.archived = True
1197 repo.archived = True
1196 Session().commit()
1198 Session().commit()
1197
1199
1198 response = self.app.get(
1200 response = self.app.get(
1199 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1201 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1200
1202
1201 msg = 'Action not supported for archived repository.'
1203 msg = 'Action not supported for archived repository.'
1202 assert_session_flash(response, msg)
1204 assert_session_flash(response, msg)
1203
1205
1204
1206
1205 def assert_pull_request_status(pull_request, expected_status):
1207 def assert_pull_request_status(pull_request, expected_status):
1206 status = ChangesetStatusModel().calculated_review_status(
1208 status = ChangesetStatusModel().calculated_review_status(
1207 pull_request=pull_request)
1209 pull_request=pull_request)
1208 assert status == expected_status
1210 assert status == expected_status
1209
1211
1210
1212
1211 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1213 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1212 @pytest.mark.usefixtures("autologin_user")
1214 @pytest.mark.usefixtures("autologin_user")
1213 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1215 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1214 response = app.get(
1216 response = app.get(
1215 route_path(route, repo_name=backend_svn.repo_name), status=404)
1217 route_path(route, repo_name=backend_svn.repo_name), status=404)
1216
1218
@@ -1,358 +1,365 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 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 import rhodecode.lib.helpers as h
30 import rhodecode.lib.helpers as h
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator)
32 LoginRequired, HasRepoPermissionAnyDecorator)
33
33
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.graphmod import _colored, _dagwalker
35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
38 from rhodecode.lib.vcs.exceptions import (
38 from rhodecode.lib.vcs.exceptions import (
39 RepositoryError, CommitDoesNotExistError,
39 RepositoryError, CommitDoesNotExistError,
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 DEFAULT_CHANGELOG_SIZE = 20
44 DEFAULT_CHANGELOG_SIZE = 20
45
45
46
46
47 class RepoChangelogView(RepoAppView):
47 class RepoChangelogView(RepoAppView):
48
48
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 """
50 """
51 This is a safe way to get commit. If an error occurs it redirects to
51 This is a safe way to get commit. If an error occurs it redirects to
52 tip with proper message
52 tip with proper message
53
53
54 :param commit_id: id of commit to fetch
54 :param commit_id: id of commit to fetch
55 :param redirect_after: toggle redirection
55 :param redirect_after: toggle redirection
56 """
56 """
57 _ = self.request.translate
57 _ = self.request.translate
58
58
59 try:
59 try:
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 except EmptyRepositoryError:
61 except EmptyRepositoryError:
62 if not redirect_after:
62 if not redirect_after:
63 return None
63 return None
64
64
65 h.flash(h.literal(
65 h.flash(h.literal(
66 _('There are no commits yet')), category='warning')
66 _('There are no commits yet')), category='warning')
67 raise HTTPFound(
67 raise HTTPFound(
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69
69
70 except (CommitDoesNotExistError, LookupError):
70 except (CommitDoesNotExistError, LookupError):
71 msg = _('No such commit exists for this repository')
71 msg = _('No such commit exists for this repository')
72 h.flash(msg, category='error')
72 h.flash(msg, category='error')
73 raise HTTPNotFound()
73 raise HTTPNotFound()
74 except RepositoryError as e:
74 except RepositoryError as e:
75 h.flash(safe_str(h.escape(e)), category='error')
75 h.flash(safe_str(h.escape(e)), category='error')
76 raise HTTPNotFound()
76 raise HTTPNotFound()
77
77
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 """
79 """
80 Generates a DAG graph for repo
80 Generates a DAG graph for repo
81
81
82 :param repo: repo instance
82 :param repo: repo instance
83 :param commits: list of commits
83 :param commits: list of commits
84 """
84 """
85 if not commits:
85 if not commits:
86 return json.dumps([]), json.dumps([])
86 return json.dumps([]), json.dumps([])
87
87
88 def serialize(commit, parents=True):
88 def serialize(commit, parents=True):
89 data = dict(
89 data = dict(
90 raw_id=commit.raw_id,
90 raw_id=commit.raw_id,
91 idx=commit.idx,
91 idx=commit.idx,
92 branch=h.escape(commit.branch),
92 branch=h.escape(commit.branch),
93 )
93 )
94 if parents:
94 if parents:
95 data['parents'] = [
95 data['parents'] = [
96 serialize(x, parents=False) for x in commit.parents]
96 serialize(x, parents=False) for x in commit.parents]
97 return data
97 return data
98
98
99 prev_data = prev_data or []
99 prev_data = prev_data or []
100 next_data = next_data or []
100 next_data = next_data or []
101
101
102 current = [serialize(x) for x in commits]
102 current = [serialize(x) for x in commits]
103 commits = prev_data + current + next_data
103 commits = prev_data + current + next_data
104
104
105 dag = _dagwalker(repo, commits)
105 dag = _dagwalker(repo, commits)
106
106
107 data = [[commit_id, vtx, edges, branch]
107 data = [[commit_id, vtx, edges, branch]
108 for commit_id, vtx, edges, branch in _colored(dag)]
108 for commit_id, vtx, edges, branch in _colored(dag)]
109 return json.dumps(data), json.dumps(current)
109 return json.dumps(data), json.dumps(current)
110
110
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
114 category='warning')
114 category='warning')
115 redirect_url = h.route_path(
115 redirect_url = h.route_path(
116 'repo_changelog_file', repo_name=repo_name,
116 'repo_commits_file', repo_name=repo_name,
117 commit_id=branch_name, f_path=f_path or '')
117 commit_id=branch_name, f_path=f_path or '')
118 raise HTTPFound(redirect_url)
118 raise HTTPFound(redirect_url)
119
119
120 def _load_changelog_data(
120 def _load_changelog_data(
121 self, c, collection, page, chunk_size, branch_name=None,
121 self, c, collection, page, chunk_size, branch_name=None,
122 dynamic=False, f_path=None, commit_id=None):
122 dynamic=False, f_path=None, commit_id=None):
123
123
124 def url_generator(**kw):
124 def url_generator(**kw):
125 query_params = {}
125 query_params = {}
126 query_params.update(kw)
126 query_params.update(kw)
127 if f_path:
127 if f_path:
128 # changelog for file
128 # changelog for file
129 return h.route_path(
129 return h.route_path(
130 'repo_changelog_file',
130 'repo_commits_file',
131 repo_name=c.rhodecode_db_repo.repo_name,
131 repo_name=c.rhodecode_db_repo.repo_name,
132 commit_id=commit_id, f_path=f_path,
132 commit_id=commit_id, f_path=f_path,
133 _query=query_params)
133 _query=query_params)
134 else:
134 else:
135 return h.route_path(
135 return h.route_path(
136 'repo_changelog',
136 'repo_commits',
137 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
137 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
138
138
139 c.total_cs = len(collection)
139 c.total_cs = len(collection)
140 c.showing_commits = min(chunk_size, c.total_cs)
140 c.showing_commits = min(chunk_size, c.total_cs)
141 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
141 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
142 items_per_page=chunk_size, branch=branch_name,
142 items_per_page=chunk_size, branch=branch_name,
143 url=url_generator)
143 url=url_generator)
144
144
145 c.next_page = c.pagination.next_page
145 c.next_page = c.pagination.next_page
146 c.prev_page = c.pagination.previous_page
146 c.prev_page = c.pagination.previous_page
147
147
148 if dynamic:
148 if dynamic:
149 if self.request.GET.get('chunk') != 'next':
149 if self.request.GET.get('chunk') != 'next':
150 c.next_page = None
150 c.next_page = None
151 if self.request.GET.get('chunk') != 'prev':
151 if self.request.GET.get('chunk') != 'prev':
152 c.prev_page = None
152 c.prev_page = None
153
153
154 page_commit_ids = [x.raw_id for x in c.pagination]
154 page_commit_ids = [x.raw_id for x in c.pagination]
155 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
155 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
156 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
156 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
157
157
158 def load_default_context(self):
158 def load_default_context(self):
159 c = self._get_local_tmpl_context(include_app_defaults=True)
159 c = self._get_local_tmpl_context(include_app_defaults=True)
160
160
161 c.rhodecode_repo = self.rhodecode_vcs_repo
161 c.rhodecode_repo = self.rhodecode_vcs_repo
162
162
163 return c
163 return c
164
164
165 def _get_preload_attrs(self):
165 def _get_preload_attrs(self):
166 pre_load = ['author', 'branch', 'date', 'message', 'parents',
166 pre_load = ['author', 'branch', 'date', 'message', 'parents',
167 'obsolete', 'phase', 'hidden']
167 'obsolete', 'phase', 'hidden']
168 return pre_load
168 return pre_load
169
169
170 @LoginRequired()
170 @LoginRequired()
171 @HasRepoPermissionAnyDecorator(
171 @HasRepoPermissionAnyDecorator(
172 'repository.read', 'repository.write', 'repository.admin')
172 'repository.read', 'repository.write', 'repository.admin')
173 @view_config(
173 @view_config(
174 route_name='repo_commits', request_method='GET',
175 renderer='rhodecode:templates/commits/changelog.mako')
176 @view_config(
177 route_name='repo_commits_file', request_method='GET',
178 renderer='rhodecode:templates/commits/changelog.mako')
179 # old routes for backward compat
180 @view_config(
174 route_name='repo_changelog', request_method='GET',
181 route_name='repo_changelog', request_method='GET',
175 renderer='rhodecode:templates/changelog/changelog.mako')
182 renderer='rhodecode:templates/commits/changelog.mako')
176 @view_config(
183 @view_config(
177 route_name='repo_changelog_file', request_method='GET',
184 route_name='repo_changelog_file', request_method='GET',
178 renderer='rhodecode:templates/changelog/changelog.mako')
185 renderer='rhodecode:templates/commits/changelog.mako')
179 def repo_changelog(self):
186 def repo_changelog(self):
180 c = self.load_default_context()
187 c = self.load_default_context()
181
188
182 commit_id = self.request.matchdict.get('commit_id')
189 commit_id = self.request.matchdict.get('commit_id')
183 f_path = self._get_f_path(self.request.matchdict)
190 f_path = self._get_f_path(self.request.matchdict)
184 show_hidden = str2bool(self.request.GET.get('evolve'))
191 show_hidden = str2bool(self.request.GET.get('evolve'))
185
192
186 chunk_size = 20
193 chunk_size = 20
187
194
188 c.branch_name = branch_name = self.request.GET.get('branch') or ''
195 c.branch_name = branch_name = self.request.GET.get('branch') or ''
189 c.book_name = book_name = self.request.GET.get('bookmark') or ''
196 c.book_name = book_name = self.request.GET.get('bookmark') or ''
190 c.f_path = f_path
197 c.f_path = f_path
191 c.commit_id = commit_id
198 c.commit_id = commit_id
192 c.show_hidden = show_hidden
199 c.show_hidden = show_hidden
193
200
194 hist_limit = safe_int(self.request.GET.get('limit')) or None
201 hist_limit = safe_int(self.request.GET.get('limit')) or None
195
202
196 p = safe_int(self.request.GET.get('page', 1), 1)
203 p = safe_int(self.request.GET.get('page', 1), 1)
197
204
198 c.selected_name = branch_name or book_name
205 c.selected_name = branch_name or book_name
199 if not commit_id and branch_name:
206 if not commit_id and branch_name:
200 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
207 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
201
208
202 c.changelog_for_path = f_path
209 c.changelog_for_path = f_path
203 pre_load = self._get_preload_attrs()
210 pre_load = self._get_preload_attrs()
204
211
205 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
212 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
206 try:
213 try:
207 if f_path:
214 if f_path:
208 log.debug('generating changelog for path %s', f_path)
215 log.debug('generating changelog for path %s', f_path)
209 # get the history for the file !
216 # get the history for the file !
210 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
217 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
211
218
212 try:
219 try:
213 collection = base_commit.get_path_history(
220 collection = base_commit.get_path_history(
214 f_path, limit=hist_limit, pre_load=pre_load)
221 f_path, limit=hist_limit, pre_load=pre_load)
215 if collection and partial_xhr:
222 if collection and partial_xhr:
216 # for ajax call we remove first one since we're looking
223 # for ajax call we remove first one since we're looking
217 # at it right now in the context of a file commit
224 # at it right now in the context of a file commit
218 collection.pop(0)
225 collection.pop(0)
219 except (NodeDoesNotExistError, CommitError):
226 except (NodeDoesNotExistError, CommitError):
220 # this node is not present at tip!
227 # this node is not present at tip!
221 try:
228 try:
222 commit = self._get_commit_or_redirect(commit_id)
229 commit = self._get_commit_or_redirect(commit_id)
223 collection = commit.get_path_history(f_path)
230 collection = commit.get_path_history(f_path)
224 except RepositoryError as e:
231 except RepositoryError as e:
225 h.flash(safe_str(e), category='warning')
232 h.flash(safe_str(e), category='warning')
226 redirect_url = h.route_path(
233 redirect_url = h.route_path(
227 'repo_changelog', repo_name=self.db_repo_name)
234 'repo_commits', repo_name=self.db_repo_name)
228 raise HTTPFound(redirect_url)
235 raise HTTPFound(redirect_url)
229 collection = list(reversed(collection))
236 collection = list(reversed(collection))
230 else:
237 else:
231 collection = self.rhodecode_vcs_repo.get_commits(
238 collection = self.rhodecode_vcs_repo.get_commits(
232 branch_name=branch_name, show_hidden=show_hidden,
239 branch_name=branch_name, show_hidden=show_hidden,
233 pre_load=pre_load, translate_tags=False)
240 pre_load=pre_load, translate_tags=False)
234
241
235 self._load_changelog_data(
242 self._load_changelog_data(
236 c, collection, p, chunk_size, c.branch_name,
243 c, collection, p, chunk_size, c.branch_name,
237 f_path=f_path, commit_id=commit_id)
244 f_path=f_path, commit_id=commit_id)
238
245
239 except EmptyRepositoryError as e:
246 except EmptyRepositoryError as e:
240 h.flash(safe_str(h.escape(e)), category='warning')
247 h.flash(safe_str(h.escape(e)), category='warning')
241 raise HTTPFound(
248 raise HTTPFound(
242 h.route_path('repo_summary', repo_name=self.db_repo_name))
249 h.route_path('repo_summary', repo_name=self.db_repo_name))
243 except HTTPFound:
250 except HTTPFound:
244 raise
251 raise
245 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
252 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
246 log.exception(safe_str(e))
253 log.exception(safe_str(e))
247 h.flash(safe_str(h.escape(e)), category='error')
254 h.flash(safe_str(h.escape(e)), category='error')
248 raise HTTPFound(
255 raise HTTPFound(
249 h.route_path('repo_changelog', repo_name=self.db_repo_name))
256 h.route_path('repo_commits', repo_name=self.db_repo_name))
250
257
251 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
258 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
252 # case when loading dynamic file history in file view
259 # case when loading dynamic file history in file view
253 # loading from ajax, we don't want the first result, it's popped
260 # loading from ajax, we don't want the first result, it's popped
254 # in the code above
261 # in the code above
255 html = render(
262 html = render(
256 'rhodecode:templates/changelog/changelog_file_history.mako',
263 'rhodecode:templates/commits/changelog_file_history.mako',
257 self._get_template_context(c), self.request)
264 self._get_template_context(c), self.request)
258 return Response(html)
265 return Response(html)
259
266
260 commit_ids = []
267 commit_ids = []
261 if not f_path:
268 if not f_path:
262 # only load graph data when not in file history mode
269 # only load graph data when not in file history mode
263 commit_ids = c.pagination
270 commit_ids = c.pagination
264
271
265 c.graph_data, c.graph_commits = self._graph(
272 c.graph_data, c.graph_commits = self._graph(
266 self.rhodecode_vcs_repo, commit_ids)
273 self.rhodecode_vcs_repo, commit_ids)
267
274
268 return self._get_template_context(c)
275 return self._get_template_context(c)
269
276
270 @LoginRequired()
277 @LoginRequired()
271 @HasRepoPermissionAnyDecorator(
278 @HasRepoPermissionAnyDecorator(
272 'repository.read', 'repository.write', 'repository.admin')
279 'repository.read', 'repository.write', 'repository.admin')
273 @view_config(
280 @view_config(
274 route_name='repo_changelog_elements', request_method=('GET', 'POST'),
281 route_name='repo_commits_elements', request_method=('GET', 'POST'),
275 renderer='rhodecode:templates/changelog/changelog_elements.mako',
282 renderer='rhodecode:templates/commits/changelog_elements.mako',
276 xhr=True)
283 xhr=True)
277 @view_config(
284 @view_config(
278 route_name='repo_changelog_elements_file', request_method=('GET', 'POST'),
285 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
279 renderer='rhodecode:templates/changelog/changelog_elements.mako',
286 renderer='rhodecode:templates/commits/changelog_elements.mako',
280 xhr=True)
287 xhr=True)
281 def repo_changelog_elements(self):
288 def repo_commits_elements(self):
282 c = self.load_default_context()
289 c = self.load_default_context()
283 commit_id = self.request.matchdict.get('commit_id')
290 commit_id = self.request.matchdict.get('commit_id')
284 f_path = self._get_f_path(self.request.matchdict)
291 f_path = self._get_f_path(self.request.matchdict)
285 show_hidden = str2bool(self.request.GET.get('evolve'))
292 show_hidden = str2bool(self.request.GET.get('evolve'))
286
293
287 chunk_size = 20
294 chunk_size = 20
288 hist_limit = safe_int(self.request.GET.get('limit')) or None
295 hist_limit = safe_int(self.request.GET.get('limit')) or None
289
296
290 def wrap_for_error(err):
297 def wrap_for_error(err):
291 html = '<tr>' \
298 html = '<tr>' \
292 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
299 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
293 '</tr>'.format(err)
300 '</tr>'.format(err)
294 return Response(html)
301 return Response(html)
295
302
296 c.branch_name = branch_name = self.request.GET.get('branch') or ''
303 c.branch_name = branch_name = self.request.GET.get('branch') or ''
297 c.book_name = book_name = self.request.GET.get('bookmark') or ''
304 c.book_name = book_name = self.request.GET.get('bookmark') or ''
298 c.f_path = f_path
305 c.f_path = f_path
299 c.commit_id = commit_id
306 c.commit_id = commit_id
300 c.show_hidden = show_hidden
307 c.show_hidden = show_hidden
301
308
302 c.selected_name = branch_name or book_name
309 c.selected_name = branch_name or book_name
303 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
310 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
304 return wrap_for_error(
311 return wrap_for_error(
305 safe_str('Branch: {} is not valid'.format(branch_name)))
312 safe_str('Branch: {} is not valid'.format(branch_name)))
306
313
307 pre_load = self._get_preload_attrs()
314 pre_load = self._get_preload_attrs()
308
315
309 if f_path:
316 if f_path:
310 try:
317 try:
311 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
318 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
312 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
319 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
313 log.exception(safe_str(e))
320 log.exception(safe_str(e))
314 raise HTTPFound(
321 raise HTTPFound(
315 h.route_path('repo_changelog', repo_name=self.db_repo_name))
322 h.route_path('repo_commits', repo_name=self.db_repo_name))
316
323
317 collection = base_commit.get_path_history(
324 collection = base_commit.get_path_history(
318 f_path, limit=hist_limit, pre_load=pre_load)
325 f_path, limit=hist_limit, pre_load=pre_load)
319 collection = list(reversed(collection))
326 collection = list(reversed(collection))
320 else:
327 else:
321 collection = self.rhodecode_vcs_repo.get_commits(
328 collection = self.rhodecode_vcs_repo.get_commits(
322 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
329 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
323 translate_tags=False)
330 translate_tags=False)
324
331
325 p = safe_int(self.request.GET.get('page', 1), 1)
332 p = safe_int(self.request.GET.get('page', 1), 1)
326 try:
333 try:
327 self._load_changelog_data(
334 self._load_changelog_data(
328 c, collection, p, chunk_size, dynamic=True,
335 c, collection, p, chunk_size, dynamic=True,
329 f_path=f_path, commit_id=commit_id)
336 f_path=f_path, commit_id=commit_id)
330 except EmptyRepositoryError as e:
337 except EmptyRepositoryError as e:
331 return wrap_for_error(safe_str(e))
338 return wrap_for_error(safe_str(e))
332 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
339 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
333 log.exception('Failed to fetch commits')
340 log.exception('Failed to fetch commits')
334 return wrap_for_error(safe_str(e))
341 return wrap_for_error(safe_str(e))
335
342
336 prev_data = None
343 prev_data = None
337 next_data = None
344 next_data = None
338
345
339 try:
346 try:
340 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
347 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
341 except json.JSONDecodeError:
348 except json.JSONDecodeError:
342 prev_graph = {}
349 prev_graph = {}
343
350
344 if self.request.GET.get('chunk') == 'prev':
351 if self.request.GET.get('chunk') == 'prev':
345 next_data = prev_graph
352 next_data = prev_graph
346 elif self.request.GET.get('chunk') == 'next':
353 elif self.request.GET.get('chunk') == 'next':
347 prev_data = prev_graph
354 prev_data = prev_graph
348
355
349 commit_ids = []
356 commit_ids = []
350 if not f_path:
357 if not f_path:
351 # only load graph data when not in file history mode
358 # only load graph data when not in file history mode
352 commit_ids = c.pagination
359 commit_ids = c.pagination
353
360
354 c.graph_data, c.graph_commits = self._graph(
361 c.graph_data, c.graph_commits = self._graph(
355 self.rhodecode_vcs_repo, commit_ids,
362 self.rhodecode_vcs_repo, commit_ids,
356 prev_data=prev_data, next_data=next_data)
363 prev_data=prev_data, next_data=next_data)
357
364
358 return self._get_template_context(c)
365 return self._get_template_context(c)
@@ -1,2070 +1,2071 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 = icon = '<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
222 files_icon = icon = '<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
223
223
224 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False):
224 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False):
225 if isinstance(file_path, str):
225 if isinstance(file_path, str):
226 file_path = safe_unicode(file_path)
226 file_path = safe_unicode(file_path)
227
227
228 route_qry = {'at': at_ref} if at_ref else None
228 route_qry = {'at': at_ref} if at_ref else None
229
229
230 # first segment is a `..` link to repo files
230 # first segment is a `..` link to repo files
231 root_name = literal(u'<i class="icon-home"></i>')
231 root_name = literal(u'<i class="icon-home"></i>')
232 url_segments = [
232 url_segments = [
233 link_to(
233 link_to(
234 root_name,
234 root_name,
235 route_path(
235 route_path(
236 'repo_files',
236 'repo_files',
237 repo_name=repo_name,
237 repo_name=repo_name,
238 commit_id=commit_id,
238 commit_id=commit_id,
239 f_path='',
239 f_path='',
240 _query=route_qry),
240 _query=route_qry),
241 )]
241 )]
242
242
243 path_segments = file_path.split('/')
243 path_segments = file_path.split('/')
244 last_cnt = len(path_segments) - 1
244 last_cnt = len(path_segments) - 1
245 for cnt, segment in enumerate(path_segments):
245 for cnt, segment in enumerate(path_segments):
246 if not segment:
246 if not segment:
247 continue
247 continue
248 segment_html = escape(segment)
248 segment_html = escape(segment)
249
249
250 if cnt != last_cnt:
250 if cnt != last_cnt:
251 url_segments.append(
251 url_segments.append(
252 link_to(
252 link_to(
253 segment_html,
253 segment_html,
254 route_path(
254 route_path(
255 'repo_files',
255 'repo_files',
256 repo_name=repo_name,
256 repo_name=repo_name,
257 commit_id=commit_id,
257 commit_id=commit_id,
258 f_path='/'.join(path_segments[:cnt + 1]),
258 f_path='/'.join(path_segments[:cnt + 1]),
259 _query=route_qry),
259 _query=route_qry),
260 ))
260 ))
261 else:
261 else:
262 url_segments.append(segment_html)
262 url_segments.append(segment_html)
263
263
264 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
264 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
265 if limit_items and len(limited_url_segments) < len(url_segments):
265 if limit_items and len(limited_url_segments) < len(url_segments):
266 url_segments = limited_url_segments
266 url_segments = limited_url_segments
267
267
268 full_path = file_path
268 full_path = file_path
269 icon = files_icon.format(escape(full_path))
269 icon = files_icon.format(escape(full_path))
270 if file_path == '':
270 if file_path == '':
271 return root_name
271 return root_name
272 else:
272 else:
273 return literal(' / '.join(url_segments) + icon)
273 return literal(' / '.join(url_segments) + icon)
274
274
275
275
276 def files_url_data(request):
276 def files_url_data(request):
277 matchdict = request.matchdict
277 matchdict = request.matchdict
278
278
279 if 'f_path' not in matchdict:
279 if 'f_path' not in matchdict:
280 matchdict['f_path'] = ''
280 matchdict['f_path'] = ''
281
281
282 if 'commit_id' not in matchdict:
282 if 'commit_id' not in matchdict:
283 matchdict['commit_id'] = 'tip'
283 matchdict['commit_id'] = 'tip'
284
284
285 return json.dumps(matchdict)
285 return json.dumps(matchdict)
286
286
287
287
288 def code_highlight(code, lexer, formatter, use_hl_filter=False):
288 def code_highlight(code, lexer, formatter, use_hl_filter=False):
289 """
289 """
290 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
290 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
291
291
292 If ``outfile`` is given and a valid file object (an object
292 If ``outfile`` is given and a valid file object (an object
293 with a ``write`` method), the result will be written to it, otherwise
293 with a ``write`` method), the result will be written to it, otherwise
294 it is returned as a string.
294 it is returned as a string.
295 """
295 """
296 if use_hl_filter:
296 if use_hl_filter:
297 # add HL filter
297 # add HL filter
298 from rhodecode.lib.index import search_utils
298 from rhodecode.lib.index import search_utils
299 lexer.add_filter(search_utils.ElasticSearchHLFilter())
299 lexer.add_filter(search_utils.ElasticSearchHLFilter())
300 return pygments.format(pygments.lex(code, lexer), formatter)
300 return pygments.format(pygments.lex(code, lexer), formatter)
301
301
302
302
303 class CodeHtmlFormatter(HtmlFormatter):
303 class CodeHtmlFormatter(HtmlFormatter):
304 """
304 """
305 My code Html Formatter for source codes
305 My code Html Formatter for source codes
306 """
306 """
307
307
308 def wrap(self, source, outfile):
308 def wrap(self, source, outfile):
309 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
309 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
310
310
311 def _wrap_code(self, source):
311 def _wrap_code(self, source):
312 for cnt, it in enumerate(source):
312 for cnt, it in enumerate(source):
313 i, t = it
313 i, t = it
314 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
314 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
315 yield i, t
315 yield i, t
316
316
317 def _wrap_tablelinenos(self, inner):
317 def _wrap_tablelinenos(self, inner):
318 dummyoutfile = StringIO.StringIO()
318 dummyoutfile = StringIO.StringIO()
319 lncount = 0
319 lncount = 0
320 for t, line in inner:
320 for t, line in inner:
321 if t:
321 if t:
322 lncount += 1
322 lncount += 1
323 dummyoutfile.write(line)
323 dummyoutfile.write(line)
324
324
325 fl = self.linenostart
325 fl = self.linenostart
326 mw = len(str(lncount + fl - 1))
326 mw = len(str(lncount + fl - 1))
327 sp = self.linenospecial
327 sp = self.linenospecial
328 st = self.linenostep
328 st = self.linenostep
329 la = self.lineanchors
329 la = self.lineanchors
330 aln = self.anchorlinenos
330 aln = self.anchorlinenos
331 nocls = self.noclasses
331 nocls = self.noclasses
332 if sp:
332 if sp:
333 lines = []
333 lines = []
334
334
335 for i in range(fl, fl + lncount):
335 for i in range(fl, fl + lncount):
336 if i % st == 0:
336 if i % st == 0:
337 if i % sp == 0:
337 if i % sp == 0:
338 if aln:
338 if aln:
339 lines.append('<a href="#%s%d" class="special">%*d</a>' %
339 lines.append('<a href="#%s%d" class="special">%*d</a>' %
340 (la, i, mw, i))
340 (la, i, mw, i))
341 else:
341 else:
342 lines.append('<span class="special">%*d</span>' % (mw, i))
342 lines.append('<span class="special">%*d</span>' % (mw, i))
343 else:
343 else:
344 if aln:
344 if aln:
345 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
345 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
346 else:
346 else:
347 lines.append('%*d' % (mw, i))
347 lines.append('%*d' % (mw, i))
348 else:
348 else:
349 lines.append('')
349 lines.append('')
350 ls = '\n'.join(lines)
350 ls = '\n'.join(lines)
351 else:
351 else:
352 lines = []
352 lines = []
353 for i in range(fl, fl + lncount):
353 for i in range(fl, fl + lncount):
354 if i % st == 0:
354 if i % st == 0:
355 if aln:
355 if aln:
356 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
356 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
357 else:
357 else:
358 lines.append('%*d' % (mw, i))
358 lines.append('%*d' % (mw, i))
359 else:
359 else:
360 lines.append('')
360 lines.append('')
361 ls = '\n'.join(lines)
361 ls = '\n'.join(lines)
362
362
363 # in case you wonder about the seemingly redundant <div> here: since the
363 # in case you wonder about the seemingly redundant <div> here: since the
364 # content in the other cell also is wrapped in a div, some browsers in
364 # content in the other cell also is wrapped in a div, some browsers in
365 # some configurations seem to mess up the formatting...
365 # some configurations seem to mess up the formatting...
366 if nocls:
366 if nocls:
367 yield 0, ('<table class="%stable">' % self.cssclass +
367 yield 0, ('<table class="%stable">' % self.cssclass +
368 '<tr><td><div class="linenodiv" '
368 '<tr><td><div class="linenodiv" '
369 'style="background-color: #f0f0f0; padding-right: 10px">'
369 'style="background-color: #f0f0f0; padding-right: 10px">'
370 '<pre style="line-height: 125%">' +
370 '<pre style="line-height: 125%">' +
371 ls + '</pre></div></td><td id="hlcode" class="code">')
371 ls + '</pre></div></td><td id="hlcode" class="code">')
372 else:
372 else:
373 yield 0, ('<table class="%stable">' % self.cssclass +
373 yield 0, ('<table class="%stable">' % self.cssclass +
374 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
374 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
375 ls + '</pre></div></td><td id="hlcode" class="code">')
375 ls + '</pre></div></td><td id="hlcode" class="code">')
376 yield 0, dummyoutfile.getvalue()
376 yield 0, dummyoutfile.getvalue()
377 yield 0, '</td></tr></table>'
377 yield 0, '</td></tr></table>'
378
378
379
379
380 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
380 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
381 def __init__(self, **kw):
381 def __init__(self, **kw):
382 # only show these line numbers if set
382 # only show these line numbers if set
383 self.only_lines = kw.pop('only_line_numbers', [])
383 self.only_lines = kw.pop('only_line_numbers', [])
384 self.query_terms = kw.pop('query_terms', [])
384 self.query_terms = kw.pop('query_terms', [])
385 self.max_lines = kw.pop('max_lines', 5)
385 self.max_lines = kw.pop('max_lines', 5)
386 self.line_context = kw.pop('line_context', 3)
386 self.line_context = kw.pop('line_context', 3)
387 self.url = kw.pop('url', None)
387 self.url = kw.pop('url', None)
388
388
389 super(CodeHtmlFormatter, self).__init__(**kw)
389 super(CodeHtmlFormatter, self).__init__(**kw)
390
390
391 def _wrap_code(self, source):
391 def _wrap_code(self, source):
392 for cnt, it in enumerate(source):
392 for cnt, it in enumerate(source):
393 i, t = it
393 i, t = it
394 t = '<pre>%s</pre>' % t
394 t = '<pre>%s</pre>' % t
395 yield i, t
395 yield i, t
396
396
397 def _wrap_tablelinenos(self, inner):
397 def _wrap_tablelinenos(self, inner):
398 yield 0, '<table class="code-highlight %stable">' % self.cssclass
398 yield 0, '<table class="code-highlight %stable">' % self.cssclass
399
399
400 last_shown_line_number = 0
400 last_shown_line_number = 0
401 current_line_number = 1
401 current_line_number = 1
402
402
403 for t, line in inner:
403 for t, line in inner:
404 if not t:
404 if not t:
405 yield t, line
405 yield t, line
406 continue
406 continue
407
407
408 if current_line_number in self.only_lines:
408 if current_line_number in self.only_lines:
409 if last_shown_line_number + 1 != current_line_number:
409 if last_shown_line_number + 1 != current_line_number:
410 yield 0, '<tr>'
410 yield 0, '<tr>'
411 yield 0, '<td class="line">...</td>'
411 yield 0, '<td class="line">...</td>'
412 yield 0, '<td id="hlcode" class="code"></td>'
412 yield 0, '<td id="hlcode" class="code"></td>'
413 yield 0, '</tr>'
413 yield 0, '</tr>'
414
414
415 yield 0, '<tr>'
415 yield 0, '<tr>'
416 if self.url:
416 if self.url:
417 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
417 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
418 self.url, current_line_number, current_line_number)
418 self.url, current_line_number, current_line_number)
419 else:
419 else:
420 yield 0, '<td class="line"><a href="">%i</a></td>' % (
420 yield 0, '<td class="line"><a href="">%i</a></td>' % (
421 current_line_number)
421 current_line_number)
422 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
422 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
423 yield 0, '</tr>'
423 yield 0, '</tr>'
424
424
425 last_shown_line_number = current_line_number
425 last_shown_line_number = current_line_number
426
426
427 current_line_number += 1
427 current_line_number += 1
428
428
429 yield 0, '</table>'
429 yield 0, '</table>'
430
430
431
431
432 def hsv_to_rgb(h, s, v):
432 def hsv_to_rgb(h, s, v):
433 """ Convert hsv color values to rgb """
433 """ Convert hsv color values to rgb """
434
434
435 if s == 0.0:
435 if s == 0.0:
436 return v, v, v
436 return v, v, v
437 i = int(h * 6.0) # XXX assume int() truncates!
437 i = int(h * 6.0) # XXX assume int() truncates!
438 f = (h * 6.0) - i
438 f = (h * 6.0) - i
439 p = v * (1.0 - s)
439 p = v * (1.0 - s)
440 q = v * (1.0 - s * f)
440 q = v * (1.0 - s * f)
441 t = v * (1.0 - s * (1.0 - f))
441 t = v * (1.0 - s * (1.0 - f))
442 i = i % 6
442 i = i % 6
443 if i == 0:
443 if i == 0:
444 return v, t, p
444 return v, t, p
445 if i == 1:
445 if i == 1:
446 return q, v, p
446 return q, v, p
447 if i == 2:
447 if i == 2:
448 return p, v, t
448 return p, v, t
449 if i == 3:
449 if i == 3:
450 return p, q, v
450 return p, q, v
451 if i == 4:
451 if i == 4:
452 return t, p, v
452 return t, p, v
453 if i == 5:
453 if i == 5:
454 return v, p, q
454 return v, p, q
455
455
456
456
457 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
457 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
458 """
458 """
459 Generator for getting n of evenly distributed colors using
459 Generator for getting n of evenly distributed colors using
460 hsv color and golden ratio. It always return same order of colors
460 hsv color and golden ratio. It always return same order of colors
461
461
462 :param n: number of colors to generate
462 :param n: number of colors to generate
463 :param saturation: saturation of returned colors
463 :param saturation: saturation of returned colors
464 :param lightness: lightness of returned colors
464 :param lightness: lightness of returned colors
465 :returns: RGB tuple
465 :returns: RGB tuple
466 """
466 """
467
467
468 golden_ratio = 0.618033988749895
468 golden_ratio = 0.618033988749895
469 h = 0.22717784590367374
469 h = 0.22717784590367374
470
470
471 for _ in xrange(n):
471 for _ in xrange(n):
472 h += golden_ratio
472 h += golden_ratio
473 h %= 1
473 h %= 1
474 HSV_tuple = [h, saturation, lightness]
474 HSV_tuple = [h, saturation, lightness]
475 RGB_tuple = hsv_to_rgb(*HSV_tuple)
475 RGB_tuple = hsv_to_rgb(*HSV_tuple)
476 yield map(lambda x: str(int(x * 256)), RGB_tuple)
476 yield map(lambda x: str(int(x * 256)), RGB_tuple)
477
477
478
478
479 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
479 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
480 """
480 """
481 Returns a function which when called with an argument returns a unique
481 Returns a function which when called with an argument returns a unique
482 color for that argument, eg.
482 color for that argument, eg.
483
483
484 :param n: number of colors to generate
484 :param n: number of colors to generate
485 :param saturation: saturation of returned colors
485 :param saturation: saturation of returned colors
486 :param lightness: lightness of returned colors
486 :param lightness: lightness of returned colors
487 :returns: css RGB string
487 :returns: css RGB string
488
488
489 >>> color_hash = color_hasher()
489 >>> color_hash = color_hasher()
490 >>> color_hash('hello')
490 >>> color_hash('hello')
491 'rgb(34, 12, 59)'
491 'rgb(34, 12, 59)'
492 >>> color_hash('hello')
492 >>> color_hash('hello')
493 'rgb(34, 12, 59)'
493 'rgb(34, 12, 59)'
494 >>> color_hash('other')
494 >>> color_hash('other')
495 'rgb(90, 224, 159)'
495 'rgb(90, 224, 159)'
496 """
496 """
497
497
498 color_dict = {}
498 color_dict = {}
499 cgenerator = unique_color_generator(
499 cgenerator = unique_color_generator(
500 saturation=saturation, lightness=lightness)
500 saturation=saturation, lightness=lightness)
501
501
502 def get_color_string(thing):
502 def get_color_string(thing):
503 if thing in color_dict:
503 if thing in color_dict:
504 col = color_dict[thing]
504 col = color_dict[thing]
505 else:
505 else:
506 col = color_dict[thing] = cgenerator.next()
506 col = color_dict[thing] = cgenerator.next()
507 return "rgb(%s)" % (', '.join(col))
507 return "rgb(%s)" % (', '.join(col))
508
508
509 return get_color_string
509 return get_color_string
510
510
511
511
512 def get_lexer_safe(mimetype=None, filepath=None):
512 def get_lexer_safe(mimetype=None, filepath=None):
513 """
513 """
514 Tries to return a relevant pygments lexer using mimetype/filepath name,
514 Tries to return a relevant pygments lexer using mimetype/filepath name,
515 defaulting to plain text if none could be found
515 defaulting to plain text if none could be found
516 """
516 """
517 lexer = None
517 lexer = None
518 try:
518 try:
519 if mimetype:
519 if mimetype:
520 lexer = get_lexer_for_mimetype(mimetype)
520 lexer = get_lexer_for_mimetype(mimetype)
521 if not lexer:
521 if not lexer:
522 lexer = get_lexer_for_filename(filepath)
522 lexer = get_lexer_for_filename(filepath)
523 except pygments.util.ClassNotFound:
523 except pygments.util.ClassNotFound:
524 pass
524 pass
525
525
526 if not lexer:
526 if not lexer:
527 lexer = get_lexer_by_name('text')
527 lexer = get_lexer_by_name('text')
528
528
529 return lexer
529 return lexer
530
530
531
531
532 def get_lexer_for_filenode(filenode):
532 def get_lexer_for_filenode(filenode):
533 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
533 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
534 return lexer
534 return lexer
535
535
536
536
537 def pygmentize(filenode, **kwargs):
537 def pygmentize(filenode, **kwargs):
538 """
538 """
539 pygmentize function using pygments
539 pygmentize function using pygments
540
540
541 :param filenode:
541 :param filenode:
542 """
542 """
543 lexer = get_lexer_for_filenode(filenode)
543 lexer = get_lexer_for_filenode(filenode)
544 return literal(code_highlight(filenode.content, lexer,
544 return literal(code_highlight(filenode.content, lexer,
545 CodeHtmlFormatter(**kwargs)))
545 CodeHtmlFormatter(**kwargs)))
546
546
547
547
548 def is_following_repo(repo_name, user_id):
548 def is_following_repo(repo_name, user_id):
549 from rhodecode.model.scm import ScmModel
549 from rhodecode.model.scm import ScmModel
550 return ScmModel().is_following_repo(repo_name, user_id)
550 return ScmModel().is_following_repo(repo_name, user_id)
551
551
552
552
553 class _Message(object):
553 class _Message(object):
554 """A message returned by ``Flash.pop_messages()``.
554 """A message returned by ``Flash.pop_messages()``.
555
555
556 Converting the message to a string returns the message text. Instances
556 Converting the message to a string returns the message text. Instances
557 also have the following attributes:
557 also have the following attributes:
558
558
559 * ``message``: the message text.
559 * ``message``: the message text.
560 * ``category``: the category specified when the message was created.
560 * ``category``: the category specified when the message was created.
561 """
561 """
562
562
563 def __init__(self, category, message):
563 def __init__(self, category, message):
564 self.category = category
564 self.category = category
565 self.message = message
565 self.message = message
566
566
567 def __str__(self):
567 def __str__(self):
568 return self.message
568 return self.message
569
569
570 __unicode__ = __str__
570 __unicode__ = __str__
571
571
572 def __html__(self):
572 def __html__(self):
573 return escape(safe_unicode(self.message))
573 return escape(safe_unicode(self.message))
574
574
575
575
576 class Flash(object):
576 class Flash(object):
577 # List of allowed categories. If None, allow any category.
577 # List of allowed categories. If None, allow any category.
578 categories = ["warning", "notice", "error", "success"]
578 categories = ["warning", "notice", "error", "success"]
579
579
580 # Default category if none is specified.
580 # Default category if none is specified.
581 default_category = "notice"
581 default_category = "notice"
582
582
583 def __init__(self, session_key="flash", categories=None,
583 def __init__(self, session_key="flash", categories=None,
584 default_category=None):
584 default_category=None):
585 """
585 """
586 Instantiate a ``Flash`` object.
586 Instantiate a ``Flash`` object.
587
587
588 ``session_key`` is the key to save the messages under in the user's
588 ``session_key`` is the key to save the messages under in the user's
589 session.
589 session.
590
590
591 ``categories`` is an optional list which overrides the default list
591 ``categories`` is an optional list which overrides the default list
592 of categories.
592 of categories.
593
593
594 ``default_category`` overrides the default category used for messages
594 ``default_category`` overrides the default category used for messages
595 when none is specified.
595 when none is specified.
596 """
596 """
597 self.session_key = session_key
597 self.session_key = session_key
598 if categories is not None:
598 if categories is not None:
599 self.categories = categories
599 self.categories = categories
600 if default_category is not None:
600 if default_category is not None:
601 self.default_category = default_category
601 self.default_category = default_category
602 if self.categories and self.default_category not in self.categories:
602 if self.categories and self.default_category not in self.categories:
603 raise ValueError(
603 raise ValueError(
604 "unrecognized default category %r" % (self.default_category,))
604 "unrecognized default category %r" % (self.default_category,))
605
605
606 def pop_messages(self, session=None, request=None):
606 def pop_messages(self, session=None, request=None):
607 """
607 """
608 Return all accumulated messages and delete them from the session.
608 Return all accumulated messages and delete them from the session.
609
609
610 The return value is a list of ``Message`` objects.
610 The return value is a list of ``Message`` objects.
611 """
611 """
612 messages = []
612 messages = []
613
613
614 if not session:
614 if not session:
615 if not request:
615 if not request:
616 request = get_current_request()
616 request = get_current_request()
617 session = request.session
617 session = request.session
618
618
619 # Pop the 'old' pylons flash messages. They are tuples of the form
619 # Pop the 'old' pylons flash messages. They are tuples of the form
620 # (category, message)
620 # (category, message)
621 for cat, msg in session.pop(self.session_key, []):
621 for cat, msg in session.pop(self.session_key, []):
622 messages.append(_Message(cat, msg))
622 messages.append(_Message(cat, msg))
623
623
624 # Pop the 'new' pyramid flash messages for each category as list
624 # Pop the 'new' pyramid flash messages for each category as list
625 # of strings.
625 # of strings.
626 for cat in self.categories:
626 for cat in self.categories:
627 for msg in session.pop_flash(queue=cat):
627 for msg in session.pop_flash(queue=cat):
628 messages.append(_Message(cat, msg))
628 messages.append(_Message(cat, msg))
629 # Map messages from the default queue to the 'notice' category.
629 # Map messages from the default queue to the 'notice' category.
630 for msg in session.pop_flash():
630 for msg in session.pop_flash():
631 messages.append(_Message('notice', msg))
631 messages.append(_Message('notice', msg))
632
632
633 session.save()
633 session.save()
634 return messages
634 return messages
635
635
636 def json_alerts(self, session=None, request=None):
636 def json_alerts(self, session=None, request=None):
637 payloads = []
637 payloads = []
638 messages = flash.pop_messages(session=session, request=request)
638 messages = flash.pop_messages(session=session, request=request)
639 if messages:
639 if messages:
640 for message in messages:
640 for message in messages:
641 subdata = {}
641 subdata = {}
642 if hasattr(message.message, 'rsplit'):
642 if hasattr(message.message, 'rsplit'):
643 flash_data = message.message.rsplit('|DELIM|', 1)
643 flash_data = message.message.rsplit('|DELIM|', 1)
644 org_message = flash_data[0]
644 org_message = flash_data[0]
645 if len(flash_data) > 1:
645 if len(flash_data) > 1:
646 subdata = json.loads(flash_data[1])
646 subdata = json.loads(flash_data[1])
647 else:
647 else:
648 org_message = message.message
648 org_message = message.message
649 payloads.append({
649 payloads.append({
650 'message': {
650 'message': {
651 'message': u'{}'.format(org_message),
651 'message': u'{}'.format(org_message),
652 'level': message.category,
652 'level': message.category,
653 'force': True,
653 'force': True,
654 'subdata': subdata
654 'subdata': subdata
655 }
655 }
656 })
656 })
657 return json.dumps(payloads)
657 return json.dumps(payloads)
658
658
659 def __call__(self, message, category=None, ignore_duplicate=False,
659 def __call__(self, message, category=None, ignore_duplicate=False,
660 session=None, request=None):
660 session=None, request=None):
661
661
662 if not session:
662 if not session:
663 if not request:
663 if not request:
664 request = get_current_request()
664 request = get_current_request()
665 session = request.session
665 session = request.session
666
666
667 session.flash(
667 session.flash(
668 message, queue=category, allow_duplicate=not ignore_duplicate)
668 message, queue=category, allow_duplicate=not ignore_duplicate)
669
669
670
670
671 flash = Flash()
671 flash = Flash()
672
672
673 #==============================================================================
673 #==============================================================================
674 # SCM FILTERS available via h.
674 # SCM FILTERS available via h.
675 #==============================================================================
675 #==============================================================================
676 from rhodecode.lib.vcs.utils import author_name, author_email
676 from rhodecode.lib.vcs.utils import author_name, author_email
677 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
677 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
678 from rhodecode.model.db import User, ChangesetStatus
678 from rhodecode.model.db import User, ChangesetStatus
679
679
680 capitalize = lambda x: x.capitalize()
680 capitalize = lambda x: x.capitalize()
681 email = author_email
681 email = author_email
682 short_id = lambda x: x[:12]
682 short_id = lambda x: x[:12]
683 hide_credentials = lambda x: ''.join(credentials_filter(x))
683 hide_credentials = lambda x: ''.join(credentials_filter(x))
684
684
685
685
686 import pytz
686 import pytz
687 import tzlocal
687 import tzlocal
688 local_timezone = tzlocal.get_localzone()
688 local_timezone = tzlocal.get_localzone()
689
689
690
690
691 def age_component(datetime_iso, value=None, time_is_local=False):
691 def age_component(datetime_iso, value=None, time_is_local=False):
692 title = value or format_date(datetime_iso)
692 title = value or format_date(datetime_iso)
693 tzinfo = '+00:00'
693 tzinfo = '+00:00'
694
694
695 # detect if we have a timezone info, otherwise, add it
695 # detect if we have a timezone info, otherwise, add it
696 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
696 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
697 force_timezone = os.environ.get('RC_TIMEZONE', '')
697 force_timezone = os.environ.get('RC_TIMEZONE', '')
698 if force_timezone:
698 if force_timezone:
699 force_timezone = pytz.timezone(force_timezone)
699 force_timezone = pytz.timezone(force_timezone)
700 timezone = force_timezone or local_timezone
700 timezone = force_timezone or local_timezone
701 offset = timezone.localize(datetime_iso).strftime('%z')
701 offset = timezone.localize(datetime_iso).strftime('%z')
702 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
702 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
703
703
704 return literal(
704 return literal(
705 '<time class="timeago tooltip" '
705 '<time class="timeago tooltip" '
706 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
706 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
707 datetime_iso, title, tzinfo))
707 datetime_iso, title, tzinfo))
708
708
709
709
710 def _shorten_commit_id(commit_id, commit_len=None):
710 def _shorten_commit_id(commit_id, commit_len=None):
711 if commit_len is None:
711 if commit_len is None:
712 request = get_current_request()
712 request = get_current_request()
713 commit_len = request.call_context.visual.show_sha_length
713 commit_len = request.call_context.visual.show_sha_length
714 return commit_id[:commit_len]
714 return commit_id[:commit_len]
715
715
716
716
717 def show_id(commit, show_idx=None, commit_len=None):
717 def show_id(commit, show_idx=None, commit_len=None):
718 """
718 """
719 Configurable function that shows ID
719 Configurable function that shows ID
720 by default it's r123:fffeeefffeee
720 by default it's r123:fffeeefffeee
721
721
722 :param commit: commit instance
722 :param commit: commit instance
723 """
723 """
724 if show_idx is None:
724 if show_idx is None:
725 request = get_current_request()
725 request = get_current_request()
726 show_idx = request.call_context.visual.show_revision_number
726 show_idx = request.call_context.visual.show_revision_number
727
727
728 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
728 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
729 if show_idx:
729 if show_idx:
730 return 'r%s:%s' % (commit.idx, raw_id)
730 return 'r%s:%s' % (commit.idx, raw_id)
731 else:
731 else:
732 return '%s' % (raw_id, )
732 return '%s' % (raw_id, )
733
733
734
734
735 def format_date(date):
735 def format_date(date):
736 """
736 """
737 use a standardized formatting for dates used in RhodeCode
737 use a standardized formatting for dates used in RhodeCode
738
738
739 :param date: date/datetime object
739 :param date: date/datetime object
740 :return: formatted date
740 :return: formatted date
741 """
741 """
742
742
743 if date:
743 if date:
744 _fmt = "%a, %d %b %Y %H:%M:%S"
744 _fmt = "%a, %d %b %Y %H:%M:%S"
745 return safe_unicode(date.strftime(_fmt))
745 return safe_unicode(date.strftime(_fmt))
746
746
747 return u""
747 return u""
748
748
749
749
750 class _RepoChecker(object):
750 class _RepoChecker(object):
751
751
752 def __init__(self, backend_alias):
752 def __init__(self, backend_alias):
753 self._backend_alias = backend_alias
753 self._backend_alias = backend_alias
754
754
755 def __call__(self, repository):
755 def __call__(self, repository):
756 if hasattr(repository, 'alias'):
756 if hasattr(repository, 'alias'):
757 _type = repository.alias
757 _type = repository.alias
758 elif hasattr(repository, 'repo_type'):
758 elif hasattr(repository, 'repo_type'):
759 _type = repository.repo_type
759 _type = repository.repo_type
760 else:
760 else:
761 _type = repository
761 _type = repository
762 return _type == self._backend_alias
762 return _type == self._backend_alias
763
763
764
764
765 is_git = _RepoChecker('git')
765 is_git = _RepoChecker('git')
766 is_hg = _RepoChecker('hg')
766 is_hg = _RepoChecker('hg')
767 is_svn = _RepoChecker('svn')
767 is_svn = _RepoChecker('svn')
768
768
769
769
770 def get_repo_type_by_name(repo_name):
770 def get_repo_type_by_name(repo_name):
771 repo = Repository.get_by_repo_name(repo_name)
771 repo = Repository.get_by_repo_name(repo_name)
772 if repo:
772 if repo:
773 return repo.repo_type
773 return repo.repo_type
774
774
775
775
776 def is_svn_without_proxy(repository):
776 def is_svn_without_proxy(repository):
777 if is_svn(repository):
777 if is_svn(repository):
778 from rhodecode.model.settings import VcsSettingsModel
778 from rhodecode.model.settings import VcsSettingsModel
779 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
779 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
780 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
780 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
781 return False
781 return False
782
782
783
783
784 def discover_user(author):
784 def discover_user(author):
785 """
785 """
786 Tries to discover RhodeCode User based on the autho string. Author string
786 Tries to discover RhodeCode User based on the autho string. Author string
787 is typically `FirstName LastName <email@address.com>`
787 is typically `FirstName LastName <email@address.com>`
788 """
788 """
789
789
790 # if author is already an instance use it for extraction
790 # if author is already an instance use it for extraction
791 if isinstance(author, User):
791 if isinstance(author, User):
792 return author
792 return author
793
793
794 # Valid email in the attribute passed, see if they're in the system
794 # Valid email in the attribute passed, see if they're in the system
795 _email = author_email(author)
795 _email = author_email(author)
796 if _email != '':
796 if _email != '':
797 user = User.get_by_email(_email, case_insensitive=True, cache=True)
797 user = User.get_by_email(_email, case_insensitive=True, cache=True)
798 if user is not None:
798 if user is not None:
799 return user
799 return user
800
800
801 # Maybe it's a username, we try to extract it and fetch by username ?
801 # Maybe it's a username, we try to extract it and fetch by username ?
802 _author = author_name(author)
802 _author = author_name(author)
803 user = User.get_by_username(_author, case_insensitive=True, cache=True)
803 user = User.get_by_username(_author, case_insensitive=True, cache=True)
804 if user is not None:
804 if user is not None:
805 return user
805 return user
806
806
807 return None
807 return None
808
808
809
809
810 def email_or_none(author):
810 def email_or_none(author):
811 # extract email from the commit string
811 # extract email from the commit string
812 _email = author_email(author)
812 _email = author_email(author)
813
813
814 # If we have an email, use it, otherwise
814 # If we have an email, use it, otherwise
815 # see if it contains a username we can get an email from
815 # see if it contains a username we can get an email from
816 if _email != '':
816 if _email != '':
817 return _email
817 return _email
818 else:
818 else:
819 user = User.get_by_username(
819 user = User.get_by_username(
820 author_name(author), case_insensitive=True, cache=True)
820 author_name(author), case_insensitive=True, cache=True)
821
821
822 if user is not None:
822 if user is not None:
823 return user.email
823 return user.email
824
824
825 # No valid email, not a valid user in the system, none!
825 # No valid email, not a valid user in the system, none!
826 return None
826 return None
827
827
828
828
829 def link_to_user(author, length=0, **kwargs):
829 def link_to_user(author, length=0, **kwargs):
830 user = discover_user(author)
830 user = discover_user(author)
831 # user can be None, but if we have it already it means we can re-use it
831 # user can be None, but if we have it already it means we can re-use it
832 # in the person() function, so we save 1 intensive-query
832 # in the person() function, so we save 1 intensive-query
833 if user:
833 if user:
834 author = user
834 author = user
835
835
836 display_person = person(author, 'username_or_name_or_email')
836 display_person = person(author, 'username_or_name_or_email')
837 if length:
837 if length:
838 display_person = shorter(display_person, length)
838 display_person = shorter(display_person, length)
839
839
840 if user:
840 if user:
841 return link_to(
841 return link_to(
842 escape(display_person),
842 escape(display_person),
843 route_path('user_profile', username=user.username),
843 route_path('user_profile', username=user.username),
844 **kwargs)
844 **kwargs)
845 else:
845 else:
846 return escape(display_person)
846 return escape(display_person)
847
847
848
848
849 def link_to_group(users_group_name, **kwargs):
849 def link_to_group(users_group_name, **kwargs):
850 return link_to(
850 return link_to(
851 escape(users_group_name),
851 escape(users_group_name),
852 route_path('user_group_profile', user_group_name=users_group_name),
852 route_path('user_group_profile', user_group_name=users_group_name),
853 **kwargs)
853 **kwargs)
854
854
855
855
856 def person(author, show_attr="username_and_name"):
856 def person(author, show_attr="username_and_name"):
857 user = discover_user(author)
857 user = discover_user(author)
858 if user:
858 if user:
859 return getattr(user, show_attr)
859 return getattr(user, show_attr)
860 else:
860 else:
861 _author = author_name(author)
861 _author = author_name(author)
862 _email = email(author)
862 _email = email(author)
863 return _author or _email
863 return _author or _email
864
864
865
865
866 def author_string(email):
866 def author_string(email):
867 if email:
867 if email:
868 user = User.get_by_email(email, case_insensitive=True, cache=True)
868 user = User.get_by_email(email, case_insensitive=True, cache=True)
869 if user:
869 if user:
870 if user.first_name or user.last_name:
870 if user.first_name or user.last_name:
871 return '%s %s &lt;%s&gt;' % (
871 return '%s %s &lt;%s&gt;' % (
872 user.first_name, user.last_name, email)
872 user.first_name, user.last_name, email)
873 else:
873 else:
874 return email
874 return email
875 else:
875 else:
876 return email
876 return email
877 else:
877 else:
878 return None
878 return None
879
879
880
880
881 def person_by_id(id_, show_attr="username_and_name"):
881 def person_by_id(id_, show_attr="username_and_name"):
882 # attr to return from fetched user
882 # attr to return from fetched user
883 person_getter = lambda usr: getattr(usr, show_attr)
883 person_getter = lambda usr: getattr(usr, show_attr)
884
884
885 #maybe it's an ID ?
885 #maybe it's an ID ?
886 if str(id_).isdigit() or isinstance(id_, int):
886 if str(id_).isdigit() or isinstance(id_, int):
887 id_ = int(id_)
887 id_ = int(id_)
888 user = User.get(id_)
888 user = User.get(id_)
889 if user is not None:
889 if user is not None:
890 return person_getter(user)
890 return person_getter(user)
891 return id_
891 return id_
892
892
893
893
894 def gravatar_with_user(request, author, show_disabled=False):
894 def gravatar_with_user(request, author, show_disabled=False):
895 _render = request.get_partial_renderer(
895 _render = request.get_partial_renderer(
896 'rhodecode:templates/base/base.mako')
896 'rhodecode:templates/base/base.mako')
897 return _render('gravatar_with_user', author, show_disabled=show_disabled)
897 return _render('gravatar_with_user', author, show_disabled=show_disabled)
898
898
899
899
900 tags_paterns = OrderedDict((
900 tags_paterns = OrderedDict((
901 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
901 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
902 '<div class="metatag" tag="lang">\\2</div>')),
902 '<div class="metatag" tag="lang">\\2</div>')),
903
903
904 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
904 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
905 '<div class="metatag" tag="see">see: \\1 </div>')),
905 '<div class="metatag" tag="see">see: \\1 </div>')),
906
906
907 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
907 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
908 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
908 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
909
909
910 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
910 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
911 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
911 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
912
912
913 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
913 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
914 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
914 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
915
915
916 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
916 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
917 '<div class="metatag" tag="state \\1">\\1</div>')),
917 '<div class="metatag" tag="state \\1">\\1</div>')),
918
918
919 # label in grey
919 # label in grey
920 ('label', (re.compile(r'\[([a-z]+)\]'),
920 ('label', (re.compile(r'\[([a-z]+)\]'),
921 '<div class="metatag" tag="label">\\1</div>')),
921 '<div class="metatag" tag="label">\\1</div>')),
922
922
923 # generic catch all in grey
923 # generic catch all in grey
924 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
924 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
925 '<div class="metatag" tag="generic">\\1</div>')),
925 '<div class="metatag" tag="generic">\\1</div>')),
926 ))
926 ))
927
927
928
928
929 def extract_metatags(value):
929 def extract_metatags(value):
930 """
930 """
931 Extract supported meta-tags from given text value
931 Extract supported meta-tags from given text value
932 """
932 """
933 tags = []
933 tags = []
934 if not value:
934 if not value:
935 return tags, ''
935 return tags, ''
936
936
937 for key, val in tags_paterns.items():
937 for key, val in tags_paterns.items():
938 pat, replace_html = val
938 pat, replace_html = val
939 tags.extend([(key, x.group()) for x in pat.finditer(value)])
939 tags.extend([(key, x.group()) for x in pat.finditer(value)])
940 value = pat.sub('', value)
940 value = pat.sub('', value)
941
941
942 return tags, value
942 return tags, value
943
943
944
944
945 def style_metatag(tag_type, value):
945 def style_metatag(tag_type, value):
946 """
946 """
947 converts tags from value into html equivalent
947 converts tags from value into html equivalent
948 """
948 """
949 if not value:
949 if not value:
950 return ''
950 return ''
951
951
952 html_value = value
952 html_value = value
953 tag_data = tags_paterns.get(tag_type)
953 tag_data = tags_paterns.get(tag_type)
954 if tag_data:
954 if tag_data:
955 pat, replace_html = tag_data
955 pat, replace_html = tag_data
956 # convert to plain `unicode` instead of a markup tag to be used in
956 # convert to plain `unicode` instead of a markup tag to be used in
957 # regex expressions. safe_unicode doesn't work here
957 # regex expressions. safe_unicode doesn't work here
958 html_value = pat.sub(replace_html, unicode(value))
958 html_value = pat.sub(replace_html, unicode(value))
959
959
960 return html_value
960 return html_value
961
961
962
962
963 def bool2icon(value, show_at_false=True):
963 def bool2icon(value, show_at_false=True):
964 """
964 """
965 Returns boolean value of a given value, represented as html element with
965 Returns boolean value of a given value, represented as html element with
966 classes that will represent icons
966 classes that will represent icons
967
967
968 :param value: given value to convert to html node
968 :param value: given value to convert to html node
969 """
969 """
970
970
971 if value: # does bool conversion
971 if value: # does bool conversion
972 return HTML.tag('i', class_="icon-true")
972 return HTML.tag('i', class_="icon-true")
973 else: # not true as bool
973 else: # not true as bool
974 if show_at_false:
974 if show_at_false:
975 return HTML.tag('i', class_="icon-false")
975 return HTML.tag('i', class_="icon-false")
976 return HTML.tag('i')
976 return HTML.tag('i')
977
977
978 #==============================================================================
978 #==============================================================================
979 # PERMS
979 # PERMS
980 #==============================================================================
980 #==============================================================================
981 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
981 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
982 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
982 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
983 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
983 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
984 csrf_token_key
984 csrf_token_key
985
985
986
986
987 #==============================================================================
987 #==============================================================================
988 # GRAVATAR URL
988 # GRAVATAR URL
989 #==============================================================================
989 #==============================================================================
990 class InitialsGravatar(object):
990 class InitialsGravatar(object):
991 def __init__(self, email_address, first_name, last_name, size=30,
991 def __init__(self, email_address, first_name, last_name, size=30,
992 background=None, text_color='#fff'):
992 background=None, text_color='#fff'):
993 self.size = size
993 self.size = size
994 self.first_name = first_name
994 self.first_name = first_name
995 self.last_name = last_name
995 self.last_name = last_name
996 self.email_address = email_address
996 self.email_address = email_address
997 self.background = background or self.str2color(email_address)
997 self.background = background or self.str2color(email_address)
998 self.text_color = text_color
998 self.text_color = text_color
999
999
1000 def get_color_bank(self):
1000 def get_color_bank(self):
1001 """
1001 """
1002 returns a predefined list of colors that gravatars can use.
1002 returns a predefined list of colors that gravatars can use.
1003 Those are randomized distinct colors that guarantee readability and
1003 Those are randomized distinct colors that guarantee readability and
1004 uniqueness.
1004 uniqueness.
1005
1005
1006 generated with: http://phrogz.net/css/distinct-colors.html
1006 generated with: http://phrogz.net/css/distinct-colors.html
1007 """
1007 """
1008 return [
1008 return [
1009 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1009 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1010 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1010 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1011 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1011 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1012 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1012 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1013 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1013 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1014 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1014 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1015 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1015 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1016 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1016 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1017 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1017 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1018 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1018 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1019 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1019 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1020 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1020 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1021 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1021 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1022 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1022 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1023 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1023 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1024 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1024 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1025 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1025 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1026 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1026 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1027 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1027 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1028 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1028 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1029 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1029 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1030 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1030 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1031 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1031 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1032 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1032 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1033 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1033 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1034 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1034 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1035 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1035 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1036 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1036 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1037 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1037 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1038 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1038 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1039 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1039 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1040 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1040 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1041 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1041 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1042 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1042 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1043 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1043 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1044 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1044 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1045 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1045 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1046 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1046 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1047 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1047 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1048 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1048 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1049 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1049 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1050 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1050 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1051 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1051 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1052 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1052 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1053 '#4f8c46', '#368dd9', '#5c0073'
1053 '#4f8c46', '#368dd9', '#5c0073'
1054 ]
1054 ]
1055
1055
1056 def rgb_to_hex_color(self, rgb_tuple):
1056 def rgb_to_hex_color(self, rgb_tuple):
1057 """
1057 """
1058 Converts an rgb_tuple passed to an hex color.
1058 Converts an rgb_tuple passed to an hex color.
1059
1059
1060 :param rgb_tuple: tuple with 3 ints represents rgb color space
1060 :param rgb_tuple: tuple with 3 ints represents rgb color space
1061 """
1061 """
1062 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1062 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1063
1063
1064 def email_to_int_list(self, email_str):
1064 def email_to_int_list(self, email_str):
1065 """
1065 """
1066 Get every byte of the hex digest value of email and turn it to integer.
1066 Get every byte of the hex digest value of email and turn it to integer.
1067 It's going to be always between 0-255
1067 It's going to be always between 0-255
1068 """
1068 """
1069 digest = md5_safe(email_str.lower())
1069 digest = md5_safe(email_str.lower())
1070 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1070 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1071
1071
1072 def pick_color_bank_index(self, email_str, color_bank):
1072 def pick_color_bank_index(self, email_str, color_bank):
1073 return self.email_to_int_list(email_str)[0] % len(color_bank)
1073 return self.email_to_int_list(email_str)[0] % len(color_bank)
1074
1074
1075 def str2color(self, email_str):
1075 def str2color(self, email_str):
1076 """
1076 """
1077 Tries to map in a stable algorithm an email to color
1077 Tries to map in a stable algorithm an email to color
1078
1078
1079 :param email_str:
1079 :param email_str:
1080 """
1080 """
1081 color_bank = self.get_color_bank()
1081 color_bank = self.get_color_bank()
1082 # pick position (module it's length so we always find it in the
1082 # pick position (module it's length so we always find it in the
1083 # bank even if it's smaller than 256 values
1083 # bank even if it's smaller than 256 values
1084 pos = self.pick_color_bank_index(email_str, color_bank)
1084 pos = self.pick_color_bank_index(email_str, color_bank)
1085 return color_bank[pos]
1085 return color_bank[pos]
1086
1086
1087 def normalize_email(self, email_address):
1087 def normalize_email(self, email_address):
1088 import unicodedata
1088 import unicodedata
1089 # default host used to fill in the fake/missing email
1089 # default host used to fill in the fake/missing email
1090 default_host = u'localhost'
1090 default_host = u'localhost'
1091
1091
1092 if not email_address:
1092 if not email_address:
1093 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1093 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1094
1094
1095 email_address = safe_unicode(email_address)
1095 email_address = safe_unicode(email_address)
1096
1096
1097 if u'@' not in email_address:
1097 if u'@' not in email_address:
1098 email_address = u'%s@%s' % (email_address, default_host)
1098 email_address = u'%s@%s' % (email_address, default_host)
1099
1099
1100 if email_address.endswith(u'@'):
1100 if email_address.endswith(u'@'):
1101 email_address = u'%s%s' % (email_address, default_host)
1101 email_address = u'%s%s' % (email_address, default_host)
1102
1102
1103 email_address = unicodedata.normalize('NFKD', email_address)\
1103 email_address = unicodedata.normalize('NFKD', email_address)\
1104 .encode('ascii', 'ignore')
1104 .encode('ascii', 'ignore')
1105 return email_address
1105 return email_address
1106
1106
1107 def get_initials(self):
1107 def get_initials(self):
1108 """
1108 """
1109 Returns 2 letter initials calculated based on the input.
1109 Returns 2 letter initials calculated based on the input.
1110 The algorithm picks first given email address, and takes first letter
1110 The algorithm picks first given email address, and takes first letter
1111 of part before @, and then the first letter of server name. In case
1111 of part before @, and then the first letter of server name. In case
1112 the part before @ is in a format of `somestring.somestring2` it replaces
1112 the part before @ is in a format of `somestring.somestring2` it replaces
1113 the server letter with first letter of somestring2
1113 the server letter with first letter of somestring2
1114
1114
1115 In case function was initialized with both first and lastname, this
1115 In case function was initialized with both first and lastname, this
1116 overrides the extraction from email by first letter of the first and
1116 overrides the extraction from email by first letter of the first and
1117 last name. We add special logic to that functionality, In case Full name
1117 last name. We add special logic to that functionality, In case Full name
1118 is compound, like Guido Von Rossum, we use last part of the last name
1118 is compound, like Guido Von Rossum, we use last part of the last name
1119 (Von Rossum) picking `R`.
1119 (Von Rossum) picking `R`.
1120
1120
1121 Function also normalizes the non-ascii characters to they ascii
1121 Function also normalizes the non-ascii characters to they ascii
1122 representation, eg Ą => A
1122 representation, eg Ą => A
1123 """
1123 """
1124 import unicodedata
1124 import unicodedata
1125 # replace non-ascii to ascii
1125 # replace non-ascii to ascii
1126 first_name = unicodedata.normalize(
1126 first_name = unicodedata.normalize(
1127 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1127 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1128 last_name = unicodedata.normalize(
1128 last_name = unicodedata.normalize(
1129 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1129 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1130
1130
1131 # do NFKD encoding, and also make sure email has proper format
1131 # do NFKD encoding, and also make sure email has proper format
1132 email_address = self.normalize_email(self.email_address)
1132 email_address = self.normalize_email(self.email_address)
1133
1133
1134 # first push the email initials
1134 # first push the email initials
1135 prefix, server = email_address.split('@', 1)
1135 prefix, server = email_address.split('@', 1)
1136
1136
1137 # check if prefix is maybe a 'first_name.last_name' syntax
1137 # check if prefix is maybe a 'first_name.last_name' syntax
1138 _dot_split = prefix.rsplit('.', 1)
1138 _dot_split = prefix.rsplit('.', 1)
1139 if len(_dot_split) == 2 and _dot_split[1]:
1139 if len(_dot_split) == 2 and _dot_split[1]:
1140 initials = [_dot_split[0][0], _dot_split[1][0]]
1140 initials = [_dot_split[0][0], _dot_split[1][0]]
1141 else:
1141 else:
1142 initials = [prefix[0], server[0]]
1142 initials = [prefix[0], server[0]]
1143
1143
1144 # then try to replace either first_name or last_name
1144 # then try to replace either first_name or last_name
1145 fn_letter = (first_name or " ")[0].strip()
1145 fn_letter = (first_name or " ")[0].strip()
1146 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1146 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1147
1147
1148 if fn_letter:
1148 if fn_letter:
1149 initials[0] = fn_letter
1149 initials[0] = fn_letter
1150
1150
1151 if ln_letter:
1151 if ln_letter:
1152 initials[1] = ln_letter
1152 initials[1] = ln_letter
1153
1153
1154 return ''.join(initials).upper()
1154 return ''.join(initials).upper()
1155
1155
1156 def get_img_data_by_type(self, font_family, img_type):
1156 def get_img_data_by_type(self, font_family, img_type):
1157 default_user = """
1157 default_user = """
1158 <svg xmlns="http://www.w3.org/2000/svg"
1158 <svg xmlns="http://www.w3.org/2000/svg"
1159 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1159 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1160 viewBox="-15 -10 439.165 429.164"
1160 viewBox="-15 -10 439.165 429.164"
1161
1161
1162 xml:space="preserve"
1162 xml:space="preserve"
1163 style="background:{background};" >
1163 style="background:{background};" >
1164
1164
1165 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1165 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1166 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1166 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1167 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1167 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1168 168.596,153.916,216.671,
1168 168.596,153.916,216.671,
1169 204.583,216.671z" fill="{text_color}"/>
1169 204.583,216.671z" fill="{text_color}"/>
1170 <path d="M407.164,374.717L360.88,
1170 <path d="M407.164,374.717L360.88,
1171 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1171 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1172 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1172 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1173 15.366-44.203,23.488-69.076,23.488c-24.877,
1173 15.366-44.203,23.488-69.076,23.488c-24.877,
1174 0-48.762-8.122-69.078-23.488
1174 0-48.762-8.122-69.078-23.488
1175 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1175 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1176 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1176 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1177 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1177 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1178 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1178 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1179 19.402-10.527 C409.699,390.129,
1179 19.402-10.527 C409.699,390.129,
1180 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1180 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1181 </svg>""".format(
1181 </svg>""".format(
1182 size=self.size,
1182 size=self.size,
1183 background='#979797', # @grey4
1183 background='#979797', # @grey4
1184 text_color=self.text_color,
1184 text_color=self.text_color,
1185 font_family=font_family)
1185 font_family=font_family)
1186
1186
1187 return {
1187 return {
1188 "default_user": default_user
1188 "default_user": default_user
1189 }[img_type]
1189 }[img_type]
1190
1190
1191 def get_img_data(self, svg_type=None):
1191 def get_img_data(self, svg_type=None):
1192 """
1192 """
1193 generates the svg metadata for image
1193 generates the svg metadata for image
1194 """
1194 """
1195 fonts = [
1195 fonts = [
1196 '-apple-system',
1196 '-apple-system',
1197 'BlinkMacSystemFont',
1197 'BlinkMacSystemFont',
1198 'Segoe UI',
1198 'Segoe UI',
1199 'Roboto',
1199 'Roboto',
1200 'Oxygen-Sans',
1200 'Oxygen-Sans',
1201 'Ubuntu',
1201 'Ubuntu',
1202 'Cantarell',
1202 'Cantarell',
1203 'Helvetica Neue',
1203 'Helvetica Neue',
1204 'sans-serif'
1204 'sans-serif'
1205 ]
1205 ]
1206 font_family = ','.join(fonts)
1206 font_family = ','.join(fonts)
1207 if svg_type:
1207 if svg_type:
1208 return self.get_img_data_by_type(font_family, svg_type)
1208 return self.get_img_data_by_type(font_family, svg_type)
1209
1209
1210 initials = self.get_initials()
1210 initials = self.get_initials()
1211 img_data = """
1211 img_data = """
1212 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1212 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1213 width="{size}" height="{size}"
1213 width="{size}" height="{size}"
1214 style="width: 100%; height: 100%; background-color: {background}"
1214 style="width: 100%; height: 100%; background-color: {background}"
1215 viewBox="0 0 {size} {size}">
1215 viewBox="0 0 {size} {size}">
1216 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1216 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1217 pointer-events="auto" fill="{text_color}"
1217 pointer-events="auto" fill="{text_color}"
1218 font-family="{font_family}"
1218 font-family="{font_family}"
1219 style="font-weight: 400; font-size: {f_size}px;">{text}
1219 style="font-weight: 400; font-size: {f_size}px;">{text}
1220 </text>
1220 </text>
1221 </svg>""".format(
1221 </svg>""".format(
1222 size=self.size,
1222 size=self.size,
1223 f_size=self.size/2.05, # scale the text inside the box nicely
1223 f_size=self.size/2.05, # scale the text inside the box nicely
1224 background=self.background,
1224 background=self.background,
1225 text_color=self.text_color,
1225 text_color=self.text_color,
1226 text=initials.upper(),
1226 text=initials.upper(),
1227 font_family=font_family)
1227 font_family=font_family)
1228
1228
1229 return img_data
1229 return img_data
1230
1230
1231 def generate_svg(self, svg_type=None):
1231 def generate_svg(self, svg_type=None):
1232 img_data = self.get_img_data(svg_type)
1232 img_data = self.get_img_data(svg_type)
1233 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1233 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1234
1234
1235
1235
1236 def initials_gravatar(email_address, first_name, last_name, size=30):
1236 def initials_gravatar(email_address, first_name, last_name, size=30):
1237 svg_type = None
1237 svg_type = None
1238 if email_address == User.DEFAULT_USER_EMAIL:
1238 if email_address == User.DEFAULT_USER_EMAIL:
1239 svg_type = 'default_user'
1239 svg_type = 'default_user'
1240 klass = InitialsGravatar(email_address, first_name, last_name, size)
1240 klass = InitialsGravatar(email_address, first_name, last_name, size)
1241 return klass.generate_svg(svg_type=svg_type)
1241 return klass.generate_svg(svg_type=svg_type)
1242
1242
1243
1243
1244 def gravatar_url(email_address, size=30, request=None):
1244 def gravatar_url(email_address, size=30, request=None):
1245 request = get_current_request()
1245 request = get_current_request()
1246 _use_gravatar = request.call_context.visual.use_gravatar
1246 _use_gravatar = request.call_context.visual.use_gravatar
1247 _gravatar_url = request.call_context.visual.gravatar_url
1247 _gravatar_url = request.call_context.visual.gravatar_url
1248
1248
1249 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1249 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1250
1250
1251 email_address = email_address or User.DEFAULT_USER_EMAIL
1251 email_address = email_address or User.DEFAULT_USER_EMAIL
1252 if isinstance(email_address, unicode):
1252 if isinstance(email_address, unicode):
1253 # hashlib crashes on unicode items
1253 # hashlib crashes on unicode items
1254 email_address = safe_str(email_address)
1254 email_address = safe_str(email_address)
1255
1255
1256 # empty email or default user
1256 # empty email or default user
1257 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1257 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1258 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1258 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1259
1259
1260 if _use_gravatar:
1260 if _use_gravatar:
1261 # TODO: Disuse pyramid thread locals. Think about another solution to
1261 # TODO: Disuse pyramid thread locals. Think about another solution to
1262 # get the host and schema here.
1262 # get the host and schema here.
1263 request = get_current_request()
1263 request = get_current_request()
1264 tmpl = safe_str(_gravatar_url)
1264 tmpl = safe_str(_gravatar_url)
1265 tmpl = tmpl.replace('{email}', email_address)\
1265 tmpl = tmpl.replace('{email}', email_address)\
1266 .replace('{md5email}', md5_safe(email_address.lower())) \
1266 .replace('{md5email}', md5_safe(email_address.lower())) \
1267 .replace('{netloc}', request.host)\
1267 .replace('{netloc}', request.host)\
1268 .replace('{scheme}', request.scheme)\
1268 .replace('{scheme}', request.scheme)\
1269 .replace('{size}', safe_str(size))
1269 .replace('{size}', safe_str(size))
1270 return tmpl
1270 return tmpl
1271 else:
1271 else:
1272 return initials_gravatar(email_address, '', '', size=size)
1272 return initials_gravatar(email_address, '', '', size=size)
1273
1273
1274
1274
1275 class Page(_Page):
1275 class Page(_Page):
1276 """
1276 """
1277 Custom pager to match rendering style with paginator
1277 Custom pager to match rendering style with paginator
1278 """
1278 """
1279
1279
1280 def _get_pos(self, cur_page, max_page, items):
1280 def _get_pos(self, cur_page, max_page, items):
1281 edge = (items / 2) + 1
1281 edge = (items / 2) + 1
1282 if (cur_page <= edge):
1282 if (cur_page <= edge):
1283 radius = max(items / 2, items - cur_page)
1283 radius = max(items / 2, items - cur_page)
1284 elif (max_page - cur_page) < edge:
1284 elif (max_page - cur_page) < edge:
1285 radius = (items - 1) - (max_page - cur_page)
1285 radius = (items - 1) - (max_page - cur_page)
1286 else:
1286 else:
1287 radius = items / 2
1287 radius = items / 2
1288
1288
1289 left = max(1, (cur_page - (radius)))
1289 left = max(1, (cur_page - (radius)))
1290 right = min(max_page, cur_page + (radius))
1290 right = min(max_page, cur_page + (radius))
1291 return left, cur_page, right
1291 return left, cur_page, right
1292
1292
1293 def _range(self, regexp_match):
1293 def _range(self, regexp_match):
1294 """
1294 """
1295 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1295 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1296
1296
1297 Arguments:
1297 Arguments:
1298
1298
1299 regexp_match
1299 regexp_match
1300 A "re" (regular expressions) match object containing the
1300 A "re" (regular expressions) match object containing the
1301 radius of linked pages around the current page in
1301 radius of linked pages around the current page in
1302 regexp_match.group(1) as a string
1302 regexp_match.group(1) as a string
1303
1303
1304 This function is supposed to be called as a callable in
1304 This function is supposed to be called as a callable in
1305 re.sub.
1305 re.sub.
1306
1306
1307 """
1307 """
1308 radius = int(regexp_match.group(1))
1308 radius = int(regexp_match.group(1))
1309
1309
1310 # Compute the first and last page number within the radius
1310 # Compute the first and last page number within the radius
1311 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1311 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1312 # -> leftmost_page = 5
1312 # -> leftmost_page = 5
1313 # -> rightmost_page = 9
1313 # -> rightmost_page = 9
1314 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1314 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1315 self.last_page,
1315 self.last_page,
1316 (radius * 2) + 1)
1316 (radius * 2) + 1)
1317 nav_items = []
1317 nav_items = []
1318
1318
1319 # Create a link to the first page (unless we are on the first page
1319 # Create a link to the first page (unless we are on the first page
1320 # or there would be no need to insert '..' spacers)
1320 # or there would be no need to insert '..' spacers)
1321 if self.page != self.first_page and self.first_page < leftmost_page:
1321 if self.page != self.first_page and self.first_page < leftmost_page:
1322 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1322 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1323
1323
1324 # Insert dots if there are pages between the first page
1324 # Insert dots if there are pages between the first page
1325 # and the currently displayed page range
1325 # and the currently displayed page range
1326 if leftmost_page - self.first_page > 1:
1326 if leftmost_page - self.first_page > 1:
1327 # Wrap in a SPAN tag if nolink_attr is set
1327 # Wrap in a SPAN tag if nolink_attr is set
1328 text = '..'
1328 text = '..'
1329 if self.dotdot_attr:
1329 if self.dotdot_attr:
1330 text = HTML.span(c=text, **self.dotdot_attr)
1330 text = HTML.span(c=text, **self.dotdot_attr)
1331 nav_items.append(text)
1331 nav_items.append(text)
1332
1332
1333 for thispage in xrange(leftmost_page, rightmost_page + 1):
1333 for thispage in xrange(leftmost_page, rightmost_page + 1):
1334 # Hilight the current page number and do not use a link
1334 # Hilight the current page number and do not use a link
1335 if thispage == self.page:
1335 if thispage == self.page:
1336 text = '%s' % (thispage,)
1336 text = '%s' % (thispage,)
1337 # Wrap in a SPAN tag if nolink_attr is set
1337 # Wrap in a SPAN tag if nolink_attr is set
1338 if self.curpage_attr:
1338 if self.curpage_attr:
1339 text = HTML.span(c=text, **self.curpage_attr)
1339 text = HTML.span(c=text, **self.curpage_attr)
1340 nav_items.append(text)
1340 nav_items.append(text)
1341 # Otherwise create just a link to that page
1341 # Otherwise create just a link to that page
1342 else:
1342 else:
1343 text = '%s' % (thispage,)
1343 text = '%s' % (thispage,)
1344 nav_items.append(self._pagerlink(thispage, text))
1344 nav_items.append(self._pagerlink(thispage, text))
1345
1345
1346 # Insert dots if there are pages between the displayed
1346 # Insert dots if there are pages between the displayed
1347 # page numbers and the end of the page range
1347 # page numbers and the end of the page range
1348 if self.last_page - rightmost_page > 1:
1348 if self.last_page - rightmost_page > 1:
1349 text = '..'
1349 text = '..'
1350 # Wrap in a SPAN tag if nolink_attr is set
1350 # Wrap in a SPAN tag if nolink_attr is set
1351 if self.dotdot_attr:
1351 if self.dotdot_attr:
1352 text = HTML.span(c=text, **self.dotdot_attr)
1352 text = HTML.span(c=text, **self.dotdot_attr)
1353 nav_items.append(text)
1353 nav_items.append(text)
1354
1354
1355 # Create a link to the very last page (unless we are on the last
1355 # Create a link to the very last page (unless we are on the last
1356 # page or there would be no need to insert '..' spacers)
1356 # page or there would be no need to insert '..' spacers)
1357 if self.page != self.last_page and rightmost_page < self.last_page:
1357 if self.page != self.last_page and rightmost_page < self.last_page:
1358 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1358 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1359
1359
1360 ## prerender links
1360 ## prerender links
1361 #_page_link = url.current()
1361 #_page_link = url.current()
1362 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1362 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1363 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1363 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1364 return self.separator.join(nav_items)
1364 return self.separator.join(nav_items)
1365
1365
1366 def pager(self, format='~2~', page_param='page', partial_param='partial',
1366 def pager(self, format='~2~', page_param='page', partial_param='partial',
1367 show_if_single_page=False, separator=' ', onclick=None,
1367 show_if_single_page=False, separator=' ', onclick=None,
1368 symbol_first='<<', symbol_last='>>',
1368 symbol_first='<<', symbol_last='>>',
1369 symbol_previous='<', symbol_next='>',
1369 symbol_previous='<', symbol_next='>',
1370 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1370 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1371 curpage_attr={'class': 'pager_curpage'},
1371 curpage_attr={'class': 'pager_curpage'},
1372 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1372 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1373
1373
1374 self.curpage_attr = curpage_attr
1374 self.curpage_attr = curpage_attr
1375 self.separator = separator
1375 self.separator = separator
1376 self.pager_kwargs = kwargs
1376 self.pager_kwargs = kwargs
1377 self.page_param = page_param
1377 self.page_param = page_param
1378 self.partial_param = partial_param
1378 self.partial_param = partial_param
1379 self.onclick = onclick
1379 self.onclick = onclick
1380 self.link_attr = link_attr
1380 self.link_attr = link_attr
1381 self.dotdot_attr = dotdot_attr
1381 self.dotdot_attr = dotdot_attr
1382
1382
1383 # Don't show navigator if there is no more than one page
1383 # Don't show navigator if there is no more than one page
1384 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1384 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1385 return ''
1385 return ''
1386
1386
1387 from string import Template
1387 from string import Template
1388 # Replace ~...~ in token format by range of pages
1388 # Replace ~...~ in token format by range of pages
1389 result = re.sub(r'~(\d+)~', self._range, format)
1389 result = re.sub(r'~(\d+)~', self._range, format)
1390
1390
1391 # Interpolate '%' variables
1391 # Interpolate '%' variables
1392 result = Template(result).safe_substitute({
1392 result = Template(result).safe_substitute({
1393 'first_page': self.first_page,
1393 'first_page': self.first_page,
1394 'last_page': self.last_page,
1394 'last_page': self.last_page,
1395 'page': self.page,
1395 'page': self.page,
1396 'page_count': self.page_count,
1396 'page_count': self.page_count,
1397 'items_per_page': self.items_per_page,
1397 'items_per_page': self.items_per_page,
1398 'first_item': self.first_item,
1398 'first_item': self.first_item,
1399 'last_item': self.last_item,
1399 'last_item': self.last_item,
1400 'item_count': self.item_count,
1400 'item_count': self.item_count,
1401 'link_first': self.page > self.first_page and \
1401 'link_first': self.page > self.first_page and \
1402 self._pagerlink(self.first_page, symbol_first) or '',
1402 self._pagerlink(self.first_page, symbol_first) or '',
1403 'link_last': self.page < self.last_page and \
1403 'link_last': self.page < self.last_page and \
1404 self._pagerlink(self.last_page, symbol_last) or '',
1404 self._pagerlink(self.last_page, symbol_last) or '',
1405 'link_previous': self.previous_page and \
1405 'link_previous': self.previous_page and \
1406 self._pagerlink(self.previous_page, symbol_previous) \
1406 self._pagerlink(self.previous_page, symbol_previous) \
1407 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1407 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1408 'link_next': self.next_page and \
1408 'link_next': self.next_page and \
1409 self._pagerlink(self.next_page, symbol_next) \
1409 self._pagerlink(self.next_page, symbol_next) \
1410 or HTML.span(symbol_next, class_="pg-next disabled")
1410 or HTML.span(symbol_next, class_="pg-next disabled")
1411 })
1411 })
1412
1412
1413 return literal(result)
1413 return literal(result)
1414
1414
1415
1415
1416 #==============================================================================
1416 #==============================================================================
1417 # REPO PAGER, PAGER FOR REPOSITORY
1417 # REPO PAGER, PAGER FOR REPOSITORY
1418 #==============================================================================
1418 #==============================================================================
1419 class RepoPage(Page):
1419 class RepoPage(Page):
1420
1420
1421 def __init__(self, collection, page=1, items_per_page=20,
1421 def __init__(self, collection, page=1, items_per_page=20,
1422 item_count=None, url=None, **kwargs):
1422 item_count=None, url=None, **kwargs):
1423
1423
1424 """Create a "RepoPage" instance. special pager for paging
1424 """Create a "RepoPage" instance. special pager for paging
1425 repository
1425 repository
1426 """
1426 """
1427 self._url_generator = url
1427 self._url_generator = url
1428
1428
1429 # Safe the kwargs class-wide so they can be used in the pager() method
1429 # Safe the kwargs class-wide so they can be used in the pager() method
1430 self.kwargs = kwargs
1430 self.kwargs = kwargs
1431
1431
1432 # Save a reference to the collection
1432 # Save a reference to the collection
1433 self.original_collection = collection
1433 self.original_collection = collection
1434
1434
1435 self.collection = collection
1435 self.collection = collection
1436
1436
1437 # The self.page is the number of the current page.
1437 # The self.page is the number of the current page.
1438 # The first page has the number 1!
1438 # The first page has the number 1!
1439 try:
1439 try:
1440 self.page = int(page) # make it int() if we get it as a string
1440 self.page = int(page) # make it int() if we get it as a string
1441 except (ValueError, TypeError):
1441 except (ValueError, TypeError):
1442 self.page = 1
1442 self.page = 1
1443
1443
1444 self.items_per_page = items_per_page
1444 self.items_per_page = items_per_page
1445
1445
1446 # Unless the user tells us how many items the collections has
1446 # Unless the user tells us how many items the collections has
1447 # we calculate that ourselves.
1447 # we calculate that ourselves.
1448 if item_count is not None:
1448 if item_count is not None:
1449 self.item_count = item_count
1449 self.item_count = item_count
1450 else:
1450 else:
1451 self.item_count = len(self.collection)
1451 self.item_count = len(self.collection)
1452
1452
1453 # Compute the number of the first and last available page
1453 # Compute the number of the first and last available page
1454 if self.item_count > 0:
1454 if self.item_count > 0:
1455 self.first_page = 1
1455 self.first_page = 1
1456 self.page_count = int(math.ceil(float(self.item_count) /
1456 self.page_count = int(math.ceil(float(self.item_count) /
1457 self.items_per_page))
1457 self.items_per_page))
1458 self.last_page = self.first_page + self.page_count - 1
1458 self.last_page = self.first_page + self.page_count - 1
1459
1459
1460 # Make sure that the requested page number is the range of
1460 # Make sure that the requested page number is the range of
1461 # valid pages
1461 # valid pages
1462 if self.page > self.last_page:
1462 if self.page > self.last_page:
1463 self.page = self.last_page
1463 self.page = self.last_page
1464 elif self.page < self.first_page:
1464 elif self.page < self.first_page:
1465 self.page = self.first_page
1465 self.page = self.first_page
1466
1466
1467 # Note: the number of items on this page can be less than
1467 # Note: the number of items on this page can be less than
1468 # items_per_page if the last page is not full
1468 # items_per_page if the last page is not full
1469 self.first_item = max(0, (self.item_count) - (self.page *
1469 self.first_item = max(0, (self.item_count) - (self.page *
1470 items_per_page))
1470 items_per_page))
1471 self.last_item = ((self.item_count - 1) - items_per_page *
1471 self.last_item = ((self.item_count - 1) - items_per_page *
1472 (self.page - 1))
1472 (self.page - 1))
1473
1473
1474 self.items = list(self.collection[self.first_item:self.last_item + 1])
1474 self.items = list(self.collection[self.first_item:self.last_item + 1])
1475
1475
1476 # Links to previous and next page
1476 # Links to previous and next page
1477 if self.page > self.first_page:
1477 if self.page > self.first_page:
1478 self.previous_page = self.page - 1
1478 self.previous_page = self.page - 1
1479 else:
1479 else:
1480 self.previous_page = None
1480 self.previous_page = None
1481
1481
1482 if self.page < self.last_page:
1482 if self.page < self.last_page:
1483 self.next_page = self.page + 1
1483 self.next_page = self.page + 1
1484 else:
1484 else:
1485 self.next_page = None
1485 self.next_page = None
1486
1486
1487 # No items available
1487 # No items available
1488 else:
1488 else:
1489 self.first_page = None
1489 self.first_page = None
1490 self.page_count = 0
1490 self.page_count = 0
1491 self.last_page = None
1491 self.last_page = None
1492 self.first_item = None
1492 self.first_item = None
1493 self.last_item = None
1493 self.last_item = None
1494 self.previous_page = None
1494 self.previous_page = None
1495 self.next_page = None
1495 self.next_page = None
1496 self.items = []
1496 self.items = []
1497
1497
1498 # This is a subclass of the 'list' type. Initialise the list now.
1498 # This is a subclass of the 'list' type. Initialise the list now.
1499 list.__init__(self, reversed(self.items))
1499 list.__init__(self, reversed(self.items))
1500
1500
1501
1501
1502 def breadcrumb_repo_link(repo):
1502 def breadcrumb_repo_link(repo):
1503 """
1503 """
1504 Makes a breadcrumbs path link to repo
1504 Makes a breadcrumbs path link to repo
1505
1505
1506 ex::
1506 ex::
1507 group >> subgroup >> repo
1507 group >> subgroup >> repo
1508
1508
1509 :param repo: a Repository instance
1509 :param repo: a Repository instance
1510 """
1510 """
1511
1511
1512 path = [
1512 path = [
1513 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1513 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1514 title='last change:{}'.format(format_date(group.last_commit_change)))
1514 title='last change:{}'.format(format_date(group.last_commit_change)))
1515 for group in repo.groups_with_parents
1515 for group in repo.groups_with_parents
1516 ] + [
1516 ] + [
1517 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1517 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1518 title='last change:{}'.format(format_date(repo.last_commit_change)))
1518 title='last change:{}'.format(format_date(repo.last_commit_change)))
1519 ]
1519 ]
1520
1520
1521 return literal(' &raquo; '.join(path))
1521 return literal(' &raquo; '.join(path))
1522
1522
1523
1523
1524 def breadcrumb_repo_group_link(repo_group):
1524 def breadcrumb_repo_group_link(repo_group):
1525 """
1525 """
1526 Makes a breadcrumbs path link to repo
1526 Makes a breadcrumbs path link to repo
1527
1527
1528 ex::
1528 ex::
1529 group >> subgroup
1529 group >> subgroup
1530
1530
1531 :param repo_group: a Repository Group instance
1531 :param repo_group: a Repository Group instance
1532 """
1532 """
1533
1533
1534 path = [
1534 path = [
1535 link_to(group.name,
1535 link_to(group.name,
1536 route_path('repo_group_home', repo_group_name=group.group_name),
1536 route_path('repo_group_home', repo_group_name=group.group_name),
1537 title='last change:{}'.format(format_date(group.last_commit_change)))
1537 title='last change:{}'.format(format_date(group.last_commit_change)))
1538 for group in repo_group.parents
1538 for group in repo_group.parents
1539 ] + [
1539 ] + [
1540 link_to(repo_group.name,
1540 link_to(repo_group.name,
1541 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1541 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1542 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1542 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1543 ]
1543 ]
1544
1544
1545 return literal(' &raquo; '.join(path))
1545 return literal(' &raquo; '.join(path))
1546
1546
1547
1547
1548 def format_byte_size_binary(file_size):
1548 def format_byte_size_binary(file_size):
1549 """
1549 """
1550 Formats file/folder sizes to standard.
1550 Formats file/folder sizes to standard.
1551 """
1551 """
1552 if file_size is None:
1552 if file_size is None:
1553 file_size = 0
1553 file_size = 0
1554
1554
1555 formatted_size = format_byte_size(file_size, binary=True)
1555 formatted_size = format_byte_size(file_size, binary=True)
1556 return formatted_size
1556 return formatted_size
1557
1557
1558
1558
1559 def urlify_text(text_, safe=True):
1559 def urlify_text(text_, safe=True):
1560 """
1560 """
1561 Extrac urls from text and make html links out of them
1561 Extrac urls from text and make html links out of them
1562
1562
1563 :param text_:
1563 :param text_:
1564 """
1564 """
1565
1565
1566 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1566 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1567 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1567 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1568
1568
1569 def url_func(match_obj):
1569 def url_func(match_obj):
1570 url_full = match_obj.groups()[0]
1570 url_full = match_obj.groups()[0]
1571 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1571 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1572 _newtext = url_pat.sub(url_func, text_)
1572 _newtext = url_pat.sub(url_func, text_)
1573 if safe:
1573 if safe:
1574 return literal(_newtext)
1574 return literal(_newtext)
1575 return _newtext
1575 return _newtext
1576
1576
1577
1577
1578 def urlify_commits(text_, repository):
1578 def urlify_commits(text_, repository):
1579 """
1579 """
1580 Extract commit ids from text and make link from them
1580 Extract commit ids from text and make link from them
1581
1581
1582 :param text_:
1582 :param text_:
1583 :param repository: repo name to build the URL with
1583 :param repository: repo name to build the URL with
1584 """
1584 """
1585
1585
1586 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1586 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1587
1587
1588 def url_func(match_obj):
1588 def url_func(match_obj):
1589 commit_id = match_obj.groups()[1]
1589 commit_id = match_obj.groups()[1]
1590 pref = match_obj.groups()[0]
1590 pref = match_obj.groups()[0]
1591 suf = match_obj.groups()[2]
1591 suf = match_obj.groups()[2]
1592
1592
1593 tmpl = (
1593 tmpl = (
1594 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1594 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1595 '%(commit_id)s</a>%(suf)s'
1595 '%(commit_id)s</a>%(suf)s'
1596 )
1596 )
1597 return tmpl % {
1597 return tmpl % {
1598 'pref': pref,
1598 'pref': pref,
1599 'cls': 'revision-link',
1599 'cls': 'revision-link',
1600 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1600 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1601 'commit_id': commit_id,
1601 'commit_id': commit_id,
1602 'suf': suf
1602 'suf': suf
1603 }
1603 }
1604
1604
1605 newtext = URL_PAT.sub(url_func, text_)
1605 newtext = URL_PAT.sub(url_func, text_)
1606
1606
1607 return newtext
1607 return newtext
1608
1608
1609
1609
1610 def _process_url_func(match_obj, repo_name, uid, entry,
1610 def _process_url_func(match_obj, repo_name, uid, entry,
1611 return_raw_data=False, link_format='html'):
1611 return_raw_data=False, link_format='html'):
1612 pref = ''
1612 pref = ''
1613 if match_obj.group().startswith(' '):
1613 if match_obj.group().startswith(' '):
1614 pref = ' '
1614 pref = ' '
1615
1615
1616 issue_id = ''.join(match_obj.groups())
1616 issue_id = ''.join(match_obj.groups())
1617
1617
1618 if link_format == 'html':
1618 if link_format == 'html':
1619 tmpl = (
1619 tmpl = (
1620 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1620 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1621 '%(issue-prefix)s%(id-repr)s'
1621 '%(issue-prefix)s%(id-repr)s'
1622 '</a>')
1622 '</a>')
1623 elif link_format == 'rst':
1623 elif link_format == 'rst':
1624 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1624 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1625 elif link_format == 'markdown':
1625 elif link_format == 'markdown':
1626 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1626 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1627 else:
1627 else:
1628 raise ValueError('Bad link_format:{}'.format(link_format))
1628 raise ValueError('Bad link_format:{}'.format(link_format))
1629
1629
1630 (repo_name_cleaned,
1630 (repo_name_cleaned,
1631 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1631 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1632
1632
1633 # variables replacement
1633 # variables replacement
1634 named_vars = {
1634 named_vars = {
1635 'id': issue_id,
1635 'id': issue_id,
1636 'repo': repo_name,
1636 'repo': repo_name,
1637 'repo_name': repo_name_cleaned,
1637 'repo_name': repo_name_cleaned,
1638 'group_name': parent_group_name
1638 'group_name': parent_group_name
1639 }
1639 }
1640 # named regex variables
1640 # named regex variables
1641 named_vars.update(match_obj.groupdict())
1641 named_vars.update(match_obj.groupdict())
1642 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1642 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1643
1643
1644 def quote_cleaner(input_str):
1644 def quote_cleaner(input_str):
1645 """Remove quotes as it's HTML"""
1645 """Remove quotes as it's HTML"""
1646 return input_str.replace('"', '')
1646 return input_str.replace('"', '')
1647
1647
1648 data = {
1648 data = {
1649 'pref': pref,
1649 'pref': pref,
1650 'cls': quote_cleaner('issue-tracker-link'),
1650 'cls': quote_cleaner('issue-tracker-link'),
1651 'url': quote_cleaner(_url),
1651 'url': quote_cleaner(_url),
1652 'id-repr': issue_id,
1652 'id-repr': issue_id,
1653 'issue-prefix': entry['pref'],
1653 'issue-prefix': entry['pref'],
1654 'serv': entry['url'],
1654 'serv': entry['url'],
1655 }
1655 }
1656 if return_raw_data:
1656 if return_raw_data:
1657 return {
1657 return {
1658 'id': issue_id,
1658 'id': issue_id,
1659 'url': _url
1659 'url': _url
1660 }
1660 }
1661 return tmpl % data
1661 return tmpl % data
1662
1662
1663
1663
1664 def get_active_pattern_entries(repo_name):
1664 def get_active_pattern_entries(repo_name):
1665 repo = None
1665 repo = None
1666 if repo_name:
1666 if repo_name:
1667 # Retrieving repo_name to avoid invalid repo_name to explode on
1667 # Retrieving repo_name to avoid invalid repo_name to explode on
1668 # IssueTrackerSettingsModel but still passing invalid name further down
1668 # IssueTrackerSettingsModel but still passing invalid name further down
1669 repo = Repository.get_by_repo_name(repo_name, cache=True)
1669 repo = Repository.get_by_repo_name(repo_name, cache=True)
1670
1670
1671 settings_model = IssueTrackerSettingsModel(repo=repo)
1671 settings_model = IssueTrackerSettingsModel(repo=repo)
1672 active_entries = settings_model.get_settings(cache=True)
1672 active_entries = settings_model.get_settings(cache=True)
1673 return active_entries
1673 return active_entries
1674
1674
1675
1675
1676 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1676 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1677
1677
1678 allowed_formats = ['html', 'rst', 'markdown']
1678 allowed_formats = ['html', 'rst', 'markdown']
1679 if link_format not in allowed_formats:
1679 if link_format not in allowed_formats:
1680 raise ValueError('Link format can be only one of:{} got {}'.format(
1680 raise ValueError('Link format can be only one of:{} got {}'.format(
1681 allowed_formats, link_format))
1681 allowed_formats, link_format))
1682
1682
1683 active_entries = active_entries or get_active_pattern_entries(repo_name)
1683 active_entries = active_entries or get_active_pattern_entries(repo_name)
1684 issues_data = []
1684 issues_data = []
1685 newtext = text_string
1685 newtext = text_string
1686
1686
1687 for uid, entry in active_entries.items():
1687 for uid, entry in active_entries.items():
1688 log.debug('found issue tracker entry with uid %s', uid)
1688 log.debug('found issue tracker entry with uid %s', uid)
1689
1689
1690 if not (entry['pat'] and entry['url']):
1690 if not (entry['pat'] and entry['url']):
1691 log.debug('skipping due to missing data')
1691 log.debug('skipping due to missing data')
1692 continue
1692 continue
1693
1693
1694 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1694 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1695 uid, entry['pat'], entry['url'], entry['pref'])
1695 uid, entry['pat'], entry['url'], entry['pref'])
1696
1696
1697 try:
1697 try:
1698 pattern = re.compile(r'%s' % entry['pat'])
1698 pattern = re.compile(r'%s' % entry['pat'])
1699 except re.error:
1699 except re.error:
1700 log.exception(
1700 log.exception(
1701 'issue tracker pattern: `%s` failed to compile',
1701 'issue tracker pattern: `%s` failed to compile',
1702 entry['pat'])
1702 entry['pat'])
1703 continue
1703 continue
1704
1704
1705 data_func = partial(
1705 data_func = partial(
1706 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1706 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1707 return_raw_data=True)
1707 return_raw_data=True)
1708
1708
1709 for match_obj in pattern.finditer(text_string):
1709 for match_obj in pattern.finditer(text_string):
1710 issues_data.append(data_func(match_obj))
1710 issues_data.append(data_func(match_obj))
1711
1711
1712 url_func = partial(
1712 url_func = partial(
1713 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1713 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1714 link_format=link_format)
1714 link_format=link_format)
1715
1715
1716 newtext = pattern.sub(url_func, newtext)
1716 newtext = pattern.sub(url_func, newtext)
1717 log.debug('processed prefix:uid `%s`', uid)
1717 log.debug('processed prefix:uid `%s`', uid)
1718
1718
1719 return newtext, issues_data
1719 return newtext, issues_data
1720
1720
1721
1721
1722 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1722 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1723 """
1723 """
1724 Parses given text message and makes proper links.
1724 Parses given text message and makes proper links.
1725 issues are linked to given issue-server, and rest is a commit link
1725 issues are linked to given issue-server, and rest is a commit link
1726
1726
1727 :param commit_text:
1727 :param commit_text:
1728 :param repository:
1728 :param repository:
1729 """
1729 """
1730 def escaper(string):
1730 def escaper(string):
1731 return string.replace('<', '&lt;').replace('>', '&gt;')
1731 return string.replace('<', '&lt;').replace('>', '&gt;')
1732
1732
1733 newtext = escaper(commit_text)
1733 newtext = escaper(commit_text)
1734
1734
1735 # extract http/https links and make them real urls
1735 # extract http/https links and make them real urls
1736 newtext = urlify_text(newtext, safe=False)
1736 newtext = urlify_text(newtext, safe=False)
1737
1737
1738 # urlify commits - extract commit ids and make link out of them, if we have
1738 # urlify commits - extract commit ids and make link out of them, if we have
1739 # the scope of repository present.
1739 # the scope of repository present.
1740 if repository:
1740 if repository:
1741 newtext = urlify_commits(newtext, repository)
1741 newtext = urlify_commits(newtext, repository)
1742
1742
1743 # process issue tracker patterns
1743 # process issue tracker patterns
1744 newtext, issues = process_patterns(newtext, repository or '',
1744 newtext, issues = process_patterns(newtext, repository or '',
1745 active_entries=active_pattern_entries)
1745 active_entries=active_pattern_entries)
1746
1746
1747 return literal(newtext)
1747 return literal(newtext)
1748
1748
1749
1749
1750 def render_binary(repo_name, file_obj):
1750 def render_binary(repo_name, file_obj):
1751 """
1751 """
1752 Choose how to render a binary file
1752 Choose how to render a binary file
1753 """
1753 """
1754
1754
1755 filename = file_obj.name
1755 filename = file_obj.name
1756
1756
1757 # images
1757 # images
1758 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1758 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1759 if fnmatch.fnmatch(filename, pat=ext):
1759 if fnmatch.fnmatch(filename, pat=ext):
1760 alt = escape(filename)
1760 alt = escape(filename)
1761 src = route_path(
1761 src = route_path(
1762 'repo_file_raw', repo_name=repo_name,
1762 'repo_file_raw', repo_name=repo_name,
1763 commit_id=file_obj.commit.raw_id,
1763 commit_id=file_obj.commit.raw_id,
1764 f_path=file_obj.path)
1764 f_path=file_obj.path)
1765 return literal(
1765 return literal(
1766 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1766 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1767
1767
1768
1768
1769 def renderer_from_filename(filename, exclude=None):
1769 def renderer_from_filename(filename, exclude=None):
1770 """
1770 """
1771 choose a renderer based on filename, this works only for text based files
1771 choose a renderer based on filename, this works only for text based files
1772 """
1772 """
1773
1773
1774 # ipython
1774 # ipython
1775 for ext in ['*.ipynb']:
1775 for ext in ['*.ipynb']:
1776 if fnmatch.fnmatch(filename, pat=ext):
1776 if fnmatch.fnmatch(filename, pat=ext):
1777 return 'jupyter'
1777 return 'jupyter'
1778
1778
1779 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1779 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1780 if is_markup:
1780 if is_markup:
1781 return is_markup
1781 return is_markup
1782 return None
1782 return None
1783
1783
1784
1784
1785 def render(source, renderer='rst', mentions=False, relative_urls=None,
1785 def render(source, renderer='rst', mentions=False, relative_urls=None,
1786 repo_name=None):
1786 repo_name=None):
1787
1787
1788 def maybe_convert_relative_links(html_source):
1788 def maybe_convert_relative_links(html_source):
1789 if relative_urls:
1789 if relative_urls:
1790 return relative_links(html_source, relative_urls)
1790 return relative_links(html_source, relative_urls)
1791 return html_source
1791 return html_source
1792
1792
1793 if renderer == 'plain':
1793 if renderer == 'plain':
1794 return literal(
1794 return literal(
1795 MarkupRenderer.plain(source, leading_newline=False))
1795 MarkupRenderer.plain(source, leading_newline=False))
1796
1796
1797 elif renderer == 'rst':
1797 elif renderer == 'rst':
1798 if repo_name:
1798 if repo_name:
1799 # process patterns on comments if we pass in repo name
1799 # process patterns on comments if we pass in repo name
1800 source, issues = process_patterns(
1800 source, issues = process_patterns(
1801 source, repo_name, link_format='rst')
1801 source, repo_name, link_format='rst')
1802
1802
1803 return literal(
1803 return literal(
1804 '<div class="rst-block">%s</div>' %
1804 '<div class="rst-block">%s</div>' %
1805 maybe_convert_relative_links(
1805 maybe_convert_relative_links(
1806 MarkupRenderer.rst(source, mentions=mentions)))
1806 MarkupRenderer.rst(source, mentions=mentions)))
1807
1807
1808 elif renderer == 'markdown':
1808 elif renderer == 'markdown':
1809 if repo_name:
1809 if repo_name:
1810 # process patterns on comments if we pass in repo name
1810 # process patterns on comments if we pass in repo name
1811 source, issues = process_patterns(
1811 source, issues = process_patterns(
1812 source, repo_name, link_format='markdown')
1812 source, repo_name, link_format='markdown')
1813
1813
1814 return literal(
1814 return literal(
1815 '<div class="markdown-block">%s</div>' %
1815 '<div class="markdown-block">%s</div>' %
1816 maybe_convert_relative_links(
1816 maybe_convert_relative_links(
1817 MarkupRenderer.markdown(source, flavored=True,
1817 MarkupRenderer.markdown(source, flavored=True,
1818 mentions=mentions)))
1818 mentions=mentions)))
1819
1819
1820 elif renderer == 'jupyter':
1820 elif renderer == 'jupyter':
1821 return literal(
1821 return literal(
1822 '<div class="ipynb">%s</div>' %
1822 '<div class="ipynb">%s</div>' %
1823 maybe_convert_relative_links(
1823 maybe_convert_relative_links(
1824 MarkupRenderer.jupyter(source)))
1824 MarkupRenderer.jupyter(source)))
1825
1825
1826 # None means just show the file-source
1826 # None means just show the file-source
1827 return None
1827 return None
1828
1828
1829
1829
1830 def commit_status(repo, commit_id):
1830 def commit_status(repo, commit_id):
1831 return ChangesetStatusModel().get_status(repo, commit_id)
1831 return ChangesetStatusModel().get_status(repo, commit_id)
1832
1832
1833
1833
1834 def commit_status_lbl(commit_status):
1834 def commit_status_lbl(commit_status):
1835 return dict(ChangesetStatus.STATUSES).get(commit_status)
1835 return dict(ChangesetStatus.STATUSES).get(commit_status)
1836
1836
1837
1837
1838 def commit_time(repo_name, commit_id):
1838 def commit_time(repo_name, commit_id):
1839 repo = Repository.get_by_repo_name(repo_name)
1839 repo = Repository.get_by_repo_name(repo_name)
1840 commit = repo.get_commit(commit_id=commit_id)
1840 commit = repo.get_commit(commit_id=commit_id)
1841 return commit.date
1841 return commit.date
1842
1842
1843
1843
1844 def get_permission_name(key):
1844 def get_permission_name(key):
1845 return dict(Permission.PERMS).get(key)
1845 return dict(Permission.PERMS).get(key)
1846
1846
1847
1847
1848 def journal_filter_help(request):
1848 def journal_filter_help(request):
1849 _ = request.translate
1849 _ = request.translate
1850 from rhodecode.lib.audit_logger import ACTIONS
1850 from rhodecode.lib.audit_logger import ACTIONS
1851 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1851 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1852
1852
1853 return _(
1853 return _(
1854 'Example filter terms:\n' +
1854 'Example filter terms:\n' +
1855 ' repository:vcs\n' +
1855 ' repository:vcs\n' +
1856 ' username:marcin\n' +
1856 ' username:marcin\n' +
1857 ' username:(NOT marcin)\n' +
1857 ' username:(NOT marcin)\n' +
1858 ' action:*push*\n' +
1858 ' action:*push*\n' +
1859 ' ip:127.0.0.1\n' +
1859 ' ip:127.0.0.1\n' +
1860 ' date:20120101\n' +
1860 ' date:20120101\n' +
1861 ' date:[20120101100000 TO 20120102]\n' +
1861 ' date:[20120101100000 TO 20120102]\n' +
1862 '\n' +
1862 '\n' +
1863 'Actions: {actions}\n' +
1863 'Actions: {actions}\n' +
1864 '\n' +
1864 '\n' +
1865 'Generate wildcards using \'*\' character:\n' +
1865 'Generate wildcards using \'*\' character:\n' +
1866 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1866 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1867 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1867 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1868 '\n' +
1868 '\n' +
1869 'Optional AND / OR operators in queries\n' +
1869 'Optional AND / OR operators in queries\n' +
1870 ' "repository:vcs OR repository:test"\n' +
1870 ' "repository:vcs OR repository:test"\n' +
1871 ' "username:test AND repository:test*"\n'
1871 ' "username:test AND repository:test*"\n'
1872 ).format(actions=actions)
1872 ).format(actions=actions)
1873
1873
1874
1874
1875 def not_mapped_error(repo_name):
1875 def not_mapped_error(repo_name):
1876 from rhodecode.translation import _
1876 from rhodecode.translation import _
1877 flash(_('%s repository is not mapped to db perhaps'
1877 flash(_('%s repository is not mapped to db perhaps'
1878 ' it was created or renamed from the filesystem'
1878 ' it was created or renamed from the filesystem'
1879 ' please run the application again'
1879 ' please run the application again'
1880 ' in order to rescan repositories') % repo_name, category='error')
1880 ' in order to rescan repositories') % repo_name, category='error')
1881
1881
1882
1882
1883 def ip_range(ip_addr):
1883 def ip_range(ip_addr):
1884 from rhodecode.model.db import UserIpMap
1884 from rhodecode.model.db import UserIpMap
1885 s, e = UserIpMap._get_ip_range(ip_addr)
1885 s, e = UserIpMap._get_ip_range(ip_addr)
1886 return '%s - %s' % (s, e)
1886 return '%s - %s' % (s, e)
1887
1887
1888
1888
1889 def form(url, method='post', needs_csrf_token=True, **attrs):
1889 def form(url, method='post', needs_csrf_token=True, **attrs):
1890 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1890 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1891 if method.lower() != 'get' and needs_csrf_token:
1891 if method.lower() != 'get' and needs_csrf_token:
1892 raise Exception(
1892 raise Exception(
1893 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1893 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1894 'CSRF token. If the endpoint does not require such token you can ' +
1894 'CSRF token. If the endpoint does not require such token you can ' +
1895 'explicitly set the parameter needs_csrf_token to false.')
1895 'explicitly set the parameter needs_csrf_token to false.')
1896
1896
1897 return wh_form(url, method=method, **attrs)
1897 return wh_form(url, method=method, **attrs)
1898
1898
1899
1899
1900 def secure_form(form_url, method="POST", multipart=False, **attrs):
1900 def secure_form(form_url, method="POST", multipart=False, **attrs):
1901 """Start a form tag that points the action to an url. This
1901 """Start a form tag that points the action to an url. This
1902 form tag will also include the hidden field containing
1902 form tag will also include the hidden field containing
1903 the auth token.
1903 the auth token.
1904
1904
1905 The url options should be given either as a string, or as a
1905 The url options should be given either as a string, or as a
1906 ``url()`` function. The method for the form defaults to POST.
1906 ``url()`` function. The method for the form defaults to POST.
1907
1907
1908 Options:
1908 Options:
1909
1909
1910 ``multipart``
1910 ``multipart``
1911 If set to True, the enctype is set to "multipart/form-data".
1911 If set to True, the enctype is set to "multipart/form-data".
1912 ``method``
1912 ``method``
1913 The method to use when submitting the form, usually either
1913 The method to use when submitting the form, usually either
1914 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1914 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1915 hidden input with name _method is added to simulate the verb
1915 hidden input with name _method is added to simulate the verb
1916 over POST.
1916 over POST.
1917
1917
1918 """
1918 """
1919 from webhelpers.pylonslib.secure_form import insecure_form
1919 from webhelpers.pylonslib.secure_form import insecure_form
1920
1920
1921 if 'request' in attrs:
1921 if 'request' in attrs:
1922 session = attrs['request'].session
1922 session = attrs['request'].session
1923 del attrs['request']
1923 del attrs['request']
1924 else:
1924 else:
1925 raise ValueError(
1925 raise ValueError(
1926 'Calling this form requires request= to be passed as argument')
1926 'Calling this form requires request= to be passed as argument')
1927
1927
1928 form = insecure_form(form_url, method, multipart, **attrs)
1928 form = insecure_form(form_url, method, multipart, **attrs)
1929 token = literal(
1929 token = literal(
1930 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1930 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1931 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1931 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1932
1932
1933 return literal("%s\n%s" % (form, token))
1933 return literal("%s\n%s" % (form, token))
1934
1934
1935
1935
1936 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1936 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1937 select_html = select(name, selected, options, **attrs)
1937 select_html = select(name, selected, options, **attrs)
1938 select2 = """
1938 select2 = """
1939 <script>
1939 <script>
1940 $(document).ready(function() {
1940 $(document).ready(function() {
1941 $('#%s').select2({
1941 $('#%s').select2({
1942 containerCssClass: 'drop-menu',
1942 containerCssClass: 'drop-menu',
1943 dropdownCssClass: 'drop-menu-dropdown',
1943 dropdownCssClass: 'drop-menu-dropdown',
1944 dropdownAutoWidth: true%s
1944 dropdownAutoWidth: true%s
1945 });
1945 });
1946 });
1946 });
1947 </script>
1947 </script>
1948 """
1948 """
1949 filter_option = """,
1949 filter_option = """,
1950 minimumResultsForSearch: -1
1950 minimumResultsForSearch: -1
1951 """
1951 """
1952 input_id = attrs.get('id') or name
1952 input_id = attrs.get('id') or name
1953 filter_enabled = "" if enable_filter else filter_option
1953 filter_enabled = "" if enable_filter else filter_option
1954 select_script = literal(select2 % (input_id, filter_enabled))
1954 select_script = literal(select2 % (input_id, filter_enabled))
1955
1955
1956 return literal(select_html+select_script)
1956 return literal(select_html+select_script)
1957
1957
1958
1958
1959 def get_visual_attr(tmpl_context_var, attr_name):
1959 def get_visual_attr(tmpl_context_var, attr_name):
1960 """
1960 """
1961 A safe way to get a variable from visual variable of template context
1961 A safe way to get a variable from visual variable of template context
1962
1962
1963 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1963 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1964 :param attr_name: name of the attribute we fetch from the c.visual
1964 :param attr_name: name of the attribute we fetch from the c.visual
1965 """
1965 """
1966 visual = getattr(tmpl_context_var, 'visual', None)
1966 visual = getattr(tmpl_context_var, 'visual', None)
1967 if not visual:
1967 if not visual:
1968 return
1968 return
1969 else:
1969 else:
1970 return getattr(visual, attr_name, None)
1970 return getattr(visual, attr_name, None)
1971
1971
1972
1972
1973 def get_last_path_part(file_node):
1973 def get_last_path_part(file_node):
1974 if not file_node.path:
1974 if not file_node.path:
1975 return u'/'
1975 return u'/'
1976
1976
1977 path = safe_unicode(file_node.path.split('/')[-1])
1977 path = safe_unicode(file_node.path.split('/')[-1])
1978 return u'../' + path
1978 return u'../' + path
1979
1979
1980
1980
1981 def route_url(*args, **kwargs):
1981 def route_url(*args, **kwargs):
1982 """
1982 """
1983 Wrapper around pyramids `route_url` (fully qualified url) function.
1983 Wrapper around pyramids `route_url` (fully qualified url) function.
1984 """
1984 """
1985 req = get_current_request()
1985 req = get_current_request()
1986 return req.route_url(*args, **kwargs)
1986 return req.route_url(*args, **kwargs)
1987
1987
1988
1988
1989 def route_path(*args, **kwargs):
1989 def route_path(*args, **kwargs):
1990 """
1990 """
1991 Wrapper around pyramids `route_path` function.
1991 Wrapper around pyramids `route_path` function.
1992 """
1992 """
1993 req = get_current_request()
1993 req = get_current_request()
1994 return req.route_path(*args, **kwargs)
1994 return req.route_path(*args, **kwargs)
1995
1995
1996
1996
1997 def route_path_or_none(*args, **kwargs):
1997 def route_path_or_none(*args, **kwargs):
1998 try:
1998 try:
1999 return route_path(*args, **kwargs)
1999 return route_path(*args, **kwargs)
2000 except KeyError:
2000 except KeyError:
2001 return None
2001 return None
2002
2002
2003
2003
2004 def current_route_path(request, **kw):
2004 def current_route_path(request, **kw):
2005 new_args = request.GET.mixed()
2005 new_args = request.GET.mixed()
2006 new_args.update(kw)
2006 new_args.update(kw)
2007 return request.current_route_path(_query=new_args)
2007 return request.current_route_path(_query=new_args)
2008
2008
2009
2009
2010 def api_call_example(method, args):
2010 def api_call_example(method, args):
2011 """
2011 """
2012 Generates an API call example via CURL
2012 Generates an API call example via CURL
2013 """
2013 """
2014 args_json = json.dumps(OrderedDict([
2014 args_json = json.dumps(OrderedDict([
2015 ('id', 1),
2015 ('id', 1),
2016 ('auth_token', 'SECRET'),
2016 ('auth_token', 'SECRET'),
2017 ('method', method),
2017 ('method', method),
2018 ('args', args)
2018 ('args', args)
2019 ]))
2019 ]))
2020 return literal(
2020 return literal(
2021 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2021 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2022 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2022 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2023 "and needs to be of `api calls` role."
2023 "and needs to be of `api calls` role."
2024 .format(
2024 .format(
2025 api_url=route_url('apiv2'),
2025 api_url=route_url('apiv2'),
2026 token_url=route_url('my_account_auth_tokens'),
2026 token_url=route_url('my_account_auth_tokens'),
2027 data=args_json))
2027 data=args_json))
2028
2028
2029
2029
2030 def notification_description(notification, request):
2030 def notification_description(notification, request):
2031 """
2031 """
2032 Generate notification human readable description based on notification type
2032 Generate notification human readable description based on notification type
2033 """
2033 """
2034 from rhodecode.model.notification import NotificationModel
2034 from rhodecode.model.notification import NotificationModel
2035 return NotificationModel().make_description(
2035 return NotificationModel().make_description(
2036 notification, translate=request.translate)
2036 notification, translate=request.translate)
2037
2037
2038
2038
2039 def go_import_header(request, db_repo=None):
2039 def go_import_header(request, db_repo=None):
2040 """
2040 """
2041 Creates a header for go-import functionality in Go Lang
2041 Creates a header for go-import functionality in Go Lang
2042 """
2042 """
2043
2043
2044 if not db_repo:
2044 if not db_repo:
2045 return
2045 return
2046 if 'go-get' not in request.GET:
2046 if 'go-get' not in request.GET:
2047 return
2047 return
2048
2048
2049 clone_url = db_repo.clone_url()
2049 clone_url = db_repo.clone_url()
2050 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2050 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2051 # we have a repo and go-get flag,
2051 # we have a repo and go-get flag,
2052 return literal('<meta name="go-import" content="{} {} {}">'.format(
2052 return literal('<meta name="go-import" content="{} {} {}">'.format(
2053 prefix, db_repo.repo_type, clone_url))
2053 prefix, db_repo.repo_type, clone_url))
2054
2054
2055
2055
2056 def reviewer_as_json(*args, **kwargs):
2056 def reviewer_as_json(*args, **kwargs):
2057 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2057 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2058 return _reviewer_as_json(*args, **kwargs)
2058 return _reviewer_as_json(*args, **kwargs)
2059
2059
2060
2060
2061 def get_repo_view_type(request):
2061 def get_repo_view_type(request):
2062 route_name = request.matched_route.name
2062 route_name = request.matched_route.name
2063 route_to_view_type = {
2063 route_to_view_type = {
2064 'repo_changelog': 'changelog',
2064 'repo_changelog': 'commits',
2065 'repo_commits': 'commits',
2065 'repo_files': 'files',
2066 'repo_files': 'files',
2066 'repo_summary': 'summary',
2067 'repo_summary': 'summary',
2067 'repo_commit': 'commit'
2068 'repo_commit': 'commit'
2068 }
2069 }
2069
2070
2070 return route_to_view_type.get(route_name)
2071 return route_to_view_type.get(route_name)
@@ -1,942 +1,942 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-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 HG repository module
22 HG repository module
23 """
23 """
24 import os
24 import os
25 import logging
25 import logging
26 import binascii
26 import binascii
27 import urllib
27 import urllib
28
28
29 from zope.cachedescriptors.property import Lazy as LazyProperty
29 from zope.cachedescriptors.property import Lazy as LazyProperty
30
30
31 from rhodecode.lib.compat import OrderedDict
31 from rhodecode.lib.compat import OrderedDict
32 from rhodecode.lib.datelib import (
32 from rhodecode.lib.datelib import (
33 date_to_timestamp_plus_offset, utcdate_fromtimestamp, makedate)
33 date_to_timestamp_plus_offset, utcdate_fromtimestamp, makedate)
34 from rhodecode.lib.utils import safe_unicode, safe_str
34 from rhodecode.lib.utils import safe_unicode, safe_str
35 from rhodecode.lib.vcs import connection, exceptions
35 from rhodecode.lib.vcs import connection, exceptions
36 from rhodecode.lib.vcs.backends.base import (
36 from rhodecode.lib.vcs.backends.base import (
37 BaseRepository, CollectionGenerator, Config, MergeResponse,
37 BaseRepository, CollectionGenerator, Config, MergeResponse,
38 MergeFailureReason, Reference, BasePathPermissionChecker)
38 MergeFailureReason, Reference, BasePathPermissionChecker)
39 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
39 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
40 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
40 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
41 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
41 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
42 from rhodecode.lib.vcs.exceptions import (
42 from rhodecode.lib.vcs.exceptions import (
43 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
43 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
44 TagDoesNotExistError, CommitDoesNotExistError, SubrepoMergeError)
44 TagDoesNotExistError, CommitDoesNotExistError, SubrepoMergeError)
45 from rhodecode.lib.vcs.compat import configparser
45 from rhodecode.lib.vcs.compat import configparser
46
46
47 hexlify = binascii.hexlify
47 hexlify = binascii.hexlify
48 nullid = "\0" * 20
48 nullid = "\0" * 20
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class MercurialRepository(BaseRepository):
53 class MercurialRepository(BaseRepository):
54 """
54 """
55 Mercurial repository backend
55 Mercurial repository backend
56 """
56 """
57 DEFAULT_BRANCH_NAME = 'default'
57 DEFAULT_BRANCH_NAME = 'default'
58
58
59 def __init__(self, repo_path, config=None, create=False, src_url=None,
59 def __init__(self, repo_path, config=None, create=False, src_url=None,
60 do_workspace_checkout=False, with_wire=None, bare=False):
60 do_workspace_checkout=False, with_wire=None, bare=False):
61 """
61 """
62 Raises RepositoryError if repository could not be find at the given
62 Raises RepositoryError if repository could not be find at the given
63 ``repo_path``.
63 ``repo_path``.
64
64
65 :param repo_path: local path of the repository
65 :param repo_path: local path of the repository
66 :param config: config object containing the repo configuration
66 :param config: config object containing the repo configuration
67 :param create=False: if set to True, would try to create repository if
67 :param create=False: if set to True, would try to create repository if
68 it does not exist rather than raising exception
68 it does not exist rather than raising exception
69 :param src_url=None: would try to clone repository from given location
69 :param src_url=None: would try to clone repository from given location
70 :param do_workspace_checkout=False: sets update of working copy after
70 :param do_workspace_checkout=False: sets update of working copy after
71 making a clone
71 making a clone
72 :param bare: not used, compatible with other VCS
72 :param bare: not used, compatible with other VCS
73 """
73 """
74
74
75 self.path = safe_str(os.path.abspath(repo_path))
75 self.path = safe_str(os.path.abspath(repo_path))
76 # mercurial since 4.4.X requires certain configuration to be present
76 # mercurial since 4.4.X requires certain configuration to be present
77 # because sometimes we init the repos with config we need to meet
77 # because sometimes we init the repos with config we need to meet
78 # special requirements
78 # special requirements
79 self.config = config if config else self.get_default_config(
79 self.config = config if config else self.get_default_config(
80 default=[('extensions', 'largefiles', '1')])
80 default=[('extensions', 'largefiles', '1')])
81 self.with_wire = with_wire
81 self.with_wire = with_wire
82
82
83 self._init_repo(create, src_url, do_workspace_checkout)
83 self._init_repo(create, src_url, do_workspace_checkout)
84
84
85 # caches
85 # caches
86 self._commit_ids = {}
86 self._commit_ids = {}
87
87
88 @LazyProperty
88 @LazyProperty
89 def _remote(self):
89 def _remote(self):
90 return connection.Hg(self.path, self.config, with_wire=self.with_wire)
90 return connection.Hg(self.path, self.config, with_wire=self.with_wire)
91
91
92 @LazyProperty
92 @LazyProperty
93 def commit_ids(self):
93 def commit_ids(self):
94 """
94 """
95 Returns list of commit ids, in ascending order. Being lazy
95 Returns list of commit ids, in ascending order. Being lazy
96 attribute allows external tools to inject shas from cache.
96 attribute allows external tools to inject shas from cache.
97 """
97 """
98 commit_ids = self._get_all_commit_ids()
98 commit_ids = self._get_all_commit_ids()
99 self._rebuild_cache(commit_ids)
99 self._rebuild_cache(commit_ids)
100 return commit_ids
100 return commit_ids
101
101
102 def _rebuild_cache(self, commit_ids):
102 def _rebuild_cache(self, commit_ids):
103 self._commit_ids = dict((commit_id, index)
103 self._commit_ids = dict((commit_id, index)
104 for index, commit_id in enumerate(commit_ids))
104 for index, commit_id in enumerate(commit_ids))
105
105
106 @LazyProperty
106 @LazyProperty
107 def branches(self):
107 def branches(self):
108 return self._get_branches()
108 return self._get_branches()
109
109
110 @LazyProperty
110 @LazyProperty
111 def branches_closed(self):
111 def branches_closed(self):
112 return self._get_branches(active=False, closed=True)
112 return self._get_branches(active=False, closed=True)
113
113
114 @LazyProperty
114 @LazyProperty
115 def branches_all(self):
115 def branches_all(self):
116 all_branches = {}
116 all_branches = {}
117 all_branches.update(self.branches)
117 all_branches.update(self.branches)
118 all_branches.update(self.branches_closed)
118 all_branches.update(self.branches_closed)
119 return all_branches
119 return all_branches
120
120
121 def _get_branches(self, active=True, closed=False):
121 def _get_branches(self, active=True, closed=False):
122 """
122 """
123 Gets branches for this repository
123 Gets branches for this repository
124 Returns only not closed active branches by default
124 Returns only not closed active branches by default
125
125
126 :param active: return also active branches
126 :param active: return also active branches
127 :param closed: return also closed branches
127 :param closed: return also closed branches
128
128
129 """
129 """
130 if self.is_empty():
130 if self.is_empty():
131 return {}
131 return {}
132
132
133 def get_name(ctx):
133 def get_name(ctx):
134 return ctx[0]
134 return ctx[0]
135
135
136 _branches = [(safe_unicode(n), hexlify(h),) for n, h in
136 _branches = [(safe_unicode(n), hexlify(h),) for n, h in
137 self._remote.branches(active, closed).items()]
137 self._remote.branches(active, closed).items()]
138
138
139 return OrderedDict(sorted(_branches, key=get_name, reverse=False))
139 return OrderedDict(sorted(_branches, key=get_name, reverse=False))
140
140
141 @LazyProperty
141 @LazyProperty
142 def tags(self):
142 def tags(self):
143 """
143 """
144 Gets tags for this repository
144 Gets tags for this repository
145 """
145 """
146 return self._get_tags()
146 return self._get_tags()
147
147
148 def _get_tags(self):
148 def _get_tags(self):
149 if self.is_empty():
149 if self.is_empty():
150 return {}
150 return {}
151
151
152 def get_name(ctx):
152 def get_name(ctx):
153 return ctx[0]
153 return ctx[0]
154
154
155 _tags = [(safe_unicode(n), hexlify(h),) for n, h in
155 _tags = [(safe_unicode(n), hexlify(h),) for n, h in
156 self._remote.tags().items()]
156 self._remote.tags().items()]
157
157
158 return OrderedDict(sorted(_tags, key=get_name, reverse=True))
158 return OrderedDict(sorted(_tags, key=get_name, reverse=True))
159
159
160 def tag(self, name, user, commit_id=None, message=None, date=None,
160 def tag(self, name, user, commit_id=None, message=None, date=None,
161 **kwargs):
161 **kwargs):
162 """
162 """
163 Creates and returns a tag for the given ``commit_id``.
163 Creates and returns a tag for the given ``commit_id``.
164
164
165 :param name: name for new tag
165 :param name: name for new tag
166 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
166 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
167 :param commit_id: commit id for which new tag would be created
167 :param commit_id: commit id for which new tag would be created
168 :param message: message of the tag's commit
168 :param message: message of the tag's commit
169 :param date: date of tag's commit
169 :param date: date of tag's commit
170
170
171 :raises TagAlreadyExistError: if tag with same name already exists
171 :raises TagAlreadyExistError: if tag with same name already exists
172 """
172 """
173 if name in self.tags:
173 if name in self.tags:
174 raise TagAlreadyExistError("Tag %s already exists" % name)
174 raise TagAlreadyExistError("Tag %s already exists" % name)
175 commit = self.get_commit(commit_id=commit_id)
175 commit = self.get_commit(commit_id=commit_id)
176 local = kwargs.setdefault('local', False)
176 local = kwargs.setdefault('local', False)
177
177
178 if message is None:
178 if message is None:
179 message = "Added tag %s for commit %s" % (name, commit.short_id)
179 message = "Added tag %s for commit %s" % (name, commit.short_id)
180
180
181 date, tz = date_to_timestamp_plus_offset(date)
181 date, tz = date_to_timestamp_plus_offset(date)
182
182
183 self._remote.tag(
183 self._remote.tag(
184 name, commit.raw_id, message, local, user, date, tz)
184 name, commit.raw_id, message, local, user, date, tz)
185 self._remote.invalidate_vcs_cache()
185 self._remote.invalidate_vcs_cache()
186
186
187 # Reinitialize tags
187 # Reinitialize tags
188 self.tags = self._get_tags()
188 self.tags = self._get_tags()
189 tag_id = self.tags[name]
189 tag_id = self.tags[name]
190
190
191 return self.get_commit(commit_id=tag_id)
191 return self.get_commit(commit_id=tag_id)
192
192
193 def remove_tag(self, name, user, message=None, date=None):
193 def remove_tag(self, name, user, message=None, date=None):
194 """
194 """
195 Removes tag with the given `name`.
195 Removes tag with the given `name`.
196
196
197 :param name: name of the tag to be removed
197 :param name: name of the tag to be removed
198 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
198 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
199 :param message: message of the tag's removal commit
199 :param message: message of the tag's removal commit
200 :param date: date of tag's removal commit
200 :param date: date of tag's removal commit
201
201
202 :raises TagDoesNotExistError: if tag with given name does not exists
202 :raises TagDoesNotExistError: if tag with given name does not exists
203 """
203 """
204 if name not in self.tags:
204 if name not in self.tags:
205 raise TagDoesNotExistError("Tag %s does not exist" % name)
205 raise TagDoesNotExistError("Tag %s does not exist" % name)
206 if message is None:
206 if message is None:
207 message = "Removed tag %s" % name
207 message = "Removed tag %s" % name
208 local = False
208 local = False
209
209
210 date, tz = date_to_timestamp_plus_offset(date)
210 date, tz = date_to_timestamp_plus_offset(date)
211
211
212 self._remote.tag(name, nullid, message, local, user, date, tz)
212 self._remote.tag(name, nullid, message, local, user, date, tz)
213 self._remote.invalidate_vcs_cache()
213 self._remote.invalidate_vcs_cache()
214 self.tags = self._get_tags()
214 self.tags = self._get_tags()
215
215
216 @LazyProperty
216 @LazyProperty
217 def bookmarks(self):
217 def bookmarks(self):
218 """
218 """
219 Gets bookmarks for this repository
219 Gets bookmarks for this repository
220 """
220 """
221 return self._get_bookmarks()
221 return self._get_bookmarks()
222
222
223 def _get_bookmarks(self):
223 def _get_bookmarks(self):
224 if self.is_empty():
224 if self.is_empty():
225 return {}
225 return {}
226
226
227 def get_name(ctx):
227 def get_name(ctx):
228 return ctx[0]
228 return ctx[0]
229
229
230 _bookmarks = [
230 _bookmarks = [
231 (safe_unicode(n), hexlify(h)) for n, h in
231 (safe_unicode(n), hexlify(h)) for n, h in
232 self._remote.bookmarks().items()]
232 self._remote.bookmarks().items()]
233
233
234 return OrderedDict(sorted(_bookmarks, key=get_name))
234 return OrderedDict(sorted(_bookmarks, key=get_name))
235
235
236 def _get_all_commit_ids(self):
236 def _get_all_commit_ids(self):
237 return self._remote.get_all_commit_ids('visible')
237 return self._remote.get_all_commit_ids('visible')
238
238
239 def get_diff(
239 def get_diff(
240 self, commit1, commit2, path='', ignore_whitespace=False,
240 self, commit1, commit2, path='', ignore_whitespace=False,
241 context=3, path1=None):
241 context=3, path1=None):
242 """
242 """
243 Returns (git like) *diff*, as plain text. Shows changes introduced by
243 Returns (git like) *diff*, as plain text. Shows changes introduced by
244 `commit2` since `commit1`.
244 `commit2` since `commit1`.
245
245
246 :param commit1: Entry point from which diff is shown. Can be
246 :param commit1: Entry point from which diff is shown. Can be
247 ``self.EMPTY_COMMIT`` - in this case, patch showing all
247 ``self.EMPTY_COMMIT`` - in this case, patch showing all
248 the changes since empty state of the repository until `commit2`
248 the changes since empty state of the repository until `commit2`
249 :param commit2: Until which commit changes should be shown.
249 :param commit2: Until which commit changes should be shown.
250 :param ignore_whitespace: If set to ``True``, would not show whitespace
250 :param ignore_whitespace: If set to ``True``, would not show whitespace
251 changes. Defaults to ``False``.
251 changes. Defaults to ``False``.
252 :param context: How many lines before/after changed lines should be
252 :param context: How many lines before/after changed lines should be
253 shown. Defaults to ``3``.
253 shown. Defaults to ``3``.
254 """
254 """
255 self._validate_diff_commits(commit1, commit2)
255 self._validate_diff_commits(commit1, commit2)
256 if path1 is not None and path1 != path:
256 if path1 is not None and path1 != path:
257 raise ValueError("Diff of two different paths not supported.")
257 raise ValueError("Diff of two different paths not supported.")
258
258
259 if path:
259 if path:
260 file_filter = [self.path, path]
260 file_filter = [self.path, path]
261 else:
261 else:
262 file_filter = None
262 file_filter = None
263
263
264 diff = self._remote.diff(
264 diff = self._remote.diff(
265 commit1.raw_id, commit2.raw_id, file_filter=file_filter,
265 commit1.raw_id, commit2.raw_id, file_filter=file_filter,
266 opt_git=True, opt_ignorews=ignore_whitespace,
266 opt_git=True, opt_ignorews=ignore_whitespace,
267 context=context)
267 context=context)
268 return MercurialDiff(diff)
268 return MercurialDiff(diff)
269
269
270 def strip(self, commit_id, branch=None):
270 def strip(self, commit_id, branch=None):
271 self._remote.strip(commit_id, update=False, backup="none")
271 self._remote.strip(commit_id, update=False, backup="none")
272
272
273 self._remote.invalidate_vcs_cache()
273 self._remote.invalidate_vcs_cache()
274 self.commit_ids = self._get_all_commit_ids()
274 self.commit_ids = self._get_all_commit_ids()
275 self._rebuild_cache(self.commit_ids)
275 self._rebuild_cache(self.commit_ids)
276
276
277 def verify(self):
277 def verify(self):
278 verify = self._remote.verify()
278 verify = self._remote.verify()
279
279
280 self._remote.invalidate_vcs_cache()
280 self._remote.invalidate_vcs_cache()
281 return verify
281 return verify
282
282
283 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
283 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
284 if commit_id1 == commit_id2:
284 if commit_id1 == commit_id2:
285 return commit_id1
285 return commit_id1
286
286
287 ancestors = self._remote.revs_from_revspec(
287 ancestors = self._remote.revs_from_revspec(
288 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
288 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
289 other_path=repo2.path)
289 other_path=repo2.path)
290 return repo2[ancestors[0]].raw_id if ancestors else None
290 return repo2[ancestors[0]].raw_id if ancestors else None
291
291
292 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
292 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
293 if commit_id1 == commit_id2:
293 if commit_id1 == commit_id2:
294 commits = []
294 commits = []
295 else:
295 else:
296 if merge:
296 if merge:
297 indexes = self._remote.revs_from_revspec(
297 indexes = self._remote.revs_from_revspec(
298 "ancestors(id(%s)) - ancestors(id(%s)) - id(%s)",
298 "ancestors(id(%s)) - ancestors(id(%s)) - id(%s)",
299 commit_id2, commit_id1, commit_id1, other_path=repo2.path)
299 commit_id2, commit_id1, commit_id1, other_path=repo2.path)
300 else:
300 else:
301 indexes = self._remote.revs_from_revspec(
301 indexes = self._remote.revs_from_revspec(
302 "id(%s)..id(%s) - id(%s)", commit_id1, commit_id2,
302 "id(%s)..id(%s) - id(%s)", commit_id1, commit_id2,
303 commit_id1, other_path=repo2.path)
303 commit_id1, other_path=repo2.path)
304
304
305 commits = [repo2.get_commit(commit_idx=idx, pre_load=pre_load)
305 commits = [repo2.get_commit(commit_idx=idx, pre_load=pre_load)
306 for idx in indexes]
306 for idx in indexes]
307
307
308 return commits
308 return commits
309
309
310 @staticmethod
310 @staticmethod
311 def check_url(url, config):
311 def check_url(url, config):
312 """
312 """
313 Function will check given url and try to verify if it's a valid
313 Function will check given url and try to verify if it's a valid
314 link. Sometimes it may happened that mercurial will issue basic
314 link. Sometimes it may happened that mercurial will issue basic
315 auth request that can cause whole API to hang when used from python
315 auth request that can cause whole API to hang when used from python
316 or other external calls.
316 or other external calls.
317
317
318 On failures it'll raise urllib2.HTTPError, exception is also thrown
318 On failures it'll raise urllib2.HTTPError, exception is also thrown
319 when the return code is non 200
319 when the return code is non 200
320 """
320 """
321 # check first if it's not an local url
321 # check first if it's not an local url
322 if os.path.isdir(url) or url.startswith('file:'):
322 if os.path.isdir(url) or url.startswith('file:'):
323 return True
323 return True
324
324
325 # Request the _remote to verify the url
325 # Request the _remote to verify the url
326 return connection.Hg.check_url(url, config.serialize())
326 return connection.Hg.check_url(url, config.serialize())
327
327
328 @staticmethod
328 @staticmethod
329 def is_valid_repository(path):
329 def is_valid_repository(path):
330 return os.path.isdir(os.path.join(path, '.hg'))
330 return os.path.isdir(os.path.join(path, '.hg'))
331
331
332 def _init_repo(self, create, src_url=None, do_workspace_checkout=False):
332 def _init_repo(self, create, src_url=None, do_workspace_checkout=False):
333 """
333 """
334 Function will check for mercurial repository in given path. If there
334 Function will check for mercurial repository in given path. If there
335 is no repository in that path it will raise an exception unless
335 is no repository in that path it will raise an exception unless
336 `create` parameter is set to True - in that case repository would
336 `create` parameter is set to True - in that case repository would
337 be created.
337 be created.
338
338
339 If `src_url` is given, would try to clone repository from the
339 If `src_url` is given, would try to clone repository from the
340 location at given clone_point. Additionally it'll make update to
340 location at given clone_point. Additionally it'll make update to
341 working copy accordingly to `do_workspace_checkout` flag.
341 working copy accordingly to `do_workspace_checkout` flag.
342 """
342 """
343 if create and os.path.exists(self.path):
343 if create and os.path.exists(self.path):
344 raise RepositoryError(
344 raise RepositoryError(
345 "Cannot create repository at %s, location already exist"
345 "Cannot create repository at %s, location already exist"
346 % self.path)
346 % self.path)
347
347
348 if src_url:
348 if src_url:
349 url = str(self._get_url(src_url))
349 url = str(self._get_url(src_url))
350 MercurialRepository.check_url(url, self.config)
350 MercurialRepository.check_url(url, self.config)
351
351
352 self._remote.clone(url, self.path, do_workspace_checkout)
352 self._remote.clone(url, self.path, do_workspace_checkout)
353
353
354 # Don't try to create if we've already cloned repo
354 # Don't try to create if we've already cloned repo
355 create = False
355 create = False
356
356
357 if create:
357 if create:
358 os.makedirs(self.path, mode=0o755)
358 os.makedirs(self.path, mode=0o755)
359
359
360 self._remote.localrepository(create)
360 self._remote.localrepository(create)
361
361
362 @LazyProperty
362 @LazyProperty
363 def in_memory_commit(self):
363 def in_memory_commit(self):
364 return MercurialInMemoryCommit(self)
364 return MercurialInMemoryCommit(self)
365
365
366 @LazyProperty
366 @LazyProperty
367 def description(self):
367 def description(self):
368 description = self._remote.get_config_value(
368 description = self._remote.get_config_value(
369 'web', 'description', untrusted=True)
369 'web', 'description', untrusted=True)
370 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
370 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
371
371
372 @LazyProperty
372 @LazyProperty
373 def contact(self):
373 def contact(self):
374 contact = (
374 contact = (
375 self._remote.get_config_value("web", "contact") or
375 self._remote.get_config_value("web", "contact") or
376 self._remote.get_config_value("ui", "username"))
376 self._remote.get_config_value("ui", "username"))
377 return safe_unicode(contact or self.DEFAULT_CONTACT)
377 return safe_unicode(contact or self.DEFAULT_CONTACT)
378
378
379 @LazyProperty
379 @LazyProperty
380 def last_change(self):
380 def last_change(self):
381 """
381 """
382 Returns last change made on this repository as
382 Returns last change made on this repository as
383 `datetime.datetime` object.
383 `datetime.datetime` object.
384 """
384 """
385 try:
385 try:
386 return self.get_commit().date
386 return self.get_commit().date
387 except RepositoryError:
387 except RepositoryError:
388 tzoffset = makedate()[1]
388 tzoffset = makedate()[1]
389 return utcdate_fromtimestamp(self._get_fs_mtime(), tzoffset)
389 return utcdate_fromtimestamp(self._get_fs_mtime(), tzoffset)
390
390
391 def _get_fs_mtime(self):
391 def _get_fs_mtime(self):
392 # fallback to filesystem
392 # fallback to filesystem
393 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
393 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
394 st_path = os.path.join(self.path, '.hg', "store")
394 st_path = os.path.join(self.path, '.hg', "store")
395 if os.path.exists(cl_path):
395 if os.path.exists(cl_path):
396 return os.stat(cl_path).st_mtime
396 return os.stat(cl_path).st_mtime
397 else:
397 else:
398 return os.stat(st_path).st_mtime
398 return os.stat(st_path).st_mtime
399
399
400 def _get_url(self, url):
400 def _get_url(self, url):
401 """
401 """
402 Returns normalized url. If schema is not given, would fall
402 Returns normalized url. If schema is not given, would fall
403 to filesystem
403 to filesystem
404 (``file:///``) schema.
404 (``file:///``) schema.
405 """
405 """
406 url = url.encode('utf8')
406 url = url.encode('utf8')
407 if url != 'default' and '://' not in url:
407 if url != 'default' and '://' not in url:
408 url = "file:" + urllib.pathname2url(url)
408 url = "file:" + urllib.pathname2url(url)
409 return url
409 return url
410
410
411 def get_hook_location(self):
411 def get_hook_location(self):
412 """
412 """
413 returns absolute path to location where hooks are stored
413 returns absolute path to location where hooks are stored
414 """
414 """
415 return os.path.join(self.path, '.hg', '.hgrc')
415 return os.path.join(self.path, '.hg', '.hgrc')
416
416
417 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, translate_tag=None):
417 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, translate_tag=None):
418 """
418 """
419 Returns ``MercurialCommit`` object representing repository's
419 Returns ``MercurialCommit`` object representing repository's
420 commit at the given `commit_id` or `commit_idx`.
420 commit at the given `commit_id` or `commit_idx`.
421 """
421 """
422 if self.is_empty():
422 if self.is_empty():
423 raise EmptyRepositoryError("There are no commits yet")
423 raise EmptyRepositoryError("There are no commits yet")
424
424
425 if commit_id is not None:
425 if commit_id is not None:
426 self._validate_commit_id(commit_id)
426 self._validate_commit_id(commit_id)
427 try:
427 try:
428 # we have cached idx, use it without contacting the remote
428 # we have cached idx, use it without contacting the remote
429 idx = self._commit_ids[commit_id]
429 idx = self._commit_ids[commit_id]
430 return MercurialCommit(self, commit_id, idx, pre_load=pre_load)
430 return MercurialCommit(self, commit_id, idx, pre_load=pre_load)
431 except KeyError:
431 except KeyError:
432 pass
432 pass
433
433
434 elif commit_idx is not None:
434 elif commit_idx is not None:
435 self._validate_commit_idx(commit_idx)
435 self._validate_commit_idx(commit_idx)
436 try:
436 try:
437 _commit_id = self.commit_ids[commit_idx]
437 _commit_id = self.commit_ids[commit_idx]
438 if commit_idx < 0:
438 if commit_idx < 0:
439 commit_idx = self.commit_ids.index(_commit_id)
439 commit_idx = self.commit_ids.index(_commit_id)
440
440
441 return MercurialCommit(self, _commit_id, commit_idx, pre_load=pre_load)
441 return MercurialCommit(self, _commit_id, commit_idx, pre_load=pre_load)
442 except IndexError:
442 except IndexError:
443 commit_id = commit_idx
443 commit_id = commit_idx
444 else:
444 else:
445 commit_id = "tip"
445 commit_id = "tip"
446
446
447 if isinstance(commit_id, unicode):
447 if isinstance(commit_id, unicode):
448 commit_id = safe_str(commit_id)
448 commit_id = safe_str(commit_id)
449
449
450 try:
450 try:
451 raw_id, idx = self._remote.lookup(commit_id, both=True)
451 raw_id, idx = self._remote.lookup(commit_id, both=True)
452 except CommitDoesNotExistError:
452 except CommitDoesNotExistError:
453 msg = "Commit %s does not exist for %s" % (commit_id, self.name)
453 msg = "Commit %s does not exist for %s" % (commit_id, self.name)
454 raise CommitDoesNotExistError(msg)
454 raise CommitDoesNotExistError(msg)
455
455
456 return MercurialCommit(self, raw_id, idx, pre_load=pre_load)
456 return MercurialCommit(self, raw_id, idx, pre_load=pre_load)
457
457
458 def get_commits(
458 def get_commits(
459 self, start_id=None, end_id=None, start_date=None, end_date=None,
459 self, start_id=None, end_id=None, start_date=None, end_date=None,
460 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
460 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
461 """
461 """
462 Returns generator of ``MercurialCommit`` objects from start to end
462 Returns generator of ``MercurialCommit`` objects from start to end
463 (both are inclusive)
463 (both are inclusive)
464
464
465 :param start_id: None, str(commit_id)
465 :param start_id: None, str(commit_id)
466 :param end_id: None, str(commit_id)
466 :param end_id: None, str(commit_id)
467 :param start_date: if specified, commits with commit date less than
467 :param start_date: if specified, commits with commit date less than
468 ``start_date`` would be filtered out from returned set
468 ``start_date`` would be filtered out from returned set
469 :param end_date: if specified, commits with commit date greater than
469 :param end_date: if specified, commits with commit date greater than
470 ``end_date`` would be filtered out from returned set
470 ``end_date`` would be filtered out from returned set
471 :param branch_name: if specified, commits not reachable from given
471 :param branch_name: if specified, commits not reachable from given
472 branch would be filtered out from returned set
472 branch would be filtered out from returned set
473 :param show_hidden: Show hidden commits such as obsolete or hidden from
473 :param show_hidden: Show hidden commits such as obsolete or hidden from
474 Mercurial evolve
474 Mercurial evolve
475 :raise BranchDoesNotExistError: If given ``branch_name`` does not
475 :raise BranchDoesNotExistError: If given ``branch_name`` does not
476 exist.
476 exist.
477 :raise CommitDoesNotExistError: If commit for given ``start`` or
477 :raise CommitDoesNotExistError: If commit for given ``start`` or
478 ``end`` could not be found.
478 ``end`` could not be found.
479 """
479 """
480 # actually we should check now if it's not an empty repo
480 # actually we should check now if it's not an empty repo
481 branch_ancestors = False
482 if self.is_empty():
481 if self.is_empty():
483 raise EmptyRepositoryError("There are no commits yet")
482 raise EmptyRepositoryError("There are no commits yet")
484 self._validate_branch_name(branch_name)
483 self._validate_branch_name(branch_name)
485
484
485 branch_ancestors = False
486 if start_id is not None:
486 if start_id is not None:
487 self._validate_commit_id(start_id)
487 self._validate_commit_id(start_id)
488 c_start = self.get_commit(commit_id=start_id)
488 c_start = self.get_commit(commit_id=start_id)
489 start_pos = self._commit_ids[c_start.raw_id]
489 start_pos = self._commit_ids[c_start.raw_id]
490 else:
490 else:
491 start_pos = None
491 start_pos = None
492
492
493 if end_id is not None:
493 if end_id is not None:
494 self._validate_commit_id(end_id)
494 self._validate_commit_id(end_id)
495 c_end = self.get_commit(commit_id=end_id)
495 c_end = self.get_commit(commit_id=end_id)
496 end_pos = max(0, self._commit_ids[c_end.raw_id])
496 end_pos = max(0, self._commit_ids[c_end.raw_id])
497 else:
497 else:
498 end_pos = None
498 end_pos = None
499
499
500 if None not in [start_id, end_id] and start_pos > end_pos:
500 if None not in [start_id, end_id] and start_pos > end_pos:
501 raise RepositoryError(
501 raise RepositoryError(
502 "Start commit '%s' cannot be after end commit '%s'" %
502 "Start commit '%s' cannot be after end commit '%s'" %
503 (start_id, end_id))
503 (start_id, end_id))
504
504
505 if end_pos is not None:
505 if end_pos is not None:
506 end_pos += 1
506 end_pos += 1
507
507
508 commit_filter = []
508 commit_filter = []
509
509
510 if branch_name and not branch_ancestors:
510 if branch_name and not branch_ancestors:
511 commit_filter.append('branch("%s")' % (branch_name,))
511 commit_filter.append('branch("%s")' % (branch_name,))
512 elif branch_name and branch_ancestors:
512 elif branch_name and branch_ancestors:
513 commit_filter.append('ancestors(branch("%s"))' % (branch_name,))
513 commit_filter.append('ancestors(branch("%s"))' % (branch_name,))
514
514
515 if start_date and not end_date:
515 if start_date and not end_date:
516 commit_filter.append('date(">%s")' % (start_date,))
516 commit_filter.append('date(">%s")' % (start_date,))
517 if end_date and not start_date:
517 if end_date and not start_date:
518 commit_filter.append('date("<%s")' % (end_date,))
518 commit_filter.append('date("<%s")' % (end_date,))
519 if start_date and end_date:
519 if start_date and end_date:
520 commit_filter.append(
520 commit_filter.append(
521 'date(">%s") and date("<%s")' % (start_date, end_date))
521 'date(">%s") and date("<%s")' % (start_date, end_date))
522
522
523 if not show_hidden:
523 if not show_hidden:
524 commit_filter.append('not obsolete()')
524 commit_filter.append('not obsolete()')
525 commit_filter.append('not hidden()')
525 commit_filter.append('not hidden()')
526
526
527 # TODO: johbo: Figure out a simpler way for this solution
527 # TODO: johbo: Figure out a simpler way for this solution
528 collection_generator = CollectionGenerator
528 collection_generator = CollectionGenerator
529 if commit_filter:
529 if commit_filter:
530 commit_filter = ' and '.join(map(safe_str, commit_filter))
530 commit_filter = ' and '.join(map(safe_str, commit_filter))
531 revisions = self._remote.rev_range([commit_filter])
531 revisions = self._remote.rev_range([commit_filter])
532 collection_generator = MercurialIndexBasedCollectionGenerator
532 collection_generator = MercurialIndexBasedCollectionGenerator
533 else:
533 else:
534 revisions = self.commit_ids
534 revisions = self.commit_ids
535
535
536 if start_pos or end_pos:
536 if start_pos or end_pos:
537 revisions = revisions[start_pos:end_pos]
537 revisions = revisions[start_pos:end_pos]
538
538
539 return collection_generator(self, revisions, pre_load=pre_load)
539 return collection_generator(self, revisions, pre_load=pre_load)
540
540
541 def pull(self, url, commit_ids=None):
541 def pull(self, url, commit_ids=None):
542 """
542 """
543 Pull changes from external location.
543 Pull changes from external location.
544
544
545 :param commit_ids: Optional. Can be set to a list of commit ids
545 :param commit_ids: Optional. Can be set to a list of commit ids
546 which shall be pulled from the other repository.
546 which shall be pulled from the other repository.
547 """
547 """
548 url = self._get_url(url)
548 url = self._get_url(url)
549 self._remote.pull(url, commit_ids=commit_ids)
549 self._remote.pull(url, commit_ids=commit_ids)
550 self._remote.invalidate_vcs_cache()
550 self._remote.invalidate_vcs_cache()
551
551
552 def fetch(self, url, commit_ids=None):
552 def fetch(self, url, commit_ids=None):
553 """
553 """
554 Backward compatibility with GIT fetch==pull
554 Backward compatibility with GIT fetch==pull
555 """
555 """
556 return self.pull(url, commit_ids=commit_ids)
556 return self.pull(url, commit_ids=commit_ids)
557
557
558 def push(self, url):
558 def push(self, url):
559 url = self._get_url(url)
559 url = self._get_url(url)
560 self._remote.sync_push(url)
560 self._remote.sync_push(url)
561
561
562 def _local_clone(self, clone_path):
562 def _local_clone(self, clone_path):
563 """
563 """
564 Create a local clone of the current repo.
564 Create a local clone of the current repo.
565 """
565 """
566 self._remote.clone(self.path, clone_path, update_after_clone=True,
566 self._remote.clone(self.path, clone_path, update_after_clone=True,
567 hooks=False)
567 hooks=False)
568
568
569 def _update(self, revision, clean=False):
569 def _update(self, revision, clean=False):
570 """
570 """
571 Update the working copy to the specified revision.
571 Update the working copy to the specified revision.
572 """
572 """
573 log.debug('Doing checkout to commit: `%s` for %s', revision, self)
573 log.debug('Doing checkout to commit: `%s` for %s', revision, self)
574 self._remote.update(revision, clean=clean)
574 self._remote.update(revision, clean=clean)
575
575
576 def _identify(self):
576 def _identify(self):
577 """
577 """
578 Return the current state of the working directory.
578 Return the current state of the working directory.
579 """
579 """
580 return self._remote.identify().strip().rstrip('+')
580 return self._remote.identify().strip().rstrip('+')
581
581
582 def _heads(self, branch=None):
582 def _heads(self, branch=None):
583 """
583 """
584 Return the commit ids of the repository heads.
584 Return the commit ids of the repository heads.
585 """
585 """
586 return self._remote.heads(branch=branch).strip().split(' ')
586 return self._remote.heads(branch=branch).strip().split(' ')
587
587
588 def _ancestor(self, revision1, revision2):
588 def _ancestor(self, revision1, revision2):
589 """
589 """
590 Return the common ancestor of the two revisions.
590 Return the common ancestor of the two revisions.
591 """
591 """
592 return self._remote.ancestor(revision1, revision2)
592 return self._remote.ancestor(revision1, revision2)
593
593
594 def _local_push(
594 def _local_push(
595 self, revision, repository_path, push_branches=False,
595 self, revision, repository_path, push_branches=False,
596 enable_hooks=False):
596 enable_hooks=False):
597 """
597 """
598 Push the given revision to the specified repository.
598 Push the given revision to the specified repository.
599
599
600 :param push_branches: allow to create branches in the target repo.
600 :param push_branches: allow to create branches in the target repo.
601 """
601 """
602 self._remote.push(
602 self._remote.push(
603 [revision], repository_path, hooks=enable_hooks,
603 [revision], repository_path, hooks=enable_hooks,
604 push_branches=push_branches)
604 push_branches=push_branches)
605
605
606 def _local_merge(self, target_ref, merge_message, user_name, user_email,
606 def _local_merge(self, target_ref, merge_message, user_name, user_email,
607 source_ref, use_rebase=False, dry_run=False):
607 source_ref, use_rebase=False, dry_run=False):
608 """
608 """
609 Merge the given source_revision into the checked out revision.
609 Merge the given source_revision into the checked out revision.
610
610
611 Returns the commit id of the merge and a boolean indicating if the
611 Returns the commit id of the merge and a boolean indicating if the
612 commit needs to be pushed.
612 commit needs to be pushed.
613 """
613 """
614 self._update(target_ref.commit_id, clean=True)
614 self._update(target_ref.commit_id, clean=True)
615
615
616 ancestor = self._ancestor(target_ref.commit_id, source_ref.commit_id)
616 ancestor = self._ancestor(target_ref.commit_id, source_ref.commit_id)
617 is_the_same_branch = self._is_the_same_branch(target_ref, source_ref)
617 is_the_same_branch = self._is_the_same_branch(target_ref, source_ref)
618
618
619 if ancestor == source_ref.commit_id:
619 if ancestor == source_ref.commit_id:
620 # Nothing to do, the changes were already integrated
620 # Nothing to do, the changes were already integrated
621 return target_ref.commit_id, False
621 return target_ref.commit_id, False
622
622
623 elif ancestor == target_ref.commit_id and is_the_same_branch:
623 elif ancestor == target_ref.commit_id and is_the_same_branch:
624 # In this case we should force a commit message
624 # In this case we should force a commit message
625 return source_ref.commit_id, True
625 return source_ref.commit_id, True
626
626
627 if use_rebase:
627 if use_rebase:
628 try:
628 try:
629 bookmark_name = 'rcbook%s%s' % (source_ref.commit_id,
629 bookmark_name = 'rcbook%s%s' % (source_ref.commit_id,
630 target_ref.commit_id)
630 target_ref.commit_id)
631 self.bookmark(bookmark_name, revision=source_ref.commit_id)
631 self.bookmark(bookmark_name, revision=source_ref.commit_id)
632 self._remote.rebase(
632 self._remote.rebase(
633 source=source_ref.commit_id, dest=target_ref.commit_id)
633 source=source_ref.commit_id, dest=target_ref.commit_id)
634 self._remote.invalidate_vcs_cache()
634 self._remote.invalidate_vcs_cache()
635 self._update(bookmark_name, clean=True)
635 self._update(bookmark_name, clean=True)
636 return self._identify(), True
636 return self._identify(), True
637 except RepositoryError:
637 except RepositoryError:
638 # The rebase-abort may raise another exception which 'hides'
638 # The rebase-abort may raise another exception which 'hides'
639 # the original one, therefore we log it here.
639 # the original one, therefore we log it here.
640 log.exception('Error while rebasing shadow repo during merge.')
640 log.exception('Error while rebasing shadow repo during merge.')
641
641
642 # Cleanup any rebase leftovers
642 # Cleanup any rebase leftovers
643 self._remote.invalidate_vcs_cache()
643 self._remote.invalidate_vcs_cache()
644 self._remote.rebase(abort=True)
644 self._remote.rebase(abort=True)
645 self._remote.invalidate_vcs_cache()
645 self._remote.invalidate_vcs_cache()
646 self._remote.update(clean=True)
646 self._remote.update(clean=True)
647 raise
647 raise
648 else:
648 else:
649 try:
649 try:
650 self._remote.merge(source_ref.commit_id)
650 self._remote.merge(source_ref.commit_id)
651 self._remote.invalidate_vcs_cache()
651 self._remote.invalidate_vcs_cache()
652 self._remote.commit(
652 self._remote.commit(
653 message=safe_str(merge_message),
653 message=safe_str(merge_message),
654 username=safe_str('%s <%s>' % (user_name, user_email)))
654 username=safe_str('%s <%s>' % (user_name, user_email)))
655 self._remote.invalidate_vcs_cache()
655 self._remote.invalidate_vcs_cache()
656 return self._identify(), True
656 return self._identify(), True
657 except RepositoryError:
657 except RepositoryError:
658 # Cleanup any merge leftovers
658 # Cleanup any merge leftovers
659 self._remote.update(clean=True)
659 self._remote.update(clean=True)
660 raise
660 raise
661
661
662 def _local_close(self, target_ref, user_name, user_email,
662 def _local_close(self, target_ref, user_name, user_email,
663 source_ref, close_message=''):
663 source_ref, close_message=''):
664 """
664 """
665 Close the branch of the given source_revision
665 Close the branch of the given source_revision
666
666
667 Returns the commit id of the close and a boolean indicating if the
667 Returns the commit id of the close and a boolean indicating if the
668 commit needs to be pushed.
668 commit needs to be pushed.
669 """
669 """
670 self._update(source_ref.commit_id)
670 self._update(source_ref.commit_id)
671 message = close_message or "Closing branch: `{}`".format(source_ref.name)
671 message = close_message or "Closing branch: `{}`".format(source_ref.name)
672 try:
672 try:
673 self._remote.commit(
673 self._remote.commit(
674 message=safe_str(message),
674 message=safe_str(message),
675 username=safe_str('%s <%s>' % (user_name, user_email)),
675 username=safe_str('%s <%s>' % (user_name, user_email)),
676 close_branch=True)
676 close_branch=True)
677 self._remote.invalidate_vcs_cache()
677 self._remote.invalidate_vcs_cache()
678 return self._identify(), True
678 return self._identify(), True
679 except RepositoryError:
679 except RepositoryError:
680 # Cleanup any commit leftovers
680 # Cleanup any commit leftovers
681 self._remote.update(clean=True)
681 self._remote.update(clean=True)
682 raise
682 raise
683
683
684 def _is_the_same_branch(self, target_ref, source_ref):
684 def _is_the_same_branch(self, target_ref, source_ref):
685 return (
685 return (
686 self._get_branch_name(target_ref) ==
686 self._get_branch_name(target_ref) ==
687 self._get_branch_name(source_ref))
687 self._get_branch_name(source_ref))
688
688
689 def _get_branch_name(self, ref):
689 def _get_branch_name(self, ref):
690 if ref.type == 'branch':
690 if ref.type == 'branch':
691 return ref.name
691 return ref.name
692 return self._remote.ctx_branch(ref.commit_id)
692 return self._remote.ctx_branch(ref.commit_id)
693
693
694 def _maybe_prepare_merge_workspace(
694 def _maybe_prepare_merge_workspace(
695 self, repo_id, workspace_id, unused_target_ref, unused_source_ref):
695 self, repo_id, workspace_id, unused_target_ref, unused_source_ref):
696 shadow_repository_path = self._get_shadow_repository_path(
696 shadow_repository_path = self._get_shadow_repository_path(
697 repo_id, workspace_id)
697 repo_id, workspace_id)
698 if not os.path.exists(shadow_repository_path):
698 if not os.path.exists(shadow_repository_path):
699 self._local_clone(shadow_repository_path)
699 self._local_clone(shadow_repository_path)
700 log.debug(
700 log.debug(
701 'Prepared shadow repository in %s', shadow_repository_path)
701 'Prepared shadow repository in %s', shadow_repository_path)
702
702
703 return shadow_repository_path
703 return shadow_repository_path
704
704
705 def _merge_repo(self, repo_id, workspace_id, target_ref,
705 def _merge_repo(self, repo_id, workspace_id, target_ref,
706 source_repo, source_ref, merge_message,
706 source_repo, source_ref, merge_message,
707 merger_name, merger_email, dry_run=False,
707 merger_name, merger_email, dry_run=False,
708 use_rebase=False, close_branch=False):
708 use_rebase=False, close_branch=False):
709
709
710 log.debug('Executing merge_repo with %s strategy, dry_run mode:%s',
710 log.debug('Executing merge_repo with %s strategy, dry_run mode:%s',
711 'rebase' if use_rebase else 'merge', dry_run)
711 'rebase' if use_rebase else 'merge', dry_run)
712 if target_ref.commit_id not in self._heads():
712 if target_ref.commit_id not in self._heads():
713 return MergeResponse(
713 return MergeResponse(
714 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD,
714 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD,
715 metadata={'target_ref': target_ref})
715 metadata={'target_ref': target_ref})
716
716
717 try:
717 try:
718 if target_ref.type == 'branch' and len(self._heads(target_ref.name)) != 1:
718 if target_ref.type == 'branch' and len(self._heads(target_ref.name)) != 1:
719 heads = '\n,'.join(self._heads(target_ref.name))
719 heads = '\n,'.join(self._heads(target_ref.name))
720 metadata = {
720 metadata = {
721 'target_ref': target_ref,
721 'target_ref': target_ref,
722 'source_ref': source_ref,
722 'source_ref': source_ref,
723 'heads': heads
723 'heads': heads
724 }
724 }
725 return MergeResponse(
725 return MergeResponse(
726 False, False, None,
726 False, False, None,
727 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
727 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
728 metadata=metadata)
728 metadata=metadata)
729 except CommitDoesNotExistError:
729 except CommitDoesNotExistError:
730 log.exception('Failure when looking up branch heads on hg target')
730 log.exception('Failure when looking up branch heads on hg target')
731 return MergeResponse(
731 return MergeResponse(
732 False, False, None, MergeFailureReason.MISSING_TARGET_REF,
732 False, False, None, MergeFailureReason.MISSING_TARGET_REF,
733 metadata={'target_ref': target_ref})
733 metadata={'target_ref': target_ref})
734
734
735 shadow_repository_path = self._maybe_prepare_merge_workspace(
735 shadow_repository_path = self._maybe_prepare_merge_workspace(
736 repo_id, workspace_id, target_ref, source_ref)
736 repo_id, workspace_id, target_ref, source_ref)
737 shadow_repo = self._get_shadow_instance(shadow_repository_path)
737 shadow_repo = self._get_shadow_instance(shadow_repository_path)
738
738
739 log.debug('Pulling in target reference %s', target_ref)
739 log.debug('Pulling in target reference %s', target_ref)
740 self._validate_pull_reference(target_ref)
740 self._validate_pull_reference(target_ref)
741 shadow_repo._local_pull(self.path, target_ref)
741 shadow_repo._local_pull(self.path, target_ref)
742
742
743 try:
743 try:
744 log.debug('Pulling in source reference %s', source_ref)
744 log.debug('Pulling in source reference %s', source_ref)
745 source_repo._validate_pull_reference(source_ref)
745 source_repo._validate_pull_reference(source_ref)
746 shadow_repo._local_pull(source_repo.path, source_ref)
746 shadow_repo._local_pull(source_repo.path, source_ref)
747 except CommitDoesNotExistError:
747 except CommitDoesNotExistError:
748 log.exception('Failure when doing local pull on hg shadow repo')
748 log.exception('Failure when doing local pull on hg shadow repo')
749 return MergeResponse(
749 return MergeResponse(
750 False, False, None, MergeFailureReason.MISSING_SOURCE_REF,
750 False, False, None, MergeFailureReason.MISSING_SOURCE_REF,
751 metadata={'source_ref': source_ref})
751 metadata={'source_ref': source_ref})
752
752
753 merge_ref = None
753 merge_ref = None
754 merge_commit_id = None
754 merge_commit_id = None
755 close_commit_id = None
755 close_commit_id = None
756 merge_failure_reason = MergeFailureReason.NONE
756 merge_failure_reason = MergeFailureReason.NONE
757 metadata = {}
757 metadata = {}
758
758
759 # enforce that close branch should be used only in case we source from
759 # enforce that close branch should be used only in case we source from
760 # an actual Branch
760 # an actual Branch
761 close_branch = close_branch and source_ref.type == 'branch'
761 close_branch = close_branch and source_ref.type == 'branch'
762
762
763 # don't allow to close branch if source and target are the same
763 # don't allow to close branch if source and target are the same
764 close_branch = close_branch and source_ref.name != target_ref.name
764 close_branch = close_branch and source_ref.name != target_ref.name
765
765
766 needs_push_on_close = False
766 needs_push_on_close = False
767 if close_branch and not use_rebase and not dry_run:
767 if close_branch and not use_rebase and not dry_run:
768 try:
768 try:
769 close_commit_id, needs_push_on_close = shadow_repo._local_close(
769 close_commit_id, needs_push_on_close = shadow_repo._local_close(
770 target_ref, merger_name, merger_email, source_ref)
770 target_ref, merger_name, merger_email, source_ref)
771 merge_possible = True
771 merge_possible = True
772 except RepositoryError:
772 except RepositoryError:
773 log.exception('Failure when doing close branch on '
773 log.exception('Failure when doing close branch on '
774 'shadow repo: %s', shadow_repo)
774 'shadow repo: %s', shadow_repo)
775 merge_possible = False
775 merge_possible = False
776 merge_failure_reason = MergeFailureReason.MERGE_FAILED
776 merge_failure_reason = MergeFailureReason.MERGE_FAILED
777 else:
777 else:
778 merge_possible = True
778 merge_possible = True
779
779
780 needs_push = False
780 needs_push = False
781 if merge_possible:
781 if merge_possible:
782 try:
782 try:
783 merge_commit_id, needs_push = shadow_repo._local_merge(
783 merge_commit_id, needs_push = shadow_repo._local_merge(
784 target_ref, merge_message, merger_name, merger_email,
784 target_ref, merge_message, merger_name, merger_email,
785 source_ref, use_rebase=use_rebase, dry_run=dry_run)
785 source_ref, use_rebase=use_rebase, dry_run=dry_run)
786 merge_possible = True
786 merge_possible = True
787
787
788 # read the state of the close action, if it
788 # read the state of the close action, if it
789 # maybe required a push
789 # maybe required a push
790 needs_push = needs_push or needs_push_on_close
790 needs_push = needs_push or needs_push_on_close
791
791
792 # Set a bookmark pointing to the merge commit. This bookmark
792 # Set a bookmark pointing to the merge commit. This bookmark
793 # may be used to easily identify the last successful merge
793 # may be used to easily identify the last successful merge
794 # commit in the shadow repository.
794 # commit in the shadow repository.
795 shadow_repo.bookmark('pr-merge', revision=merge_commit_id)
795 shadow_repo.bookmark('pr-merge', revision=merge_commit_id)
796 merge_ref = Reference('book', 'pr-merge', merge_commit_id)
796 merge_ref = Reference('book', 'pr-merge', merge_commit_id)
797 except SubrepoMergeError:
797 except SubrepoMergeError:
798 log.exception(
798 log.exception(
799 'Subrepo merge error during local merge on hg shadow repo.')
799 'Subrepo merge error during local merge on hg shadow repo.')
800 merge_possible = False
800 merge_possible = False
801 merge_failure_reason = MergeFailureReason.SUBREPO_MERGE_FAILED
801 merge_failure_reason = MergeFailureReason.SUBREPO_MERGE_FAILED
802 needs_push = False
802 needs_push = False
803 except RepositoryError:
803 except RepositoryError:
804 log.exception('Failure when doing local merge on hg shadow repo')
804 log.exception('Failure when doing local merge on hg shadow repo')
805 merge_possible = False
805 merge_possible = False
806 merge_failure_reason = MergeFailureReason.MERGE_FAILED
806 merge_failure_reason = MergeFailureReason.MERGE_FAILED
807 needs_push = False
807 needs_push = False
808
808
809 if merge_possible and not dry_run:
809 if merge_possible and not dry_run:
810 if needs_push:
810 if needs_push:
811 # In case the target is a bookmark, update it, so after pushing
811 # In case the target is a bookmark, update it, so after pushing
812 # the bookmarks is also updated in the target.
812 # the bookmarks is also updated in the target.
813 if target_ref.type == 'book':
813 if target_ref.type == 'book':
814 shadow_repo.bookmark(
814 shadow_repo.bookmark(
815 target_ref.name, revision=merge_commit_id)
815 target_ref.name, revision=merge_commit_id)
816 try:
816 try:
817 shadow_repo_with_hooks = self._get_shadow_instance(
817 shadow_repo_with_hooks = self._get_shadow_instance(
818 shadow_repository_path,
818 shadow_repository_path,
819 enable_hooks=True)
819 enable_hooks=True)
820 # This is the actual merge action, we push from shadow
820 # This is the actual merge action, we push from shadow
821 # into origin.
821 # into origin.
822 # Note: the push_branches option will push any new branch
822 # Note: the push_branches option will push any new branch
823 # defined in the source repository to the target. This may
823 # defined in the source repository to the target. This may
824 # be dangerous as branches are permanent in Mercurial.
824 # be dangerous as branches are permanent in Mercurial.
825 # This feature was requested in issue #441.
825 # This feature was requested in issue #441.
826 shadow_repo_with_hooks._local_push(
826 shadow_repo_with_hooks._local_push(
827 merge_commit_id, self.path, push_branches=True,
827 merge_commit_id, self.path, push_branches=True,
828 enable_hooks=True)
828 enable_hooks=True)
829
829
830 # maybe we also need to push the close_commit_id
830 # maybe we also need to push the close_commit_id
831 if close_commit_id:
831 if close_commit_id:
832 shadow_repo_with_hooks._local_push(
832 shadow_repo_with_hooks._local_push(
833 close_commit_id, self.path, push_branches=True,
833 close_commit_id, self.path, push_branches=True,
834 enable_hooks=True)
834 enable_hooks=True)
835 merge_succeeded = True
835 merge_succeeded = True
836 except RepositoryError:
836 except RepositoryError:
837 log.exception(
837 log.exception(
838 'Failure when doing local push from the shadow '
838 'Failure when doing local push from the shadow '
839 'repository to the target repository at %s.', self.path)
839 'repository to the target repository at %s.', self.path)
840 merge_succeeded = False
840 merge_succeeded = False
841 merge_failure_reason = MergeFailureReason.PUSH_FAILED
841 merge_failure_reason = MergeFailureReason.PUSH_FAILED
842 metadata['target'] = 'hg shadow repo'
842 metadata['target'] = 'hg shadow repo'
843 metadata['merge_commit'] = merge_commit_id
843 metadata['merge_commit'] = merge_commit_id
844 else:
844 else:
845 merge_succeeded = True
845 merge_succeeded = True
846 else:
846 else:
847 merge_succeeded = False
847 merge_succeeded = False
848
848
849 return MergeResponse(
849 return MergeResponse(
850 merge_possible, merge_succeeded, merge_ref, merge_failure_reason,
850 merge_possible, merge_succeeded, merge_ref, merge_failure_reason,
851 metadata=metadata)
851 metadata=metadata)
852
852
853 def _get_shadow_instance(self, shadow_repository_path, enable_hooks=False):
853 def _get_shadow_instance(self, shadow_repository_path, enable_hooks=False):
854 config = self.config.copy()
854 config = self.config.copy()
855 if not enable_hooks:
855 if not enable_hooks:
856 config.clear_section('hooks')
856 config.clear_section('hooks')
857 return MercurialRepository(shadow_repository_path, config)
857 return MercurialRepository(shadow_repository_path, config)
858
858
859 def _validate_pull_reference(self, reference):
859 def _validate_pull_reference(self, reference):
860 if not (reference.name in self.bookmarks or
860 if not (reference.name in self.bookmarks or
861 reference.name in self.branches or
861 reference.name in self.branches or
862 self.get_commit(reference.commit_id)):
862 self.get_commit(reference.commit_id)):
863 raise CommitDoesNotExistError(
863 raise CommitDoesNotExistError(
864 'Unknown branch, bookmark or commit id')
864 'Unknown branch, bookmark or commit id')
865
865
866 def _local_pull(self, repository_path, reference):
866 def _local_pull(self, repository_path, reference):
867 """
867 """
868 Fetch a branch, bookmark or commit from a local repository.
868 Fetch a branch, bookmark or commit from a local repository.
869 """
869 """
870 repository_path = os.path.abspath(repository_path)
870 repository_path = os.path.abspath(repository_path)
871 if repository_path == self.path:
871 if repository_path == self.path:
872 raise ValueError('Cannot pull from the same repository')
872 raise ValueError('Cannot pull from the same repository')
873
873
874 reference_type_to_option_name = {
874 reference_type_to_option_name = {
875 'book': 'bookmark',
875 'book': 'bookmark',
876 'branch': 'branch',
876 'branch': 'branch',
877 }
877 }
878 option_name = reference_type_to_option_name.get(
878 option_name = reference_type_to_option_name.get(
879 reference.type, 'revision')
879 reference.type, 'revision')
880
880
881 if option_name == 'revision':
881 if option_name == 'revision':
882 ref = reference.commit_id
882 ref = reference.commit_id
883 else:
883 else:
884 ref = reference.name
884 ref = reference.name
885
885
886 options = {option_name: [ref]}
886 options = {option_name: [ref]}
887 self._remote.pull_cmd(repository_path, hooks=False, **options)
887 self._remote.pull_cmd(repository_path, hooks=False, **options)
888 self._remote.invalidate_vcs_cache()
888 self._remote.invalidate_vcs_cache()
889
889
890 def bookmark(self, bookmark, revision=None):
890 def bookmark(self, bookmark, revision=None):
891 if isinstance(bookmark, unicode):
891 if isinstance(bookmark, unicode):
892 bookmark = safe_str(bookmark)
892 bookmark = safe_str(bookmark)
893 self._remote.bookmark(bookmark, revision=revision)
893 self._remote.bookmark(bookmark, revision=revision)
894 self._remote.invalidate_vcs_cache()
894 self._remote.invalidate_vcs_cache()
895
895
896 def get_path_permissions(self, username):
896 def get_path_permissions(self, username):
897 hgacl_file = os.path.join(self.path, '.hg/hgacl')
897 hgacl_file = os.path.join(self.path, '.hg/hgacl')
898
898
899 def read_patterns(suffix):
899 def read_patterns(suffix):
900 svalue = None
900 svalue = None
901 for section, option in [
901 for section, option in [
902 ('narrowacl', username + suffix),
902 ('narrowacl', username + suffix),
903 ('narrowacl', 'default' + suffix),
903 ('narrowacl', 'default' + suffix),
904 ('narrowhgacl', username + suffix),
904 ('narrowhgacl', username + suffix),
905 ('narrowhgacl', 'default' + suffix)
905 ('narrowhgacl', 'default' + suffix)
906 ]:
906 ]:
907 try:
907 try:
908 svalue = hgacl.get(section, option)
908 svalue = hgacl.get(section, option)
909 break # stop at the first value we find
909 break # stop at the first value we find
910 except configparser.NoOptionError:
910 except configparser.NoOptionError:
911 pass
911 pass
912 if not svalue:
912 if not svalue:
913 return None
913 return None
914 result = ['/']
914 result = ['/']
915 for pattern in svalue.split():
915 for pattern in svalue.split():
916 result.append(pattern)
916 result.append(pattern)
917 if '*' not in pattern and '?' not in pattern:
917 if '*' not in pattern and '?' not in pattern:
918 result.append(pattern + '/*')
918 result.append(pattern + '/*')
919 return result
919 return result
920
920
921 if os.path.exists(hgacl_file):
921 if os.path.exists(hgacl_file):
922 try:
922 try:
923 hgacl = configparser.RawConfigParser()
923 hgacl = configparser.RawConfigParser()
924 hgacl.read(hgacl_file)
924 hgacl.read(hgacl_file)
925
925
926 includes = read_patterns('.includes')
926 includes = read_patterns('.includes')
927 excludes = read_patterns('.excludes')
927 excludes = read_patterns('.excludes')
928 return BasePathPermissionChecker.create_from_patterns(
928 return BasePathPermissionChecker.create_from_patterns(
929 includes, excludes)
929 includes, excludes)
930 except BaseException as e:
930 except BaseException as e:
931 msg = 'Cannot read ACL settings from {} on {}: {}'.format(
931 msg = 'Cannot read ACL settings from {} on {}: {}'.format(
932 hgacl_file, self.name, e)
932 hgacl_file, self.name, e)
933 raise exceptions.RepositoryRequirementError(msg)
933 raise exceptions.RepositoryRequirementError(msg)
934 else:
934 else:
935 return None
935 return None
936
936
937
937
938 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
938 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
939
939
940 def _commit_factory(self, commit_id):
940 def _commit_factory(self, commit_id):
941 return self.repo.get_commit(
941 return self.repo.get_commit(
942 commit_idx=commit_id, pre_load=self.pre_load)
942 commit_idx=commit_id, pre_load=self.pre_load)
@@ -1,360 +1,360 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-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 SVN repository module
22 SVN repository module
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import urllib
27 import urllib
28
28
29 from zope.cachedescriptors.property import Lazy as LazyProperty
29 from zope.cachedescriptors.property import Lazy as LazyProperty
30
30
31 from rhodecode.lib.compat import OrderedDict
31 from rhodecode.lib.compat import OrderedDict
32 from rhodecode.lib.datelib import date_astimestamp
32 from rhodecode.lib.datelib import date_astimestamp
33 from rhodecode.lib.utils import safe_str, safe_unicode
33 from rhodecode.lib.utils import safe_str, safe_unicode
34 from rhodecode.lib.vcs import connection, path as vcspath
34 from rhodecode.lib.vcs import connection, path as vcspath
35 from rhodecode.lib.vcs.backends import base
35 from rhodecode.lib.vcs.backends import base
36 from rhodecode.lib.vcs.backends.svn.commit import (
36 from rhodecode.lib.vcs.backends.svn.commit import (
37 SubversionCommit, _date_from_svn_properties)
37 SubversionCommit, _date_from_svn_properties)
38 from rhodecode.lib.vcs.backends.svn.diff import SubversionDiff
38 from rhodecode.lib.vcs.backends.svn.diff import SubversionDiff
39 from rhodecode.lib.vcs.backends.svn.inmemory import SubversionInMemoryCommit
39 from rhodecode.lib.vcs.backends.svn.inmemory import SubversionInMemoryCommit
40 from rhodecode.lib.vcs.conf import settings
40 from rhodecode.lib.vcs.conf import settings
41 from rhodecode.lib.vcs.exceptions import (
41 from rhodecode.lib.vcs.exceptions import (
42 CommitDoesNotExistError, EmptyRepositoryError, RepositoryError,
42 CommitDoesNotExistError, EmptyRepositoryError, RepositoryError,
43 VCSError, NodeDoesNotExistError)
43 VCSError, NodeDoesNotExistError)
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class SubversionRepository(base.BaseRepository):
49 class SubversionRepository(base.BaseRepository):
50 """
50 """
51 Subversion backend implementation
51 Subversion backend implementation
52
52
53 .. important::
53 .. important::
54
54
55 It is very important to distinguish the commit index and the commit id
55 It is very important to distinguish the commit index and the commit id
56 which is assigned by Subversion. The first one is always handled as an
56 which is assigned by Subversion. The first one is always handled as an
57 `int` by this implementation. The commit id assigned by Subversion on
57 `int` by this implementation. The commit id assigned by Subversion on
58 the other side will always be a `str`.
58 the other side will always be a `str`.
59
59
60 There is a specific trap since the first commit will have the index
60 There is a specific trap since the first commit will have the index
61 ``0`` but the svn id will be ``"1"``.
61 ``0`` but the svn id will be ``"1"``.
62
62
63 """
63 """
64
64
65 # Note: Subversion does not really have a default branch name.
65 # Note: Subversion does not really have a default branch name.
66 DEFAULT_BRANCH_NAME = None
66 DEFAULT_BRANCH_NAME = None
67
67
68 contact = base.BaseRepository.DEFAULT_CONTACT
68 contact = base.BaseRepository.DEFAULT_CONTACT
69 description = base.BaseRepository.DEFAULT_DESCRIPTION
69 description = base.BaseRepository.DEFAULT_DESCRIPTION
70
70
71 def __init__(self, repo_path, config=None, create=False, src_url=None, bare=False,
71 def __init__(self, repo_path, config=None, create=False, src_url=None, bare=False,
72 **kwargs):
72 **kwargs):
73 self.path = safe_str(os.path.abspath(repo_path))
73 self.path = safe_str(os.path.abspath(repo_path))
74 self.config = config if config else self.get_default_config()
74 self.config = config if config else self.get_default_config()
75
75
76 self._init_repo(create, src_url)
76 self._init_repo(create, src_url)
77
77
78 @LazyProperty
78 @LazyProperty
79 def _remote(self):
79 def _remote(self):
80 return connection.Svn(self.path, self.config)
80 return connection.Svn(self.path, self.config)
81
81
82 def _init_repo(self, create, src_url):
82 def _init_repo(self, create, src_url):
83 if create and os.path.exists(self.path):
83 if create and os.path.exists(self.path):
84 raise RepositoryError(
84 raise RepositoryError(
85 "Cannot create repository at %s, location already exist"
85 "Cannot create repository at %s, location already exist"
86 % self.path)
86 % self.path)
87
87
88 if create:
88 if create:
89 self._remote.create_repository(settings.SVN_COMPATIBLE_VERSION)
89 self._remote.create_repository(settings.SVN_COMPATIBLE_VERSION)
90 if src_url:
90 if src_url:
91 src_url = _sanitize_url(src_url)
91 src_url = _sanitize_url(src_url)
92 self._remote.import_remote_repository(src_url)
92 self._remote.import_remote_repository(src_url)
93 else:
93 else:
94 self._check_path()
94 self._check_path()
95
95
96 @LazyProperty
96 @LazyProperty
97 def commit_ids(self):
97 def commit_ids(self):
98 head = self._remote.lookup(None)
98 head = self._remote.lookup(None)
99 return [str(r) for r in xrange(1, head + 1)]
99 return [str(r) for r in xrange(1, head + 1)]
100
100
101 def run_svn_command(self, cmd, **opts):
101 def run_svn_command(self, cmd, **opts):
102 """
102 """
103 Runs given ``cmd`` as svn command and returns tuple
103 Runs given ``cmd`` as svn command and returns tuple
104 (stdout, stderr).
104 (stdout, stderr).
105
105
106 :param cmd: full svn command to be executed
106 :param cmd: full svn command to be executed
107 :param opts: env options to pass into Subprocess command
107 :param opts: env options to pass into Subprocess command
108 """
108 """
109 if not isinstance(cmd, list):
109 if not isinstance(cmd, list):
110 raise ValueError('cmd must be a list, got %s instead' % type(cmd))
110 raise ValueError('cmd must be a list, got %s instead' % type(cmd))
111
111
112 skip_stderr_log = opts.pop('skip_stderr_log', False)
112 skip_stderr_log = opts.pop('skip_stderr_log', False)
113 out, err = self._remote.run_svn_command(cmd, **opts)
113 out, err = self._remote.run_svn_command(cmd, **opts)
114 if err and not skip_stderr_log:
114 if err and not skip_stderr_log:
115 log.debug('Stderr output of svn command "%s":\n%s', cmd, err)
115 log.debug('Stderr output of svn command "%s":\n%s', cmd, err)
116 return out, err
116 return out, err
117
117
118 @LazyProperty
118 @LazyProperty
119 def branches(self):
119 def branches(self):
120 return self._tags_or_branches('vcs_svn_branch')
120 return self._tags_or_branches('vcs_svn_branch')
121
121
122 @LazyProperty
122 @LazyProperty
123 def branches_closed(self):
123 def branches_closed(self):
124 return {}
124 return {}
125
125
126 @LazyProperty
126 @LazyProperty
127 def bookmarks(self):
127 def bookmarks(self):
128 return {}
128 return {}
129
129
130 @LazyProperty
130 @LazyProperty
131 def branches_all(self):
131 def branches_all(self):
132 # TODO: johbo: Implement proper branch support
132 # TODO: johbo: Implement proper branch support
133 all_branches = {}
133 all_branches = {}
134 all_branches.update(self.branches)
134 all_branches.update(self.branches)
135 all_branches.update(self.branches_closed)
135 all_branches.update(self.branches_closed)
136 return all_branches
136 return all_branches
137
137
138 @LazyProperty
138 @LazyProperty
139 def tags(self):
139 def tags(self):
140 return self._tags_or_branches('vcs_svn_tag')
140 return self._tags_or_branches('vcs_svn_tag')
141
141
142 def _tags_or_branches(self, config_section):
142 def _tags_or_branches(self, config_section):
143 found_items = {}
143 found_items = {}
144
144
145 if self.is_empty():
145 if self.is_empty():
146 return {}
146 return {}
147
147
148 for pattern in self._patterns_from_section(config_section):
148 for pattern in self._patterns_from_section(config_section):
149 pattern = vcspath.sanitize(pattern)
149 pattern = vcspath.sanitize(pattern)
150 tip = self.get_commit()
150 tip = self.get_commit()
151 try:
151 try:
152 if pattern.endswith('*'):
152 if pattern.endswith('*'):
153 basedir = tip.get_node(vcspath.dirname(pattern))
153 basedir = tip.get_node(vcspath.dirname(pattern))
154 directories = basedir.dirs
154 directories = basedir.dirs
155 else:
155 else:
156 directories = (tip.get_node(pattern), )
156 directories = (tip.get_node(pattern), )
157 except NodeDoesNotExistError:
157 except NodeDoesNotExistError:
158 continue
158 continue
159 found_items.update(
159 found_items.update(
160 (safe_unicode(n.path),
160 (safe_unicode(n.path),
161 self.commit_ids[-1])
161 self.commit_ids[-1])
162 for n in directories)
162 for n in directories)
163
163
164 def get_name(item):
164 def get_name(item):
165 return item[0]
165 return item[0]
166
166
167 return OrderedDict(sorted(found_items.items(), key=get_name))
167 return OrderedDict(sorted(found_items.items(), key=get_name))
168
168
169 def _patterns_from_section(self, section):
169 def _patterns_from_section(self, section):
170 return (pattern for key, pattern in self.config.items(section))
170 return (pattern for key, pattern in self.config.items(section))
171
171
172 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
172 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
173 if self != repo2:
173 if self != repo2:
174 raise ValueError(
174 raise ValueError(
175 "Subversion does not support getting common ancestor of"
175 "Subversion does not support getting common ancestor of"
176 " different repositories.")
176 " different repositories.")
177
177
178 if int(commit_id1) < int(commit_id2):
178 if int(commit_id1) < int(commit_id2):
179 return commit_id1
179 return commit_id1
180 return commit_id2
180 return commit_id2
181
181
182 def verify(self):
182 def verify(self):
183 verify = self._remote.verify()
183 verify = self._remote.verify()
184
184
185 self._remote.invalidate_vcs_cache()
185 self._remote.invalidate_vcs_cache()
186 return verify
186 return verify
187
187
188 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
188 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
189 # TODO: johbo: Implement better comparison, this is a very naive
189 # TODO: johbo: Implement better comparison, this is a very naive
190 # version which does not allow to compare branches, tags or folders
190 # version which does not allow to compare branches, tags or folders
191 # at all.
191 # at all.
192 if repo2 != self:
192 if repo2 != self:
193 raise ValueError(
193 raise ValueError(
194 "Subversion does not support comparison of of different "
194 "Subversion does not support comparison of of different "
195 "repositories.")
195 "repositories.")
196
196
197 if commit_id1 == commit_id2:
197 if commit_id1 == commit_id2:
198 return []
198 return []
199
199
200 commit_idx1 = self._get_commit_idx(commit_id1)
200 commit_idx1 = self._get_commit_idx(commit_id1)
201 commit_idx2 = self._get_commit_idx(commit_id2)
201 commit_idx2 = self._get_commit_idx(commit_id2)
202
202
203 commits = [
203 commits = [
204 self.get_commit(commit_idx=idx)
204 self.get_commit(commit_idx=idx)
205 for idx in range(commit_idx1 + 1, commit_idx2 + 1)]
205 for idx in range(commit_idx1 + 1, commit_idx2 + 1)]
206
206
207 return commits
207 return commits
208
208
209 def _get_commit_idx(self, commit_id):
209 def _get_commit_idx(self, commit_id):
210 try:
210 try:
211 svn_rev = int(commit_id)
211 svn_rev = int(commit_id)
212 except:
212 except:
213 # TODO: johbo: this might be only one case, HEAD, check this
213 # TODO: johbo: this might be only one case, HEAD, check this
214 svn_rev = self._remote.lookup(commit_id)
214 svn_rev = self._remote.lookup(commit_id)
215 commit_idx = svn_rev - 1
215 commit_idx = svn_rev - 1
216 if commit_idx >= len(self.commit_ids):
216 if commit_idx >= len(self.commit_ids):
217 raise CommitDoesNotExistError(
217 raise CommitDoesNotExistError(
218 "Commit at index %s does not exist." % (commit_idx, ))
218 "Commit at index %s does not exist." % (commit_idx, ))
219 return commit_idx
219 return commit_idx
220
220
221 @staticmethod
221 @staticmethod
222 def check_url(url, config):
222 def check_url(url, config):
223 """
223 """
224 Check if `url` is a valid source to import a Subversion repository.
224 Check if `url` is a valid source to import a Subversion repository.
225 """
225 """
226 # convert to URL if it's a local directory
226 # convert to URL if it's a local directory
227 if os.path.isdir(url):
227 if os.path.isdir(url):
228 url = 'file://' + urllib.pathname2url(url)
228 url = 'file://' + urllib.pathname2url(url)
229 return connection.Svn.check_url(url, config.serialize())
229 return connection.Svn.check_url(url, config.serialize())
230
230
231 @staticmethod
231 @staticmethod
232 def is_valid_repository(path):
232 def is_valid_repository(path):
233 try:
233 try:
234 SubversionRepository(path)
234 SubversionRepository(path)
235 return True
235 return True
236 except VCSError:
236 except VCSError:
237 pass
237 pass
238 return False
238 return False
239
239
240 def _check_path(self):
240 def _check_path(self):
241 if not os.path.exists(self.path):
241 if not os.path.exists(self.path):
242 raise VCSError('Path "%s" does not exist!' % (self.path, ))
242 raise VCSError('Path "%s" does not exist!' % (self.path, ))
243 if not self._remote.is_path_valid_repository(self.path):
243 if not self._remote.is_path_valid_repository(self.path):
244 raise VCSError(
244 raise VCSError(
245 'Path "%s" does not contain a Subversion repository' %
245 'Path "%s" does not contain a Subversion repository' %
246 (self.path, ))
246 (self.path, ))
247
247
248 @LazyProperty
248 @LazyProperty
249 def last_change(self):
249 def last_change(self):
250 """
250 """
251 Returns last change made on this repository as
251 Returns last change made on this repository as
252 `datetime.datetime` object.
252 `datetime.datetime` object.
253 """
253 """
254 # Subversion always has a first commit which has id "0" and contains
254 # Subversion always has a first commit which has id "0" and contains
255 # what we are looking for.
255 # what we are looking for.
256 last_id = len(self.commit_ids)
256 last_id = len(self.commit_ids)
257 properties = self._remote.revision_properties(last_id)
257 properties = self._remote.revision_properties(last_id)
258 return _date_from_svn_properties(properties)
258 return _date_from_svn_properties(properties)
259
259
260 @LazyProperty
260 @LazyProperty
261 def in_memory_commit(self):
261 def in_memory_commit(self):
262 return SubversionInMemoryCommit(self)
262 return SubversionInMemoryCommit(self)
263
263
264 def get_hook_location(self):
264 def get_hook_location(self):
265 """
265 """
266 returns absolute path to location where hooks are stored
266 returns absolute path to location where hooks are stored
267 """
267 """
268 return os.path.join(self.path, 'hooks')
268 return os.path.join(self.path, 'hooks')
269
269
270 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, translate_tag=None):
270 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, translate_tag=None):
271 if self.is_empty():
271 if self.is_empty():
272 raise EmptyRepositoryError("There are no commits yet")
272 raise EmptyRepositoryError("There are no commits yet")
273 if commit_id is not None:
273 if commit_id is not None:
274 self._validate_commit_id(commit_id)
274 self._validate_commit_id(commit_id)
275 elif commit_idx is not None:
275 elif commit_idx is not None:
276 self._validate_commit_idx(commit_idx)
276 self._validate_commit_idx(commit_idx)
277 try:
277 try:
278 commit_id = self.commit_ids[commit_idx]
278 commit_id = self.commit_ids[commit_idx]
279 except IndexError:
279 except IndexError:
280 raise CommitDoesNotExistError
280 raise CommitDoesNotExistError('No commit with idx: {}'.format(commit_idx))
281
281
282 commit_id = self._sanitize_commit_id(commit_id)
282 commit_id = self._sanitize_commit_id(commit_id)
283 commit = SubversionCommit(repository=self, commit_id=commit_id)
283 commit = SubversionCommit(repository=self, commit_id=commit_id)
284 return commit
284 return commit
285
285
286 def get_commits(
286 def get_commits(
287 self, start_id=None, end_id=None, start_date=None, end_date=None,
287 self, start_id=None, end_id=None, start_date=None, end_date=None,
288 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
288 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
289 if self.is_empty():
289 if self.is_empty():
290 raise EmptyRepositoryError("There are no commit_ids yet")
290 raise EmptyRepositoryError("There are no commit_ids yet")
291 self._validate_branch_name(branch_name)
291 self._validate_branch_name(branch_name)
292
292
293 if start_id is not None:
293 if start_id is not None:
294 self._validate_commit_id(start_id)
294 self._validate_commit_id(start_id)
295 if end_id is not None:
295 if end_id is not None:
296 self._validate_commit_id(end_id)
296 self._validate_commit_id(end_id)
297
297
298 start_raw_id = self._sanitize_commit_id(start_id)
298 start_raw_id = self._sanitize_commit_id(start_id)
299 start_pos = self.commit_ids.index(start_raw_id) if start_id else None
299 start_pos = self.commit_ids.index(start_raw_id) if start_id else None
300 end_raw_id = self._sanitize_commit_id(end_id)
300 end_raw_id = self._sanitize_commit_id(end_id)
301 end_pos = max(0, self.commit_ids.index(end_raw_id)) if end_id else None
301 end_pos = max(0, self.commit_ids.index(end_raw_id)) if end_id else None
302
302
303 if None not in [start_id, end_id] and start_pos > end_pos:
303 if None not in [start_id, end_id] and start_pos > end_pos:
304 raise RepositoryError(
304 raise RepositoryError(
305 "Start commit '%s' cannot be after end commit '%s'" %
305 "Start commit '%s' cannot be after end commit '%s'" %
306 (start_id, end_id))
306 (start_id, end_id))
307 if end_pos is not None:
307 if end_pos is not None:
308 end_pos += 1
308 end_pos += 1
309
309
310 # Date based filtering
310 # Date based filtering
311 if start_date or end_date:
311 if start_date or end_date:
312 start_raw_id, end_raw_id = self._remote.lookup_interval(
312 start_raw_id, end_raw_id = self._remote.lookup_interval(
313 date_astimestamp(start_date) if start_date else None,
313 date_astimestamp(start_date) if start_date else None,
314 date_astimestamp(end_date) if end_date else None)
314 date_astimestamp(end_date) if end_date else None)
315 start_pos = start_raw_id - 1
315 start_pos = start_raw_id - 1
316 end_pos = end_raw_id
316 end_pos = end_raw_id
317
317
318 commit_ids = self.commit_ids
318 commit_ids = self.commit_ids
319
319
320 # TODO: johbo: Reconsider impact of DEFAULT_BRANCH_NAME here
320 # TODO: johbo: Reconsider impact of DEFAULT_BRANCH_NAME here
321 if branch_name not in [None, self.DEFAULT_BRANCH_NAME]:
321 if branch_name not in [None, self.DEFAULT_BRANCH_NAME]:
322 svn_rev = long(self.commit_ids[-1])
322 svn_rev = long(self.commit_ids[-1])
323 commit_ids = self._remote.node_history(
323 commit_ids = self._remote.node_history(
324 path=branch_name, revision=svn_rev, limit=None)
324 path=branch_name, revision=svn_rev, limit=None)
325 commit_ids = [str(i) for i in reversed(commit_ids)]
325 commit_ids = [str(i) for i in reversed(commit_ids)]
326
326
327 if start_pos or end_pos:
327 if start_pos or end_pos:
328 commit_ids = commit_ids[start_pos:end_pos]
328 commit_ids = commit_ids[start_pos:end_pos]
329 return base.CollectionGenerator(self, commit_ids, pre_load=pre_load)
329 return base.CollectionGenerator(self, commit_ids, pre_load=pre_load)
330
330
331 def _sanitize_commit_id(self, commit_id):
331 def _sanitize_commit_id(self, commit_id):
332 if commit_id and commit_id.isdigit():
332 if commit_id and commit_id.isdigit():
333 if int(commit_id) <= len(self.commit_ids):
333 if int(commit_id) <= len(self.commit_ids):
334 return commit_id
334 return commit_id
335 else:
335 else:
336 raise CommitDoesNotExistError(
336 raise CommitDoesNotExistError(
337 "Commit %s does not exist." % (commit_id, ))
337 "Commit %s does not exist." % (commit_id, ))
338 if commit_id not in [
338 if commit_id not in [
339 None, 'HEAD', 'tip', self.DEFAULT_BRANCH_NAME]:
339 None, 'HEAD', 'tip', self.DEFAULT_BRANCH_NAME]:
340 raise CommitDoesNotExistError(
340 raise CommitDoesNotExistError(
341 "Commit id %s not understood." % (commit_id, ))
341 "Commit id %s not understood." % (commit_id, ))
342 svn_rev = self._remote.lookup('HEAD')
342 svn_rev = self._remote.lookup('HEAD')
343 return str(svn_rev)
343 return str(svn_rev)
344
344
345 def get_diff(
345 def get_diff(
346 self, commit1, commit2, path=None, ignore_whitespace=False,
346 self, commit1, commit2, path=None, ignore_whitespace=False,
347 context=3, path1=None):
347 context=3, path1=None):
348 self._validate_diff_commits(commit1, commit2)
348 self._validate_diff_commits(commit1, commit2)
349 svn_rev1 = long(commit1.raw_id)
349 svn_rev1 = long(commit1.raw_id)
350 svn_rev2 = long(commit2.raw_id)
350 svn_rev2 = long(commit2.raw_id)
351 diff = self._remote.diff(
351 diff = self._remote.diff(
352 svn_rev1, svn_rev2, path1=path1, path2=path,
352 svn_rev1, svn_rev2, path1=path1, path2=path,
353 ignore_whitespace=ignore_whitespace, context=context)
353 ignore_whitespace=ignore_whitespace, context=context)
354 return SubversionDiff(diff)
354 return SubversionDiff(diff)
355
355
356
356
357 def _sanitize_url(url):
357 def _sanitize_url(url):
358 if '://' not in url:
358 if '://' not in url:
359 url = 'file://' + urllib.pathname2url(url)
359 url = 'file://' + urllib.pathname2url(url)
360 return url
360 return url
@@ -1,133 +1,133 b''
1 // Global keyboard bindings
1 // Global keyboard bindings
2
2
3 function setRCMouseBindings(repoName, repoLandingRev) {
3 function setRCMouseBindings(repoName, repoLandingRev) {
4
4
5 /** custom callback for supressing mousetrap from firing */
5 /** custom callback for supressing mousetrap from firing */
6 Mousetrap.stopCallback = function(e, element) {
6 Mousetrap.stopCallback = function(e, element) {
7 // if the element has the class "mousetrap" then no need to stop
7 // if the element has the class "mousetrap" then no need to stop
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
9 return false;
9 return false;
10 }
10 }
11
11
12 // stop for input, select, and textarea
12 // stop for input, select, and textarea
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
14 };
14 };
15
15
16 // general help "?"
16 // general help "?"
17 Mousetrap.bind(['?'], function(e) {
17 Mousetrap.bind(['?'], function(e) {
18 $('#help_kb').modal({});
18 $('#help_kb').modal({});
19 });
19 });
20
20
21 // / open the quick filter
21 // / open the quick filter
22 Mousetrap.bind(['/'], function(e) {
22 Mousetrap.bind(['/'], function(e) {
23 $('#main_filter').get(0).focus();
23 $('#main_filter').get(0).focus();
24
24
25 // return false to prevent default browser behavior
25 // return false to prevent default browser behavior
26 // and stop event from bubbling
26 // and stop event from bubbling
27 return false;
27 return false;
28 });
28 });
29
29
30 // ctrl/command+b, show the the main bar
30 // ctrl/command+b, show the the main bar
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
32 var $headerInner = $('#header-inner'),
32 var $headerInner = $('#header-inner'),
33 $content = $('#content');
33 $content = $('#content');
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
35 $headerInner.removeClass('hover');
35 $headerInner.removeClass('hover');
36 $content.removeClass('hover');
36 $content.removeClass('hover');
37 } else {
37 } else {
38 $headerInner.addClass('hover');
38 $headerInner.addClass('hover');
39 $content.addClass('hover');
39 $content.addClass('hover');
40 }
40 }
41 return false;
41 return false;
42 });
42 });
43
43
44 // general nav g + action
44 // general nav g + action
45 Mousetrap.bind(['g h'], function(e) {
45 Mousetrap.bind(['g h'], function(e) {
46 window.location = pyroutes.url('home');
46 window.location = pyroutes.url('home');
47 });
47 });
48 Mousetrap.bind(['g g'], function(e) {
48 Mousetrap.bind(['g g'], function(e) {
49 window.location = pyroutes.url('gists_show', {'private': 1});
49 window.location = pyroutes.url('gists_show', {'private': 1});
50 });
50 });
51 Mousetrap.bind(['g G'], function(e) {
51 Mousetrap.bind(['g G'], function(e) {
52 window.location = pyroutes.url('gists_show', {'public': 1});
52 window.location = pyroutes.url('gists_show', {'public': 1});
53 });
53 });
54
54
55 Mousetrap.bind(['g 0'], function(e) {
55 Mousetrap.bind(['g 0'], function(e) {
56 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 0});
56 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 0});
57 });
57 });
58 Mousetrap.bind(['g 1'], function(e) {
58 Mousetrap.bind(['g 1'], function(e) {
59 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 1});
59 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 1});
60 });
60 });
61 Mousetrap.bind(['g 2'], function(e) {
61 Mousetrap.bind(['g 2'], function(e) {
62 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 2});
62 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 2});
63 });
63 });
64 Mousetrap.bind(['g 3'], function(e) {
64 Mousetrap.bind(['g 3'], function(e) {
65 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 3});
65 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 3});
66 });
66 });
67 Mousetrap.bind(['g 4'], function(e) {
67 Mousetrap.bind(['g 4'], function(e) {
68 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 4});
68 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 4});
69 });
69 });
70 Mousetrap.bind(['g 5'], function(e) {
70 Mousetrap.bind(['g 5'], function(e) {
71 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 5});
71 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 5});
72 });
72 });
73 Mousetrap.bind(['g 6'], function(e) {
73 Mousetrap.bind(['g 6'], function(e) {
74 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 6});
74 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 6});
75 });
75 });
76 Mousetrap.bind(['g 7'], function(e) {
76 Mousetrap.bind(['g 7'], function(e) {
77 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 7});
77 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 7});
78 });
78 });
79 Mousetrap.bind(['g 8'], function(e) {
79 Mousetrap.bind(['g 8'], function(e) {
80 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 8});
80 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 8});
81 });
81 });
82 Mousetrap.bind(['g 9'], function(e) {
82 Mousetrap.bind(['g 9'], function(e) {
83 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 9});
83 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 9});
84 });
84 });
85
85
86 Mousetrap.bind(['n g'], function(e) {
86 Mousetrap.bind(['n g'], function(e) {
87 window.location = pyroutes.url('gists_new');
87 window.location = pyroutes.url('gists_new');
88 });
88 });
89 Mousetrap.bind(['n r'], function(e) {
89 Mousetrap.bind(['n r'], function(e) {
90 window.location = pyroutes.url('repo_new');
90 window.location = pyroutes.url('repo_new');
91 });
91 });
92
92
93 if (repoName && repoName !== '') {
93 if (repoName && repoName !== '') {
94 // nav in repo context
94 // nav in repo context
95 Mousetrap.bind(['g s'], function(e) {
95 Mousetrap.bind(['g s'], function(e) {
96 window.location = pyroutes.url(
96 window.location = pyroutes.url(
97 'repo_summary', {'repo_name': repoName});
97 'repo_summary', {'repo_name': repoName});
98 });
98 });
99 Mousetrap.bind(['g c'], function(e) {
99 Mousetrap.bind(['g c'], function(e) {
100 window.location = pyroutes.url(
100 window.location = pyroutes.url(
101 'repo_changelog', {'repo_name': repoName});
101 'repo_commits', {'repo_name': repoName});
102 });
102 });
103 Mousetrap.bind(['g F'], function(e) {
103 Mousetrap.bind(['g F'], function(e) {
104 window.location = pyroutes.url(
104 window.location = pyroutes.url(
105 'repo_files',
105 'repo_files',
106 {
106 {
107 'repo_name': repoName,
107 'repo_name': repoName,
108 'commit_id': repoLandingRev,
108 'commit_id': repoLandingRev,
109 'f_path': '',
109 'f_path': '',
110 'search': '1'
110 'search': '1'
111 });
111 });
112 });
112 });
113 Mousetrap.bind(['g f'], function(e) {
113 Mousetrap.bind(['g f'], function(e) {
114 window.location = pyroutes.url(
114 window.location = pyroutes.url(
115 'repo_files',
115 'repo_files',
116 {
116 {
117 'repo_name': repoName,
117 'repo_name': repoName,
118 'commit_id': repoLandingRev,
118 'commit_id': repoLandingRev,
119 'f_path': ''
119 'f_path': ''
120 });
120 });
121 });
121 });
122 Mousetrap.bind(['g o'], function(e) {
122 Mousetrap.bind(['g o'], function(e) {
123 window.location = pyroutes.url(
123 window.location = pyroutes.url(
124 'edit_repo', {'repo_name': repoName});
124 'edit_repo', {'repo_name': repoName});
125 });
125 });
126 Mousetrap.bind(['g O'], function(e) {
126 Mousetrap.bind(['g O'], function(e) {
127 window.location = pyroutes.url(
127 window.location = pyroutes.url(
128 'edit_repo_perms', {'repo_name': repoName});
128 'edit_repo_perms', {'repo_name': repoName});
129 });
129 });
130 }
130 }
131 }
131 }
132
132
133 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
133 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
@@ -1,371 +1,374 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('store_user_session_value', '/_store_session_attr', []);
152 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
153 pyroutes.register('journal', '/_admin/journal', []);
153 pyroutes.register('journal', '/_admin/journal', []);
154 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
154 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
155 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
155 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
156 pyroutes.register('journal_public', '/_admin/public_journal', []);
156 pyroutes.register('journal_public', '/_admin/public_journal', []);
157 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
157 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
158 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
158 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
159 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
159 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
160 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
160 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
161 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
161 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
162 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
162 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']);
163 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']);
164 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
165 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
165 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']);
166 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']);
167 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']);
168 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']);
169 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']);
170 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']);
171 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']);
172 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']);
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_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['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_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_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_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
176 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']);
177 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']);
178 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']);
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', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', '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:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
181 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']);
182 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']);
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:annotated', '/%(repo_name)s/annotate/%(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_previous', '/%(repo_name)s/annotate-previous/%(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_nodetree_full', '/%(repo_name)s/nodetree_full/%(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:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
187 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']);
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_file_raw', '/%(repo_name)s/raw/%(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_download', '/%(repo_name)s/download/%(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:legacy', '/%(repo_name)s/rawfile/%(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_history', '/%(repo_name)s/history/%(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_authors', '/%(repo_name)s/authors/%(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_files_remove_file', '/%(repo_name)s/remove_file/%(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_delete_file', '/%(repo_name)s/delete_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_edit_file', '/%(repo_name)s/edit_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_update_file', '/%(repo_name)s/update_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_add_file', '/%(repo_name)s/add_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_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_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']);
200 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
201 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
201 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
202 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
202 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
203 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_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_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
208 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
204 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
206 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
210 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
208 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']);
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']);
209 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
212 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
210 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
213 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
211 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
214 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
212 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
215 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
213 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
216 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
214 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
217 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
215 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
218 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
216 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
219 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
217 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
220 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
218 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
221 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
219 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
222 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
220 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
223 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
221 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
224 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
222 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
225 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
223 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
226 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
224 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['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']);
225 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['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']);
226 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['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']);
227 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']);
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']);
228 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
231 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
229 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
232 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
230 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
231 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
236 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
237 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
238 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
236 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
239 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
237 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
240 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
238 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
241 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
239 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
242 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
240 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
243 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
241 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
244 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
242 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
245 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
243 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
246 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
244 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
247 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
245 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
248 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
246 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
249 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
247 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
250 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
248 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
251 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
249 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
252 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
250 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
253 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
251 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
254 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
252 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
255 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
253 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
256 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
254 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
257 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
255 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
258 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
256 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
259 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
257 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
260 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
258 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
261 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
259 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
262 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
260 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
263 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
261 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
264 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
262 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
265 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
263 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
266 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
264 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
267 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
265 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
268 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
266 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
269 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
267 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
270 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
268 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
271 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
269 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
272 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
270 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
273 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
271 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
274 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
272 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
275 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
273 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
276 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
274 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['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']);
275 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
278 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
276 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
279 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
277 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
280 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
278 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['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']);
279 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
282 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
280 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
283 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
281 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
284 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
282 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
285 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
283 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
286 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
284 pyroutes.register('search', '/_admin/search', []);
287 pyroutes.register('search', '/_admin/search', []);
285 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
288 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
286 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
289 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
287 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
290 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
288 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
291 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
289 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
292 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
290 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
293 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
291 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
294 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
292 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
295 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
293 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
296 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
294 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
297 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
295 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
298 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
296 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
299 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
297 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
300 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
298 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
301 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
299 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
302 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
300 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
303 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
301 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
304 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
302 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
305 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
303 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
306 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
304 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
307 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
305 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
308 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
306 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
309 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
307 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
310 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
308 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
311 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
309 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
312 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
310 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
313 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
311 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
314 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
312 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
315 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
313 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
316 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
314 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
317 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
315 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
318 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
316 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
319 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
317 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
320 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
318 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
321 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
319 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
322 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
320 pyroutes.register('gists_show', '/_admin/gists', []);
323 pyroutes.register('gists_show', '/_admin/gists', []);
321 pyroutes.register('gists_new', '/_admin/gists/new', []);
324 pyroutes.register('gists_new', '/_admin/gists/new', []);
322 pyroutes.register('gists_create', '/_admin/gists/create', []);
325 pyroutes.register('gists_create', '/_admin/gists/create', []);
323 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
326 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
324 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
327 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
325 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
328 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
326 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
329 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
327 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
330 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
328 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
331 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
329 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
332 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
330 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
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']);
331 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
334 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
332 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
335 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
333 pyroutes.register('apiv2', '/_admin/api', []);
336 pyroutes.register('apiv2', '/_admin/api', []);
334 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
337 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
335 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
338 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
336 pyroutes.register('login', '/_admin/login', []);
339 pyroutes.register('login', '/_admin/login', []);
337 pyroutes.register('register', '/_admin/register', []);
340 pyroutes.register('register', '/_admin/register', []);
338 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
341 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
339 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
342 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
340 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['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']);
341 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
344 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
342 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
345 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
343 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
346 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
344 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
347 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
345 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
348 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
346 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
349 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
347 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
350 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
348 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
351 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
349 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
352 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
350 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
353 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
351 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
354 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
352 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
355 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
353 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
356 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
354 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
357 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
355 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
358 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
356 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
359 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
357 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
360 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
358 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
361 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
359 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
362 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
360 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
363 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
361 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
364 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
362 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
365 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
363 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
366 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
364 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
367 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
365 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
368 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
366 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
369 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
367 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
370 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
368 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
371 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
369 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
372 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
370 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
373 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
371 }
374 }
@@ -1,189 +1,189 b''
1 // # Copyright (C) 2016-2019 RhodeCode GmbH
1 // # Copyright (C) 2016-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 var CommitsController = function () {
20 var CommitsController = function () {
21 var self = this;
21 var self = this;
22 this.$graphCanvas = $('#graph_canvas');
22 this.$graphCanvas = $('#graph_canvas');
23 this.$commitCounter = $('#commit-counter');
23 this.$commitCounter = $('#commit-counter');
24
24
25 this.getCurrentGraphData = function () {
25 this.getCurrentGraphData = function () {
26 // raw form
26 // raw form
27 return self.$graphCanvas.data('commits');
27 return self.$graphCanvas.data('commits');
28 };
28 };
29
29
30 this.setLabelText = function (graphData) {
30 this.setLabelText = function (graphData) {
31 var shown = $('.commit_hash').length;
31 var shown = $('.commit_hash').length;
32 var total = self.$commitCounter.data('total');
32 var total = self.$commitCounter.data('total');
33
33
34 if (shown == 1) {
34 if (shown == 1) {
35 var text = _gettext('showing {0} out of {1} commit').format(shown, total);
35 var text = _gettext('showing {0} out of {1} commit').format(shown, total);
36 } else {
36 } else {
37 var text = _gettext('showing {0} out of {1} commits').format(shown, total);
37 var text = _gettext('showing {0} out of {1} commits').format(shown, total);
38 }
38 }
39 self.$commitCounter.html(text)
39 self.$commitCounter.html(text)
40 };
40 };
41
41
42 this.reloadGraph = function (chunk) {
42 this.reloadGraph = function (chunk) {
43 chunk = chunk || 'next';
43 chunk = chunk || 'next';
44
44
45 // reset state on re-render !
45 // reset state on re-render !
46 self.$graphCanvas.html('');
46 self.$graphCanvas.html('');
47
47
48 var edgeData = $("[data-graph]").data('graph') || this.$graphCanvas.data('graph') || [];
48 var edgeData = $("[data-graph]").data('graph') || this.$graphCanvas.data('graph') || [];
49
49
50 // Determine max number of edges per row in graph
50 // Determine max number of edges per row in graph
51 var edgeCount = 1;
51 var edgeCount = 1;
52 $.each(edgeData, function (i, item) {
52 $.each(edgeData, function (i, item) {
53 $.each(item[2], function (key, value) {
53 $.each(item[2], function (key, value) {
54 if (value[1] > edgeCount) {
54 if (value[1] > edgeCount) {
55 edgeCount = value[1];
55 edgeCount = value[1];
56 }
56 }
57 });
57 });
58 });
58 });
59
59
60 var x_step = Math.min(10, Math.floor(86 / edgeCount));
60 var x_step = Math.min(10, Math.floor(86 / edgeCount));
61 var graph_options = {
61 var graph_options = {
62 width: 100,
62 width: 100,
63 height: $('#changesets').find('.commits-range').height(),
63 height: $('#changesets').find('.commits-range').height(),
64 x_step: x_step,
64 x_step: x_step,
65 y_step: 42,
65 y_step: 42,
66 dotRadius: 3.5,
66 dotRadius: 3.5,
67 lineWidth: 2.5
67 lineWidth: 2.5
68 };
68 };
69
69
70 var prevCommitsData = this.$graphCanvas.data('commits') || [];
70 var prevCommitsData = this.$graphCanvas.data('commits') || [];
71 var nextCommitsData = $("[data-graph]").data('commits') || [];
71 var nextCommitsData = $("[data-graph]").data('commits') || [];
72
72
73 if (chunk == 'next') {
73 if (chunk == 'next') {
74 var commitData = $.merge(prevCommitsData, nextCommitsData);
74 var commitData = $.merge(prevCommitsData, nextCommitsData);
75 } else {
75 } else {
76 var commitData = $.merge(nextCommitsData, prevCommitsData);
76 var commitData = $.merge(nextCommitsData, prevCommitsData);
77 }
77 }
78
78
79 this.$graphCanvas.data('graph', edgeData);
79 this.$graphCanvas.data('graph', edgeData);
80 this.$graphCanvas.data('commits', commitData);
80 this.$graphCanvas.data('commits', commitData);
81
81
82 // destroy dynamic loaded graph
82 // destroy dynamic loaded graph
83 $("[data-graph]").remove();
83 $("[data-graph]").remove();
84
84
85 this.$graphCanvas.commits(graph_options);
85 this.$graphCanvas.commits(graph_options);
86
86
87 this.setLabelText(edgeData);
87 this.setLabelText(edgeData);
88 if ($('.load-more-commits').find('.prev-commits').get(0)) {
88 if ($('.load-more-commits').find('.prev-commits').get(0)) {
89 var padding = 75;
89 var padding = 75;
90
90
91 } else {
91 } else {
92 var padding = 43;
92 var padding = 43;
93 }
93 }
94 $('#graph_nodes').css({'padding-top': padding});
94 $('#graph_nodes').css({'padding-top': padding});
95 };
95 };
96
96
97 this.getChunkUrl = function (page, chunk, branch, commit_id, f_path) {
97 this.getChunkUrl = function (page, chunk, branch, commit_id, f_path) {
98 var urlData = {
98 var urlData = {
99 'repo_name': templateContext.repo_name,
99 'repo_name': templateContext.repo_name,
100 'page': page,
100 'page': page,
101 'chunk': chunk
101 'chunk': chunk
102 };
102 };
103
103
104 if (branch !== undefined && branch !== '') {
104 if (branch !== undefined && branch !== '') {
105 urlData['branch'] = branch;
105 urlData['branch'] = branch;
106 }
106 }
107 if (commit_id !== undefined && commit_id !== '') {
107 if (commit_id !== undefined && commit_id !== '') {
108 urlData['commit_id'] = commit_id;
108 urlData['commit_id'] = commit_id;
109 }
109 }
110 if (f_path !== undefined && f_path !== '') {
110 if (f_path !== undefined && f_path !== '') {
111 urlData['f_path'] = f_path;
111 urlData['f_path'] = f_path;
112 }
112 }
113
113
114 if (urlData['commit_id'] && urlData['f_path']) {
114 if (urlData['commit_id'] && urlData['f_path']) {
115 return pyroutes.url('repo_changelog_elements_file', urlData);
115 return pyroutes.url('repo_commits_elements_file', urlData);
116 }
116 }
117 else {
117 else {
118 return pyroutes.url('repo_changelog_elements', urlData);
118 return pyroutes.url('repo_commits_elements', urlData);
119 }
119 }
120
120
121 };
121 };
122
122
123 this.loadNext = function (node, page, branch, commit_id, f_path) {
123 this.loadNext = function (node, page, branch, commit_id, f_path) {
124 var loadUrl = this.getChunkUrl(page, 'next', branch, commit_id, f_path);
124 var loadUrl = this.getChunkUrl(page, 'next', branch, commit_id, f_path);
125 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
125 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
126
126
127 $.post(loadUrl, postData, function (data) {
127 $.post(loadUrl, postData, function (data) {
128 $(node).closest('tbody').append(data);
128 $(node).closest('tbody').append(data);
129 $(node).closest('td').remove();
129 $(node).closest('td').remove();
130 self.reloadGraph('next');
130 self.reloadGraph('next');
131 })
131 })
132 };
132 };
133
133
134 this.loadPrev = function (node, page, branch, commit_id, f_path) {
134 this.loadPrev = function (node, page, branch, commit_id, f_path) {
135 var loadUrl = this.getChunkUrl(page, 'prev', branch, commit_id, f_path);
135 var loadUrl = this.getChunkUrl(page, 'prev', branch, commit_id, f_path);
136 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
136 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
137
137
138 $.post(loadUrl, postData, function (data) {
138 $.post(loadUrl, postData, function (data) {
139 $(node).closest('tbody').prepend(data);
139 $(node).closest('tbody').prepend(data);
140 $(node).closest('td').remove();
140 $(node).closest('td').remove();
141 self.reloadGraph('prev');
141 self.reloadGraph('prev');
142 })
142 })
143 };
143 };
144
144
145 this.expandCommit = function (node, reloadGraph) {
145 this.expandCommit = function (node, reloadGraph) {
146 reloadGraph = reloadGraph || false;
146 reloadGraph = reloadGraph || false;
147
147
148 var target_expand = $(node);
148 var target_expand = $(node);
149 var cid = target_expand.data('commitId');
149 var cid = target_expand.data('commitId');
150
150
151 if (target_expand.hasClass('open')) {
151 if (target_expand.hasClass('open')) {
152 $('#c-' + cid).css({
152 $('#c-' + cid).css({
153 'height': '1.5em',
153 'height': '1.5em',
154 'white-space': 'nowrap',
154 'white-space': 'nowrap',
155 'text-overflow': 'ellipsis',
155 'text-overflow': 'ellipsis',
156 'overflow': 'hidden'
156 'overflow': 'hidden'
157 });
157 });
158 $('#t-' + cid).css({
158 $('#t-' + cid).css({
159 'height': 'auto',
159 'height': 'auto',
160 'line-height': '.9em',
160 'line-height': '.9em',
161 'text-overflow': 'ellipsis',
161 'text-overflow': 'ellipsis',
162 'overflow': 'hidden',
162 'overflow': 'hidden',
163 'white-space': 'nowrap'
163 'white-space': 'nowrap'
164 });
164 });
165 target_expand.removeClass('open');
165 target_expand.removeClass('open');
166 }
166 }
167 else {
167 else {
168 $('#c-' + cid).css({
168 $('#c-' + cid).css({
169 'height': 'auto',
169 'height': 'auto',
170 'white-space': 'pre-line',
170 'white-space': 'pre-line',
171 'text-overflow': 'initial',
171 'text-overflow': 'initial',
172 'overflow': 'visible'
172 'overflow': 'visible'
173 });
173 });
174 $('#t-' + cid).css({
174 $('#t-' + cid).css({
175 'height': 'auto',
175 'height': 'auto',
176 'max-height': 'none',
176 'max-height': 'none',
177 'text-overflow': 'initial',
177 'text-overflow': 'initial',
178 'overflow': 'visible',
178 'overflow': 'visible',
179 'white-space': 'normal'
179 'white-space': 'normal'
180 });
180 });
181 target_expand.addClass('open');
181 target_expand.addClass('open');
182 }
182 }
183
183
184 if (reloadGraph) {
184 if (reloadGraph) {
185 // redraw the graph
185 // redraw the graph
186 self.reloadGraph();
186 self.reloadGraph();
187 }
187 }
188 }
188 }
189 };
189 };
@@ -1,965 +1,965 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 % if c.rhodecode_name:
14 % if c.rhodecode_name:
15 <div class="branding">
15 <div class="branding">
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 </div>
17 </div>
18 % endif
18 % endif
19 </div>
19 </div>
20 <!-- MENU BAR NAV -->
20 <!-- MENU BAR NAV -->
21 ${self.menu_bar_nav()}
21 ${self.menu_bar_nav()}
22 <!-- END MENU BAR NAV -->
22 <!-- END MENU BAR NAV -->
23 </div>
23 </div>
24 </div>
24 </div>
25 ${self.menu_bar_subnav()}
25 ${self.menu_bar_subnav()}
26 <!-- END HEADER -->
26 <!-- END HEADER -->
27
27
28 <!-- CONTENT -->
28 <!-- CONTENT -->
29 <div id="content" class="wrapper">
29 <div id="content" class="wrapper">
30
30
31 <rhodecode-toast id="notifications"></rhodecode-toast>
31 <rhodecode-toast id="notifications"></rhodecode-toast>
32
32
33 <div class="main">
33 <div class="main">
34 ${next.main()}
34 ${next.main()}
35 </div>
35 </div>
36 </div>
36 </div>
37 <!-- END CONTENT -->
37 <!-- END CONTENT -->
38
38
39 </div>
39 </div>
40 <!-- FOOTER -->
40 <!-- FOOTER -->
41 <div id="footer">
41 <div id="footer">
42 <div id="footer-inner" class="title wrapper">
42 <div id="footer-inner" class="title wrapper">
43 <div>
43 <div>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 % if c.visual.show_version:
45 % if c.visual.show_version:
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 % endif
47 % endif
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 % if c.visual.rhodecode_support_url:
49 % if c.visual.rhodecode_support_url:
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 % endif
51 % endif
52 </p>
52 </p>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 <p class="server-instance" style="display:${sid}">
54 <p class="server-instance" style="display:${sid}">
55 ## display hidden instance ID if specially defined
55 ## display hidden instance ID if specially defined
56 % if c.rhodecode_instanceid:
56 % if c.rhodecode_instanceid:
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 % endif
58 % endif
59 </p>
59 </p>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <!-- END FOOTER -->
64 <!-- END FOOTER -->
65
65
66 ### MAKO DEFS ###
66 ### MAKO DEFS ###
67
67
68 <%def name="menu_bar_subnav()">
68 <%def name="menu_bar_subnav()">
69 </%def>
69 </%def>
70
70
71 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 <div class="${class_}">
72 <div class="${class_}">
73 ${self.breadcrumbs_links()}
73 ${self.breadcrumbs_links()}
74 </div>
74 </div>
75 </%def>
75 </%def>
76
76
77 <%def name="admin_menu(active=None)">
77 <%def name="admin_menu(active=None)">
78 <%
78 <%
79 def is_active(selected):
79 def is_active(selected):
80 if selected == active:
80 if selected == active:
81 return "active"
81 return "active"
82 %>
82 %>
83
83
84 <div id="context-bar">
84 <div id="context-bar">
85 <div class="wrapper">
85 <div class="wrapper">
86 <div class="title">
86 <div class="title">
87 <div class="title-content">
87 <div class="title-content">
88 <div class="title-main">
88 <div class="title-main">
89 % if c.is_super_admin:
89 % if c.is_super_admin:
90 ${_('Super Admin Panel')}
90 ${_('Super Admin Panel')}
91 % else:
91 % else:
92 ${_('Delegated Admin Panel')}
92 ${_('Delegated Admin Panel')}
93 % endif
93 % endif
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 <ul id="context-pages" class="navigation horizontal-list">
98 <ul id="context-pages" class="navigation horizontal-list">
99
99
100 ## super admin case
100 ## super admin case
101 % if c.is_super_admin:
101 % if c.is_super_admin:
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
112
112
113 ## delegated admin
113 ## delegated admin
114 % elif c.is_delegated_admin:
114 % elif c.is_delegated_admin:
115 <%
115 <%
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
119 %>
119 %>
120
120
121 %if repositories:
121 %if repositories:
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 %endif
123 %endif
124 %if repository_groups:
124 %if repository_groups:
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 %endif
126 %endif
127 %if user_groups:
127 %if user_groups:
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 %endif
129 %endif
130 % endif
130 % endif
131 </ul>
131 </ul>
132
132
133 </div>
133 </div>
134 <div class="clear"></div>
134 <div class="clear"></div>
135 </div>
135 </div>
136 </%def>
136 </%def>
137
137
138 <%def name="dt_info_panel(elements)">
138 <%def name="dt_info_panel(elements)">
139 <dl class="dl-horizontal">
139 <dl class="dl-horizontal">
140 %for dt, dd, title, show_items in elements:
140 %for dt, dd, title, show_items in elements:
141 <dt>${dt}:</dt>
141 <dt>${dt}:</dt>
142 <dd title="${h.tooltip(title)}">
142 <dd title="${h.tooltip(title)}">
143 %if callable(dd):
143 %if callable(dd):
144 ## allow lazy evaluation of elements
144 ## allow lazy evaluation of elements
145 ${dd()}
145 ${dd()}
146 %else:
146 %else:
147 ${dd}
147 ${dd}
148 %endif
148 %endif
149 %if show_items:
149 %if show_items:
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 %endif
151 %endif
152 </dd>
152 </dd>
153
153
154 %if show_items:
154 %if show_items:
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 %for item in show_items:
156 %for item in show_items:
157 <dt></dt>
157 <dt></dt>
158 <dd>${item}</dd>
158 <dd>${item}</dd>
159 %endfor
159 %endfor
160 </div>
160 </div>
161 %endif
161 %endif
162
162
163 %endfor
163 %endfor
164 </dl>
164 </dl>
165 </%def>
165 </%def>
166
166
167 <%def name="gravatar(email, size=16)">
167 <%def name="gravatar(email, size=16)">
168 <%
168 <%
169 if (size > 16):
169 if (size > 16):
170 gravatar_class = 'gravatar gravatar-large'
170 gravatar_class = 'gravatar gravatar-large'
171 else:
171 else:
172 gravatar_class = 'gravatar'
172 gravatar_class = 'gravatar'
173 %>
173 %>
174 <%doc>
174 <%doc>
175 TODO: johbo: For now we serve double size images to make it smooth
175 TODO: johbo: For now we serve double size images to make it smooth
176 for retina. This is how it worked until now. Should be replaced
176 for retina. This is how it worked until now. Should be replaced
177 with a better solution at some point.
177 with a better solution at some point.
178 </%doc>
178 </%doc>
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
180 </%def>
180 </%def>
181
181
182
182
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
184 <% email = h.email_or_none(contact) %>
184 <% email = h.email_or_none(contact) %>
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
186 ${self.gravatar(email, size)}
186 ${self.gravatar(email, size)}
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
188 </div>
188 </div>
189 </%def>
189 </%def>
190
190
191
191
192 <%def name="repo_page_title(repo_instance)">
192 <%def name="repo_page_title(repo_instance)">
193 <div class="title-content repo-title">
193 <div class="title-content repo-title">
194
194
195 <div class="title-main">
195 <div class="title-main">
196 ## SVN/HG/GIT icons
196 ## SVN/HG/GIT icons
197 %if h.is_hg(repo_instance):
197 %if h.is_hg(repo_instance):
198 <i class="icon-hg"></i>
198 <i class="icon-hg"></i>
199 %endif
199 %endif
200 %if h.is_git(repo_instance):
200 %if h.is_git(repo_instance):
201 <i class="icon-git"></i>
201 <i class="icon-git"></i>
202 %endif
202 %endif
203 %if h.is_svn(repo_instance):
203 %if h.is_svn(repo_instance):
204 <i class="icon-svn"></i>
204 <i class="icon-svn"></i>
205 %endif
205 %endif
206
206
207 ## public/private
207 ## public/private
208 %if repo_instance.private:
208 %if repo_instance.private:
209 <i class="icon-repo-private"></i>
209 <i class="icon-repo-private"></i>
210 %else:
210 %else:
211 <i class="icon-repo-public"></i>
211 <i class="icon-repo-public"></i>
212 %endif
212 %endif
213
213
214 ## repo name with group name
214 ## repo name with group name
215 ${h.breadcrumb_repo_link(repo_instance)}
215 ${h.breadcrumb_repo_link(repo_instance)}
216
216
217 ## Context Actions
217 ## Context Actions
218 <div class="pull-right">
218 <div class="pull-right">
219 %if c.rhodecode_user.username != h.DEFAULT_USER:
219 %if c.rhodecode_user.username != h.DEFAULT_USER:
220 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
220 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
221
221
222 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
222 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
223 % if c.repository_is_user_following:
223 % if c.repository_is_user_following:
224 <i class="icon-eye-off"></i>${_('Unwatch')}
224 <i class="icon-eye-off"></i>${_('Unwatch')}
225 % else:
225 % else:
226 <i class="icon-eye"></i>${_('Watch')}
226 <i class="icon-eye"></i>${_('Watch')}
227 % endif
227 % endif
228
228
229 </a>
229 </a>
230 %else:
230 %else:
231 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
231 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
232 %endif
232 %endif
233 </div>
233 </div>
234
234
235 </div>
235 </div>
236
236
237 ## FORKED
237 ## FORKED
238 %if repo_instance.fork:
238 %if repo_instance.fork:
239 <p class="discreet">
239 <p class="discreet">
240 <i class="icon-code-fork"></i> ${_('Fork of')}
240 <i class="icon-code-fork"></i> ${_('Fork of')}
241 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
241 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
242 </p>
242 </p>
243 %endif
243 %endif
244
244
245 ## IMPORTED FROM REMOTE
245 ## IMPORTED FROM REMOTE
246 %if repo_instance.clone_uri:
246 %if repo_instance.clone_uri:
247 <p class="discreet">
247 <p class="discreet">
248 <i class="icon-code-fork"></i> ${_('Clone from')}
248 <i class="icon-code-fork"></i> ${_('Clone from')}
249 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
249 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
250 </p>
250 </p>
251 %endif
251 %endif
252
252
253 ## LOCKING STATUS
253 ## LOCKING STATUS
254 %if repo_instance.locked[0]:
254 %if repo_instance.locked[0]:
255 <p class="locking_locked discreet">
255 <p class="locking_locked discreet">
256 <i class="icon-repo-lock"></i>
256 <i class="icon-repo-lock"></i>
257 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
257 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
258 </p>
258 </p>
259 %elif repo_instance.enable_locking:
259 %elif repo_instance.enable_locking:
260 <p class="locking_unlocked discreet">
260 <p class="locking_unlocked discreet">
261 <i class="icon-repo-unlock"></i>
261 <i class="icon-repo-unlock"></i>
262 ${_('Repository not locked. Pull repository to lock it.')}
262 ${_('Repository not locked. Pull repository to lock it.')}
263 </p>
263 </p>
264 %endif
264 %endif
265
265
266 </div>
266 </div>
267 </%def>
267 </%def>
268
268
269 <%def name="repo_menu(active=None)">
269 <%def name="repo_menu(active=None)">
270 <%
270 <%
271 def is_active(selected):
271 def is_active(selected):
272 if selected == active:
272 if selected == active:
273 return "active"
273 return "active"
274 %>
274 %>
275
275
276 <!--- REPO CONTEXT BAR -->
276 <!--- REPO CONTEXT BAR -->
277 <div id="context-bar">
277 <div id="context-bar">
278 <div class="wrapper">
278 <div class="wrapper">
279
279
280 <div class="title">
280 <div class="title">
281 ${self.repo_page_title(c.rhodecode_db_repo)}
281 ${self.repo_page_title(c.rhodecode_db_repo)}
282 </div>
282 </div>
283
283
284 <ul id="context-pages" class="navigation horizontal-list">
284 <ul id="context-pages" class="navigation horizontal-list">
285 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
285 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
286 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
286 <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
287 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
287 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
288 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
288 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
289
289
290 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
290 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
291 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
291 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
292 <li class="${is_active('showpullrequest')}">
292 <li class="${is_active('showpullrequest')}">
293 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
293 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
294 <div class="menulabel">
294 <div class="menulabel">
295 %if c.repository_pull_requests == 1:
295 %if c.repository_pull_requests == 1:
296 ${c.repository_pull_requests} ${_('Pull Request')}
296 ${c.repository_pull_requests} ${_('Pull Request')}
297 %else:
297 %else:
298 ${c.repository_pull_requests} ${_('Pull Requests')}
298 ${c.repository_pull_requests} ${_('Pull Requests')}
299 %endif
299 %endif
300 </div>
300 </div>
301 </a>
301 </a>
302 </li>
302 </li>
303 %endif
303 %endif
304
304
305 <li class="${is_active('artifacts')}"><a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}"><div class="menulabel">${_('Artifacts')} (BETA)</div></a></li>
305 <li class="${is_active('artifacts')}"><a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}"><div class="menulabel">${_('Artifacts')} (BETA)</div></a></li>
306
306
307 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
307 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
308 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
308 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
309 %endif
309 %endif
310
310
311 ## determine if we have "any" option available
311 ## determine if we have "any" option available
312 <%
312 <%
313 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
313 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
314 has_actions = (c.rhodecode_user.username != h.DEFAULT_USER and c.rhodecode_db_repo.repo_type in ['git','hg'] ) or can_lock
314 has_actions = (c.rhodecode_user.username != h.DEFAULT_USER and c.rhodecode_db_repo.repo_type in ['git','hg'] ) or can_lock
315 %>
315 %>
316 <li class="${is_active('options')}">
316 <li class="${is_active('options')}">
317 % if has_actions:
317 % if has_actions:
318 <a class="menulink dropdown">
318 <a class="menulink dropdown">
319 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
319 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
320 </a>
320 </a>
321 <ul class="submenu">
321 <ul class="submenu">
322 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork this repository')}</a></li>
322 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork this repository')}</a></li>
323 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
323 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
324 %if can_lock:
324 %if can_lock:
325 %if c.rhodecode_db_repo.locked[0]:
325 %if c.rhodecode_db_repo.locked[0]:
326 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
326 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
327 %else:
327 %else:
328 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
328 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
329 %endif
329 %endif
330 %endif
330 %endif
331 </ul>
331 </ul>
332 % else:
332 % else:
333 <a class="menulink disabled">
333 <a class="menulink disabled">
334 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
334 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
335 </a>
335 </a>
336 % endif
336 % endif
337 </li>
337 </li>
338
338
339 </ul>
339 </ul>
340 </div>
340 </div>
341 <div class="clear"></div>
341 <div class="clear"></div>
342 </div>
342 </div>
343 % if c.rhodecode_db_repo.archived:
343 % if c.rhodecode_db_repo.archived:
344 <div class="alert alert-warning text-center">
344 <div class="alert alert-warning text-center">
345 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
345 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
346 </div>
346 </div>
347 % endif
347 % endif
348 <!--- REPO END CONTEXT BAR -->
348 <!--- REPO END CONTEXT BAR -->
349
349
350 </%def>
350 </%def>
351
351
352 <%def name="repo_group_page_title(repo_group_instance)">
352 <%def name="repo_group_page_title(repo_group_instance)">
353 <div class="title-content">
353 <div class="title-content">
354 <div class="title-main">
354 <div class="title-main">
355 ## Repository Group icon
355 ## Repository Group icon
356 <i class="icon-repo-group"></i>
356 <i class="icon-repo-group"></i>
357
357
358 ## repo name with group name
358 ## repo name with group name
359 ${h.breadcrumb_repo_group_link(repo_group_instance)}
359 ${h.breadcrumb_repo_group_link(repo_group_instance)}
360 </div>
360 </div>
361
361
362 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
362 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
363 <div class="repo-group-desc discreet">
363 <div class="repo-group-desc discreet">
364 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
364 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
365 </div>
365 </div>
366
366
367 </div>
367 </div>
368 </%def>
368 </%def>
369
369
370 <%def name="repo_group_menu(active=None)">
370 <%def name="repo_group_menu(active=None)">
371 <%
371 <%
372 def is_active(selected):
372 def is_active(selected):
373 if selected == active:
373 if selected == active:
374 return "active"
374 return "active"
375
375
376 gr_name = c.repo_group.group_name if c.repo_group else None
376 gr_name = c.repo_group.group_name if c.repo_group else None
377 # create repositories with write permission on group is set to true
377 # create repositories with write permission on group is set to true
378 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
378 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
379 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
379 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
380 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
380 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
381
381
382 %>
382 %>
383
383
384 <!--- REPO GROUP CONTEXT BAR -->
384 <!--- REPO GROUP CONTEXT BAR -->
385 <div id="context-bar">
385 <div id="context-bar">
386 <div class="wrapper">
386 <div class="wrapper">
387 <div class="title">
387 <div class="title">
388 ${self.repo_group_page_title(c.repo_group)}
388 ${self.repo_group_page_title(c.repo_group)}
389 </div>
389 </div>
390
390
391 <ul id="context-pages" class="navigation horizontal-list">
391 <ul id="context-pages" class="navigation horizontal-list">
392 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
392 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
393 % if c.is_super_admin or group_admin:
393 % if c.is_super_admin or group_admin:
394 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a></li>
394 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a></li>
395 % endif
395 % endif
396 ## determine if we have "any" option available
396 ## determine if we have "any" option available
397 <%
397 <%
398 can_create_repos = c.is_super_admin or group_admin or (group_write and create_on_write)
398 can_create_repos = c.is_super_admin or group_admin or (group_write and create_on_write)
399 can_create_repo_groups = c.is_super_admin or group_admin
399 can_create_repo_groups = c.is_super_admin or group_admin
400 has_actions = can_create_repos or can_create_repo_groups
400 has_actions = can_create_repos or can_create_repo_groups
401 %>
401 %>
402 <li class="${is_active('options')}">
402 <li class="${is_active('options')}">
403 % if has_actions:
403 % if has_actions:
404 <a class="menulink dropdown">
404 <a class="menulink dropdown">
405 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
405 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
406 </a>
406 </a>
407 <ul class="submenu">
407 <ul class="submenu">
408 %if can_create_repos:
408 %if can_create_repos:
409 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
409 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
410 %endif
410 %endif
411 %if can_create_repo_groups:
411 %if can_create_repo_groups:
412 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Repository Group')}</a></li>
412 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Repository Group')}</a></li>
413 %endif
413 %endif
414 </ul>
414 </ul>
415 % else:
415 % else:
416 <a class="menulink disabled">
416 <a class="menulink disabled">
417 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
417 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
418 </a>
418 </a>
419 % endif
419 % endif
420 </li>
420 </li>
421 </ul>
421 </ul>
422 </div>
422 </div>
423 <div class="clear"></div>
423 <div class="clear"></div>
424 </div>
424 </div>
425
425
426 <!--- REPO GROUP CONTEXT BAR -->
426 <!--- REPO GROUP CONTEXT BAR -->
427
427
428 </%def>
428 </%def>
429
429
430
430
431 <%def name="usermenu(active=False)">
431 <%def name="usermenu(active=False)">
432 ## USER MENU
432 ## USER MENU
433 <li id="quick_login_li" class="${'active' if active else ''}">
433 <li id="quick_login_li" class="${'active' if active else ''}">
434 % if c.rhodecode_user.username == h.DEFAULT_USER:
434 % if c.rhodecode_user.username == h.DEFAULT_USER:
435 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
435 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
436 ${gravatar(c.rhodecode_user.email, 20)}
436 ${gravatar(c.rhodecode_user.email, 20)}
437 <span class="user">
437 <span class="user">
438 <span>${_('Sign in')}</span>
438 <span>${_('Sign in')}</span>
439 </span>
439 </span>
440 </a>
440 </a>
441 % else:
441 % else:
442 ## logged in user
442 ## logged in user
443 <a id="quick_login_link" class="menulink childs">
443 <a id="quick_login_link" class="menulink childs">
444 ${gravatar(c.rhodecode_user.email, 20)}
444 ${gravatar(c.rhodecode_user.email, 20)}
445 <span class="user">
445 <span class="user">
446 <span class="menu_link_user">${c.rhodecode_user.username}</span>
446 <span class="menu_link_user">${c.rhodecode_user.username}</span>
447 <div class="show_more"></div>
447 <div class="show_more"></div>
448 </span>
448 </span>
449 </a>
449 </a>
450 ## subnav with menu for logged in user
450 ## subnav with menu for logged in user
451 <div class="user-menu submenu">
451 <div class="user-menu submenu">
452 <div id="quick_login">
452 <div id="quick_login">
453 %if c.rhodecode_user.username != h.DEFAULT_USER:
453 %if c.rhodecode_user.username != h.DEFAULT_USER:
454 <div class="">
454 <div class="">
455 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
455 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
456 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
456 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
457 <div class="email">${c.rhodecode_user.email}</div>
457 <div class="email">${c.rhodecode_user.email}</div>
458 </div>
458 </div>
459 <div class="">
459 <div class="">
460 <ol class="links">
460 <ol class="links">
461 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
461 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
462 % if c.rhodecode_user.personal_repo_group:
462 % if c.rhodecode_user.personal_repo_group:
463 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
463 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
464 % endif
464 % endif
465 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
465 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
466 ## bookmark-items
466 ## bookmark-items
467 <li class="bookmark-items">
467 <li class="bookmark-items">
468 ${_('Bookmarks')}
468 ${_('Bookmarks')}
469 <div class="pull-right">
469 <div class="pull-right">
470 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
470 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
471 </div>
471 </div>
472 </li>
472 </li>
473 % if not c.bookmark_items:
473 % if not c.bookmark_items:
474 <li>
474 <li>
475 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
475 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
476 </li>
476 </li>
477 % endif
477 % endif
478 % for item in c.bookmark_items:
478 % for item in c.bookmark_items:
479 <li>
479 <li>
480 % if item.repository:
480 % if item.repository:
481 <div>
481 <div>
482 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
482 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
483 <code>${item.position}</code>
483 <code>${item.position}</code>
484 % if item.repository.repo_type == 'hg':
484 % if item.repository.repo_type == 'hg':
485 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
485 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
486 % elif item.repository.repo_type == 'git':
486 % elif item.repository.repo_type == 'git':
487 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
487 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
488 % elif item.repository.repo_type == 'svn':
488 % elif item.repository.repo_type == 'svn':
489 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
489 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
490 % endif
490 % endif
491 ${(item.title or h.shorter(item.repository.repo_name, 30))}
491 ${(item.title or h.shorter(item.repository.repo_name, 30))}
492 </a>
492 </a>
493 </div>
493 </div>
494 % elif item.repository_group:
494 % elif item.repository_group:
495 <div>
495 <div>
496 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
496 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
497 <code>${item.position}</code>
497 <code>${item.position}</code>
498 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
498 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
499 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
499 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
500 </a>
500 </a>
501 </div>
501 </div>
502 % else:
502 % else:
503 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
503 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
504 <code>${item.position}</code>
504 <code>${item.position}</code>
505 ${item.title}
505 ${item.title}
506 </a>
506 </a>
507 % endif
507 % endif
508 </li>
508 </li>
509 % endfor
509 % endfor
510
510
511 <li class="logout">
511 <li class="logout">
512 ${h.secure_form(h.route_path('logout'), request=request)}
512 ${h.secure_form(h.route_path('logout'), request=request)}
513 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
513 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
514 ${h.end_form()}
514 ${h.end_form()}
515 </li>
515 </li>
516 </ol>
516 </ol>
517 </div>
517 </div>
518 %endif
518 %endif
519 </div>
519 </div>
520 </div>
520 </div>
521 ## unread counter
521 ## unread counter
522 <div class="pill_container">
522 <div class="pill_container">
523 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
523 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
524 </div>
524 </div>
525 % endif
525 % endif
526 </li>
526 </li>
527 </%def>
527 </%def>
528
528
529 <%def name="menu_items(active=None)">
529 <%def name="menu_items(active=None)">
530 <%
530 <%
531 def is_active(selected):
531 def is_active(selected):
532 if selected == active:
532 if selected == active:
533 return "active"
533 return "active"
534 return ""
534 return ""
535 %>
535 %>
536
536
537 <ul id="quick" class="main_nav navigation horizontal-list">
537 <ul id="quick" class="main_nav navigation horizontal-list">
538 ## notice box for important system messages
538 ## notice box for important system messages
539 <li style="display: none">
539 <li style="display: none">
540 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
540 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
541 <div class="menulabel-notice" >
541 <div class="menulabel-notice" >
542 0
542 0
543 </div>
543 </div>
544 </a>
544 </a>
545 </li>
545 </li>
546
546
547 ## Main filter
547 ## Main filter
548 <li>
548 <li>
549 <div class="menulabel main_filter_box">
549 <div class="menulabel main_filter_box">
550 <div class="main_filter_input_box">
550 <div class="main_filter_input_box">
551 <ul class="searchItems">
551 <ul class="searchItems">
552
552
553 % if c.template_context['search_context']['repo_id']:
553 % if c.template_context['search_context']['repo_id']:
554 <li class="searchTag searchTagFilter searchTagHidable" >
554 <li class="searchTag searchTagFilter searchTagHidable" >
555 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
555 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
556 <span class="tag">
556 <span class="tag">
557 This repo
557 This repo
558 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
558 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
559 </span>
559 </span>
560 ##</a>
560 ##</a>
561 </li>
561 </li>
562 % elif c.template_context['search_context']['repo_group_id']:
562 % elif c.template_context['search_context']['repo_group_id']:
563 <li class="searchTag searchTagFilter searchTagHidable">
563 <li class="searchTag searchTagFilter searchTagHidable">
564 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
564 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
565 <span class="tag">
565 <span class="tag">
566 This group
566 This group
567 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
567 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
568 </span>
568 </span>
569 ##</a>
569 ##</a>
570 </li>
570 </li>
571 % endif
571 % endif
572
572
573 <li class="searchTagInput">
573 <li class="searchTagInput">
574 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
574 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
575 </li>
575 </li>
576 <li class="searchTag searchTagHelp">
576 <li class="searchTag searchTagHelp">
577 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
577 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
578 </li>
578 </li>
579 </ul>
579 </ul>
580 </div>
580 </div>
581 </div>
581 </div>
582
582
583 <div id="main_filter_help" style="display: none">
583 <div id="main_filter_help" style="display: none">
584 - Use '/' key to quickly access this field.
584 - Use '/' key to quickly access this field.
585
585
586 - Enter a name of repository, or repository group for quick search.
586 - Enter a name of repository, or repository group for quick search.
587
587
588 - Prefix query to allow special search:
588 - Prefix query to allow special search:
589
589
590 user:admin, to search for usernames, always global
590 user:admin, to search for usernames, always global
591
591
592 user_group:devops, to search for user groups, always global
592 user_group:devops, to search for user groups, always global
593
593
594 commit:efced4, to search for commits, scoped to repositories or groups
594 commit:efced4, to search for commits, scoped to repositories or groups
595
595
596 file:models.py, to search for file paths, scoped to repositories or groups
596 file:models.py, to search for file paths, scoped to repositories or groups
597
597
598 % if c.template_context['search_context']['repo_id']:
598 % if c.template_context['search_context']['repo_id']:
599 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
599 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
600 % elif c.template_context['search_context']['repo_group_id']:
600 % elif c.template_context['search_context']['repo_group_id']:
601 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
601 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
602 % else:
602 % else:
603 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
603 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
604 % endif
604 % endif
605 </div>
605 </div>
606 </li>
606 </li>
607
607
608 ## ROOT MENU
608 ## ROOT MENU
609 <li class="${is_active('home')}">
609 <li class="${is_active('home')}">
610 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
610 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
611 <div class="menulabel">${_('Home')}</div>
611 <div class="menulabel">${_('Home')}</div>
612 </a>
612 </a>
613 </li>
613 </li>
614
614
615 %if c.rhodecode_user.username != h.DEFAULT_USER:
615 %if c.rhodecode_user.username != h.DEFAULT_USER:
616 <li class="${is_active('journal')}">
616 <li class="${is_active('journal')}">
617 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
617 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
618 <div class="menulabel">${_('Journal')}</div>
618 <div class="menulabel">${_('Journal')}</div>
619 </a>
619 </a>
620 </li>
620 </li>
621 %else:
621 %else:
622 <li class="${is_active('journal')}">
622 <li class="${is_active('journal')}">
623 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
623 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
624 <div class="menulabel">${_('Public journal')}</div>
624 <div class="menulabel">${_('Public journal')}</div>
625 </a>
625 </a>
626 </li>
626 </li>
627 %endif
627 %endif
628
628
629 <li class="${is_active('gists')}">
629 <li class="${is_active('gists')}">
630 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
630 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
631 <div class="menulabel">${_('Gists')}</div>
631 <div class="menulabel">${_('Gists')}</div>
632 </a>
632 </a>
633 </li>
633 </li>
634
634
635 % if c.is_super_admin or c.is_delegated_admin:
635 % if c.is_super_admin or c.is_delegated_admin:
636 <li class="${is_active('admin')}">
636 <li class="${is_active('admin')}">
637 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
637 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
638 <div class="menulabel">${_('Admin')} </div>
638 <div class="menulabel">${_('Admin')} </div>
639 </a>
639 </a>
640 </li>
640 </li>
641 % endif
641 % endif
642
642
643 ## render extra user menu
643 ## render extra user menu
644 ${usermenu(active=(active=='my_account'))}
644 ${usermenu(active=(active=='my_account'))}
645
645
646 % if c.debug_style:
646 % if c.debug_style:
647 <li>
647 <li>
648 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
648 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
649 <div class="menulabel">${_('[Style]')}</div>
649 <div class="menulabel">${_('[Style]')}</div>
650 </a>
650 </a>
651 </li>
651 </li>
652 % endif
652 % endif
653 </ul>
653 </ul>
654
654
655 <script type="text/javascript">
655 <script type="text/javascript">
656 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
656 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
657
657
658 var formatRepoResult = function(result, container, query, escapeMarkup) {
658 var formatRepoResult = function(result, container, query, escapeMarkup) {
659 return function(data, escapeMarkup) {
659 return function(data, escapeMarkup) {
660 if (!data.repo_id){
660 if (!data.repo_id){
661 return data.text; // optgroup text Repositories
661 return data.text; // optgroup text Repositories
662 }
662 }
663
663
664 var tmpl = '';
664 var tmpl = '';
665 var repoType = data['repo_type'];
665 var repoType = data['repo_type'];
666 var repoName = data['text'];
666 var repoName = data['text'];
667
667
668 if(data && data.type == 'repo'){
668 if(data && data.type == 'repo'){
669 if(repoType === 'hg'){
669 if(repoType === 'hg'){
670 tmpl += '<i class="icon-hg"></i> ';
670 tmpl += '<i class="icon-hg"></i> ';
671 }
671 }
672 else if(repoType === 'git'){
672 else if(repoType === 'git'){
673 tmpl += '<i class="icon-git"></i> ';
673 tmpl += '<i class="icon-git"></i> ';
674 }
674 }
675 else if(repoType === 'svn'){
675 else if(repoType === 'svn'){
676 tmpl += '<i class="icon-svn"></i> ';
676 tmpl += '<i class="icon-svn"></i> ';
677 }
677 }
678 if(data['private']){
678 if(data['private']){
679 tmpl += '<i class="icon-lock" ></i> ';
679 tmpl += '<i class="icon-lock" ></i> ';
680 }
680 }
681 else if(visualShowPublicIcon){
681 else if(visualShowPublicIcon){
682 tmpl += '<i class="icon-unlock-alt"></i> ';
682 tmpl += '<i class="icon-unlock-alt"></i> ';
683 }
683 }
684 }
684 }
685 tmpl += escapeMarkup(repoName);
685 tmpl += escapeMarkup(repoName);
686 return tmpl;
686 return tmpl;
687
687
688 }(result, escapeMarkup);
688 }(result, escapeMarkup);
689 };
689 };
690
690
691 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
691 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
692 return function(data, escapeMarkup) {
692 return function(data, escapeMarkup) {
693 if (!data.repo_group_id){
693 if (!data.repo_group_id){
694 return data.text; // optgroup text Repositories
694 return data.text; // optgroup text Repositories
695 }
695 }
696
696
697 var tmpl = '';
697 var tmpl = '';
698 var repoGroupName = data['text'];
698 var repoGroupName = data['text'];
699
699
700 if(data){
700 if(data){
701
701
702 tmpl += '<i class="icon-repo-group"></i> ';
702 tmpl += '<i class="icon-repo-group"></i> ';
703
703
704 }
704 }
705 tmpl += escapeMarkup(repoGroupName);
705 tmpl += escapeMarkup(repoGroupName);
706 return tmpl;
706 return tmpl;
707
707
708 }(result, escapeMarkup);
708 }(result, escapeMarkup);
709 };
709 };
710
710
711 var escapeRegExChars = function (value) {
711 var escapeRegExChars = function (value) {
712 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
712 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
713 };
713 };
714
714
715 var getRepoIcon = function(repo_type) {
715 var getRepoIcon = function(repo_type) {
716 if (repo_type === 'hg') {
716 if (repo_type === 'hg') {
717 return '<i class="icon-hg"></i> ';
717 return '<i class="icon-hg"></i> ';
718 }
718 }
719 else if (repo_type === 'git') {
719 else if (repo_type === 'git') {
720 return '<i class="icon-git"></i> ';
720 return '<i class="icon-git"></i> ';
721 }
721 }
722 else if (repo_type === 'svn') {
722 else if (repo_type === 'svn') {
723 return '<i class="icon-svn"></i> ';
723 return '<i class="icon-svn"></i> ';
724 }
724 }
725 return ''
725 return ''
726 };
726 };
727
727
728 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
728 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
729
729
730 if (value.split(':').length === 2) {
730 if (value.split(':').length === 2) {
731 value = value.split(':')[1]
731 value = value.split(':')[1]
732 }
732 }
733
733
734 var searchType = data['type'];
734 var searchType = data['type'];
735 var valueDisplay = data['value_display'];
735 var valueDisplay = data['value_display'];
736
736
737 var pattern = '(' + escapeRegExChars(value) + ')';
737 var pattern = '(' + escapeRegExChars(value) + ')';
738
738
739 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
739 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
740
740
741 // highlight match
741 // highlight match
742 if (searchType != 'text') {
742 if (searchType != 'text') {
743 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
743 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
744 }
744 }
745
745
746 var icon = '';
746 var icon = '';
747
747
748 if (searchType === 'hint') {
748 if (searchType === 'hint') {
749 icon += '<i class="icon-repo-group"></i> ';
749 icon += '<i class="icon-repo-group"></i> ';
750 }
750 }
751 // full text search
751 // full text search
752 else if (searchType === 'search') {
752 else if (searchType === 'search') {
753 icon += '<i class="icon-more"></i> ';
753 icon += '<i class="icon-more"></i> ';
754 }
754 }
755 // repository
755 // repository
756 else if (searchType === 'repo') {
756 else if (searchType === 'repo') {
757
757
758 var repoIcon = getRepoIcon(data['repo_type']);
758 var repoIcon = getRepoIcon(data['repo_type']);
759 icon += repoIcon;
759 icon += repoIcon;
760
760
761 if (data['private']) {
761 if (data['private']) {
762 icon += '<i class="icon-lock" ></i> ';
762 icon += '<i class="icon-lock" ></i> ';
763 }
763 }
764 else if (visualShowPublicIcon) {
764 else if (visualShowPublicIcon) {
765 icon += '<i class="icon-unlock-alt"></i> ';
765 icon += '<i class="icon-unlock-alt"></i> ';
766 }
766 }
767 }
767 }
768 // repository groups
768 // repository groups
769 else if (searchType === 'repo_group') {
769 else if (searchType === 'repo_group') {
770 icon += '<i class="icon-repo-group"></i> ';
770 icon += '<i class="icon-repo-group"></i> ';
771 }
771 }
772 // user group
772 // user group
773 else if (searchType === 'user_group') {
773 else if (searchType === 'user_group') {
774 icon += '<i class="icon-group"></i> ';
774 icon += '<i class="icon-group"></i> ';
775 }
775 }
776 // user
776 // user
777 else if (searchType === 'user') {
777 else if (searchType === 'user') {
778 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
778 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
779 }
779 }
780 // commit
780 // commit
781 else if (searchType === 'commit') {
781 else if (searchType === 'commit') {
782 var repo_data = data['repo_data'];
782 var repo_data = data['repo_data'];
783 var repoIcon = getRepoIcon(repo_data['repository_type']);
783 var repoIcon = getRepoIcon(repo_data['repository_type']);
784 if (repoIcon) {
784 if (repoIcon) {
785 icon += repoIcon;
785 icon += repoIcon;
786 } else {
786 } else {
787 icon += '<i class="icon-tag"></i>';
787 icon += '<i class="icon-tag"></i>';
788 }
788 }
789 }
789 }
790 // file
790 // file
791 else if (searchType === 'file') {
791 else if (searchType === 'file') {
792 var repo_data = data['repo_data'];
792 var repo_data = data['repo_data'];
793 var repoIcon = getRepoIcon(repo_data['repository_type']);
793 var repoIcon = getRepoIcon(repo_data['repository_type']);
794 if (repoIcon) {
794 if (repoIcon) {
795 icon += repoIcon;
795 icon += repoIcon;
796 } else {
796 } else {
797 icon += '<i class="icon-tag"></i>';
797 icon += '<i class="icon-tag"></i>';
798 }
798 }
799 }
799 }
800 // generic text
800 // generic text
801 else if (searchType === 'text') {
801 else if (searchType === 'text') {
802 icon = '';
802 icon = '';
803 }
803 }
804
804
805 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
805 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
806 return tmpl.format(icon, valueDisplay);
806 return tmpl.format(icon, valueDisplay);
807 };
807 };
808
808
809 var handleSelect = function(element, suggestion) {
809 var handleSelect = function(element, suggestion) {
810 if (suggestion.type === "hint") {
810 if (suggestion.type === "hint") {
811 // we skip action
811 // we skip action
812 $('#main_filter').focus();
812 $('#main_filter').focus();
813 }
813 }
814 else if (suggestion.type === "text") {
814 else if (suggestion.type === "text") {
815 // we skip action
815 // we skip action
816 $('#main_filter').focus();
816 $('#main_filter').focus();
817
817
818 } else {
818 } else {
819 window.location = suggestion['url'];
819 window.location = suggestion['url'];
820 }
820 }
821 };
821 };
822
822
823 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
823 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
824 if (queryLowerCase.split(':').length === 2) {
824 if (queryLowerCase.split(':').length === 2) {
825 queryLowerCase = queryLowerCase.split(':')[1]
825 queryLowerCase = queryLowerCase.split(':')[1]
826 }
826 }
827 if (suggestion.type === "text") {
827 if (suggestion.type === "text") {
828 // special case we don't want to "skip" display for
828 // special case we don't want to "skip" display for
829 return true
829 return true
830 }
830 }
831 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
831 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
832 };
832 };
833
833
834 var cleanContext = {
834 var cleanContext = {
835 repo_view_type: null,
835 repo_view_type: null,
836
836
837 repo_id: null,
837 repo_id: null,
838 repo_name: "",
838 repo_name: "",
839
839
840 repo_group_id: null,
840 repo_group_id: null,
841 repo_group_name: null
841 repo_group_name: null
842 };
842 };
843 var removeGoToFilter = function () {
843 var removeGoToFilter = function () {
844 $('.searchTagHidable').hide();
844 $('.searchTagHidable').hide();
845 $('#main_filter').autocomplete(
845 $('#main_filter').autocomplete(
846 'setOptions', {params:{search_context: cleanContext}});
846 'setOptions', {params:{search_context: cleanContext}});
847 };
847 };
848
848
849 $('#main_filter').autocomplete({
849 $('#main_filter').autocomplete({
850 serviceUrl: pyroutes.url('goto_switcher_data'),
850 serviceUrl: pyroutes.url('goto_switcher_data'),
851 params: {
851 params: {
852 "search_context": templateContext.search_context
852 "search_context": templateContext.search_context
853 },
853 },
854 minChars:2,
854 minChars:2,
855 maxHeight:400,
855 maxHeight:400,
856 deferRequestBy: 300, //miliseconds
856 deferRequestBy: 300, //miliseconds
857 tabDisabled: true,
857 tabDisabled: true,
858 autoSelectFirst: false,
858 autoSelectFirst: false,
859 formatResult: autocompleteMainFilterFormatResult,
859 formatResult: autocompleteMainFilterFormatResult,
860 lookupFilter: autocompleteMainFilterResult,
860 lookupFilter: autocompleteMainFilterResult,
861 onSelect: function (element, suggestion) {
861 onSelect: function (element, suggestion) {
862 handleSelect(element, suggestion);
862 handleSelect(element, suggestion);
863 return false;
863 return false;
864 },
864 },
865 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
865 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
866 if (jqXHR !== 'abort') {
866 if (jqXHR !== 'abort') {
867 alert("Error during search.\nError code: {0}".format(textStatus));
867 alert("Error during search.\nError code: {0}".format(textStatus));
868 window.location = '';
868 window.location = '';
869 }
869 }
870 }
870 }
871 });
871 });
872
872
873 showMainFilterBox = function () {
873 showMainFilterBox = function () {
874 $('#main_filter_help').toggle();
874 $('#main_filter_help').toggle();
875 };
875 };
876
876
877 $('#main_filter').on('keydown.autocomplete', function (e) {
877 $('#main_filter').on('keydown.autocomplete', function (e) {
878
878
879 var BACKSPACE = 8;
879 var BACKSPACE = 8;
880 var el = $(e.currentTarget);
880 var el = $(e.currentTarget);
881 if(e.which === BACKSPACE){
881 if(e.which === BACKSPACE){
882 var inputVal = el.val();
882 var inputVal = el.val();
883 if (inputVal === ""){
883 if (inputVal === ""){
884 removeGoToFilter()
884 removeGoToFilter()
885 }
885 }
886 }
886 }
887 });
887 });
888
888
889 </script>
889 </script>
890 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
890 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
891 </%def>
891 </%def>
892
892
893 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
893 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
894 <div class="modal-dialog">
894 <div class="modal-dialog">
895 <div class="modal-content">
895 <div class="modal-content">
896 <div class="modal-header">
896 <div class="modal-header">
897 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
897 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
898 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
898 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
899 </div>
899 </div>
900 <div class="modal-body">
900 <div class="modal-body">
901 <div class="block-left">
901 <div class="block-left">
902 <table class="keyboard-mappings">
902 <table class="keyboard-mappings">
903 <tbody>
903 <tbody>
904 <tr>
904 <tr>
905 <th></th>
905 <th></th>
906 <th>${_('Site-wide shortcuts')}</th>
906 <th>${_('Site-wide shortcuts')}</th>
907 </tr>
907 </tr>
908 <%
908 <%
909 elems = [
909 elems = [
910 ('/', 'Use quick search box'),
910 ('/', 'Use quick search box'),
911 ('g h', 'Goto home page'),
911 ('g h', 'Goto home page'),
912 ('g g', 'Goto my private gists page'),
912 ('g g', 'Goto my private gists page'),
913 ('g G', 'Goto my public gists page'),
913 ('g G', 'Goto my public gists page'),
914 ('g 0-9', 'Goto bookmarked items from 0-9'),
914 ('g 0-9', 'Goto bookmarked items from 0-9'),
915 ('n r', 'New repository page'),
915 ('n r', 'New repository page'),
916 ('n g', 'New gist page'),
916 ('n g', 'New gist page'),
917 ]
917 ]
918 %>
918 %>
919 %for key, desc in elems:
919 %for key, desc in elems:
920 <tr>
920 <tr>
921 <td class="keys">
921 <td class="keys">
922 <span class="key tag">${key}</span>
922 <span class="key tag">${key}</span>
923 </td>
923 </td>
924 <td>${desc}</td>
924 <td>${desc}</td>
925 </tr>
925 </tr>
926 %endfor
926 %endfor
927 </tbody>
927 </tbody>
928 </table>
928 </table>
929 </div>
929 </div>
930 <div class="block-left">
930 <div class="block-left">
931 <table class="keyboard-mappings">
931 <table class="keyboard-mappings">
932 <tbody>
932 <tbody>
933 <tr>
933 <tr>
934 <th></th>
934 <th></th>
935 <th>${_('Repositories')}</th>
935 <th>${_('Repositories')}</th>
936 </tr>
936 </tr>
937 <%
937 <%
938 elems = [
938 elems = [
939 ('g s', 'Goto summary page'),
939 ('g s', 'Goto summary page'),
940 ('g c', 'Goto changelog page'),
940 ('g c', 'Goto changelog page'),
941 ('g f', 'Goto files page'),
941 ('g f', 'Goto files page'),
942 ('g F', 'Goto files page with file search activated'),
942 ('g F', 'Goto files page with file search activated'),
943 ('g p', 'Goto pull requests page'),
943 ('g p', 'Goto pull requests page'),
944 ('g o', 'Goto repository settings'),
944 ('g o', 'Goto repository settings'),
945 ('g O', 'Goto repository permissions settings'),
945 ('g O', 'Goto repository permissions settings'),
946 ]
946 ]
947 %>
947 %>
948 %for key, desc in elems:
948 %for key, desc in elems:
949 <tr>
949 <tr>
950 <td class="keys">
950 <td class="keys">
951 <span class="key tag">${key}</span>
951 <span class="key tag">${key}</span>
952 </td>
952 </td>
953 <td>${desc}</td>
953 <td>${desc}</td>
954 </tr>
954 </tr>
955 %endfor
955 %endfor
956 </tbody>
956 </tbody>
957 </table>
957 </table>
958 </div>
958 </div>
959 </div>
959 </div>
960 <div class="modal-footer">
960 <div class="modal-footer">
961 </div>
961 </div>
962 </div><!-- /.modal-content -->
962 </div><!-- /.modal-content -->
963 </div><!-- /.modal-dialog -->
963 </div><!-- /.modal-dialog -->
964 </div><!-- /.modal -->
964 </div><!-- /.modal -->
965
965
@@ -1,350 +1,350 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
4 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5
5
6 <%def name="title()">
6 <%def name="title()">
7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
8 %if c.rhodecode_name:
8 %if c.rhodecode_name:
9 &middot; ${h.branding(c.rhodecode_name)}
9 &middot; ${h.branding(c.rhodecode_name)}
10 %endif
10 %endif
11 </%def>
11 </%def>
12
12
13 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
14 ${self.menu_items(active='repositories')}
14 ${self.menu_items(active='repositories')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_subnav()">
17 <%def name="menu_bar_subnav()">
18 ${self.repo_menu(active='changelog')}
18 ${self.repo_menu(active='commits')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <script>
22 <script>
23 // TODO: marcink switch this to pyroutes
23 // TODO: marcink switch this to pyroutes
24 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
24 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
26 </script>
26 </script>
27 <div class="box">
27 <div class="box">
28
28
29 <div id="changeset_compare_view_content" class="summary changeset">
29 <div id="changeset_compare_view_content" class="summary changeset">
30 <div class="summary-detail">
30 <div class="summary-detail">
31 <div class="fieldset">
31 <div class="fieldset">
32 <div class="left-label-summary">
32 <div class="left-label-summary">
33 <p>${_('Commit')}</p>
33 <p>${_('Commit')}</p>
34 <div class="right-label-summary">
34 <div class="right-label-summary">
35 <code>
35 <code>
36 ${h.show_id(c.commit)}
36 ${h.show_id(c.commit)}
37 </code>
37 </code>
38 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
38 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
39 % if hasattr(c.commit, 'phase'):
39 % if hasattr(c.commit, 'phase'):
40 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span>
40 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span>
41 % endif
41 % endif
42
42
43 ## obsolete commits
43 ## obsolete commits
44 % if hasattr(c.commit, 'obsolete'):
44 % if hasattr(c.commit, 'obsolete'):
45 % if c.commit.obsolete:
45 % if c.commit.obsolete:
46 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
46 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
47 % endif
47 % endif
48 % endif
48 % endif
49
49
50 ## hidden commits
50 ## hidden commits
51 % if hasattr(c.commit, 'hidden'):
51 % if hasattr(c.commit, 'hidden'):
52 % if c.commit.hidden:
52 % if c.commit.hidden:
53 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
53 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
54 % endif
54 % endif
55 % endif
55 % endif
56
56
57
57
58 <div class="pull-right">
58 <div class="pull-right">
59 <span id="parent_link">
59 <span id="parent_link">
60 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
60 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
61 </span>
61 </span>
62 |
62 |
63 <span id="child_link">
63 <span id="child_link">
64 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
64 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
65 </span>
65 </span>
66 </div>
66 </div>
67
67
68 </div>
68 </div>
69 </div>
69 </div>
70 </div>
70 </div>
71
71
72
72
73
73
74 <div class="fieldset">
74 <div class="fieldset">
75 <div class="left-label-summary">
75 <div class="left-label-summary">
76 <p>${_('Description')}:</p>
76 <p>${_('Description')}:</p>
77 <div class="right-label-summary">
77 <div class="right-label-summary">
78 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
78 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
79 <div id="message_expand" style="display:none;">
79 <div id="message_expand" style="display:none;">
80 ${_('Expand')}
80 ${_('Expand')}
81 </div>
81 </div>
82 </div>
82 </div>
83 </div>
83 </div>
84 </div>
84 </div>
85
85
86 %if c.statuses:
86 %if c.statuses:
87 <div class="fieldset">
87 <div class="fieldset">
88 <div class="left-label-summary">
88 <div class="left-label-summary">
89 <p>${_('Commit status')}:</p>
89 <p>${_('Commit status')}:</p>
90 <div class="right-label-summary">
90 <div class="right-label-summary">
91 <div class="changeset-status-ico">
91 <div class="changeset-status-ico">
92 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
92 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
93 </div>
93 </div>
94 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
94 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
95 </div>
95 </div>
96 </div>
96 </div>
97 </div>
97 </div>
98 %endif
98 %endif
99
99
100 <div class="fieldset">
100 <div class="fieldset">
101 <div class="left-label-summary">
101 <div class="left-label-summary">
102 <p>${_('References')}:</p>
102 <p>${_('References')}:</p>
103 <div class="right-label-summary">
103 <div class="right-label-summary">
104 <div class="tags">
104 <div class="tags">
105 %if c.commit.merge:
105 %if c.commit.merge:
106 <span class="mergetag tag">
106 <span class="mergetag tag">
107 <i class="icon-merge"></i>${_('merge')}
107 <i class="icon-merge"></i>${_('merge')}
108 </span>
108 </span>
109 %endif
109 %endif
110
110
111 %if h.is_hg(c.rhodecode_repo):
111 %if h.is_hg(c.rhodecode_repo):
112 %for book in c.commit.bookmarks:
112 %for book in c.commit.bookmarks:
113 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
113 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
114 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
114 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
115 </span>
115 </span>
116 %endfor
116 %endfor
117 %endif
117 %endif
118
118
119 %for tag in c.commit.tags:
119 %for tag in c.commit.tags:
120 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
120 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
121 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
121 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
122 </span>
122 </span>
123 %endfor
123 %endfor
124
124
125 %if c.commit.branch:
125 %if c.commit.branch:
126 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}">
126 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}">
127 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=c.commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
127 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=c.commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
128 </span>
128 </span>
129 %endif
129 %endif
130 </div>
130 </div>
131 </div>
131 </div>
132 </div>
132 </div>
133 </div>
133 </div>
134
134
135 <div class="fieldset">
135 <div class="fieldset">
136 <div class="left-label-summary">
136 <div class="left-label-summary">
137 <p>${_('Diff options')}:</p>
137 <p>${_('Diff options')}:</p>
138 <div class="right-label-summary">
138 <div class="right-label-summary">
139 <div class="diff-actions">
139 <div class="diff-actions">
140 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
140 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
141 ${_('Raw Diff')}
141 ${_('Raw Diff')}
142 </a>
142 </a>
143 |
143 |
144 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
144 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
145 ${_('Patch Diff')}
145 ${_('Patch Diff')}
146 </a>
146 </a>
147 |
147 |
148 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
148 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
149 ${_('Download Diff')}
149 ${_('Download Diff')}
150 </a>
150 </a>
151 </div>
151 </div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </div>
154 </div>
155
155
156 <div class="fieldset">
156 <div class="fieldset">
157 <div class="left-label-summary">
157 <div class="left-label-summary">
158 <p>${_('Comments')}:</p>
158 <p>${_('Comments')}:</p>
159 <div class="right-label-summary">
159 <div class="right-label-summary">
160 <div class="comments-number">
160 <div class="comments-number">
161 %if c.comments:
161 %if c.comments:
162 <a href="#comments">${_ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
162 <a href="#comments">${_ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
163 %else:
163 %else:
164 ${_ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
164 ${_ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
165 %endif
165 %endif
166 %if c.inline_cnt:
166 %if c.inline_cnt:
167 <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
167 <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
168 %else:
168 %else:
169 ${_ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
169 ${_ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
170 %endif
170 %endif
171 </div>
171 </div>
172 </div>
172 </div>
173 </div>
173 </div>
174 </div>
174 </div>
175
175
176 <div class="fieldset">
176 <div class="fieldset">
177 <div class="left-label-summary">
177 <div class="left-label-summary">
178 <p>${_('Unresolved TODOs')}:</p>
178 <p>${_('Unresolved TODOs')}:</p>
179 <div class="right-label-summary">
179 <div class="right-label-summary">
180 <div class="comments-number">
180 <div class="comments-number">
181 % if c.unresolved_comments:
181 % if c.unresolved_comments:
182 % for co in c.unresolved_comments:
182 % for co in c.unresolved_comments:
183 <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','}
183 <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','}
184 % endfor
184 % endfor
185 % else:
185 % else:
186 ${_('There are no unresolved TODOs')}
186 ${_('There are no unresolved TODOs')}
187 % endif
187 % endif
188 </div>
188 </div>
189 </div>
189 </div>
190 </div>
190 </div>
191 </div>
191 </div>
192
192
193 <div class="fieldset">
193 <div class="fieldset">
194 <div class="left-label-summary">
194 <div class="left-label-summary">
195 <p>${_('Author')}</p>
195 <p>${_('Author')}</p>
196
196
197 <div class="right-label-summary">
197 <div class="right-label-summary">
198 ${self.gravatar_with_user(c.commit.author)}
198 ${self.gravatar_with_user(c.commit.author)}
199 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
199 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
200 </div>
200 </div>
201 </div>
201 </div>
202
202
203 <div class="clear-fix"></div>
203 <div class="clear-fix"></div>
204
204
205 </div> <!-- end summary-detail -->
205 </div> <!-- end summary-detail -->
206 </div> <!-- end summary -->
206 </div> <!-- end summary -->
207 </div>
207 </div>
208 <div class="cs_files">
208 <div class="cs_files">
209 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
209 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
210 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id])}
210 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id])}
211 ${cbdiffs.render_diffset(
211 ${cbdiffs.render_diffset(
212 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
212 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
213 </div>
213 </div>
214
214
215 ## template for inline comment form
215 ## template for inline comment form
216 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
216 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
217
217
218 ## render comments
218 ## render comments
219 ${comment.generate_comments(c.comments)}
219 ${comment.generate_comments(c.comments)}
220
220
221 ## main comment form and it status
221 ## main comment form and it status
222 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
222 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
223 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
223 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
224 </div>
224 </div>
225
225
226 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
226 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
227 <script type="text/javascript">
227 <script type="text/javascript">
228
228
229 $(document).ready(function() {
229 $(document).ready(function() {
230
230
231 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
231 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
232 if($('#trimmed_message_box').height() === boxmax){
232 if($('#trimmed_message_box').height() === boxmax){
233 $('#message_expand').show();
233 $('#message_expand').show();
234 }
234 }
235
235
236 $('#message_expand').on('click', function(e){
236 $('#message_expand').on('click', function(e){
237 $('#trimmed_message_box').css('max-height', 'none');
237 $('#trimmed_message_box').css('max-height', 'none');
238 $(this).hide();
238 $(this).hide();
239 });
239 });
240
240
241 $('.show-inline-comments').on('click', function(e){
241 $('.show-inline-comments').on('click', function(e){
242 var boxid = $(this).attr('data-comment-id');
242 var boxid = $(this).attr('data-comment-id');
243 var button = $(this);
243 var button = $(this);
244
244
245 if(button.hasClass("comments-visible")) {
245 if(button.hasClass("comments-visible")) {
246 $('#{0} .inline-comments'.format(boxid)).each(function(index){
246 $('#{0} .inline-comments'.format(boxid)).each(function(index){
247 $(this).hide();
247 $(this).hide();
248 });
248 });
249 button.removeClass("comments-visible");
249 button.removeClass("comments-visible");
250 } else {
250 } else {
251 $('#{0} .inline-comments'.format(boxid)).each(function(index){
251 $('#{0} .inline-comments'.format(boxid)).each(function(index){
252 $(this).show();
252 $(this).show();
253 });
253 });
254 button.addClass("comments-visible");
254 button.addClass("comments-visible");
255 }
255 }
256 });
256 });
257
257
258
258
259 // next links
259 // next links
260 $('#child_link').on('click', function(e){
260 $('#child_link').on('click', function(e){
261 // fetch via ajax what is going to be the next link, if we have
261 // fetch via ajax what is going to be the next link, if we have
262 // >1 links show them to user to choose
262 // >1 links show them to user to choose
263 if(!$('#child_link').hasClass('disabled')){
263 if(!$('#child_link').hasClass('disabled')){
264 $.ajax({
264 $.ajax({
265 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
265 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
266 success: function(data) {
266 success: function(data) {
267 if(data.results.length === 0){
267 if(data.results.length === 0){
268 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
268 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
269 }
269 }
270 if(data.results.length === 1){
270 if(data.results.length === 1){
271 var commit = data.results[0];
271 var commit = data.results[0];
272 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
272 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
273 }
273 }
274 else if(data.results.length === 2){
274 else if(data.results.length === 2){
275 $('#child_link').addClass('disabled');
275 $('#child_link').addClass('disabled');
276 $('#child_link').addClass('double');
276 $('#child_link').addClass('double');
277 var _html = '';
277 var _html = '';
278 _html +='<a title="__title__" href="__url__">__rev__</a> '
278 _html +='<a title="__title__" href="__url__">__rev__</a> '
279 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
279 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
280 .replace('__title__', data.results[0].message)
280 .replace('__title__', data.results[0].message)
281 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
281 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
282 _html +=' | ';
282 _html +=' | ';
283 _html +='<a title="__title__" href="__url__">__rev__</a> '
283 _html +='<a title="__title__" href="__url__">__rev__</a> '
284 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
284 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
285 .replace('__title__', data.results[1].message)
285 .replace('__title__', data.results[1].message)
286 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
286 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
287 $('#child_link').html(_html);
287 $('#child_link').html(_html);
288 }
288 }
289 }
289 }
290 });
290 });
291 e.preventDefault();
291 e.preventDefault();
292 }
292 }
293 });
293 });
294
294
295 // prev links
295 // prev links
296 $('#parent_link').on('click', function(e){
296 $('#parent_link').on('click', function(e){
297 // fetch via ajax what is going to be the next link, if we have
297 // fetch via ajax what is going to be the next link, if we have
298 // >1 links show them to user to choose
298 // >1 links show them to user to choose
299 if(!$('#parent_link').hasClass('disabled')){
299 if(!$('#parent_link').hasClass('disabled')){
300 $.ajax({
300 $.ajax({
301 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
301 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
302 success: function(data) {
302 success: function(data) {
303 if(data.results.length === 0){
303 if(data.results.length === 0){
304 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
304 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
305 }
305 }
306 if(data.results.length === 1){
306 if(data.results.length === 1){
307 var commit = data.results[0];
307 var commit = data.results[0];
308 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
308 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
309 }
309 }
310 else if(data.results.length === 2){
310 else if(data.results.length === 2){
311 $('#parent_link').addClass('disabled');
311 $('#parent_link').addClass('disabled');
312 $('#parent_link').addClass('double');
312 $('#parent_link').addClass('double');
313 var _html = '';
313 var _html = '';
314 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
314 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
315 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
315 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
316 .replace('__title__', data.results[0].message)
316 .replace('__title__', data.results[0].message)
317 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
317 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
318 _html +=' | ';
318 _html +=' | ';
319 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
319 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
320 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
320 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
321 .replace('__title__', data.results[1].message)
321 .replace('__title__', data.results[1].message)
322 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
322 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
323 $('#parent_link').html(_html);
323 $('#parent_link').html(_html);
324 }
324 }
325 }
325 }
326 });
326 });
327 e.preventDefault();
327 e.preventDefault();
328 }
328 }
329 });
329 });
330
330
331 if (location.hash) {
331 if (location.hash) {
332 var result = splitDelimitedHash(location.hash);
332 var result = splitDelimitedHash(location.hash);
333 var line = $('html').find(result.loc);
333 var line = $('html').find(result.loc);
334 if (line.length > 0){
334 if (line.length > 0){
335 offsetScroll(line, 70);
335 offsetScroll(line, 70);
336 }
336 }
337 }
337 }
338
338
339 // browse tree @ revision
339 // browse tree @ revision
340 $('#files_link').on('click', function(e){
340 $('#files_link').on('click', function(e){
341 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
341 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
342 e.preventDefault();
342 e.preventDefault();
343 });
343 });
344
344
345 // inject comments into their proper positions
345 // inject comments into their proper positions
346 var file_comments = $('.inline-comment-placeholder');
346 var file_comments = $('.inline-comment-placeholder');
347 })
347 })
348 </script>
348 </script>
349
349
350 </%def>
350 </%def>
@@ -1,102 +1,102 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Commits') % c.repo_name} -
5 ${_('%s Commits') % c.repo_name} -
6 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
6 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
7 ...
7 ...
8 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
8 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
9 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
9 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 ${_('Commits')} -
16 ${_('Commits')} -
17 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
17 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
18 ...
18 ...
19 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
19 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
20 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
20 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='repositories')}
24 ${self.menu_items(active='repositories')}
25 </%def>
25 </%def>
26
26
27 <%def name="menu_bar_subnav()">
27 <%def name="menu_bar_subnav()">
28 ${self.repo_menu(active='changelog')}
28 ${self.repo_menu(active='commits')}
29 </%def>
29 </%def>
30
30
31 <%def name="main()">
31 <%def name="main()">
32
32
33 <div class="summary changeset">
33 <div class="summary changeset">
34 <div class="summary-detail">
34 <div class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <span class="breadcrumbs files_location">
36 <span class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${_('Commit Range')}
38 ${_('Commit Range')}
39 <code>
39 <code>
40 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
40 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
41 </code>
41 </code>
42 </h4>
42 </h4>
43 </span>
43 </span>
44 </div>
44 </div>
45
45
46 <div class="fieldset">
46 <div class="fieldset">
47 <div class="left-label">
47 <div class="left-label">
48 ${_('Diff option')}:
48 ${_('Diff option')}:
49 </div>
49 </div>
50 <div class="right-content">
50 <div class="right-content">
51 <div class="btn btn-primary">
51 <div class="btn btn-primary">
52 <a href="${h.route_path('repo_compare',
52 <a href="${h.route_path('repo_compare',
53 repo_name=c.repo_name,
53 repo_name=c.repo_name,
54 source_ref_type='rev',
54 source_ref_type='rev',
55 source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'),
55 source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'),
56 target_ref_type='rev',
56 target_ref_type='rev',
57 target_ref=c.commit_ranges[-1].raw_id)}"
57 target_ref=c.commit_ranges[-1].raw_id)}"
58 >
58 >
59 ${_('Show combined compare')}
59 ${_('Show combined compare')}
60 </a>
60 </a>
61 </div>
61 </div>
62 </div>
62 </div>
63 </div>
63 </div>
64
64
65 </div> <!-- end summary-detail -->
65 </div> <!-- end summary-detail -->
66
66
67 </div> <!-- end summary -->
67 </div> <!-- end summary -->
68
68
69 <div id="changeset_compare_view_content">
69 <div id="changeset_compare_view_content">
70 <div class="pull-left">
70 <div class="pull-left">
71 <div class="btn-group">
71 <div class="btn-group">
72 <a
72 <a
73 class="btn"
73 class="btn"
74 href="#"
74 href="#"
75 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
75 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
76 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
76 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
77 </a>
77 </a>
78 <a
78 <a
79 class="btn"
79 class="btn"
80 href="#"
80 href="#"
81 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
81 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
82 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
82 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
83 </a>
83 </a>
84 </div>
84 </div>
85 </div>
85 </div>
86 ## Commit range generated below
86 ## Commit range generated below
87 <%include file="../compare/compare_commits.mako"/>
87 <%include file="../compare/compare_commits.mako"/>
88 <div class="cs_files">
88 <div class="cs_files">
89 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
89 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
90 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
90 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
91 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
91 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
92 ${cbdiffs.render_diffset_menu()}
92 ${cbdiffs.render_diffset_menu()}
93 %for commit in c.commit_ranges:
93 %for commit in c.commit_ranges:
94 ${cbdiffs.render_diffset(
94 ${cbdiffs.render_diffset(
95 diffset=c.changes[commit.raw_id],
95 diffset=c.changes[commit.raw_id],
96 collapse_when_files_over=5,
96 collapse_when_files_over=5,
97 commit=commit,
97 commit=commit,
98 )}
98 )}
99 %endfor
99 %endfor
100 </div>
100 </div>
101 </div>
101 </div>
102 </%def>
102 </%def>
@@ -1,314 +1,314 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name}
6 ${_('%s Changelog') % c.repo_name}
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path}
8 /${c.changelog_for_path}
9 %endif
9 %endif
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 %if c.changelog_for_path:
16 %if c.changelog_for_path:
17 /${c.changelog_for_path}
17 /${c.changelog_for_path}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='changelog')}
26 ${self.repo_menu(active='commits')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30
30
31 <div class="box">
31 <div class="box">
32 <div class="title">
32 <div class="title">
33 <ul class="links">
33 <ul class="links">
34 <li>
34 <li>
35 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
35 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
36 %if c.rhodecode_db_repo.fork:
36 %if c.rhodecode_db_repo.fork:
37 <span>
37 <span>
38 <a id="compare_fork_button"
38 <a id="compare_fork_button"
39 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
39 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
40 class="btn btn-small"
40 class="btn btn-small"
41 href="${h.route_path('repo_compare',
41 href="${h.route_path('repo_compare',
42 repo_name=c.rhodecode_db_repo.fork.repo_name,
42 repo_name=c.rhodecode_db_repo.fork.repo_name,
43 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
43 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
44 source_ref=c.rhodecode_db_repo.landing_rev[1],
44 source_ref=c.rhodecode_db_repo.landing_rev[1],
45 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
45 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
46 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
46 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
47 _query=dict(merge=1, target_repo=c.repo_name))}"
47 _query=dict(merge=1, target_repo=c.repo_name))}"
48 >
48 >
49 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
49 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
50 </a>
50 </a>
51 </span>
51 </span>
52 %endif
52 %endif
53
53
54 ## pr open link
54 ## pr open link
55 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
55 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
56 <span>
56 <span>
57 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
57 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
58 ${_('Open new pull request')}
58 ${_('Open new pull request')}
59 </a>
59 </a>
60 </span>
60 </span>
61 %endif
61 %endif
62
62
63 ## clear selection
63 ## clear selection
64 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
64 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
65 ${_('Clear selection')}
65 ${_('Clear selection')}
66 </div>
66 </div>
67
67
68 </li>
68 </li>
69 </ul>
69 </ul>
70 </div>
70 </div>
71
71
72 % if c.pagination:
72 % if c.pagination:
73 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
73 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
74
74
75 <div class="graph-header">
75 <div class="graph-header">
76 <div id="filter_changelog">
76 <div id="filter_changelog">
77 ${h.hidden('branch_filter')}
77 ${h.hidden('branch_filter')}
78 %if c.selected_name:
78 %if c.selected_name:
79 <div class="btn btn-default" id="clear_filter" >
79 <div class="btn btn-default" id="clear_filter" >
80 ${_('Clear filter')}
80 ${_('Clear filter')}
81 </div>
81 </div>
82 %endif
82 %endif
83 </div>
83 </div>
84 ${self.breadcrumbs('breadcrumbs_light')}
84 ${self.breadcrumbs('breadcrumbs_light')}
85 <div class="pull-right">
85 <div class="pull-right">
86 % if h.is_hg(c.rhodecode_repo):
86 % if h.is_hg(c.rhodecode_repo):
87 % if c.show_hidden:
87 % if c.show_hidden:
88 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
88 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
89 % else:
89 % else:
90 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
90 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
91 % endif
91 % endif
92 % else:
92 % else:
93 <span class="action-link disabled">${_('Show hidden')}</span>
93 <span class="action-link disabled">${_('Show hidden')}</span>
94 % endif
94 % endif
95 </div>
95 </div>
96 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
96 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
97 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
97 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
98 </div>
98 </div>
99 </div>
99 </div>
100
100
101 <div id="graph">
101 <div id="graph">
102 <div class="graph-col-wrapper">
102 <div class="graph-col-wrapper">
103 <div id="graph_nodes">
103 <div id="graph_nodes">
104 <div id="graph_canvas"></div>
104 <div id="graph_canvas"></div>
105 </div>
105 </div>
106 <div id="graph_content" class="main-content graph_full_width">
106 <div id="graph_content" class="main-content graph_full_width">
107
107
108 <div class="table">
108 <div class="table">
109 <table id="changesets" class="rctable">
109 <table id="changesets" class="rctable">
110 <tr>
110 <tr>
111 ## checkbox
111 ## checkbox
112 <th></th>
112 <th></th>
113 <th></th>
113 <th></th>
114
114
115 <th>${_('Commit')}</th>
115 <th>${_('Commit')}</th>
116
116
117 ## commit message expand arrow
117 ## commit message expand arrow
118 <th></th>
118 <th></th>
119 <th>${_('Commit Message')}</th>
119 <th>${_('Commit Message')}</th>
120
120
121 <th>${_('Age')}</th>
121 <th>${_('Age')}</th>
122 <th>${_('Author')}</th>
122 <th>${_('Author')}</th>
123
123
124 <th>${_('Refs')}</th>
124 <th>${_('Refs')}</th>
125 ## comments
125 ## comments
126 <th></th>
126 <th></th>
127 </tr>
127 </tr>
128
128
129 <tbody class="commits-range">
129 <tbody class="commits-range">
130 <%include file='changelog_elements.mako'/>
130 <%include file='changelog_elements.mako'/>
131 </tbody>
131 </tbody>
132 </table>
132 </table>
133 </div>
133 </div>
134 </div>
134 </div>
135 <div class="pagination-wh pagination-left">
135 <div class="pagination-wh pagination-left">
136 ${c.pagination.pager('$link_previous ~2~ $link_next')}
136 ${c.pagination.pager('$link_previous ~2~ $link_next')}
137 </div>
137 </div>
138 </div>
138 </div>
139
139
140 <script type="text/javascript">
140 <script type="text/javascript">
141 var cache = {};
141 var cache = {};
142 $(function(){
142 $(function(){
143
143
144 // Create links to commit ranges when range checkboxes are selected
144 // Create links to commit ranges when range checkboxes are selected
145 var $commitCheckboxes = $('.commit-range');
145 var $commitCheckboxes = $('.commit-range');
146 // cache elements
146 // cache elements
147 var $commitRangeContainer = $('#rev_range_container');
147 var $commitRangeContainer = $('#rev_range_container');
148 var $commitRangeClear = $('#rev_range_clear');
148 var $commitRangeClear = $('#rev_range_clear');
149
149
150 var checkboxRangeSelector = function(e){
150 var checkboxRangeSelector = function(e){
151 var selectedCheckboxes = [];
151 var selectedCheckboxes = [];
152 for (pos in $commitCheckboxes){
152 for (pos in $commitCheckboxes){
153 if($commitCheckboxes[pos].checked){
153 if($commitCheckboxes[pos].checked){
154 selectedCheckboxes.push($commitCheckboxes[pos]);
154 selectedCheckboxes.push($commitCheckboxes[pos]);
155 }
155 }
156 }
156 }
157 var open_new_pull_request = $('#open_new_pull_request');
157 var open_new_pull_request = $('#open_new_pull_request');
158 if(open_new_pull_request){
158 if(open_new_pull_request){
159 var selected_changes = selectedCheckboxes.length;
159 var selected_changes = selectedCheckboxes.length;
160 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type == 'svn') {
160 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type == 'svn') {
161 open_new_pull_request.hide();
161 open_new_pull_request.hide();
162 } else {
162 } else {
163 if (selected_changes == 1) {
163 if (selected_changes == 1) {
164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
165 } else if (selected_changes == 0) {
165 } else if (selected_changes == 0) {
166 open_new_pull_request.html(_gettext('Open new pull request'));
166 open_new_pull_request.html(_gettext('Open new pull request'));
167 }
167 }
168 open_new_pull_request.show();
168 open_new_pull_request.show();
169 }
169 }
170 }
170 }
171
171
172 if (selectedCheckboxes.length>0){
172 if (selectedCheckboxes.length>0){
173 var revEnd = selectedCheckboxes[0].name;
173 var revEnd = selectedCheckboxes[0].name;
174 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
174 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
175 var url = pyroutes.url('repo_commit',
175 var url = pyroutes.url('repo_commit',
176 {'repo_name': '${c.repo_name}',
176 {'repo_name': '${c.repo_name}',
177 'commit_id': revStart+'...'+revEnd});
177 'commit_id': revStart+'...'+revEnd});
178
178
179 var link = (revStart == revEnd)
179 var link = (revStart == revEnd)
180 ? _gettext('Show selected commit __S')
180 ? _gettext('Show selected commit __S')
181 : _gettext('Show selected commits __S ... __E');
181 : _gettext('Show selected commits __S ... __E');
182
182
183 link = link.replace('__S', revStart.substr(0,6));
183 link = link.replace('__S', revStart.substr(0,6));
184 link = link.replace('__E', revEnd.substr(0,6));
184 link = link.replace('__E', revEnd.substr(0,6));
185
185
186 $commitRangeContainer
186 $commitRangeContainer
187 .attr('href',url)
187 .attr('href',url)
188 .html(link)
188 .html(link)
189 .show();
189 .show();
190
190
191 $commitRangeClear.show();
191 $commitRangeClear.show();
192 var _url = pyroutes.url('pullrequest_new',
192 var _url = pyroutes.url('pullrequest_new',
193 {'repo_name': '${c.repo_name}',
193 {'repo_name': '${c.repo_name}',
194 'commit': revEnd});
194 'commit': revEnd});
195 open_new_pull_request.attr('href', _url);
195 open_new_pull_request.attr('href', _url);
196 $('#compare_fork_button').hide();
196 $('#compare_fork_button').hide();
197 } else {
197 } else {
198 $commitRangeContainer.hide();
198 $commitRangeContainer.hide();
199 $commitRangeClear.hide();
199 $commitRangeClear.hide();
200
200
201 %if c.branch_name:
201 %if c.branch_name:
202 var _url = pyroutes.url('pullrequest_new',
202 var _url = pyroutes.url('pullrequest_new',
203 {'repo_name': '${c.repo_name}',
203 {'repo_name': '${c.repo_name}',
204 'branch':'${c.branch_name}'});
204 'branch':'${c.branch_name}'});
205 open_new_pull_request.attr('href', _url);
205 open_new_pull_request.attr('href', _url);
206 %else:
206 %else:
207 var _url = pyroutes.url('pullrequest_new',
207 var _url = pyroutes.url('pullrequest_new',
208 {'repo_name': '${c.repo_name}'});
208 {'repo_name': '${c.repo_name}'});
209 open_new_pull_request.attr('href', _url);
209 open_new_pull_request.attr('href', _url);
210 %endif
210 %endif
211 $('#compare_fork_button').show();
211 $('#compare_fork_button').show();
212 }
212 }
213 };
213 };
214
214
215 $commitCheckboxes.on('click', checkboxRangeSelector);
215 $commitCheckboxes.on('click', checkboxRangeSelector);
216
216
217 $commitRangeClear.on('click',function(e) {
217 $commitRangeClear.on('click',function(e) {
218 $commitCheckboxes.attr('checked', false);
218 $commitCheckboxes.attr('checked', false);
219 checkboxRangeSelector();
219 checkboxRangeSelector();
220 e.preventDefault();
220 e.preventDefault();
221 });
221 });
222
222
223 // make sure the buttons are consistent when navigate back and forth
223 // make sure the buttons are consistent when navigate back and forth
224 checkboxRangeSelector();
224 checkboxRangeSelector();
225
225
226 var msgs = $('.message');
226 var msgs = $('.message');
227 // get first element height
227 // get first element height
228 var el = $('#graph_content .container')[0];
228 var el = $('#graph_content .container')[0];
229 var row_h = el.clientHeight;
229 var row_h = el.clientHeight;
230 for (var i=0; i < msgs.length; i++) {
230 for (var i=0; i < msgs.length; i++) {
231 var m = msgs[i];
231 var m = msgs[i];
232
232
233 var h = m.clientHeight;
233 var h = m.clientHeight;
234 var pad = $(m).css('padding');
234 var pad = $(m).css('padding');
235 if (h > row_h) {
235 if (h > row_h) {
236 var offset = row_h - (h+12);
236 var offset = row_h - (h+12);
237 $(m.nextElementSibling).css('display','block');
237 $(m.nextElementSibling).css('display','block');
238 $(m.nextElementSibling).css('margin-top',offset+'px');
238 $(m.nextElementSibling).css('margin-top',offset+'px');
239 }
239 }
240 }
240 }
241
241
242 $("#clear_filter").on("click", function() {
242 $("#clear_filter").on("click", function() {
243 var filter = {'repo_name': '${c.repo_name}'};
243 var filter = {'repo_name': '${c.repo_name}'};
244 window.location = pyroutes.url('repo_changelog', filter);
244 window.location = pyroutes.url('repo_commits', filter);
245 });
245 });
246
246
247 $("#branch_filter").select2({
247 $("#branch_filter").select2({
248 'dropdownAutoWidth': true,
248 'dropdownAutoWidth': true,
249 'width': 'resolve',
249 'width': 'resolve',
250 'placeholder': "${c.selected_name or _('Filter changelog')}",
250 'placeholder': "${c.selected_name or _('Filter changelog')}",
251 containerCssClass: "drop-menu",
251 containerCssClass: "drop-menu",
252 dropdownCssClass: "drop-menu-dropdown",
252 dropdownCssClass: "drop-menu-dropdown",
253 query: function(query){
253 query: function(query){
254 var key = 'cache';
254 var key = 'cache';
255 var cached = cache[key] ;
255 var cached = cache[key] ;
256 if(cached) {
256 if(cached) {
257 var data = {results: []};
257 var data = {results: []};
258 //filter results
258 //filter results
259 $.each(cached.results, function(){
259 $.each(cached.results, function(){
260 var section = this.text;
260 var section = this.text;
261 var children = [];
261 var children = [];
262 $.each(this.children, function(){
262 $.each(this.children, function(){
263 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
263 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
264 children.push({'id': this.id, 'text': this.text, 'type': this.type})
264 children.push({'id': this.id, 'text': this.text, 'type': this.type})
265 }
265 }
266 });
266 });
267 data.results.push({'text': section, 'children': children});
267 data.results.push({'text': section, 'children': children});
268 query.callback({results: data.results});
268 query.callback({results: data.results});
269 });
269 });
270 }else{
270 }else{
271 $.ajax({
271 $.ajax({
272 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
272 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
273 data: {},
273 data: {},
274 dataType: 'json',
274 dataType: 'json',
275 type: 'GET',
275 type: 'GET',
276 success: function(data) {
276 success: function(data) {
277 cache[key] = data;
277 cache[key] = data;
278 query.callback({results: data.results});
278 query.callback({results: data.results});
279 }
279 }
280 })
280 })
281 }
281 }
282 }
282 }
283 });
283 });
284 $('#branch_filter').on('change', function(e){
284 $('#branch_filter').on('change', function(e){
285 var data = $('#branch_filter').select2('data');
285 var data = $('#branch_filter').select2('data');
286 //type: branch_closed
286 //type: branch_closed
287 var selected = data.text;
287 var selected = data.text;
288 var filter = {'repo_name': '${c.repo_name}'};
288 var filter = {'repo_name': '${c.repo_name}'};
289 if(data.type == 'branch' || data.type == 'branch_closed'){
289 if(data.type == 'branch' || data.type == 'branch_closed'){
290 filter["branch"] = selected;
290 filter["branch"] = selected;
291 if (data.type == 'branch_closed') {
291 if (data.type == 'branch_closed') {
292 filter["evolve"] = '1';
292 filter["evolve"] = '1';
293 }
293 }
294 }
294 }
295 else if (data.type == 'book'){
295 else if (data.type == 'book'){
296 filter["bookmark"] = selected;
296 filter["bookmark"] = selected;
297 }
297 }
298 window.location = pyroutes.url('repo_changelog', filter);
298 window.location = pyroutes.url('repo_commits', filter);
299 });
299 });
300
300
301 commitsController = new CommitsController();
301 commitsController = new CommitsController();
302 % if not c.changelog_for_path:
302 % if not c.changelog_for_path:
303 commitsController.reloadGraph();
303 commitsController.reloadGraph();
304 % endif
304 % endif
305
305
306 });
306 });
307
307
308 </script>
308 </script>
309 </div>
309 </div>
310 % else:
310 % else:
311 ${_('There are no changes yet')}
311 ${_('There are no changes yet')}
312 % endif
312 % endif
313 </div>
313 </div>
314 </%def>
314 </%def>
@@ -1,155 +1,155 b''
1 ## small box that displays changed/added/removed details fetched by AJAX
1 ## small box that displays changed/added/removed details fetched by AJAX
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 % if c.prev_page:
4 % if c.prev_page:
5 <tr>
5 <tr>
6 <td colspan="9" class="load-more-commits">
6 <td colspan="9" class="load-more-commits">
7 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}', '${c.commit_id}', '${c.f_path}');return false">
7 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}', '${c.commit_id}', '${c.f_path}');return false">
8 ${_('load previous')}
8 ${_('load previous')}
9 </a>
9 </a>
10 </td>
10 </td>
11 </tr>
11 </tr>
12 % endif
12 % endif
13
13
14 ## to speed up lookups cache some functions before the loop
14 ## to speed up lookups cache some functions before the loop
15 <%
15 <%
16 active_patterns = h.get_active_pattern_entries(c.repo_name)
16 active_patterns = h.get_active_pattern_entries(c.repo_name)
17 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
17 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
18 %>
18 %>
19
19
20 % for cnt,commit in enumerate(c.pagination):
20 % for cnt,commit in enumerate(c.pagination):
21 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
21 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
22
22
23 <td class="td-checkbox">
23 <td class="td-checkbox">
24 ${h.checkbox(commit.raw_id,class_="commit-range")}
24 ${h.checkbox(commit.raw_id,class_="commit-range")}
25 </td>
25 </td>
26
26
27 <td class="td-status">
27 <td class="td-status">
28 %if c.statuses.get(commit.raw_id):
28 %if c.statuses.get(commit.raw_id):
29 <div class="changeset-status-ico">
29 <div class="changeset-status-ico">
30 %if c.statuses.get(commit.raw_id)[2]:
30 %if c.statuses.get(commit.raw_id)[2]:
31 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
31 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
32 <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div>
32 <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div>
33 </a>
33 </a>
34 %else:
34 %else:
35 <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
35 <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
36 <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div>
36 <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div>
37 </a>
37 </a>
38 %endif
38 %endif
39 </div>
39 </div>
40 %else:
40 %else:
41 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
41 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
42 %endif
42 %endif
43 </td>
43 </td>
44
44
45 <td class="td-hash">
45 <td class="td-hash">
46 <code>
46 <code>
47
47
48 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">
48 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">
49 <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span>
49 <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span>
50 </a>
50 </a>
51
51
52 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
52 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
53
53
54 ## COMMIT PHASES
54 ## COMMIT PHASES
55
55
56 ## Draft
56 ## Draft
57 % if hasattr(commit, 'phase'):
57 % if hasattr(commit, 'phase'):
58 % if commit.phase != 'public':
58 % if commit.phase != 'public':
59 <span class="tag phase-${commit.phase} tooltip" title="${_('{} commit phase').format(commit.phase)}">${commit.phase[0].upper()}</span>
59 <span class="tag phase-${commit.phase} tooltip" title="${_('{} commit phase').format(commit.phase)}">${commit.phase[0].upper()}</span>
60 % endif
60 % endif
61 % endif
61 % endif
62
62
63 ## obsolete commits
63 ## obsolete commits
64 % if hasattr(commit, 'obsolete') and commit.obsolete:
64 % if hasattr(commit, 'obsolete') and commit.obsolete:
65 <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Obsolete Evolve State')}">O</span>
65 <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Obsolete Evolve State')}">O</span>
66 % endif
66 % endif
67
67
68 ## hidden commits
68 ## hidden commits
69 % if hasattr(commit, 'hidden') and commit.hidden:
69 % if hasattr(commit, 'hidden') and commit.hidden:
70 <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Hidden Evolve State')}">H</span>
70 <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Hidden Evolve State')}">H</span>
71 % endif
71 % endif
72
72
73 </code>
73 </code>
74 </td>
74 </td>
75
75
76 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this, true); return false">
76 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this, true); return false">
77 <i class="icon-expand-linked"></i>&nbsp;
77 <i class="icon-expand-linked"></i>&nbsp;
78 </td>
78 </td>
79 <td class="td-description mid">
79 <td class="td-description mid">
80 <div class="log-container truncate-wrap">
80 <div class="log-container truncate-wrap">
81 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${urlify_commit_message(commit.message, c.repo_name)}</div>
81 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${urlify_commit_message(commit.message, c.repo_name)}</div>
82 </div>
82 </div>
83 </td>
83 </td>
84
84
85 <td class="td-time">
85 <td class="td-time">
86 ${h.age_component(commit.date)}
86 ${h.age_component(commit.date)}
87 </td>
87 </td>
88 <td class="td-user">
88 <td class="td-user">
89 ${base.gravatar_with_user(commit.author)}
89 ${base.gravatar_with_user(commit.author)}
90 </td>
90 </td>
91
91
92 <td class="td-tags tags-col">
92 <td class="td-tags tags-col">
93 <div id="t-${commit.raw_id}">
93 <div id="t-${commit.raw_id}">
94
94
95 ## merge
95 ## merge
96 %if commit.merge:
96 %if commit.merge:
97 <span class="tag mergetag">
97 <span class="tag mergetag">
98 <i class="icon-merge"></i>${_('merge')}
98 <i class="icon-merge"></i>${_('merge')}
99 </span>
99 </span>
100 %endif
100 %endif
101
101
102 ## branch
102 ## branch
103 %if commit.branch:
103 %if commit.branch:
104 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
104 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
105 <a href="${h.route_path('repo_changelog',repo_name=c.repo_name,_query=dict(branch=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
105 <a href="${h.route_path('repo_commits',repo_name=c.repo_name,_query=dict(branch=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
106 </span>
106 </span>
107 %endif
107 %endif
108
108
109 ## bookmarks
109 ## bookmarks
110 %if h.is_hg(c.rhodecode_repo):
110 %if h.is_hg(c.rhodecode_repo):
111 %for book in commit.bookmarks:
111 %for book in commit.bookmarks:
112 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
112 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
113 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
113 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
114 </span>
114 </span>
115 %endfor
115 %endfor
116 %endif
116 %endif
117
117
118 ## tags
118 ## tags
119 %for tag in commit.tags:
119 %for tag in commit.tags:
120 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
120 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
121 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
121 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
122 </span>
122 </span>
123 %endfor
123 %endfor
124
124
125 </div>
125 </div>
126 </td>
126 </td>
127
127
128 <td class="td-comments comments-col">
128 <td class="td-comments comments-col">
129 <% cs_comments = c.comments.get(commit.raw_id,[]) %>
129 <% cs_comments = c.comments.get(commit.raw_id,[]) %>
130 % if cs_comments:
130 % if cs_comments:
131 <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % cs_comments[0].comment_id)}">
131 <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % cs_comments[0].comment_id)}">
132 <i class="icon-comment"></i> ${len(cs_comments)}
132 <i class="icon-comment"></i> ${len(cs_comments)}
133 </a>
133 </a>
134 % else:
134 % else:
135 <i class="icon-comment"></i> ${len(cs_comments)}
135 <i class="icon-comment"></i> ${len(cs_comments)}
136 % endif
136 % endif
137 </td>
137 </td>
138
138
139 </tr>
139 </tr>
140 % endfor
140 % endfor
141
141
142 % if c.next_page:
142 % if c.next_page:
143 <tr>
143 <tr>
144 <td colspan="10" class="load-more-commits">
144 <td colspan="10" class="load-more-commits">
145 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}', '${c.commit_id}', '${c.f_path}');return false">
145 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}', '${c.commit_id}', '${c.f_path}');return false">
146 ${_('load next')}
146 ${_('load next')}
147 </a>
147 </a>
148 </td>
148 </td>
149 </tr>
149 </tr>
150 % endif
150 % endif
151 <tr class="chunk-graph-data" style="display:none"
151 <tr class="chunk-graph-data" style="display:none"
152 data-graph='${c.graph_data|n}'
152 data-graph='${c.graph_data|n}'
153 data-node='${c.prev_page}:${c.next_page}'
153 data-node='${c.prev_page}:${c.next_page}'
154 data-commits='${c.graph_commits|n}'>
154 data-commits='${c.graph_commits|n}'>
155 </tr> No newline at end of file
155 </tr>
@@ -1,51 +1,51 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2 <div class="table">
2 <div class="table">
3
3
4 <table class="table rctable file_history">
4 <table class="table rctable file_history">
5 %for cnt,cs in enumerate(c.pagination):
5 %for cnt,cs in enumerate(c.pagination):
6 <tr id="chg_${cnt+1}" class="${('tablerow%s' % (cnt%2))}">
6 <tr id="chg_${cnt+1}" class="${('tablerow%s' % (cnt%2))}">
7 <td class="td-user">
7 <td class="td-user">
8 ${base.gravatar_with_user(cs.author, 16)}
8 ${base.gravatar_with_user(cs.author, 16)}
9 </td>
9 </td>
10 <td class="td-time">
10 <td class="td-time">
11 <div class="date">
11 <div class="date">
12 ${h.age_component(cs.date)}
12 ${h.age_component(cs.date)}
13 </div>
13 </div>
14 </td>
14 </td>
15 <td class="td-message">
15 <td class="td-message">
16 <div class="log-container">
16 <div class="log-container">
17 <div class="message_history" title="${h.tooltip(cs.message)}">
17 <div class="message_history" title="${h.tooltip(cs.message)}">
18 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id)}">
18 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id)}">
19 ${h.shorter(cs.message, 75)}
19 ${h.shorter(cs.message, 75)}
20 </a>
20 </a>
21 </div>
21 </div>
22 </div>
22 </div>
23 </td>
23 </td>
24 <td class="td-hash">
24 <td class="td-hash">
25 <code>
25 <code>
26 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id)}">
26 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id)}">
27 <span>${h.show_id(cs)}</span>
27 <span>${h.show_id(cs)}</span>
28 </a>
28 </a>
29 </code>
29 </code>
30 </td>
30 </td>
31 <td class="td-actions">
31 <td class="td-actions">
32 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=cs.raw_id,f_path=c.changelog_for_path)}">
32 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=cs.raw_id,f_path=c.changelog_for_path)}">
33 ${_('Show File')}
33 ${_('Show File')}
34 </a>
34 </a>
35 </td>
35 </td>
36 <td class="td-actions">
36 <td class="td-actions">
37 <a href="${h.route_path('repo_compare',repo_name=c.repo_name, source_ref_type="rev", source_ref=cs.raw_id,target_ref_type="rev", target_ref=c.commit_id,_query=dict(merge='1',f_path=c.changelog_for_path))}">
37 <a href="${h.route_path('repo_compare',repo_name=c.repo_name, source_ref_type="rev", source_ref=cs.raw_id,target_ref_type="rev", target_ref=c.commit_id,_query=dict(merge='1',f_path=c.changelog_for_path))}">
38 <span title="${'Diff {} vs {}'.format(cs.raw_id[:8],c.commit_id[:8])}">${_('Diff File')}</span>
38 <span title="${'Diff {} vs {}'.format(cs.raw_id[:8],c.commit_id[:8])}">${_('Diff File')}</span>
39 </a>
39 </a>
40 </td>
40 </td>
41 </tr>
41 </tr>
42 %endfor
42 %endfor
43 <tr>
43 <tr>
44 <td colspan="6">
44 <td colspan="6">
45 <a id="file_history_overview_full" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit_id, f_path=c.f_path)}">
45 <a id="file_history_overview_full" href="${h.route_path('repo_commits_file',repo_name=c.repo_name, commit_id=c.commit_id, f_path=c.f_path)}">
46 ${_('Show Full History')}
46 ${_('Show Full History')}
47 </a>
47 </a>
48 </td>
48 </td>
49 </tr>
49 </tr>
50 </table>
50 </table>
51 </div>
51 </div>
@@ -1,450 +1,450 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 <%def name="metatags_help()">
6 <%def name="metatags_help()">
7 <table>
7 <table>
8 <%
8 <%
9 example_tags = [
9 example_tags = [
10 ('state','[stable]'),
10 ('state','[stable]'),
11 ('state','[stale]'),
11 ('state','[stale]'),
12 ('state','[featured]'),
12 ('state','[featured]'),
13 ('state','[dev]'),
13 ('state','[dev]'),
14 ('state','[dead]'),
14 ('state','[dead]'),
15 ('state','[deprecated]'),
15 ('state','[deprecated]'),
16
16
17 ('label','[personal]'),
17 ('label','[personal]'),
18 ('generic','[v2.0.0]'),
18 ('generic','[v2.0.0]'),
19
19
20 ('lang','[lang =&gt; JavaScript]'),
20 ('lang','[lang =&gt; JavaScript]'),
21 ('license','[license =&gt; LicenseName]'),
21 ('license','[license =&gt; LicenseName]'),
22
22
23 ('ref','[requires =&gt; RepoName]'),
23 ('ref','[requires =&gt; RepoName]'),
24 ('ref','[recommends =&gt; GroupName]'),
24 ('ref','[recommends =&gt; GroupName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
29 ]
29 ]
30 %>
30 %>
31 % for tag_type, tag in example_tags:
31 % for tag_type, tag in example_tags:
32 <tr>
32 <tr>
33 <td>${tag|n}</td>
33 <td>${tag|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 </tr>
35 </tr>
36 % endfor
36 % endfor
37 </table>
37 </table>
38 </%def>
38 </%def>
39
39
40 ## REPOSITORY RENDERERS
40 ## REPOSITORY RENDERERS
41 <%def name="quick_menu(repo_name)">
41 <%def name="quick_menu(repo_name)">
42 <i class="icon-more"></i>
42 <i class="icon-more"></i>
43 <div class="menu_items_container hidden">
43 <div class="menu_items_container hidden">
44 <ul class="menu_items">
44 <ul class="menu_items">
45 <li>
45 <li>
46 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
46 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
47 <span>${_('Summary')}</span>
47 <span>${_('Summary')}</span>
48 </a>
48 </a>
49 </li>
49 </li>
50 <li>
50 <li>
51 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
51 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
52 <span>${_('Changelog')}</span>
52 <span>${_('Commits')}</span>
53 </a>
53 </a>
54 </li>
54 </li>
55 <li>
55 <li>
56 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
56 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
57 <span>${_('Files')}</span>
57 <span>${_('Files')}</span>
58 </a>
58 </a>
59 </li>
59 </li>
60 <li>
60 <li>
61 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
61 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
62 <span>${_('Fork')}</span>
62 <span>${_('Fork')}</span>
63 </a>
63 </a>
64 </li>
64 </li>
65 </ul>
65 </ul>
66 </div>
66 </div>
67 </%def>
67 </%def>
68
68
69 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
69 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
70 <%
70 <%
71 def get_name(name,short_name=short_name):
71 def get_name(name,short_name=short_name):
72 if short_name:
72 if short_name:
73 return name.split('/')[-1]
73 return name.split('/')[-1]
74 else:
74 else:
75 return name
75 return name
76 %>
76 %>
77 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
77 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
78 ##NAME
78 ##NAME
79 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
79 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
80
80
81 ##TYPE OF REPO
81 ##TYPE OF REPO
82 %if h.is_hg(rtype):
82 %if h.is_hg(rtype):
83 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
83 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
84 %elif h.is_git(rtype):
84 %elif h.is_git(rtype):
85 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
85 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
86 %elif h.is_svn(rtype):
86 %elif h.is_svn(rtype):
87 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
87 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
88 %endif
88 %endif
89
89
90 ##PRIVATE/PUBLIC
90 ##PRIVATE/PUBLIC
91 %if private is True and c.visual.show_private_icon:
91 %if private is True and c.visual.show_private_icon:
92 <i class="icon-lock" title="${_('Private repository')}"></i>
92 <i class="icon-lock" title="${_('Private repository')}"></i>
93 %elif private is False and c.visual.show_public_icon:
93 %elif private is False and c.visual.show_public_icon:
94 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
94 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
95 %else:
95 %else:
96 <span></span>
96 <span></span>
97 %endif
97 %endif
98 ${get_name(name)}
98 ${get_name(name)}
99 </a>
99 </a>
100 %if fork_of:
100 %if fork_of:
101 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
101 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
102 %endif
102 %endif
103 %if rstate == 'repo_state_pending':
103 %if rstate == 'repo_state_pending':
104 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
104 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
105 (${_('creating...')})
105 (${_('creating...')})
106 </span>
106 </span>
107 %endif
107 %endif
108
108
109 </div>
109 </div>
110 </%def>
110 </%def>
111
111
112 <%def name="repo_desc(description, stylify_metatags)">
112 <%def name="repo_desc(description, stylify_metatags)">
113 <%
113 <%
114 tags, description = h.extract_metatags(description)
114 tags, description = h.extract_metatags(description)
115 %>
115 %>
116
116
117 <div class="truncate-wrap">
117 <div class="truncate-wrap">
118 % if stylify_metatags:
118 % if stylify_metatags:
119 % for tag_type, tag in tags:
119 % for tag_type, tag in tags:
120 ${h.style_metatag(tag_type, tag)|n}
120 ${h.style_metatag(tag_type, tag)|n}
121 % endfor
121 % endfor
122 % endif
122 % endif
123 ${description}
123 ${description}
124 </div>
124 </div>
125
125
126 </%def>
126 </%def>
127
127
128 <%def name="last_change(last_change)">
128 <%def name="last_change(last_change)">
129 ${h.age_component(last_change, time_is_local=True)}
129 ${h.age_component(last_change, time_is_local=True)}
130 </%def>
130 </%def>
131
131
132 <%def name="revision(name,rev,tip,author,last_msg, commit_date)">
132 <%def name="revision(name,rev,tip,author,last_msg, commit_date)">
133 <div>
133 <div>
134 %if rev >= 0:
134 %if rev >= 0:
135 <code><a title="${h.tooltip('%s\n%s\n\n%s' % (author, commit_date, last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
135 <code><a title="${h.tooltip('%s\n%s\n\n%s' % (author, commit_date, last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
136 %else:
136 %else:
137 ${_('No commits yet')}
137 ${_('No commits yet')}
138 %endif
138 %endif
139 </div>
139 </div>
140 </%def>
140 </%def>
141
141
142 <%def name="rss(name)">
142 <%def name="rss(name)">
143 %if c.rhodecode_user.username != h.DEFAULT_USER:
143 %if c.rhodecode_user.username != h.DEFAULT_USER:
144 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
144 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
145 %else:
145 %else:
146 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
146 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
147 %endif
147 %endif
148 </%def>
148 </%def>
149
149
150 <%def name="atom(name)">
150 <%def name="atom(name)">
151 %if c.rhodecode_user.username != h.DEFAULT_USER:
151 %if c.rhodecode_user.username != h.DEFAULT_USER:
152 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
152 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
153 %else:
153 %else:
154 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
154 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
155 %endif
155 %endif
156 </%def>
156 </%def>
157
157
158 <%def name="user_gravatar(email, size=16)">
158 <%def name="user_gravatar(email, size=16)">
159 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
159 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
160 ${base.gravatar(email, 16)}
160 ${base.gravatar(email, 16)}
161 </div>
161 </div>
162 </%def>
162 </%def>
163
163
164 <%def name="repo_actions(repo_name, super_user=True)">
164 <%def name="repo_actions(repo_name, super_user=True)">
165 <div>
165 <div>
166 <div class="grid_edit">
166 <div class="grid_edit">
167 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
167 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
168 <i class="icon-pencil"></i>Edit</a>
168 <i class="icon-pencil"></i>Edit</a>
169 </div>
169 </div>
170 <div class="grid_delete">
170 <div class="grid_delete">
171 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
171 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
172 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
172 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
173 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
173 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
174 ${h.end_form()}
174 ${h.end_form()}
175 </div>
175 </div>
176 </div>
176 </div>
177 </%def>
177 </%def>
178
178
179 <%def name="repo_state(repo_state)">
179 <%def name="repo_state(repo_state)">
180 <div>
180 <div>
181 %if repo_state == 'repo_state_pending':
181 %if repo_state == 'repo_state_pending':
182 <div class="tag tag4">${_('Creating')}</div>
182 <div class="tag tag4">${_('Creating')}</div>
183 %elif repo_state == 'repo_state_created':
183 %elif repo_state == 'repo_state_created':
184 <div class="tag tag1">${_('Created')}</div>
184 <div class="tag tag1">${_('Created')}</div>
185 %else:
185 %else:
186 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
186 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
187 %endif
187 %endif
188 </div>
188 </div>
189 </%def>
189 </%def>
190
190
191
191
192 ## REPO GROUP RENDERERS
192 ## REPO GROUP RENDERERS
193 <%def name="quick_repo_group_menu(repo_group_name)">
193 <%def name="quick_repo_group_menu(repo_group_name)">
194 <i class="icon-more"></i>
194 <i class="icon-more"></i>
195 <div class="menu_items_container hidden">
195 <div class="menu_items_container hidden">
196 <ul class="menu_items">
196 <ul class="menu_items">
197 <li>
197 <li>
198 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
198 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
199 </li>
199 </li>
200
200
201 </ul>
201 </ul>
202 </div>
202 </div>
203 </%def>
203 </%def>
204
204
205 <%def name="repo_group_name(repo_group_name, children_groups=None)">
205 <%def name="repo_group_name(repo_group_name, children_groups=None)">
206 <div>
206 <div>
207 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
207 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
208 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
208 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
209 %if children_groups:
209 %if children_groups:
210 ${h.literal(' &raquo; '.join(children_groups))}
210 ${h.literal(' &raquo; '.join(children_groups))}
211 %else:
211 %else:
212 ${repo_group_name}
212 ${repo_group_name}
213 %endif
213 %endif
214 </a>
214 </a>
215 </div>
215 </div>
216 </%def>
216 </%def>
217
217
218 <%def name="repo_group_desc(description, personal, stylify_metatags)">
218 <%def name="repo_group_desc(description, personal, stylify_metatags)">
219
219
220 <%
220 <%
221 tags, description = h.extract_metatags(description)
221 tags, description = h.extract_metatags(description)
222 %>
222 %>
223
223
224 <div class="truncate-wrap">
224 <div class="truncate-wrap">
225 % if personal:
225 % if personal:
226 <div class="metatag" tag="personal">${_('personal')}</div>
226 <div class="metatag" tag="personal">${_('personal')}</div>
227 % endif
227 % endif
228
228
229 % if stylify_metatags:
229 % if stylify_metatags:
230 % for tag_type, tag in tags:
230 % for tag_type, tag in tags:
231 ${h.style_metatag(tag_type, tag)|n}
231 ${h.style_metatag(tag_type, tag)|n}
232 % endfor
232 % endfor
233 % endif
233 % endif
234 ${description}
234 ${description}
235 </div>
235 </div>
236
236
237 </%def>
237 </%def>
238
238
239 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
239 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
240 <div class="grid_edit">
240 <div class="grid_edit">
241 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
241 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
242 </div>
242 </div>
243 <div class="grid_delete">
243 <div class="grid_delete">
244 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
244 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
245 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
245 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
246 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
246 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
247 ${h.end_form()}
247 ${h.end_form()}
248 </div>
248 </div>
249 </%def>
249 </%def>
250
250
251
251
252 <%def name="user_actions(user_id, username)">
252 <%def name="user_actions(user_id, username)">
253 <div class="grid_edit">
253 <div class="grid_edit">
254 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
254 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
255 <i class="icon-pencil"></i>${_('Edit')}</a>
255 <i class="icon-pencil"></i>${_('Edit')}</a>
256 </div>
256 </div>
257 <div class="grid_delete">
257 <div class="grid_delete">
258 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
258 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
259 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
259 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
260 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
260 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
261 ${h.end_form()}
261 ${h.end_form()}
262 </div>
262 </div>
263 </%def>
263 </%def>
264
264
265 <%def name="user_group_actions(user_group_id, user_group_name)">
265 <%def name="user_group_actions(user_group_id, user_group_name)">
266 <div class="grid_edit">
266 <div class="grid_edit">
267 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
267 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
268 </div>
268 </div>
269 <div class="grid_delete">
269 <div class="grid_delete">
270 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
270 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
271 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
271 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
272 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
272 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
273 ${h.end_form()}
273 ${h.end_form()}
274 </div>
274 </div>
275 </%def>
275 </%def>
276
276
277
277
278 <%def name="user_name(user_id, username)">
278 <%def name="user_name(user_id, username)">
279 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
279 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
280 </%def>
280 </%def>
281
281
282 <%def name="user_profile(username)">
282 <%def name="user_profile(username)">
283 ${base.gravatar_with_user(username, 16)}
283 ${base.gravatar_with_user(username, 16)}
284 </%def>
284 </%def>
285
285
286 <%def name="user_group_name(user_group_name)">
286 <%def name="user_group_name(user_group_name)">
287 <div>
287 <div>
288 <i class="icon-user-group" title="${_('User group')}"></i>
288 <i class="icon-user-group" title="${_('User group')}"></i>
289 ${h.link_to_group(user_group_name)}
289 ${h.link_to_group(user_group_name)}
290 </div>
290 </div>
291 </%def>
291 </%def>
292
292
293
293
294 ## GISTS
294 ## GISTS
295
295
296 <%def name="gist_gravatar(full_contact)">
296 <%def name="gist_gravatar(full_contact)">
297 <div class="gist_gravatar">
297 <div class="gist_gravatar">
298 ${base.gravatar(full_contact, 30)}
298 ${base.gravatar(full_contact, 30)}
299 </div>
299 </div>
300 </%def>
300 </%def>
301
301
302 <%def name="gist_access_id(gist_access_id, full_contact)">
302 <%def name="gist_access_id(gist_access_id, full_contact)">
303 <div>
303 <div>
304 <b>
304 <b>
305 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
305 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
306 </b>
306 </b>
307 </div>
307 </div>
308 </%def>
308 </%def>
309
309
310 <%def name="gist_author(full_contact, created_on, expires)">
310 <%def name="gist_author(full_contact, created_on, expires)">
311 ${base.gravatar_with_user(full_contact, 16)}
311 ${base.gravatar_with_user(full_contact, 16)}
312 </%def>
312 </%def>
313
313
314
314
315 <%def name="gist_created(created_on)">
315 <%def name="gist_created(created_on)">
316 <div class="created">
316 <div class="created">
317 ${h.age_component(created_on, time_is_local=True)}
317 ${h.age_component(created_on, time_is_local=True)}
318 </div>
318 </div>
319 </%def>
319 </%def>
320
320
321 <%def name="gist_expires(expires)">
321 <%def name="gist_expires(expires)">
322 <div class="created">
322 <div class="created">
323 %if expires == -1:
323 %if expires == -1:
324 ${_('never')}
324 ${_('never')}
325 %else:
325 %else:
326 ${h.age_component(h.time_to_utcdatetime(expires))}
326 ${h.age_component(h.time_to_utcdatetime(expires))}
327 %endif
327 %endif
328 </div>
328 </div>
329 </%def>
329 </%def>
330
330
331 <%def name="gist_type(gist_type)">
331 <%def name="gist_type(gist_type)">
332 %if gist_type != 'public':
332 %if gist_type != 'public':
333 <div class="tag">${_('Private')}</div>
333 <div class="tag">${_('Private')}</div>
334 %endif
334 %endif
335 </%def>
335 </%def>
336
336
337 <%def name="gist_description(gist_description)">
337 <%def name="gist_description(gist_description)">
338 ${gist_description}
338 ${gist_description}
339 </%def>
339 </%def>
340
340
341
341
342 ## PULL REQUESTS GRID RENDERERS
342 ## PULL REQUESTS GRID RENDERERS
343
343
344 <%def name="pullrequest_target_repo(repo_name)">
344 <%def name="pullrequest_target_repo(repo_name)">
345 <div class="truncate">
345 <div class="truncate">
346 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
346 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
347 </div>
347 </div>
348 </%def>
348 </%def>
349 <%def name="pullrequest_status(status)">
349 <%def name="pullrequest_status(status)">
350 <div class="${'flag_status %s' % status} pull-left"></div>
350 <div class="${'flag_status %s' % status} pull-left"></div>
351 </%def>
351 </%def>
352
352
353 <%def name="pullrequest_title(title, description)">
353 <%def name="pullrequest_title(title, description)">
354 ${title}
354 ${title}
355 </%def>
355 </%def>
356
356
357 <%def name="pullrequest_comments(comments_nr)">
357 <%def name="pullrequest_comments(comments_nr)">
358 <i class="icon-comment"></i> ${comments_nr}
358 <i class="icon-comment"></i> ${comments_nr}
359 </%def>
359 </%def>
360
360
361 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
361 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
362 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
362 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
363 % if short:
363 % if short:
364 #${pull_request_id}
364 #${pull_request_id}
365 % else:
365 % else:
366 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
366 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
367 % endif
367 % endif
368 </a>
368 </a>
369 </%def>
369 </%def>
370
370
371 <%def name="pullrequest_updated_on(updated_on)">
371 <%def name="pullrequest_updated_on(updated_on)">
372 ${h.age_component(h.time_to_utcdatetime(updated_on))}
372 ${h.age_component(h.time_to_utcdatetime(updated_on))}
373 </%def>
373 </%def>
374
374
375 <%def name="pullrequest_author(full_contact)">
375 <%def name="pullrequest_author(full_contact)">
376 ${base.gravatar_with_user(full_contact, 16)}
376 ${base.gravatar_with_user(full_contact, 16)}
377 </%def>
377 </%def>
378
378
379
379
380 ## ARTIFACT RENDERERS
380 ## ARTIFACT RENDERERS
381
381
382 <%def name="repo_artifact_uid(repo_name, file_uid)">
382 <%def name="repo_artifact_uid(repo_name, file_uid)">
383 <code><a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">${file_uid}</a></code>
383 <code><a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">${file_uid}</a></code>
384 </%def>
384 </%def>
385
385
386 <%def name="repo_artifact_uid_action(repo_name, file_uid)">
386 <%def name="repo_artifact_uid_action(repo_name, file_uid)">
387 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${h.route_url('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}" title="${_('Copy the full url')}"></i>
387 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${h.route_url('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}" title="${_('Copy the full url')}"></i>
388 </%def>
388 </%def>
389
389
390 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
390 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
391 ## <div class="grid_edit">
391 ## <div class="grid_edit">
392 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
392 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
393 ## </div>
393 ## </div>
394 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
394 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
395 <div class="grid_delete">
395 <div class="grid_delete">
396 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
396 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
397 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
397 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
398 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
398 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
399 ${h.end_form()}
399 ${h.end_form()}
400 </div>
400 </div>
401 % endif
401 % endif
402 </%def>
402 </%def>
403
403
404 <%def name="markup_form(form_id, form_text='', help_text=None)">
404 <%def name="markup_form(form_id, form_text='', help_text=None)">
405
405
406 <div class="markup-form">
406 <div class="markup-form">
407 <div class="markup-form-area">
407 <div class="markup-form-area">
408 <div class="markup-form-area-header">
408 <div class="markup-form-area-header">
409 <ul class="nav-links clearfix">
409 <ul class="nav-links clearfix">
410 <li class="active">
410 <li class="active">
411 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
411 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
412 </li>
412 </li>
413 <li class="">
413 <li class="">
414 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
414 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
415 </li>
415 </li>
416 </ul>
416 </ul>
417 </div>
417 </div>
418
418
419 <div class="markup-form-area-write" style="display: block;">
419 <div class="markup-form-area-write" style="display: block;">
420 <div id="edit-container_${form_id}">
420 <div id="edit-container_${form_id}">
421 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
421 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
422 </div>
422 </div>
423 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
423 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
424 <div id="preview-box_${form_id}" class="preview-box"></div>
424 <div id="preview-box_${form_id}" class="preview-box"></div>
425 </div>
425 </div>
426 </div>
426 </div>
427
427
428 <div class="markup-form-area-footer">
428 <div class="markup-form-area-footer">
429 <div class="toolbar">
429 <div class="toolbar">
430 <div class="toolbar-text">
430 <div class="toolbar-text">
431 ${(_('Parsed using %s syntax') % (
431 ${(_('Parsed using %s syntax') % (
432 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
432 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
433 )
433 )
434 )|n}
434 )|n}
435 </div>
435 </div>
436 </div>
436 </div>
437 </div>
437 </div>
438 </div>
438 </div>
439
439
440 <div class="markup-form-footer">
440 <div class="markup-form-footer">
441 % if help_text:
441 % if help_text:
442 <span class="help-block">${help_text}</span>
442 <span class="help-block">${help_text}</span>
443 % endif
443 % endif
444 </div>
444 </div>
445 </div>
445 </div>
446 <script type="text/javascript">
446 <script type="text/javascript">
447 new MarkupForm('${form_id}');
447 new MarkupForm('${form_id}');
448 </script>
448 </script>
449
449
450 </%def>
450 </%def>
@@ -1,1160 +1,1160 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%inherit file="/debug_style/index.html"/>
3 <%inherit file="/debug_style/index.html"/>
4
4
5 <%def name="breadcrumbs_links()">
5 <%def name="breadcrumbs_links()">
6 ${h.link_to(_('Style'), h.route_path('debug_style_home'))}
6 ${h.link_to(_('Style'), h.route_path('debug_style_home'))}
7 &raquo;
7 &raquo;
8 ${c.active}
8 ${c.active}
9 </%def>
9 </%def>
10
10
11 <%def name="js_extra()">
11 <%def name="js_extra()">
12 </%def>
12 </%def>
13
13
14 <%def name="css_extra()">
14 <%def name="css_extra()">
15 </%def>
15 </%def>
16
16
17
17
18 <%def name="real_main()">
18 <%def name="real_main()">
19 <div class="box">
19 <div class="box">
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 </div>
22 </div>
23
23
24 ##main
24 ##main
25 <div class='sidebar-col-wrapper'>
25 <div class='sidebar-col-wrapper'>
26 ${self.sidebar()}
26 ${self.sidebar()}
27
27
28 <div class="main-content">
28 <div class="main-content">
29
29
30
30
31
31
32 <h2>Code Blocks</h2>
32 <h2>Code Blocks</h2>
33
33
34 <dl class="dl-horizontal">
34 <dl class="dl-horizontal">
35 <dt><code>.codeblock</code></dt>
35 <dt><code>.codeblock</code></dt>
36 <dd>Used as a wrapping element around <code>.code-header</code> and
36 <dd>Used as a wrapping element around <code>.code-header</code> and
37 <code>.code-body</code>. Used to show the content of a file or a
37 <code>.code-body</code>. Used to show the content of a file or a
38 Gist.</dd>
38 Gist.</dd>
39
39
40 <dt><code>.diffblock</code></dt>
40 <dt><code>.diffblock</code></dt>
41 <dd>Used as a wrapping element to show a diff in a Commit or Pull
41 <dd>Used as a wrapping element to show a diff in a Commit or Pull
42 Request page. Contains usually <code>.code-header</code>,
42 Request page. Contains usually <code>.code-header</code>,
43 <code>.code-body</code> and in the edit case a <code>.message</code>.
43 <code>.code-body</code> and in the edit case a <code>.message</code>.
44 </dd>
44 </dd>
45 </dl>
45 </dl>
46
46
47
47
48 <p>Code Blocks are used in the following areas:</p>
48 <p>Code Blocks are used in the following areas:</p>
49
49
50 <ul>
50 <ul>
51 <li>Commit: Showing the Diff (still called Changeset in a few
51 <li>Commit: Showing the Diff (still called Changeset in a few
52 places).</li>
52 places).</li>
53 <li>File: Display a file, annotations, and edit a file.</li>
53 <li>File: Display a file, annotations, and edit a file.</li>
54 <li>Gist: Show the Gist and edit it.</li>
54 <li>Gist: Show the Gist and edit it.</li>
55 <li>Pull Request: Display the Diff of a Pull Request.</li>
55 <li>Pull Request: Display the Diff of a Pull Request.</li>
56 </ul>
56 </ul>
57
57
58
58
59
59
60 <!--
60 <!--
61 Compare Commits
61 Compare Commits
62 -->
62 -->
63 <h2>Compare Commits</h2>
63 <h2>Compare Commits</h2>
64
64
65 <div id="c-e589e34d6be8-5ab783e6d81b" class="diffblock margined comm">
65 <div id="c-e589e34d6be8-5ab783e6d81b" class="diffblock margined comm">
66 <div class="code-header">
66 <div class="code-header">
67 <div title="Go back to changed files overview">
67 <div title="Go back to changed files overview">
68 <a href="#changes_box">
68 <a href="#changes_box">
69 <i class="icon-circle-arrow-up"></i>
69 <i class="icon-circle-arrow-up"></i>
70 </a>
70 </a>
71 </div>
71 </div>
72 <div class="changeset_header">
72 <div class="changeset_header">
73 <div class="changeset_file">
73 <div class="changeset_file">
74 <i class="icon-file"></i>
74 <i class="icon-file"></i>
75 <a href="/example/files/e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d/rhodecode/public/css/code-block.less">rhodecode/public/css/code-block.less</a>
75 <a href="/example/files/e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d/rhodecode/public/css/code-block.less">rhodecode/public/css/code-block.less</a>
76 </div>
76 </div>
77 <div class="diff-actions">
77 <div class="diff-actions">
78 <a href="/example/diff/rhodecode/public/css/code-block.less?fulldiff=1&amp;diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=diff&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Show full diff for this file">
78 <a href="/example/diff/rhodecode/public/css/code-block.less?fulldiff=1&amp;diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=diff&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Show full diff for this file">
79 <img class="icon" src="/images/icons/page_white_go.png">
79 <img class="icon" src="/images/icons/page_white_go.png">
80 </a>
80 </a>
81 <a href="/example/diff-2way/rhodecode/public/css/code-block.less?fulldiff=1&amp;diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=diff&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Show full side-by-side diff for this file">
81 <a href="/example/diff-2way/rhodecode/public/css/code-block.less?fulldiff=1&amp;diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=diff&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Show full side-by-side diff for this file">
82 <img class="icon" src="/images/icons/application_double.png">
82 <img class="icon" src="/images/icons/application_double.png">
83 </a>
83 </a>
84 <a href="/example/diff/rhodecode/public/css/code-block.less?diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=raw&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Raw diff" tt_title="Raw diff">
84 <a href="/example/diff/rhodecode/public/css/code-block.less?diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=raw&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Raw diff" tt_title="Raw diff">
85 <img class="icon" src="/images/icons/page_white.png">
85 <img class="icon" src="/images/icons/page_white.png">
86 </a>
86 </a>
87 <a href="/example/diff/rhodecode/public/css/code-block.less?diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=download&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Download diff">
87 <a href="/example/diff/rhodecode/public/css/code-block.less?diff1=d12301bafcc0aea15c9283d3af018daee2b04cd9&amp;diff=download&amp;diff2=e589e34d6be8ec2b44017f6c2e0bbe782f1aba6d" class="tooltip" title="Download diff">
88 <img class="icon" src="/images/icons/page_save.png">
88 <img class="icon" src="/images/icons/page_save.png">
89 </a>
89 </a>
90 <a class="tooltip" href="/example/changeset/d12301bafcc0aea15c9283d3af018daee2b04cd9...80ead1899f50a894889e19ffeb49c9cebf5bf045?c-e589e34d6be8-5ab783e6d81b=WS%3A1&amp;c-e589e34d6be8-5ab783e6d81b=C%3A3#c-e589e34d6be8-5ab783e6d81b" title="Ignore white space"><img alt="Ignore white space" class="icon" src="/images/icons/text_strikethrough.png"></a>
90 <a class="tooltip" href="/example/changeset/d12301bafcc0aea15c9283d3af018daee2b04cd9...80ead1899f50a894889e19ffeb49c9cebf5bf045?c-e589e34d6be8-5ab783e6d81b=WS%3A1&amp;c-e589e34d6be8-5ab783e6d81b=C%3A3#c-e589e34d6be8-5ab783e6d81b" title="Ignore white space"><img alt="Ignore white space" class="icon" src="/images/icons/text_strikethrough.png"></a>
91 <a class="tooltip" href="/example/changeset/d12301bafcc0aea15c9283d3af018daee2b04cd9...80ead1899f50a894889e19ffeb49c9cebf5bf045?c-e589e34d6be8-5ab783e6d81b=C%3A6#c-e589e34d6be8-5ab783e6d81b" title="increase diff context to 6 lines"><img alt="increase diff context to 6 lines" class="icon" src="/images/icons/table_add.png"></a>
91 <a class="tooltip" href="/example/changeset/d12301bafcc0aea15c9283d3af018daee2b04cd9...80ead1899f50a894889e19ffeb49c9cebf5bf045?c-e589e34d6be8-5ab783e6d81b=C%3A6#c-e589e34d6be8-5ab783e6d81b" title="increase diff context to 6 lines"><img alt="increase diff context to 6 lines" class="icon" src="/images/icons/table_add.png"></a>
92 </div>
92 </div>
93 <span>
93 <span>
94 <label>
94 <label>
95 Show inline comments
95 Show inline comments
96 <input checked="checked" class="show-inline-comments" id="" id_for="c-e589e34d6be8-5ab783e6d81b" name="" type="checkbox" value="1">
96 <input checked="checked" class="show-inline-comments" id="" id_for="c-e589e34d6be8-5ab783e6d81b" name="" type="checkbox" value="1">
97 </label>
97 </label>
98 </span>
98 </span>
99 </div>
99 </div>
100 </div>
100 </div>
101 <div class="code-body">
101 <div class="code-body">
102 <div class="full_f_path" path="rhodecode/public/css/code-block.less"></div>
102 <div class="full_f_path" path="rhodecode/public/css/code-block.less"></div>
103 <table class="code-difftable">
103 <table class="code-difftable">
104 <tbody><tr class="line context">
104 <tbody><tr class="line context">
105 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o...">...</a></td>
105 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o...">...</a></td>
106 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n...">...</a></td>
106 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n...">...</a></td>
107 <td class="code no-comment">
107 <td class="code no-comment">
108 <pre>@@ -391,7 +391,7 @@
108 <pre>@@ -391,7 +391,7 @@
109 </pre>
109 </pre>
110 </td>
110 </td>
111 </tr>
111 </tr>
112 <tr class="line unmod">
112 <tr class="line unmod">
113 <td id="rhodecodepubliccsscode-blockless_o391" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o391">391</a></td>
113 <td id="rhodecodepubliccsscode-blockless_o391" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o391">391</a></td>
114 <td id="rhodecodepubliccsscode-blockless_n391" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n391">391</a></td>
114 <td id="rhodecodepubliccsscode-blockless_n391" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n391">391</a></td>
115 <td class="code no-comment">
115 <td class="code no-comment">
116 <pre>} /* Existing line, it might have a quite long content actually and in this case we might need some horizontal scrolling. The remaining text here is just used to make this line very long.
116 <pre>} /* Existing line, it might have a quite long content actually and in this case we might need some horizontal scrolling. The remaining text here is just used to make this line very long.
117 </pre>
117 </pre>
118 </td>
118 </td>
119 </tr>
119 </tr>
120 <tr class="line unmod">
120 <tr class="line unmod">
121 <td id="rhodecodepubliccsscode-blockless_o392" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o392">392</a></td>
121 <td id="rhodecodepubliccsscode-blockless_o392" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o392">392</a></td>
122 <td id="rhodecodepubliccsscode-blockless_n392" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n392">392</a></td>
122 <td id="rhodecodepubliccsscode-blockless_n392" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n392">392</a></td>
123 <td class="code no-comment">
123 <td class="code no-comment">
124 <pre></pre>
124 <pre></pre>
125 </td>
125 </td>
126 </tr>
126 </tr>
127 <tr class="line unmod">
127 <tr class="line unmod">
128 <td id="rhodecodepubliccsscode-blockless_o393" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o393">393</a></td>
128 <td id="rhodecodepubliccsscode-blockless_o393" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o393">393</a></td>
129 <td id="rhodecodepubliccsscode-blockless_n393" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n393">393</a></td>
129 <td id="rhodecodepubliccsscode-blockless_n393" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n393">393</a></td>
130 <td class="code no-comment">
130 <td class="code no-comment">
131 <pre>.code-body.textarea.editor,
131 <pre>.code-body.textarea.editor,
132 </pre>
132 </pre>
133 </td>
133 </td>
134 </tr>
134 </tr>
135 <tr class="line del">
135 <tr class="line del">
136 <td id="rhodecodepubliccsscode-blockless_o394" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o394">394</a></td>
136 <td id="rhodecodepubliccsscode-blockless_o394" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o394">394</a></td>
137 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n"></a></td>
137 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n"></a></td>
138 <td class="code no-comment">
138 <td class="code no-comment">
139 <pre>div.code-body{
139 <pre>div.code-body{
140 </pre>
140 </pre>
141 </td>
141 </td>
142 </tr>
142 </tr>
143 <tr class="line add">
143 <tr class="line add">
144 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
144 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
145 <td id="rhodecodepubliccsscode-blockless_n394" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n394">394</a></td>
145 <td id="rhodecodepubliccsscode-blockless_n394" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n394">394</a></td>
146 <td class="code no-comment">
146 <td class="code no-comment">
147 <pre>div.code-body<ins> </ins>{
147 <pre>div.code-body<ins> </ins>{
148 </pre>
148 </pre>
149 </td>
149 </td>
150 </tr>
150 </tr>
151 <tr class="line unmod">
151 <tr class="line unmod">
152 <td id="rhodecodepubliccsscode-blockless_o395" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o395">395</a></td>
152 <td id="rhodecodepubliccsscode-blockless_o395" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o395">395</a></td>
153 <td id="rhodecodepubliccsscode-blockless_n395" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n395">395</a></td>
153 <td id="rhodecodepubliccsscode-blockless_n395" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n395">395</a></td>
154 <td class="code no-comment">
154 <td class="code no-comment">
155 <pre> float: left;
155 <pre> float: left;
156 </pre>
156 </pre>
157 </td>
157 </td>
158 </tr>
158 </tr>
159 <tr class="line unmod">
159 <tr class="line unmod">
160 <td id="rhodecodepubliccsscode-blockless_o396" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o396">396</a></td>
160 <td id="rhodecodepubliccsscode-blockless_o396" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o396">396</a></td>
161 <td id="rhodecodepubliccsscode-blockless_n396" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n396">396</a></td>
161 <td id="rhodecodepubliccsscode-blockless_n396" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n396">396</a></td>
162 <td class="code no-comment">
162 <td class="code no-comment">
163 <pre> position: relative;
163 <pre> position: relative;
164 </pre>
164 </pre>
165 </td>
165 </td>
166 </tr>
166 </tr>
167 <tr class="line unmod">
167 <tr class="line unmod">
168 <td id="rhodecodepubliccsscode-blockless_o397" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o397">397</a></td>
168 <td id="rhodecodepubliccsscode-blockless_o397" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o397">397</a></td>
169 <td id="rhodecodepubliccsscode-blockless_n397" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n397">397</a></td>
169 <td id="rhodecodepubliccsscode-blockless_n397" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n397">397</a></td>
170 <td class="code no-comment">
170 <td class="code no-comment">
171 <pre> max-width: none;
171 <pre> max-width: none;
172 </pre>
172 </pre>
173 </td>
173 </td>
174 </tr>
174 </tr>
175 <tr class="line context">
175 <tr class="line context">
176 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o...">...</a></td>
176 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o...">...</a></td>
177 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n...">...</a></td>
177 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n...">...</a></td>
178 <td class="code no-comment">
178 <td class="code no-comment">
179 <pre>@@ -399,3 +399,6 @@
179 <pre>@@ -399,3 +399,6 @@
180 </pre>
180 </pre>
181 </td>
181 </td>
182 </tr>
182 </tr>
183 <tr class="line unmod">
183 <tr class="line unmod">
184 <td id="rhodecodepubliccsscode-blockless_o399" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o399">399</a></td>
184 <td id="rhodecodepubliccsscode-blockless_o399" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o399">399</a></td>
185 <td id="rhodecodepubliccsscode-blockless_n399" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n399">399</a></td>
185 <td id="rhodecodepubliccsscode-blockless_n399" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n399">399</a></td>
186 <td class="code no-comment">
186 <td class="code no-comment">
187 <pre> box-sizing: border-box;
187 <pre> box-sizing: border-box;
188 </pre>
188 </pre>
189 </td>
189 </td>
190 </tr>
190 </tr>
191 <tr class="line unmod">
191 <tr class="line unmod">
192 <td id="rhodecodepubliccsscode-blockless_o400" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o400">400</a></td>
192 <td id="rhodecodepubliccsscode-blockless_o400" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o400">400</a></td>
193 <td id="rhodecodepubliccsscode-blockless_n400" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n400">400</a></td>
193 <td id="rhodecodepubliccsscode-blockless_n400" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n400">400</a></td>
194 <td class="code no-comment">
194 <td class="code no-comment">
195 <pre>}
195 <pre>}
196 </pre>
196 </pre>
197 </td>
197 </td>
198 </tr>
198 </tr>
199 <tr class="line unmod">
199 <tr class="line unmod">
200 <td id="rhodecodepubliccsscode-blockless_o401" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o401">401</a></td>
200 <td id="rhodecodepubliccsscode-blockless_o401" class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o401">401</a></td>
201 <td id="rhodecodepubliccsscode-blockless_n401" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n401">401</a></td>
201 <td id="rhodecodepubliccsscode-blockless_n401" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n401">401</a></td>
202 <td class="code no-comment">
202 <td class="code no-comment">
203 <pre></pre>
203 <pre></pre>
204 </td>
204 </td>
205 </tr>
205 </tr>
206 <tr class="line add">
206 <tr class="line add">
207 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
207 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
208 <td id="rhodecodepubliccsscode-blockless_n402" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n402">402</a></td>
208 <td id="rhodecodepubliccsscode-blockless_n402" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n402">402</a></td>
209 <td class="code no-comment">
209 <td class="code no-comment">
210 <pre>.code-body td{
210 <pre>.code-body td{
211 </pre>
211 </pre>
212 </td>
212 </td>
213 </tr>
213 </tr>
214 <tr class="line add">
214 <tr class="line add">
215 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
215 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
216 <td id="rhodecodepubliccsscode-blockless_n403" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n403">403</a></td>
216 <td id="rhodecodepubliccsscode-blockless_n403" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n403">403</a></td>
217 <td class="code no-comment">
217 <td class="code no-comment">
218 <pre> line-height: 1.2em;
218 <pre> line-height: 1.2em;
219 </pre>
219 </pre>
220 </td>
220 </td>
221 </tr>
221 </tr>
222 <tr class="line add">
222 <tr class="line add">
223 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
223 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o"></a></td>
224 <td id="rhodecodepubliccsscode-blockless_n404" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n404">404</a></td>
224 <td id="rhodecodepubliccsscode-blockless_n404" class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n404">404</a></td>
225 <td class="code no-comment">
225 <td class="code no-comment">
226 <pre>}
226 <pre>}
227 </pre>
227 </pre>
228 </td>
228 </td>
229 </tr>
229 </tr>
230 <tr class="line context">
230 <tr class="line context">
231 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o...">...</a></td>
231 <td class="lineno old"><a href="#rhodecodepubliccsscode-blockless_o...">...</a></td>
232 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n...">...</a></td>
232 <td class="lineno new"><a href="#rhodecodepubliccsscode-blockless_n...">...</a></td>
233 <td class="code no-comment">
233 <td class="code no-comment">
234 <pre> No newline at end of file
234 <pre> No newline at end of file
235 </pre>
235 </pre>
236 </td>
236 </td>
237 </tr>
237 </tr>
238 </tbody></table>
238 </tbody></table>
239 </div>
239 </div>
240 </div>
240 </div>
241
241
242
242
243
243
244
244
245
245
246
246
247 <!--
247 <!--
248 Pull Request
248 Pull Request
249 -->
249 -->
250
250
251 <h2>Pull Request</h2>
251 <h2>Pull Request</h2>
252
252
253 <div class="cs_files">
253 <div class="cs_files">
254 <table class="compare_view_files">
254 <table class="compare_view_files">
255
255
256 <tbody><tr class="cs_M collapse_file" fid="c--5f1d017cf13b">
256 <tbody><tr class="cs_M collapse_file" fid="c--5f1d017cf13b">
257 <td class="cs_icon_td">
257 <td class="cs_icon_td">
258 <span class="collapse_file_icon" fid="c--5f1d017cf13b"></span>
258 <span class="collapse_file_icon" fid="c--5f1d017cf13b"></span>
259 </td>
259 </td>
260 <td class="cs_icon_td">
260 <td class="cs_icon_td">
261 <div class="flag_status not_reviewed hidden"></div>
261 <div class="flag_status not_reviewed hidden"></div>
262 </td>
262 </td>
263 <td id="a_c--5f1d017cf13b">
263 <td id="a_c--5f1d017cf13b">
264 <a class="compare_view_filepath" href="#a_c--5f1d017cf13b">
264 <a class="compare_view_filepath" href="#a_c--5f1d017cf13b">
265 rhodecode/public/css/main.less
265 rhodecode/public/css/main.less
266 </a>
266 </a>
267 <span id="diff_c--5f1d017cf13b" class="diff_links" style="">
267 <span id="diff_c--5f1d017cf13b" class="diff_links" style="">
268 <a href="/example/diff/rhodecode/public/css/main.less?fulldiff=1&amp;diff1=f73e9946825c8a7ef2c1178cd1e67986d5831f8f&amp;diff=diff&amp;diff2=27eb56cf467ca849112536d62decb2ed020b3ebc">
268 <a href="/example/diff/rhodecode/public/css/main.less?fulldiff=1&amp;diff1=f73e9946825c8a7ef2c1178cd1e67986d5831f8f&amp;diff=diff&amp;diff2=27eb56cf467ca849112536d62decb2ed020b3ebc">
269 Unified Diff
269 Unified Diff
270 </a>
270 </a>
271 |
271 |
272 <a href="/example/diff-2way/rhodecode/public/css/main.less?fulldiff=1&amp;diff1=f73e9946825c8a7ef2c1178cd1e67986d5831f8f&amp;diff=diff&amp;diff2=27eb56cf467ca849112536d62decb2ed020b3ebc">
272 <a href="/example/diff-2way/rhodecode/public/css/main.less?fulldiff=1&amp;diff1=f73e9946825c8a7ef2c1178cd1e67986d5831f8f&amp;diff=diff&amp;diff2=27eb56cf467ca849112536d62decb2ed020b3ebc">
273 Side-by-side Diff
273 Side-by-side Diff
274 </a>
274 </a>
275 </span>
275 </span>
276 </td>
276 </td>
277 <td>
277 <td>
278 <div class="changes pull-right"><div style="width:100px"><div class="added top-left-rounded-corner-mid bottom-left-rounded-corner-mid" style="width:33.3333333333%">1</div><div class="deleted top-right-rounded-corner-mid bottom-right-rounded-corner-mid" style="width:66.6666666667%">2</div></div></div>
278 <div class="changes pull-right"><div style="width:100px"><div class="added top-left-rounded-corner-mid bottom-left-rounded-corner-mid" style="width:33.3333333333%">1</div><div class="deleted top-right-rounded-corner-mid bottom-right-rounded-corner-mid" style="width:66.6666666667%">2</div></div></div>
279 <div class="comment-bubble pull-right" data-path="rhodecode/public/css/main.less">
279 <div class="comment-bubble pull-right" data-path="rhodecode/public/css/main.less">
280 <i class="icon-comment"></i>
280 <i class="icon-comment"></i>
281 </div>
281 </div>
282 </td>
282 </td>
283 </tr>
283 </tr>
284 <tr id="tr_c--5f1d017cf13b">
284 <tr id="tr_c--5f1d017cf13b">
285 <td></td>
285 <td></td>
286 <td></td>
286 <td></td>
287 <td class="injected_diff" colspan="2">
287 <td class="injected_diff" colspan="2">
288
288
289 <div class="diff-container" id="diff-container-140360026534904">
289 <div class="diff-container" id="diff-container-140360026534904">
290 <div id="c--5f1d017cf13b_target"></div>
290 <div id="c--5f1d017cf13b_target"></div>
291 <div id="c--5f1d017cf13b" class="diffblock margined comm">
291 <div id="c--5f1d017cf13b" class="diffblock margined comm">
292 <div class="code-body">
292 <div class="code-body">
293 <div class="full_f_path" path="rhodecode/public/css/main.less" style="display: none;"></div>
293 <div class="full_f_path" path="rhodecode/public/css/main.less" style="display: none;"></div>
294 <table class="code-difftable">
294 <table class="code-difftable">
295 <tbody><tr class="line context">
295 <tbody><tr class="line context">
296 <td class="lineno old"><a href="#rhodecodepubliccssmainless_o...">...</a></td>
296 <td class="lineno old"><a href="#rhodecodepubliccssmainless_o...">...</a></td>
297 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n...">...</a></td>
297 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n...">...</a></td>
298 <td class="code ">
298 <td class="code ">
299 <pre>@@ -2110,7 +2110,6 @@
299 <pre>@@ -2110,7 +2110,6 @@
300 </pre>
300 </pre>
301 </td>
301 </td>
302 </tr>
302 </tr>
303 <tr class="line unmod">
303 <tr class="line unmod">
304 <td id="rhodecodepubliccssmainless_o2110" class="lineno old"><a href="#rhodecodepubliccssmainless_o2110">2110</a></td>
304 <td id="rhodecodepubliccssmainless_o2110" class="lineno old"><a href="#rhodecodepubliccssmainless_o2110">2110</a></td>
305 <td id="rhodecodepubliccssmainless_n2110" class="lineno new"><a href="#rhodecodepubliccssmainless_n2110">2110</a></td>
305 <td id="rhodecodepubliccssmainless_n2110" class="lineno new"><a href="#rhodecodepubliccssmainless_n2110">2110</a></td>
306 <td class="code ">
306 <td class="code ">
307 <pre><span class="tab-escape"> </span>width: auto !important;
307 <pre><span class="tab-escape"> </span>width: auto !important;
308 </pre>
308 </pre>
309 </td>
309 </td>
310 </tr>
310 </tr>
311 <tr class="line unmod">
311 <tr class="line unmod">
312 <td id="rhodecodepubliccssmainless_o2111" class="lineno old"><a href="#rhodecodepubliccssmainless_o2111">2111</a></td>
312 <td id="rhodecodepubliccssmainless_o2111" class="lineno old"><a href="#rhodecodepubliccssmainless_o2111">2111</a></td>
313 <td id="rhodecodepubliccssmainless_n2111" class="lineno new"><a href="#rhodecodepubliccssmainless_n2111">2111</a></td>
313 <td id="rhodecodepubliccssmainless_n2111" class="lineno new"><a href="#rhodecodepubliccssmainless_n2111">2111</a></td>
314 <td class="code ">
314 <td class="code ">
315 <pre><span class="tab-escape"> </span>min-width: 160px;
315 <pre><span class="tab-escape"> </span>min-width: 160px;
316 </pre>
316 </pre>
317 </td>
317 </td>
318 </tr>
318 </tr>
319 <tr class="line unmod">
319 <tr class="line unmod">
320 <td id="rhodecodepubliccssmainless_o2112" class="lineno old"><a href="#rhodecodepubliccssmainless_o2112">2112</a></td>
320 <td id="rhodecodepubliccssmainless_o2112" class="lineno old"><a href="#rhodecodepubliccssmainless_o2112">2112</a></td>
321 <td id="rhodecodepubliccssmainless_n2112" class="lineno new"><a href="#rhodecodepubliccssmainless_n2112">2112</a></td>
321 <td id="rhodecodepubliccssmainless_n2112" class="lineno new"><a href="#rhodecodepubliccssmainless_n2112">2112</a></td>
322 <td class="code ">
322 <td class="code ">
323 <pre><span class="tab-escape"> </span>margin: @padding @padding @padding 0;
323 <pre><span class="tab-escape"> </span>margin: @padding @padding @padding 0;
324 </pre>
324 </pre>
325 </td>
325 </td>
326 </tr>
326 </tr>
327 <tr class="line del">
327 <tr class="line del">
328 <td id="rhodecodepubliccssmainless_o2113" class="lineno old"><a href="#rhodecodepubliccssmainless_o2113">2113</a></td>
328 <td id="rhodecodepubliccssmainless_o2113" class="lineno old"><a href="#rhodecodepubliccssmainless_o2113">2113</a></td>
329 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n"></a></td>
329 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n"></a></td>
330 <td class="code ">
330 <td class="code ">
331 <pre><span class="tab-escape"> </span>padding: .9em; /* Old comment which was making this line a very long line so that we might have to deal with it by either adding horizontal scrolling or some smart way of breaking this line. */
331 <pre><span class="tab-escape"> </span>padding: .9em; /* Old comment which was making this line a very long line so that we might have to deal with it by either adding horizontal scrolling or some smart way of breaking this line. */
332 </pre>
332 </pre>
333 </td>
333 </td>
334 </tr>
334 </tr>
335 <tr class="line unmod">
335 <tr class="line unmod">
336 <td id="rhodecodepubliccssmainless_o2114" class="lineno old"><a href="#rhodecodepubliccssmainless_o2114">2114</a></td>
336 <td id="rhodecodepubliccssmainless_o2114" class="lineno old"><a href="#rhodecodepubliccssmainless_o2114">2114</a></td>
337 <td id="rhodecodepubliccssmainless_n2113" class="lineno new"><a href="#rhodecodepubliccssmainless_n2113">2113</a></td>
337 <td id="rhodecodepubliccssmainless_n2113" class="lineno new"><a href="#rhodecodepubliccssmainless_n2113">2113</a></td>
338 <td class="code ">
338 <td class="code ">
339 <pre> line-height: 1em;
339 <pre> line-height: 1em;
340 </pre>
340 </pre>
341 </td>
341 </td>
342 </tr>
342 </tr>
343 <tr class="line unmod">
343 <tr class="line unmod">
344 <td id="rhodecodepubliccssmainless_o2115" class="lineno old"><a href="#rhodecodepubliccssmainless_o2115">2115</a></td>
344 <td id="rhodecodepubliccssmainless_o2115" class="lineno old"><a href="#rhodecodepubliccssmainless_o2115">2115</a></td>
345 <td id="rhodecodepubliccssmainless_n2114" class="lineno new"><a href="#rhodecodepubliccssmainless_n2114">2114</a></td>
345 <td id="rhodecodepubliccssmainless_n2114" class="lineno new"><a href="#rhodecodepubliccssmainless_n2114">2114</a></td>
346 <td class="code ">
346 <td class="code ">
347 <pre><span class="tab-escape"> </span>z-index: 100;//js sets the menu below it to 9999
347 <pre><span class="tab-escape"> </span>z-index: 100;//js sets the menu below it to 9999
348 </pre>
348 </pre>
349 </td>
349 </td>
350 </tr>
350 </tr>
351 <tr class="line unmod">
351 <tr class="line unmod">
352 <td id="rhodecodepubliccssmainless_o2116" class="lineno old"><a href="#rhodecodepubliccssmainless_o2116">2116</a></td>
352 <td id="rhodecodepubliccssmainless_o2116" class="lineno old"><a href="#rhodecodepubliccssmainless_o2116">2116</a></td>
353 <td id="rhodecodepubliccssmainless_n2115" class="lineno new"><a href="#rhodecodepubliccssmainless_n2115">2115</a></td>
353 <td id="rhodecodepubliccssmainless_n2115" class="lineno new"><a href="#rhodecodepubliccssmainless_n2115">2115</a></td>
354 <td class="code ">
354 <td class="code ">
355 <pre><span class="tab-escape"> </span>background-color: white;
355 <pre><span class="tab-escape"> </span>background-color: white;
356 </pre>
356 </pre>
357 </td>
357 </td>
358 </tr>
358 </tr>
359 <tr class="line context">
359 <tr class="line context">
360 <td class="lineno old"><a href="#rhodecodepubliccssmainless_o...">...</a></td>
360 <td class="lineno old"><a href="#rhodecodepubliccssmainless_o...">...</a></td>
361 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n...">...</a></td>
361 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n...">...</a></td>
362 <td class="code ">
362 <td class="code ">
363 <pre>@@ -2118,7 +2117,7 @@
363 <pre>@@ -2118,7 +2117,7 @@
364 </pre>
364 </pre>
365 </td>
365 </td>
366 </tr>
366 </tr>
367 <tr class="line unmod">
367 <tr class="line unmod">
368 <td id="rhodecodepubliccssmainless_o2118" class="lineno old"><a href="#rhodecodepubliccssmainless_o2118">2118</a></td>
368 <td id="rhodecodepubliccssmainless_o2118" class="lineno old"><a href="#rhodecodepubliccssmainless_o2118">2118</a></td>
369 <td id="rhodecodepubliccssmainless_n2117" class="lineno new"><a href="#rhodecodepubliccssmainless_n2117">2117</a></td>
369 <td id="rhodecodepubliccssmainless_n2117" class="lineno new"><a href="#rhodecodepubliccssmainless_n2117">2117</a></td>
370 <td class="code ">
370 <td class="code ">
371 <pre></pre>
371 <pre></pre>
372 </td>
372 </td>
373 </tr>
373 </tr>
374 <tr class="line unmod">
374 <tr class="line unmod">
375 <td id="rhodecodepubliccssmainless_o2119" class="lineno old"><a href="#rhodecodepubliccssmainless_o2119">2119</a></td>
375 <td id="rhodecodepubliccssmainless_o2119" class="lineno old"><a href="#rhodecodepubliccssmainless_o2119">2119</a></td>
376 <td id="rhodecodepubliccssmainless_n2118" class="lineno new"><a href="#rhodecodepubliccssmainless_n2118">2118</a></td>
376 <td id="rhodecodepubliccssmainless_n2118" class="lineno new"><a href="#rhodecodepubliccssmainless_n2118">2118</a></td>
377 <td class="code ">
377 <td class="code ">
378 <pre><span class="tab-escape"> </span>a {
378 <pre><span class="tab-escape"> </span>a {
379 </pre>
379 </pre>
380 </td>
380 </td>
381 </tr>
381 </tr>
382 <tr class="line unmod">
382 <tr class="line unmod">
383 <td id="rhodecodepubliccssmainless_o2120" class="lineno old"><a href="#rhodecodepubliccssmainless_o2120">2120</a></td>
383 <td id="rhodecodepubliccssmainless_o2120" class="lineno old"><a href="#rhodecodepubliccssmainless_o2120">2120</a></td>
384 <td id="rhodecodepubliccssmainless_n2119" class="lineno new"><a href="#rhodecodepubliccssmainless_n2119">2119</a></td>
384 <td id="rhodecodepubliccssmainless_n2119" class="lineno new"><a href="#rhodecodepubliccssmainless_n2119">2119</a></td>
385 <td class="code ">
385 <td class="code ">
386 <pre><span class="tab-escape"> </span><span class="tab-escape"> </span>display:block;
386 <pre><span class="tab-escape"> </span><span class="tab-escape"> </span>display:block;
387 </pre>
387 </pre>
388 </td>
388 </td>
389 </tr>
389 </tr>
390 <tr class="line del">
390 <tr class="line del">
391 <td id="rhodecodepubliccssmainless_o2121" class="lineno old"><a href="#rhodecodepubliccssmainless_o2121">2121</a></td>
391 <td id="rhodecodepubliccssmainless_o2121" class="lineno old"><a href="#rhodecodepubliccssmainless_o2121">2121</a></td>
392 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n"></a></td>
392 <td class="lineno new"><a href="#rhodecodepubliccssmainless_n"></a></td>
393 <td class="code ">
393 <td class="code ">
394 <pre><span class="tab-escape"> </span><del><span< del=""> <del>class=</del><del>"tab-escape"</del><del>&gt; </del>padding: <del>0</del>;
394 <pre><span class="tab-escape"> </span><del><span< del=""> <del>class=</del><del>"tab-escape"</del><del>&gt; </del>padding: <del>0</del>;
395 </span<></del></pre>
395 </span<></del></pre>
396 </td>
396 </td>
397 </tr>
397 </tr>
398 <tr class="line add">
398 <tr class="line add">
399 <td class="lineno old"><a href="#rhodecodepubliccssmainless_o"></a></td>
399 <td class="lineno old"><a href="#rhodecodepubliccssmainless_o"></a></td>
400 <td id="rhodecodepubliccssmainless_n2120" class="lineno new"><a href="#rhodecodepubliccssmainless_n2120">2120</a></td>
400 <td id="rhodecodepubliccssmainless_n2120" class="lineno new"><a href="#rhodecodepubliccssmainless_n2120">2120</a></td>
401 <td class="code ">
401 <td class="code ">
402 <pre><span class="tab-escape"> </span><ins> </ins> <ins> </ins><ins> </ins>padding: <ins>.9em</ins>;
402 <pre><span class="tab-escape"> </span><ins> </ins> <ins> </ins><ins> </ins>padding: <ins>.9em</ins>;
403 </pre>
403 </pre>
404 </td>
404 </td>
405 </tr>
405 </tr>
406 <tr class="line unmod">
406 <tr class="line unmod">
407 <td id="rhodecodepubliccssmainless_o2122" class="lineno old"><a href="#rhodecodepubliccssmainless_o2122">2122</a></td>
407 <td id="rhodecodepubliccssmainless_o2122" class="lineno old"><a href="#rhodecodepubliccssmainless_o2122">2122</a></td>
408 <td id="rhodecodepubliccssmainless_n2121" class="lineno new"><a href="#rhodecodepubliccssmainless_n2121">2121</a></td>
408 <td id="rhodecodepubliccssmainless_n2121" class="lineno new"><a href="#rhodecodepubliccssmainless_n2121">2121</a></td>
409 <td class="code ">
409 <td class="code ">
410 <pre></pre>
410 <pre></pre>
411 </td>
411 </td>
412 </tr>
412 </tr>
413 <tr class="line unmod">
413 <tr class="line unmod">
414 <td id="rhodecodepubliccssmainless_o2123" class="lineno old"><a href="#rhodecodepubliccssmainless_o2123">2123</a></td>
414 <td id="rhodecodepubliccssmainless_o2123" class="lineno old"><a href="#rhodecodepubliccssmainless_o2123">2123</a></td>
415 <td id="rhodecodepubliccssmainless_n2122" class="lineno new"><a href="#rhodecodepubliccssmainless_n2122">2122</a></td>
415 <td id="rhodecodepubliccssmainless_n2122" class="lineno new"><a href="#rhodecodepubliccssmainless_n2122">2122</a></td>
416 <td class="code ">
416 <td class="code ">
417 <pre><span class="tab-escape"> </span><span class="tab-escape"> </span>&amp;:after {
417 <pre><span class="tab-escape"> </span><span class="tab-escape"> </span>&amp;:after {
418 </pre>
418 </pre>
419 </td>
419 </td>
420 </tr>
420 </tr>
421 <tr class="line unmod">
421 <tr class="line unmod">
422 <td id="rhodecodepubliccssmainless_o2124" class="lineno old"><a href="#rhodecodepubliccssmainless_o2124">2124</a></td>
422 <td id="rhodecodepubliccssmainless_o2124" class="lineno old"><a href="#rhodecodepubliccssmainless_o2124">2124</a></td>
423 <td id="rhodecodepubliccssmainless_n2123" class="lineno new"><a href="#rhodecodepubliccssmainless_n2123">2123</a></td>
423 <td id="rhodecodepubliccssmainless_n2123" class="lineno new"><a href="#rhodecodepubliccssmainless_n2123">2123</a></td>
424 <td class="code ">
424 <td class="code ">
425 <pre><span class="tab-escape"> </span><span class="tab-escape"> </span><span class="tab-escape"> </span>content: "\00A0\25BE";
425 <pre><span class="tab-escape"> </span><span class="tab-escape"> </span><span class="tab-escape"> </span>content: "\00A0\25BE";
426 </pre>
426 </pre>
427 </td>
427 </td>
428 </tr>
428 </tr>
429 </tbody></table>
429 </tbody></table>
430 </div>
430 </div>
431 </div>
431 </div>
432 </div>
432 </div>
433
433
434 </td>
434 </td>
435 </tr>
435 </tr>
436 </tbody></table>
436 </tbody></table>
437 </div>
437 </div>
438
438
439
439
440
440
441
441
442
442
443
443
444
444
445
445
446
446
447 <!--
447 <!--
448 File View
448 File View
449 -->
449 -->
450
450
451 ##TODO: lisa: I believe this needs to be updated as the layout has changed.
451 ##TODO: lisa: I believe this needs to be updated as the layout has changed.
452 <h2>File View</h2>
452 <h2>File View</h2>
453
453
454 <div class="codeblock">
454 <div class="codeblock">
455 <div class="code-header">
455 <div class="code-header">
456 <div class="stats">
456 <div class="stats">
457 <div class="img">
457 <div class="img">
458 <i class="icon-file"></i>
458 <i class="icon-file"></i>
459 <span class="revision_id item"><a href="/example/changeset/fc252256eb0fcb4f2613e66f0126ea27967ae28c">r5487:fc252256eb0f</a></span>
459 <span class="revision_id item"><a href="/example/changeset/fc252256eb0fcb4f2613e66f0126ea27967ae28c">r5487:fc252256eb0f</a></span>
460 <span>1.2 KiB</span>
460 <span>1.2 KiB</span>
461 <span class="item last">text/x-python</span>
461 <span class="item last">text/x-python</span>
462 <div class="buttons">
462 <div class="buttons">
463
463
464 <a id="file_history_overview" class="btn btn-mini" href="#">
464 <a id="file_history_overview" class="btn btn-mini" href="#">
465 <i class="icon-time"></i> history
465 <i class="icon-time"></i> history
466 </a>
466 </a>
467 <a id="file_history_overview_full" class="btn btn-mini" style="display: none" href="/example/changelog/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">
467 <a id="file_history_overview_full" class="btn btn-mini" style="display: none" href="/example/changelog/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">
468 <i class="icon-time"></i> show full history
468 <i class="icon-time"></i> show full history
469 </a>
469 </a>
470 <a class="btn btn-mini" href="/example/annotate/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">annotation</a>
470 <a class="btn btn-mini" href="/example/annotate/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">annotation</a>
471 <a class="btn btn-mini" href="/example/raw/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">raw</a>
471 <a class="btn btn-mini" href="/example/raw/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">raw</a>
472 <a class="btn btn-mini" href="/example/rawfile/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">
472 <a class="btn btn-mini" href="/example/rawfile/fc252256eb0fcb4f2613e66f0126ea27967ae28c/rhodecode/websetup.py">
473 <i class="icon-archive"></i> download
473 <i class="icon-archive"></i> download
474 </a>
474 </a>
475
475
476 <a class="btn btn-mini disabled tooltip" href="#" title="Editing files allowed only when on branch head commit">edit</a>
476 <a class="btn btn-mini disabled tooltip" href="#" title="Editing files allowed only when on branch head commit">edit</a>
477 <a class="btn btn-mini btn-danger disabled tooltip" href="#" title="Deleting files allowed only when on branch head commit">delete</a>
477 <a class="btn btn-mini btn-danger disabled tooltip" href="#" title="Deleting files allowed only when on branch head commit">delete</a>
478 </div>
478 </div>
479 </div>
479 </div>
480 </div>
480 </div>
481 <div id="file_history_container"></div>
481 <div id="file_history_container"></div>
482 <div class="author">
482 <div class="author">
483 <div class="gravatar">
483 <div class="gravatar">
484 <img alt="gravatar" src="https://secure.gravatar.com/avatar/99e27b99c64003ca8c9875c9e3843495?d=identicon&amp;s=32" height="16" width="16">
484 <img alt="gravatar" src="https://secure.gravatar.com/avatar/99e27b99c64003ca8c9875c9e3843495?d=identicon&amp;s=32" height="16" width="16">
485 </div>
485 </div>
486 <div title="Marcin Kuzminski <marcin@python-works.com>" class="user">Marcin Kuzminski - <span class="tooltip" title="Wed, 02 Jul 2014 08:48:15">6m and 12d ago</span></div>
486 <div title="Marcin Kuzminski <marcin@python-works.com>" class="user">Marcin Kuzminski - <span class="tooltip" title="Wed, 02 Jul 2014 08:48:15">6m and 12d ago</span></div>
487 </div>
487 </div>
488 <div id="trimmed_message_box" class="commit">License changes</div>
488 <div id="trimmed_message_box" class="commit">License changes</div>
489 <div id="message_expand" style="display: none;">
489 <div id="message_expand" style="display: none;">
490 <i class="icon-resize-vertical"></i>
490 <i class="icon-resize-vertical"></i>
491 expand
491 expand
492 <i class="icon-resize-vertical"></i>
492 <i class="icon-resize-vertical"></i>
493 </div>
493 </div>
494 </div>
494 </div>
495 <div class="code-body">
495 <div class="code-body">
496 <table class="code-highlighttable"><tbody><tr><td class="linenos"><div class="linenodiv"><pre><a href="#L1"> 1</a>
496 <table class="code-highlighttable"><tbody><tr><td class="linenos"><div class="linenodiv"><pre><a href="#L1"> 1</a>
497 <a href="#L2"> 2</a>
497 <a href="#L2"> 2</a>
498 <a href="#L3"> 3</a>
498 <a href="#L3"> 3</a>
499 <a href="#L4"> 4</a>
499 <a href="#L4"> 4</a>
500 <a href="#L5"> 5</a>
500 <a href="#L5"> 5</a>
501 <a href="#L6"> 6</a>
501 <a href="#L6"> 6</a>
502 <a href="#L7"> 7</a>
502 <a href="#L7"> 7</a>
503 <a href="#L8"> 8</a>
503 <a href="#L8"> 8</a>
504 <a href="#L9"> 9</a>
504 <a href="#L9"> 9</a>
505 <a href="#L10">10</a>
505 <a href="#L10">10</a>
506 <a href="#L11">11</a>
506 <a href="#L11">11</a>
507 <a href="#L12">12</a>
507 <a href="#L12">12</a>
508 <a href="#L13">13</a>
508 <a href="#L13">13</a>
509 <a href="#L14">14</a>
509 <a href="#L14">14</a>
510 <a href="#L15">15</a>
510 <a href="#L15">15</a>
511 <a href="#L16">16</a>
511 <a href="#L16">16</a>
512 <a href="#L17">17</a>
512 <a href="#L17">17</a>
513 <a href="#L18">18</a>
513 <a href="#L18">18</a>
514 <a href="#L19">19</a>
514 <a href="#L19">19</a>
515 <a href="#L20">20</a>
515 <a href="#L20">20</a>
516 <a href="#L21">21</a>
516 <a href="#L21">21</a>
517 <a href="#L22">22</a>
517 <a href="#L22">22</a>
518 <a href="#L23">23</a>
518 <a href="#L23">23</a>
519 <a href="#L24">24</a>
519 <a href="#L24">24</a>
520 <a href="#L25">25</a>
520 <a href="#L25">25</a>
521 <a href="#L26">26</a>
521 <a href="#L26">26</a>
522 <a href="#L27">27</a>
522 <a href="#L27">27</a>
523 <a href="#L28">28</a>
523 <a href="#L28">28</a>
524 <a href="#L29">29</a>
524 <a href="#L29">29</a>
525 <a href="#L30">30</a>
525 <a href="#L30">30</a>
526 <a href="#L31">31</a>
526 <a href="#L31">31</a>
527 <a href="#L32">32</a>
527 <a href="#L32">32</a>
528 <a href="#L33">33</a>
528 <a href="#L33">33</a>
529 <a href="#L34">34</a>
529 <a href="#L34">34</a>
530 <a href="#L35">35</a>
530 <a href="#L35">35</a>
531 <a href="#L36">36</a>
531 <a href="#L36">36</a>
532 <a href="#L37">37</a>
532 <a href="#L37">37</a>
533 <a href="#L38">38</a>
533 <a href="#L38">38</a>
534 <a href="#L39">39</a>
534 <a href="#L39">39</a>
535 <a href="#L40">40</a>
535 <a href="#L40">40</a>
536 <a href="#L41">41</a>
536 <a href="#L41">41</a>
537 <a href="#L42">42</a></pre></div></td><td id="hlcode" class="code"><div class="code-highlight"><pre><div id="L1"><a name="L-1"></a><span class="c"># -*- coding: utf-8 -*-</span>
537 <a href="#L42">42</a></pre></div></td><td id="hlcode" class="code"><div class="code-highlight"><pre><div id="L1"><a name="L-1"></a><span class="c"># -*- coding: utf-8 -*-</span>
538 </div><div id="L2"><a name="L-2"></a>
538 </div><div id="L2"><a name="L-2"></a>
539 </div><div id="L3"><a name="L-3"></a><span class="c"># Published under Business Source License.</span>
539 </div><div id="L3"><a name="L-3"></a><span class="c"># Published under Business Source License.</span>
540 </div><div id="L4"><a name="L-4"></a><span class="c"># Read the full license text at https://rhodecode.com/licenses.</span>
540 </div><div id="L4"><a name="L-4"></a><span class="c"># Read the full license text at https://rhodecode.com/licenses.</span>
541 </div><div id="L5"><a name="L-5"></a><span class="sd">"""</span>
541 </div><div id="L5"><a name="L-5"></a><span class="sd">"""</span>
542 </div><div id="L6"><a name="L-6"></a><span class="sd">rhodecode.websetup</span>
542 </div><div id="L6"><a name="L-6"></a><span class="sd">rhodecode.websetup</span>
543 </div><div id="L7"><a name="L-7"></a><span class="sd">~~~~~~~~~~~~~~~~~~</span>
543 </div><div id="L7"><a name="L-7"></a><span class="sd">~~~~~~~~~~~~~~~~~~</span>
544 </div><div id="L8"><a name="L-8"></a>
544 </div><div id="L8"><a name="L-8"></a>
545 </div><div id="L9"><a name="L-9"></a><span class="sd">Weboperations and setup for rhodecode. Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.</span>
545 </div><div id="L9"><a name="L-9"></a><span class="sd">Weboperations and setup for rhodecode. Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.</span>
546 </div><div id="L10"><a name="L-10"></a>
546 </div><div id="L10"><a name="L-10"></a>
547 </div><div id="L11"><a name="L-11"></a><span class="sd">:created_on: Dec 11, 2010</span>
547 </div><div id="L11"><a name="L-11"></a><span class="sd">:created_on: Dec 11, 2010</span>
548 </div><div id="L12"><a name="L-12"></a><span class="sd">:author: marcink</span>
548 </div><div id="L12"><a name="L-12"></a><span class="sd">:author: marcink</span>
549 </div><div id="L13"><a name="L-13"></a><span class="sd">:copyright: (c) 2013-2015 RhodeCode GmbH.</span>
549 </div><div id="L13"><a name="L-13"></a><span class="sd">:copyright: (c) 2013-2015 RhodeCode GmbH.</span>
550 </div><div id="L14"><a name="L-14"></a><span class="sd">:license: Business Source License, see LICENSE for more details.</span>
550 </div><div id="L14"><a name="L-14"></a><span class="sd">:license: Business Source License, see LICENSE for more details.</span>
551 </div><div id="L15"><a name="L-15"></a><span class="sd">"""</span>
551 </div><div id="L15"><a name="L-15"></a><span class="sd">"""</span>
552 </div><div id="L16"><a name="L-16"></a>
552 </div><div id="L16"><a name="L-16"></a>
553 </div><div id="L17"><a name="L-17"></a><span class="kn">import</span> <span class="nn">logging</span>
553 </div><div id="L17"><a name="L-17"></a><span class="kn">import</span> <span class="nn">logging</span>
554 </div><div id="L18"><a name="L-18"></a>
554 </div><div id="L18"><a name="L-18"></a>
555 </div><div id="L19"><a name="L-19"></a><span class="kn">from</span> <span class="nn">rhodecode.config.environment</span> <span class="kn">import</span> <span class="n">load_environment</span>
555 </div><div id="L19"><a name="L-19"></a><span class="kn">from</span> <span class="nn">rhodecode.config.environment</span> <span class="kn">import</span> <span class="n">load_environment</span>
556 </div><div id="L20"><a name="L-20"></a><span class="kn">from</span> <span class="nn">rhodecode.lib.db_manage</span> <span class="kn">import</span> <span class="n">DbManage</span>
556 </div><div id="L20"><a name="L-20"></a><span class="kn">from</span> <span class="nn">rhodecode.lib.db_manage</span> <span class="kn">import</span> <span class="n">DbManage</span>
557 </div><div id="L21"><a name="L-21"></a><span class="kn">from</span> <span class="nn">rhodecode.model.meta</span> <span class="kn">import</span> <span class="n">Session</span>
557 </div><div id="L21"><a name="L-21"></a><span class="kn">from</span> <span class="nn">rhodecode.model.meta</span> <span class="kn">import</span> <span class="n">Session</span>
558 </div><div id="L22"><a name="L-22"></a>
558 </div><div id="L22"><a name="L-22"></a>
559 </div><div id="L23"><a name="L-23"></a>
559 </div><div id="L23"><a name="L-23"></a>
560 </div><div id="L24"><a name="L-24"></a><span class="n">log</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>
560 </div><div id="L24"><a name="L-24"></a><span class="n">log</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>
561 </div><div id="L25"><a name="L-25"></a>
561 </div><div id="L25"><a name="L-25"></a>
562 </div><div id="L26"><a name="L-26"></a>
562 </div><div id="L26"><a name="L-26"></a>
563 </div><div id="L27"><a name="L-27"></a><span class="k">def</span> <span class="nf">setup_app</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">conf</span><span class="p">,</span> <span class="nb">vars</span><span class="p">):</span>
563 </div><div id="L27"><a name="L-27"></a><span class="k">def</span> <span class="nf">setup_app</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">conf</span><span class="p">,</span> <span class="nb">vars</span><span class="p">):</span>
564 </div><div id="L28"><a name="L-28"></a> <span class="sd">"""Place any commands to setup rhodecode here"""</span>
564 </div><div id="L28"><a name="L-28"></a> <span class="sd">"""Place any commands to setup rhodecode here"""</span>
565 </div><div id="L29"><a name="L-29"></a> <span class="n">dbconf</span> <span class="o">=</span> <span class="n">conf</span><span class="p">[</span><span class="s">'sqlalchemy.db1.url'</span><span class="p">]</span>
565 </div><div id="L29"><a name="L-29"></a> <span class="n">dbconf</span> <span class="o">=</span> <span class="n">conf</span><span class="p">[</span><span class="s">'sqlalchemy.db1.url'</span><span class="p">]</span>
566 </div><div id="L30"><a name="L-30"></a> <span class="n">dbmanage</span> <span class="o">=</span> <span class="n">DbManage</span><span class="p">(</span><span class="n">log_sql</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">dbconf</span><span class="o">=</span><span class="n">dbconf</span><span class="p">,</span> <span class="n">root</span><span class="o">=</span><span class="n">conf</span><span class="p">[</span><span class="s">'here'</span><span class="p">],</span>
566 </div><div id="L30"><a name="L-30"></a> <span class="n">dbmanage</span> <span class="o">=</span> <span class="n">DbManage</span><span class="p">(</span><span class="n">log_sql</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">dbconf</span><span class="o">=</span><span class="n">dbconf</span><span class="p">,</span> <span class="n">root</span><span class="o">=</span><span class="n">conf</span><span class="p">[</span><span class="s">'here'</span><span class="p">],</span>
567 </div><div id="L31"><a name="L-31"></a> <span class="n">tests</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">cli_args</span><span class="o">=</span><span class="n">command</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">__dict__</span><span class="p">)</span>
567 </div><div id="L31"><a name="L-31"></a> <span class="n">tests</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">cli_args</span><span class="o">=</span><span class="n">command</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">__dict__</span><span class="p">)</span>
568 </div><div id="L32"><a name="L-32"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_tables</span><span class="p">(</span><span class="n">override</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
568 </div><div id="L32"><a name="L-32"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_tables</span><span class="p">(</span><span class="n">override</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
569 </div><div id="L33"><a name="L-33"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">set_db_version</span><span class="p">()</span>
569 </div><div id="L33"><a name="L-33"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">set_db_version</span><span class="p">()</span>
570 </div><div id="L34"><a name="L-34"></a> <span class="n">opts</span> <span class="o">=</span> <span class="n">dbmanage</span><span class="o">.</span><span class="n">config_prompt</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>
570 </div><div id="L34"><a name="L-34"></a> <span class="n">opts</span> <span class="o">=</span> <span class="n">dbmanage</span><span class="o">.</span><span class="n">config_prompt</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>
571 </div><div id="L35"><a name="L-35"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_settings</span><span class="p">(</span><span class="n">opts</span><span class="p">)</span>
571 </div><div id="L35"><a name="L-35"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_settings</span><span class="p">(</span><span class="n">opts</span><span class="p">)</span>
572 </div><div id="L36"><a name="L-36"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_default_user</span><span class="p">()</span>
572 </div><div id="L36"><a name="L-36"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_default_user</span><span class="p">()</span>
573 </div><div id="L37"><a name="L-37"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">admin_prompt</span><span class="p">()</span>
573 </div><div id="L37"><a name="L-37"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">admin_prompt</span><span class="p">()</span>
574 </div><div id="L38"><a name="L-38"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_permissions</span><span class="p">()</span>
574 </div><div id="L38"><a name="L-38"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">create_permissions</span><span class="p">()</span>
575 </div><div id="L39"><a name="L-39"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">populate_default_permissions</span><span class="p">()</span>
575 </div><div id="L39"><a name="L-39"></a> <span class="n">dbmanage</span><span class="o">.</span><span class="n">populate_default_permissions</span><span class="p">()</span>
576 </div><div id="L40"><a name="L-40"></a> <span class="n">Session</span><span class="p">()</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
576 </div><div id="L40"><a name="L-40"></a> <span class="n">Session</span><span class="p">()</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
577 </div><div id="L41"><a name="L-41"></a> <span class="n">load_environment</span><span class="p">(</span><span class="n">conf</span><span class="o">.</span><span class="n">global_conf</span><span class="p">,</span> <span class="n">conf</span><span class="o">.</span><span class="n">local_conf</span><span class="p">,</span> <span class="n">initial</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
577 </div><div id="L41"><a name="L-41"></a> <span class="n">load_environment</span><span class="p">(</span><span class="n">conf</span><span class="o">.</span><span class="n">global_conf</span><span class="p">,</span> <span class="n">conf</span><span class="o">.</span><span class="n">local_conf</span><span class="p">,</span> <span class="n">initial</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
578 </div><div id="L42"><a name="L-42"></a> <span class="n">DbManage</span><span class="o">.</span><span class="n">check_waitress</span><span class="p">()</span>
578 </div><div id="L42"><a name="L-42"></a> <span class="n">DbManage</span><span class="o">.</span><span class="n">check_waitress</span><span class="p">()</span>
579 </div></pre></div>
579 </div></pre></div>
580 </td></tr></tbody></table>
580 </td></tr></tbody></table>
581 </div>
581 </div>
582 </div>
582 </div>
583
583
584
584
585
585
586
586
587
587
588
588
589
589
590
590
591
591
592 <!--
592 <!--
593 Gist Edit
593 Gist Edit
594 -->
594 -->
595
595
596
596
597 <h2>Gist Edit</h2>
597 <h2>Gist Edit</h2>
598
598
599 <div class="codeblock">
599 <div class="codeblock">
600 <div class="code-header">
600 <div class="code-header">
601 <div class="form">
601 <div class="form">
602 <div class="fields">
602 <div class="fields">
603 <input id="filename" name="filename" placeholder="name this file..." size="30" type="text">
603 <input id="filename" name="filename" placeholder="name this file..." size="30" type="text">
604 <div class="select2-container drop-menu" id="s2id_mimetype"><a href="javascript:void(0)" class="select2-choice" tabindex="-1"> <span class="select2-chosen" id="select2-chosen-3">Python</span><abbr class="select2-search-choice-close"></abbr> <span class="select2-arrow" role="presentation"><b role="presentation"></b></span></a><label for="s2id_autogen3" class="select2-offscreen"></label><input class="select2-focusser select2-offscreen" type="text" aria-haspopup="true" role="button" aria-labelledby="select2-chosen-3" id="s2id_autogen3"><div class="select2-drop select2-display-none drop-menu-dropdown select2-with-searchbox"> <div class="select2-search"> <label for="s2id_autogen3_search" class="select2-offscreen"></label> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input" role="combobox" aria-expanded="true" aria-autocomplete="list" aria-owns="select2-results-3" id="s2id_autogen3_search" placeholder=""> </div> <ul class="select2-results" role="listbox" id="select2-results-3"> </ul></div></div><select id="mimetype" name="mimetype" tabindex="-1" title="" style="display: none;">
604 <div class="select2-container drop-menu" id="s2id_mimetype"><a href="javascript:void(0)" class="select2-choice" tabindex="-1"> <span class="select2-chosen" id="select2-chosen-3">Python</span><abbr class="select2-search-choice-close"></abbr> <span class="select2-arrow" role="presentation"><b role="presentation"></b></span></a><label for="s2id_autogen3" class="select2-offscreen"></label><input class="select2-focusser select2-offscreen" type="text" aria-haspopup="true" role="button" aria-labelledby="select2-chosen-3" id="s2id_autogen3"><div class="select2-drop select2-display-none drop-menu-dropdown select2-with-searchbox"> <div class="select2-search"> <label for="s2id_autogen3_search" class="select2-offscreen"></label> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input" role="combobox" aria-expanded="true" aria-autocomplete="list" aria-owns="select2-results-3" id="s2id_autogen3_search" placeholder=""> </div> <ul class="select2-results" role="listbox" id="select2-results-3"> </ul></div></div><select id="mimetype" name="mimetype" tabindex="-1" title="" style="display: none;">
605 <option selected="selected" value="plain">plain</option>
605 <option selected="selected" value="plain">plain</option>
606 <option value="text/apl" mode="apl">APL</option><option value="text/x-asterisk" mode="asterisk">Asterisk</option><option value="text/x-csrc" mode="clike">C</option><option value="text/x-c++src" mode="clike">C++</option><option value="text/x-cobol" mode="cobol">Cobol</option><option value="text/x-java" mode="clike">Java</option><option value="text/x-csharp" mode="clike">C#</option><option value="text/x-scala" mode="clike">Scala</option><option value="text/x-clojure" mode="clojure">Clojure</option><option value="text/x-coffeescript" mode="coffeescript">CoffeeScript</option><option value="text/x-common-lisp" mode="commonlisp">Common Lisp</option><option value="text/css" mode="css">CSS</option><option value="text/x-d" mode="d">D</option><option value="text/x-diff" mode="diff">diff</option><option value="application/xml-dtd" mode="dtd">DTD</option><option value="text/x-dylan" mode="dylan">Dylan</option><option value="text/x-ecl" mode="ecl">ECL</option><option value="text/x-eiffel" mode="eiffel">Eiffel</option><option value="text/x-erlang" mode="erlang">Erlang</option><option value="text/x-fortran" mode="fortran">Fortran</option><option value="text/x-fsharp" mode="mllike">F#</option><option value="text/x-gas" mode="gas">Gas</option><option value="text/x-go" mode="go">GO</option><option value="text/x-feature" mode="gherkin">Gherkin</option><option value="text/x-go" mode="go">Go</option><option value="text/x-groovy" mode="groovy">Groovy</option><option value="text/x-haml" mode="haml">HAML</option><option value="text/x-haskell" mode="haskell">Haskell</option><option value="text/x-haxe" mode="haxe">Haxe</option><option value="application/x-aspx" mode="htmlembedded">ASP.NET</option><option value="application/x-ejs" mode="htmlembedded">Embedded Javascript</option><option value="application/x-jsp" mode="htmlembedded">JavaServer Pages</option><option value="text/html" mode="htmlmixed">HTML</option><option value="message/http" mode="http">HTTP</option><option value="text/x-jade" mode="jade">Jade</option><option value="text/javascript" mode="javascript">JavaScript</option><option value="application/json" mode="javascript">JSON</option><option value="application/typescript" mode="javascript">TypeScript</option><option value="jinja2" mode="jinja2">Jinja2</option><option value="text/x-julia" mode="julia">Julia</option><option value="text/x-less" mode="less">LESS</option><option value="text/x-livescript" mode="livescript">LiveScript</option><option value="text/x-lua" mode="lua">Lua</option><option value="text/x-markdown" mode="markdown">Markdown (GitHub-flavour)</option><option value="text/mirc" mode="mirc">mIRC</option><option value="text/x-nginx-conf" mode="nginx">Nginx</option><option value="text/n-triples" mode="ntriples">NTriples</option><option value="text/x-ocaml" mode="ocaml">OCaml</option><option value="text/x-ocaml" mode="mllike">OCaml</option><option value="text/x-octave" mode="octave">Octave</option><option value="text/x-pascal" mode="pascal">Pascal</option><option value="null" mode="pegjs">PEG.js</option><option value="text/x-perl" mode="perl">Perl</option><option value="text/x-php" mode="php">PHP</option><option value="text/x-pig" mode="pig">Pig</option><option value="text/plain" mode="null">Plain Text</option><option value="text/x-properties" mode="properties">Properties files</option><option value="text/x-python" mode="python">Python</option><option value="text/x-puppet" mode="puppet">Puppet</option><option value="text/x-rsrc" mode="r">R</option><option value="text/x-rst" mode="rst">reStructuredText</option><option value="text/x-ruby" mode="ruby">Ruby</option><option value="text/x-rustsrc" mode="rust">Rust</option><option value="text/x-sass" mode="sass">Sass</option><option value="text/x-scheme" mode="scheme">Scheme</option><option value="text/x-scss" mode="css">SCSS</option><option value="text/x-sh" mode="shell">Shell</option><option value="application/sieve" mode="sieve">Sieve</option><option value="text/x-stsrc" mode="smalltalk">Smalltalk</option><option value="text/x-smarty" mode="smarty">Smarty</option><option value="text/x-smarty" mode="smartymixed">SmartyMixed</option><option value="text/x-solr" mode="solr">Solr</option><option value="application/x-sparql-query" mode="sparql">SPARQL</option><option value="text/x-sql" mode="sql">SQL</option><option value="text/x-mariadb" mode="sql">MariaDB</option><option value="text/x-stex" mode="stex">sTeX</option><option value="text/x-latex" mode="stex">LaTeX</option><option value="text/x-systemverilog" mode="verilog">SystemVerilog</option><option value="text/x-tcl" mode="tcl">Tcl</option><option value="text/x-tiddlywiki" mode="tiddlywiki">TiddlyWiki </option><option value="text/tiki" mode="tiki">Tiki wiki</option><option value="text/x-toml" mode="toml">TOML</option><option value="text/turtle" mode="turtle">Turtle</option><option value="text/x-vb" mode="vb">VB.NET</option><option value="text/vbscript" mode="vbscript">VBScript</option><option value="text/velocity" mode="velocity">Velocity</option><option value="text/x-verilog" mode="verilog">Verilog</option><option value="application/xml" mode="xml">XML</option><option value="text/html" mode="xml">HTML</option><option value="application/xquery" mode="xquery">XQuery</option><option value="text/x-yaml" mode="yaml">YAML</option><option value="text/x-z80" mode="z80">Z80</option></select>
606 <option value="text/apl" mode="apl">APL</option><option value="text/x-asterisk" mode="asterisk">Asterisk</option><option value="text/x-csrc" mode="clike">C</option><option value="text/x-c++src" mode="clike">C++</option><option value="text/x-cobol" mode="cobol">Cobol</option><option value="text/x-java" mode="clike">Java</option><option value="text/x-csharp" mode="clike">C#</option><option value="text/x-scala" mode="clike">Scala</option><option value="text/x-clojure" mode="clojure">Clojure</option><option value="text/x-coffeescript" mode="coffeescript">CoffeeScript</option><option value="text/x-common-lisp" mode="commonlisp">Common Lisp</option><option value="text/css" mode="css">CSS</option><option value="text/x-d" mode="d">D</option><option value="text/x-diff" mode="diff">diff</option><option value="application/xml-dtd" mode="dtd">DTD</option><option value="text/x-dylan" mode="dylan">Dylan</option><option value="text/x-ecl" mode="ecl">ECL</option><option value="text/x-eiffel" mode="eiffel">Eiffel</option><option value="text/x-erlang" mode="erlang">Erlang</option><option value="text/x-fortran" mode="fortran">Fortran</option><option value="text/x-fsharp" mode="mllike">F#</option><option value="text/x-gas" mode="gas">Gas</option><option value="text/x-go" mode="go">GO</option><option value="text/x-feature" mode="gherkin">Gherkin</option><option value="text/x-go" mode="go">Go</option><option value="text/x-groovy" mode="groovy">Groovy</option><option value="text/x-haml" mode="haml">HAML</option><option value="text/x-haskell" mode="haskell">Haskell</option><option value="text/x-haxe" mode="haxe">Haxe</option><option value="application/x-aspx" mode="htmlembedded">ASP.NET</option><option value="application/x-ejs" mode="htmlembedded">Embedded Javascript</option><option value="application/x-jsp" mode="htmlembedded">JavaServer Pages</option><option value="text/html" mode="htmlmixed">HTML</option><option value="message/http" mode="http">HTTP</option><option value="text/x-jade" mode="jade">Jade</option><option value="text/javascript" mode="javascript">JavaScript</option><option value="application/json" mode="javascript">JSON</option><option value="application/typescript" mode="javascript">TypeScript</option><option value="jinja2" mode="jinja2">Jinja2</option><option value="text/x-julia" mode="julia">Julia</option><option value="text/x-less" mode="less">LESS</option><option value="text/x-livescript" mode="livescript">LiveScript</option><option value="text/x-lua" mode="lua">Lua</option><option value="text/x-markdown" mode="markdown">Markdown (GitHub-flavour)</option><option value="text/mirc" mode="mirc">mIRC</option><option value="text/x-nginx-conf" mode="nginx">Nginx</option><option value="text/n-triples" mode="ntriples">NTriples</option><option value="text/x-ocaml" mode="ocaml">OCaml</option><option value="text/x-ocaml" mode="mllike">OCaml</option><option value="text/x-octave" mode="octave">Octave</option><option value="text/x-pascal" mode="pascal">Pascal</option><option value="null" mode="pegjs">PEG.js</option><option value="text/x-perl" mode="perl">Perl</option><option value="text/x-php" mode="php">PHP</option><option value="text/x-pig" mode="pig">Pig</option><option value="text/plain" mode="null">Plain Text</option><option value="text/x-properties" mode="properties">Properties files</option><option value="text/x-python" mode="python">Python</option><option value="text/x-puppet" mode="puppet">Puppet</option><option value="text/x-rsrc" mode="r">R</option><option value="text/x-rst" mode="rst">reStructuredText</option><option value="text/x-ruby" mode="ruby">Ruby</option><option value="text/x-rustsrc" mode="rust">Rust</option><option value="text/x-sass" mode="sass">Sass</option><option value="text/x-scheme" mode="scheme">Scheme</option><option value="text/x-scss" mode="css">SCSS</option><option value="text/x-sh" mode="shell">Shell</option><option value="application/sieve" mode="sieve">Sieve</option><option value="text/x-stsrc" mode="smalltalk">Smalltalk</option><option value="text/x-smarty" mode="smarty">Smarty</option><option value="text/x-smarty" mode="smartymixed">SmartyMixed</option><option value="text/x-solr" mode="solr">Solr</option><option value="application/x-sparql-query" mode="sparql">SPARQL</option><option value="text/x-sql" mode="sql">SQL</option><option value="text/x-mariadb" mode="sql">MariaDB</option><option value="text/x-stex" mode="stex">sTeX</option><option value="text/x-latex" mode="stex">LaTeX</option><option value="text/x-systemverilog" mode="verilog">SystemVerilog</option><option value="text/x-tcl" mode="tcl">Tcl</option><option value="text/x-tiddlywiki" mode="tiddlywiki">TiddlyWiki </option><option value="text/tiki" mode="tiki">Tiki wiki</option><option value="text/x-toml" mode="toml">TOML</option><option value="text/turtle" mode="turtle">Turtle</option><option value="text/x-vb" mode="vb">VB.NET</option><option value="text/vbscript" mode="vbscript">VBScript</option><option value="text/velocity" mode="velocity">Velocity</option><option value="text/x-verilog" mode="verilog">Verilog</option><option value="application/xml" mode="xml">XML</option><option value="text/html" mode="xml">HTML</option><option value="application/xquery" mode="xquery">XQuery</option><option value="text/x-yaml" mode="yaml">YAML</option><option value="text/x-z80" mode="z80">Z80</option></select>
607 <script>
607 <script>
608 $(document).ready(function() {
608 $(document).ready(function() {
609 $('#mimetype').select2({
609 $('#mimetype').select2({
610 containerCssClass: 'drop-menu',
610 containerCssClass: 'drop-menu',
611 dropdownCssClass: 'drop-menu-dropdown',
611 dropdownCssClass: 'drop-menu-dropdown',
612 dropdownAutoWidth: true
612 dropdownAutoWidth: true
613 });
613 });
614 });
614 });
615 </script>
615 </script>
616
616
617 </div>
617 </div>
618 </div>
618 </div>
619 </div>
619 </div>
620 <div id="editor_container">
620 <div id="editor_container">
621 <div id="editor_pre"></div>
621 <div id="editor_pre"></div>
622 <textarea id="editor" name="content" style="display: none;"></textarea><div class="CodeMirror cm-s-default"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 484px; left: 219.4091796875px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" style="position: absolute; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div class="CodeMirror-hscrollbar" style="left: 29px; min-height: 18px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar" style="min-width: 18px; display: block; bottom: 0px;"><div style="min-width: 1px; height: 619px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="min-width: 700.269653320313px; margin-left: 29px; min-height: 619px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><div class="CodeMirror-linenumber CodeMirror-gutter-elt"><div>47</div></div></div><div style="position: relative; z-index: 1; display: none;"></div><div class="CodeMirror-code"><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">1</div></div><pre><span class="cm-keyword">import</span> <span class="cm-variable">re</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">2</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">3</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">django</span>.<span class="cm-variable">utils</span>.<span class="cm-variable">text</span> <span class="cm-keyword">import</span> <span class="cm-variable">compress_sequence</span>, <span class="cm-variable">compress_string</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">4</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">django</span>.<span class="cm-variable">utils</span>.<span class="cm-variable">cache</span> <span class="cm-keyword">import</span> <span class="cm-variable">patch_vary_headers</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">5</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">6</div></div><pre><span class="cm-variable">re_accepts_gzip</span> = <span class="cm-variable">re</span>.<span class="cm-builtin">compile</span>(<span class="cm-string">r'\bgzip\b'</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">7</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">8</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">9</div></div><pre><span class="cm-keyword">class</span> <span class="cm-def">GZipMiddleware</span>(<span class="cm-builtin">object</span>): # Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">10</div></div><pre> <span class="cm-string">"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">11</div></div><pre><span class="cm-string"> This middleware compresses content if the browser allows gzip compression.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">12</div></div><pre><span class="cm-string"> It sets the Vary header accordingly, so that caches will base their storage</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">13</div></div><pre><span class="cm-string"> on the Accept-Encoding header.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">14</div></div><pre><span class="cm-string"> """</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">15</div></div><pre> <span class="cm-keyword">def</span> <span class="cm-def">process_response</span>(<span class="cm-variable-2">self</span>, <span class="cm-variable">request</span>, <span class="cm-variable">response</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">16</div></div><pre> <span class="cm-comment"># It's not worth attempting to compress really short responses.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">17</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-operator">not</span> <span class="cm-variable">response</span>.<span class="cm-variable">streaming</span> <span class="cm-operator">and</span> <span class="cm-builtin">len</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>) <span class="cm-operator">&lt;</span> <span class="cm-number">200</span>:</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">18</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">19</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">20</div></div><pre> <span class="cm-comment"># Avoid gzipping if we've already got a content-encoding.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">21</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-variable">response</span>.<span class="cm-variable">has_header</span>(<span class="cm-string">'Content-Encoding'</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">22</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">23</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">24</div></div><pre> <span class="cm-variable">patch_vary_headers</span>(<span class="cm-variable">response</span>, (<span class="cm-string">'Accept-Encoding'</span>,))</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">25</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">26</div></div><pre> <span class="cm-variable">ae</span> = <span class="cm-variable">request</span>.<span class="cm-variable">META</span>.<span class="cm-variable">get</span>(<span class="cm-string">'HTTP_ACCEPT_ENCODING'</span>, <span class="cm-string">''</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">27</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-operator">not</span> <span class="cm-variable">re_accepts_gzip</span>.<span class="cm-variable">search</span>(<span class="cm-variable">ae</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">28</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">29</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">30</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-variable">response</span>.<span class="cm-variable">streaming</span>:</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">31</div></div><pre> <span class="cm-comment"># Delete the `Content-Length` header for streaming content, because</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">32</div></div><pre> <span class="cm-comment"># we won't know the compressed size until we stream it.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">33</div></div><pre> <span class="cm-variable">response</span>.<span class="cm-variable">streaming_content</span> = <span class="cm-variable">compress_sequence</span>(<span class="cm-variable">response</span>.<span class="cm-variable">streaming_content</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">34</div></div><pre> <span class="cm-keyword">del</span> <span class="cm-variable">response</span>[<span class="cm-string">'Content-Length'</span>]</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">35</div></div><pre> <span class="cm-keyword">else</span>:</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">36</div></div><pre> <span class="cm-comment"># Return the compressed content only if it's actually shorter.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">37</div></div><pre> <span class="cm-variable">compressed_content</span> = <span class="cm-variable">compress_string</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">38</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-builtin">len</span>(<span class="cm-variable">compressed_content</span>) <span class="cm-operator">&gt;=</span> <span class="cm-builtin">len</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">39</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">40</div></div><pre> <span class="cm-variable">response</span>.<span class="cm-variable">content</span> = <span class="cm-variable">compressed_content</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">41</div></div><pre> <span class="cm-variable">response</span>[<span class="cm-string">'Content-Length'</span>] = <span class="cm-builtin">str</span>(<span class="cm-builtin">len</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>))</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">42</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">43</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-variable">response</span>.<span class="cm-variable">has_header</span>(<span class="cm-string">'ETag'</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">44</div></div><pre> <span class="cm-variable">response</span>[<span class="cm-string">'ETag'</span>] = <span class="cm-variable">re</span>.<span class="cm-variable">sub</span>(<span class="cm-string">'"$'</span>, <span class="cm-string">';gzip"'</span>, <span class="cm-variable">response</span>[<span class="cm-string">'ETag'</span>])</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">45</div></div><pre> <span class="cm-variable">response</span>[<span class="cm-string">'Content-Encoding'</span>] = <span class="cm-string">'gzip'</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">46</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">47</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div></div><div class="CodeMirror-cursor" style="left: 189.4091796875px; top: 598px; height: 13px;">&nbsp;</div><div class="CodeMirror-cursor CodeMirror-secondarycursor" style="display: none;">&nbsp;</div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 619px;"></div><div class="CodeMirror-gutters" style="height: 619px;"><div class="CodeMirror-gutter CodeMirror-linenumbers" style="width: 28px;"></div></div></div></div>
622 <textarea id="editor" name="content" style="display: none;"></textarea><div class="CodeMirror cm-s-default"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 484px; left: 219.4091796875px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" style="position: absolute; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div class="CodeMirror-hscrollbar" style="left: 29px; min-height: 18px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar" style="min-width: 18px; display: block; bottom: 0px;"><div style="min-width: 1px; height: 619px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="min-width: 700.269653320313px; margin-left: 29px; min-height: 619px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><div class="CodeMirror-linenumber CodeMirror-gutter-elt"><div>47</div></div></div><div style="position: relative; z-index: 1; display: none;"></div><div class="CodeMirror-code"><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">1</div></div><pre><span class="cm-keyword">import</span> <span class="cm-variable">re</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">2</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">3</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">django</span>.<span class="cm-variable">utils</span>.<span class="cm-variable">text</span> <span class="cm-keyword">import</span> <span class="cm-variable">compress_sequence</span>, <span class="cm-variable">compress_string</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">4</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">django</span>.<span class="cm-variable">utils</span>.<span class="cm-variable">cache</span> <span class="cm-keyword">import</span> <span class="cm-variable">patch_vary_headers</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">5</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">6</div></div><pre><span class="cm-variable">re_accepts_gzip</span> = <span class="cm-variable">re</span>.<span class="cm-builtin">compile</span>(<span class="cm-string">r'\bgzip\b'</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">7</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">8</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">9</div></div><pre><span class="cm-keyword">class</span> <span class="cm-def">GZipMiddleware</span>(<span class="cm-builtin">object</span>): # Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">10</div></div><pre> <span class="cm-string">"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">11</div></div><pre><span class="cm-string"> This middleware compresses content if the browser allows gzip compression.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">12</div></div><pre><span class="cm-string"> It sets the Vary header accordingly, so that caches will base their storage</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">13</div></div><pre><span class="cm-string"> on the Accept-Encoding header.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">14</div></div><pre><span class="cm-string"> """</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">15</div></div><pre> <span class="cm-keyword">def</span> <span class="cm-def">process_response</span>(<span class="cm-variable-2">self</span>, <span class="cm-variable">request</span>, <span class="cm-variable">response</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">16</div></div><pre> <span class="cm-comment"># It's not worth attempting to compress really short responses.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">17</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-operator">not</span> <span class="cm-variable">response</span>.<span class="cm-variable">streaming</span> <span class="cm-operator">and</span> <span class="cm-builtin">len</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>) <span class="cm-operator">&lt;</span> <span class="cm-number">200</span>:</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">18</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">19</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">20</div></div><pre> <span class="cm-comment"># Avoid gzipping if we've already got a content-encoding.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">21</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-variable">response</span>.<span class="cm-variable">has_header</span>(<span class="cm-string">'Content-Encoding'</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">22</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">23</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">24</div></div><pre> <span class="cm-variable">patch_vary_headers</span>(<span class="cm-variable">response</span>, (<span class="cm-string">'Accept-Encoding'</span>,))</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">25</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">26</div></div><pre> <span class="cm-variable">ae</span> = <span class="cm-variable">request</span>.<span class="cm-variable">META</span>.<span class="cm-variable">get</span>(<span class="cm-string">'HTTP_ACCEPT_ENCODING'</span>, <span class="cm-string">''</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">27</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-operator">not</span> <span class="cm-variable">re_accepts_gzip</span>.<span class="cm-variable">search</span>(<span class="cm-variable">ae</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">28</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">29</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">30</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-variable">response</span>.<span class="cm-variable">streaming</span>:</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">31</div></div><pre> <span class="cm-comment"># Delete the `Content-Length` header for streaming content, because</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">32</div></div><pre> <span class="cm-comment"># we won't know the compressed size until we stream it.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">33</div></div><pre> <span class="cm-variable">response</span>.<span class="cm-variable">streaming_content</span> = <span class="cm-variable">compress_sequence</span>(<span class="cm-variable">response</span>.<span class="cm-variable">streaming_content</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">34</div></div><pre> <span class="cm-keyword">del</span> <span class="cm-variable">response</span>[<span class="cm-string">'Content-Length'</span>]</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">35</div></div><pre> <span class="cm-keyword">else</span>:</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">36</div></div><pre> <span class="cm-comment"># Return the compressed content only if it's actually shorter.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">37</div></div><pre> <span class="cm-variable">compressed_content</span> = <span class="cm-variable">compress_string</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">38</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-builtin">len</span>(<span class="cm-variable">compressed_content</span>) <span class="cm-operator">&gt;=</span> <span class="cm-builtin">len</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">39</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">40</div></div><pre> <span class="cm-variable">response</span>.<span class="cm-variable">content</span> = <span class="cm-variable">compressed_content</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">41</div></div><pre> <span class="cm-variable">response</span>[<span class="cm-string">'Content-Length'</span>] = <span class="cm-builtin">str</span>(<span class="cm-builtin">len</span>(<span class="cm-variable">response</span>.<span class="cm-variable">content</span>))</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">42</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">43</div></div><pre> <span class="cm-keyword">if</span> <span class="cm-variable">response</span>.<span class="cm-variable">has_header</span>(<span class="cm-string">'ETag'</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">44</div></div><pre> <span class="cm-variable">response</span>[<span class="cm-string">'ETag'</span>] = <span class="cm-variable">re</span>.<span class="cm-variable">sub</span>(<span class="cm-string">'"$'</span>, <span class="cm-string">';gzip"'</span>, <span class="cm-variable">response</span>[<span class="cm-string">'ETag'</span>])</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">45</div></div><pre> <span class="cm-variable">response</span>[<span class="cm-string">'Content-Encoding'</span>] = <span class="cm-string">'gzip'</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">46</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">47</div></div><pre> <span class="cm-keyword">return</span> <span class="cm-variable">response</span></pre></div></div><div class="CodeMirror-cursor" style="left: 189.4091796875px; top: 598px; height: 13px;">&nbsp;</div><div class="CodeMirror-cursor CodeMirror-secondarycursor" style="display: none;">&nbsp;</div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 619px;"></div><div class="CodeMirror-gutters" style="height: 619px;"><div class="CodeMirror-gutter CodeMirror-linenumbers" style="width: 28px;"></div></div></div></div>
623 </div>
623 </div>
624 </div>
624 </div>
625
625
626
626
627
627
628
628
629
629
630 <!--
630 <!--
631 File Edit
631 File Edit
632 -->
632 -->
633
633
634 <h2>File Edit</h2>
634 <h2>File Edit</h2>
635
635
636 <div class="codeblock">
636 <div class="codeblock">
637 <div class="code-header">
637 <div class="code-header">
638 <div class="stats">
638 <div class="stats">
639 <i class="icon-file"></i>
639 <i class="icon-file"></i>
640 <span class="item"><a href="/example/changeset/80ead1899f50a894889e19ffeb49c9cebf5bf045">r8248:80ead1899f50</a></span>
640 <span class="item"><a href="/example/changeset/80ead1899f50a894889e19ffeb49c9cebf5bf045">r8248:80ead1899f50</a></span>
641 <span class="item">1.2 KiB</span>
641 <span class="item">1.2 KiB</span>
642 <span class="item last">text/x-python</span>
642 <span class="item last">text/x-python</span>
643 <div class="buttons">
643 <div class="buttons">
644 <a class="btn btn-mini" href="/example/changelog/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">
644 <a class="btn btn-mini" href="/example/commits/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">
645 <i class="icon-time"></i> history
645 <i class="icon-time"></i> history
646 </a>
646 </a>
647
647
648 <a class="btn btn-mini" href="/example/files/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">source</a>
648 <a class="btn btn-mini" href="/example/files/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">source</a>
649 <a class="btn btn-mini" href="/example/raw/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">raw</a>
649 <a class="btn btn-mini" href="/example/raw/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">raw</a>
650 <a class="btn btn-mini" href="/example/rawfile/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">
650 <a class="btn btn-mini" href="/example/rawfile/80ead1899f50a894889e19ffeb49c9cebf5bf045/rhodecode/websetup.py">
651 <i class="icon-archive"></i> download
651 <i class="icon-archive"></i> download
652 </a>
652 </a>
653 </div>
653 </div>
654 </div>
654 </div>
655 <div class="form">
655 <div class="form">
656 <label for="set_mode">Editing file:</label>
656 <label for="set_mode">Editing file:</label>
657 rhodecode /
657 rhodecode /
658 <input type="text" name="filename" value="websetup.py">
658 <input type="text" name="filename" value="websetup.py">
659
659
660 <div class="select2-container drop-menu" id="s2id_set_mode"><a href="javascript:void(0)" class="select2-choice" tabindex="-1"> <span class="select2-chosen" id="select2-chosen-2">plain</span><abbr class="select2-search-choice-close"></abbr> <span class="select2-arrow" role="presentation"><b role="presentation"></b></span></a><label for="s2id_autogen2" class="select2-offscreen">Editing file:</label><input class="select2-focusser select2-offscreen" type="text" aria-haspopup="true" role="button" aria-labelledby="select2-chosen-2" id="s2id_autogen2"><div class="select2-drop select2-display-none drop-menu-dropdown select2-with-searchbox"> <div class="select2-search"> <label for="s2id_autogen2_search" class="select2-offscreen">Editing file:</label> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input" role="combobox" aria-expanded="true" aria-autocomplete="list" aria-owns="select2-results-2" id="s2id_autogen2_search" placeholder=""> </div> <ul class="select2-results" role="listbox" id="select2-results-2"> </ul></div></div><select id="set_mode" name="set_mode" tabindex="-1" title="Editing file:" style="display: none;">
660 <div class="select2-container drop-menu" id="s2id_set_mode"><a href="javascript:void(0)" class="select2-choice" tabindex="-1"> <span class="select2-chosen" id="select2-chosen-2">plain</span><abbr class="select2-search-choice-close"></abbr> <span class="select2-arrow" role="presentation"><b role="presentation"></b></span></a><label for="s2id_autogen2" class="select2-offscreen">Editing file:</label><input class="select2-focusser select2-offscreen" type="text" aria-haspopup="true" role="button" aria-labelledby="select2-chosen-2" id="s2id_autogen2"><div class="select2-drop select2-display-none drop-menu-dropdown select2-with-searchbox"> <div class="select2-search"> <label for="s2id_autogen2_search" class="select2-offscreen">Editing file:</label> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input" role="combobox" aria-expanded="true" aria-autocomplete="list" aria-owns="select2-results-2" id="s2id_autogen2_search" placeholder=""> </div> <ul class="select2-results" role="listbox" id="select2-results-2"> </ul></div></div><select id="set_mode" name="set_mode" tabindex="-1" title="Editing file:" style="display: none;">
661 <option selected="selected" value="plain">plain</option>
661 <option selected="selected" value="plain">plain</option>
662 <option value="apl">APL</option><option value="asterisk">Asterisk</option><option value="clike">C</option><option value="clike">C++</option><option value="cobol">Cobol</option><option value="clike">Java</option><option value="clike">C#</option><option value="clike">Scala</option><option value="clojure">Clojure</option><option value="coffeescript">CoffeeScript</option><option value="commonlisp">Common Lisp</option><option value="css">CSS</option><option value="d">D</option><option value="diff">diff</option><option value="dtd">DTD</option><option value="dylan">Dylan</option><option value="ecl">ECL</option><option value="eiffel">Eiffel</option><option value="erlang">Erlang</option><option value="fortran">Fortran</option><option value="mllike">F#</option><option value="gas">Gas</option><option value="go">GO</option><option value="gherkin">Gherkin</option><option value="go">Go</option><option value="groovy">Groovy</option><option value="haml">HAML</option><option value="haskell">Haskell</option><option value="haxe">Haxe</option><option value="htmlembedded">ASP.NET</option><option value="htmlembedded">Embedded Javascript</option><option value="htmlembedded">JavaServer Pages</option><option value="htmlmixed">HTML</option><option value="http">HTTP</option><option value="jade">Jade</option><option value="javascript">JavaScript</option><option value="javascript">JSON</option><option value="javascript">TypeScript</option><option value="jinja2">Jinja2</option><option value="julia">Julia</option><option value="less">LESS</option><option value="livescript">LiveScript</option><option value="lua">Lua</option><option value="markdown">Markdown (GitHub-flavour)</option><option value="mirc">mIRC</option><option value="nginx">Nginx</option><option value="ntriples">NTriples</option><option value="ocaml">OCaml</option><option value="mllike">OCaml</option><option value="octave">Octave</option><option value="pascal">Pascal</option><option value="pegjs">PEG.js</option><option value="perl">Perl</option><option value="php">PHP</option><option value="pig">Pig</option><option value="null">Plain Text</option><option value="properties">Properties files</option><option value="python" selected="selected">Python</option><option value="puppet">Puppet</option><option value="r">R</option><option value="rst">reStructuredText</option><option value="ruby">Ruby</option><option value="rust">Rust</option><option value="sass">Sass</option><option value="scheme">Scheme</option><option value="css">SCSS</option><option value="shell">Shell</option><option value="sieve">Sieve</option><option value="smalltalk">Smalltalk</option><option value="smarty">Smarty</option><option value="smartymixed">SmartyMixed</option><option value="solr">Solr</option><option value="sparql">SPARQL</option><option value="sql">SQL</option><option value="sql">MariaDB</option><option value="stex">sTeX</option><option value="stex">LaTeX</option><option value="verilog">SystemVerilog</option><option value="tcl">Tcl</option><option value="tiddlywiki">TiddlyWiki </option><option value="tiki">Tiki wiki</option><option value="toml">TOML</option><option value="turtle">Turtle</option><option value="vb">VB.NET</option><option value="vbscript">VBScript</option><option value="velocity">Velocity</option><option value="verilog">Verilog</option><option value="xml">XML</option><option value="xml">HTML</option><option value="xquery">XQuery</option><option value="yaml">YAML</option><option value="z80">Z80</option></select>
662 <option value="apl">APL</option><option value="asterisk">Asterisk</option><option value="clike">C</option><option value="clike">C++</option><option value="cobol">Cobol</option><option value="clike">Java</option><option value="clike">C#</option><option value="clike">Scala</option><option value="clojure">Clojure</option><option value="coffeescript">CoffeeScript</option><option value="commonlisp">Common Lisp</option><option value="css">CSS</option><option value="d">D</option><option value="diff">diff</option><option value="dtd">DTD</option><option value="dylan">Dylan</option><option value="ecl">ECL</option><option value="eiffel">Eiffel</option><option value="erlang">Erlang</option><option value="fortran">Fortran</option><option value="mllike">F#</option><option value="gas">Gas</option><option value="go">GO</option><option value="gherkin">Gherkin</option><option value="go">Go</option><option value="groovy">Groovy</option><option value="haml">HAML</option><option value="haskell">Haskell</option><option value="haxe">Haxe</option><option value="htmlembedded">ASP.NET</option><option value="htmlembedded">Embedded Javascript</option><option value="htmlembedded">JavaServer Pages</option><option value="htmlmixed">HTML</option><option value="http">HTTP</option><option value="jade">Jade</option><option value="javascript">JavaScript</option><option value="javascript">JSON</option><option value="javascript">TypeScript</option><option value="jinja2">Jinja2</option><option value="julia">Julia</option><option value="less">LESS</option><option value="livescript">LiveScript</option><option value="lua">Lua</option><option value="markdown">Markdown (GitHub-flavour)</option><option value="mirc">mIRC</option><option value="nginx">Nginx</option><option value="ntriples">NTriples</option><option value="ocaml">OCaml</option><option value="mllike">OCaml</option><option value="octave">Octave</option><option value="pascal">Pascal</option><option value="pegjs">PEG.js</option><option value="perl">Perl</option><option value="php">PHP</option><option value="pig">Pig</option><option value="null">Plain Text</option><option value="properties">Properties files</option><option value="python" selected="selected">Python</option><option value="puppet">Puppet</option><option value="r">R</option><option value="rst">reStructuredText</option><option value="ruby">Ruby</option><option value="rust">Rust</option><option value="sass">Sass</option><option value="scheme">Scheme</option><option value="css">SCSS</option><option value="shell">Shell</option><option value="sieve">Sieve</option><option value="smalltalk">Smalltalk</option><option value="smarty">Smarty</option><option value="smartymixed">SmartyMixed</option><option value="solr">Solr</option><option value="sparql">SPARQL</option><option value="sql">SQL</option><option value="sql">MariaDB</option><option value="stex">sTeX</option><option value="stex">LaTeX</option><option value="verilog">SystemVerilog</option><option value="tcl">Tcl</option><option value="tiddlywiki">TiddlyWiki </option><option value="tiki">Tiki wiki</option><option value="toml">TOML</option><option value="turtle">Turtle</option><option value="vb">VB.NET</option><option value="vbscript">VBScript</option><option value="velocity">Velocity</option><option value="verilog">Verilog</option><option value="xml">XML</option><option value="xml">HTML</option><option value="xquery">XQuery</option><option value="yaml">YAML</option><option value="z80">Z80</option></select>
663 <script>
663 <script>
664 $(document).ready(function() {
664 $(document).ready(function() {
665 $('#set_mode').select2({
665 $('#set_mode').select2({
666 containerCssClass: 'drop-menu',
666 containerCssClass: 'drop-menu',
667 dropdownCssClass: 'drop-menu-dropdown',
667 dropdownCssClass: 'drop-menu-dropdown',
668 dropdownAutoWidth: true
668 dropdownAutoWidth: true
669 });
669 });
670 });
670 });
671 </script>
671 </script>
672
672
673 <label for="line_wrap">line wraps</label>
673 <label for="line_wrap">line wraps</label>
674 <div class="select2-container drop-menu" id="s2id_line_wrap"><a href="javascript:void(0)" class="select2-choice" tabindex="-1"> <span class="select2-chosen" id="select2-chosen-3">off</span><abbr class="select2-search-choice-close"></abbr> <span class="select2-arrow" role="presentation"><b role="presentation"></b></span></a><label for="s2id_autogen3" class="select2-offscreen">line wraps</label><input class="select2-focusser select2-offscreen" type="text" aria-haspopup="true" role="button" aria-labelledby="select2-chosen-3" id="s2id_autogen3"><div class="select2-drop select2-display-none drop-menu-dropdown"> <div class="select2-search select2-search-hidden select2-offscreen"> <label for="s2id_autogen3_search" class="select2-offscreen">line wraps</label> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input" role="combobox" aria-expanded="true" aria-autocomplete="list" aria-owns="select2-results-3" id="s2id_autogen3_search" placeholder=""> </div> <ul class="select2-results" role="listbox" id="select2-results-3"> </ul></div></div><select id="line_wrap" name="line_wrap" tabindex="-1" title="line wraps" style="display: none;">
674 <div class="select2-container drop-menu" id="s2id_line_wrap"><a href="javascript:void(0)" class="select2-choice" tabindex="-1"> <span class="select2-chosen" id="select2-chosen-3">off</span><abbr class="select2-search-choice-close"></abbr> <span class="select2-arrow" role="presentation"><b role="presentation"></b></span></a><label for="s2id_autogen3" class="select2-offscreen">line wraps</label><input class="select2-focusser select2-offscreen" type="text" aria-haspopup="true" role="button" aria-labelledby="select2-chosen-3" id="s2id_autogen3"><div class="select2-drop select2-display-none drop-menu-dropdown"> <div class="select2-search select2-search-hidden select2-offscreen"> <label for="s2id_autogen3_search" class="select2-offscreen">line wraps</label> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input" role="combobox" aria-expanded="true" aria-autocomplete="list" aria-owns="select2-results-3" id="s2id_autogen3_search" placeholder=""> </div> <ul class="select2-results" role="listbox" id="select2-results-3"> </ul></div></div><select id="line_wrap" name="line_wrap" tabindex="-1" title="line wraps" style="display: none;">
675 <option value="on">on</option>
675 <option value="on">on</option>
676 <option selected="selected" value="off">off</option>
676 <option selected="selected" value="off">off</option>
677 </select>
677 </select>
678 <script>
678 <script>
679 $(document).ready(function() {
679 $(document).ready(function() {
680 $('#line_wrap').select2({
680 $('#line_wrap').select2({
681 containerCssClass: 'drop-menu',
681 containerCssClass: 'drop-menu',
682 dropdownCssClass: 'drop-menu-dropdown',
682 dropdownCssClass: 'drop-menu-dropdown',
683 dropdownAutoWidth: true,
683 dropdownAutoWidth: true,
684 minimumResultsForSearch: -1
684 minimumResultsForSearch: -1
685
685
686 });
686 });
687 });
687 });
688 </script>
688 </script>
689
689
690 <div id="render_preview" class="btn btn-mini hidden disabled">Preview</div>
690 <div id="render_preview" class="btn btn-mini hidden disabled">Preview</div>
691 </div>
691 </div>
692 </div>
692 </div>
693 <div id="editor_container">
693 <div id="editor_container">
694 <pre id="editor_pre"></pre>
694 <pre id="editor_pre"></pre>
695 <textarea id="editor" name="content" style="display: none;"># -*- coding: utf-8 -*-
695 <textarea id="editor" name="content" style="display: none;"># -*- coding: utf-8 -*-
696
696
697 # Published under Commercial License.
697 # Published under Commercial License.
698 # Read the full license text at https://rhodecode.com/licenses.
698 # Read the full license text at https://rhodecode.com/licenses.
699 """
699 """
700 rhodecode.websetup
700 rhodecode.websetup
701 ~~~~~~~~~~~~~~~~~~
701 ~~~~~~~~~~~~~~~~~~
702
702
703 Weboperations and setup for rhodecode
703 Weboperations and setup for rhodecode
704
704
705 :created_on: Dec 11, 2010
705 :created_on: Dec 11, 2010
706 :author: marcink
706 :author: marcink
707 :copyright: (c) 2013-2015 RhodeCode GmbH.
707 :copyright: (c) 2013-2015 RhodeCode GmbH.
708 :license: Commercial License, see LICENSE for more details.
708 :license: Commercial License, see LICENSE for more details.
709 """
709 """
710
710
711 import logging
711 import logging
712
712
713 from rhodecode.config.environment import load_environment
713 from rhodecode.config.environment import load_environment
714 from rhodecode.lib.db_manage import DbManage
714 from rhodecode.lib.db_manage import DbManage
715 from rhodecode.model.meta import Session
715 from rhodecode.model.meta import Session
716
716
717
717
718 log = logging.getLogger(__name__)
718 log = logging.getLogger(__name__)
719
719
720
720
721 def setup_app(command, conf, vars):
721 def setup_app(command, conf, vars):
722 """Place any commands to setup rhodecode here"""
722 """Place any commands to setup rhodecode here"""
723 dbconf = conf['sqlalchemy.db1.url']
723 dbconf = conf['sqlalchemy.db1.url']
724 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=conf['here'],
724 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=conf['here'],
725 tests=False, cli_args=command.options.__dict__)
725 tests=False, cli_args=command.options.__dict__)
726 dbmanage.create_tables(override=True)
726 dbmanage.create_tables(override=True)
727 dbmanage.set_db_version()
727 dbmanage.set_db_version()
728 opts = dbmanage.config_prompt(None)
728 opts = dbmanage.config_prompt(None)
729 dbmanage.create_settings(opts)
729 dbmanage.create_settings(opts)
730 dbmanage.create_default_user()
730 dbmanage.create_default_user()
731 dbmanage.admin_prompt()
731 dbmanage.admin_prompt()
732 dbmanage.create_permissions()
732 dbmanage.create_permissions()
733 dbmanage.populate_default_permissions()
733 dbmanage.populate_default_permissions()
734 Session().commit()
734 Session().commit()
735 load_environment(conf.global_conf, conf.local_conf, initial=True)
735 load_environment(conf.global_conf, conf.local_conf, initial=True)
736 </textarea><div class="CodeMirror cm-s-default CodeMirror-focused"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 5px; left: 34px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" style="position: absolute; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div class="CodeMirror-hscrollbar" style="left: 29px; min-height: 18px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar" style="display: block; bottom: 0px; min-width: 18px;"><div style="min-width: 1px; height: 554px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="min-width: 579.350463867188px; margin-left: 29px; min-height: 554px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><div style="width: 50px; height: 50px; overflow-x: scroll;"></div></div><div style="position: relative; z-index: 1; display: none;"></div><div class="CodeMirror-code"><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">1</div></div><pre><span class="cm-comment"># -*- coding: utf-8 -*-</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">2</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">3</div></div><pre><span class="cm-comment"># Published under Commercial License.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">4</div></div><pre><span class="cm-comment"># Read the full license text at https://rhodecode.com/licenses.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">5</div></div><pre><span class="cm-string">"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">6</div></div><pre><span class="cm-string">rhodecode.websetup</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">7</div></div><pre><span class="cm-string">~~~~~~~~~~~~~~~~~~</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">8</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">9</div></div><pre><span class="cm-string">Weboperations and setup for rhodecode</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">10</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">11</div></div><pre><span class="cm-string">:created_on: Dec 11, 2010</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">12</div></div><pre><span class="cm-string">:author: marcink</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">13</div></div><pre><span class="cm-string">:copyright: (c) 2013-2015 RhodeCode GmbH.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">14</div></div><pre><span class="cm-string">:license: Commercial License, see LICENSE for more details.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">15</div></div><pre><span class="cm-string">"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">16</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">17</div></div><pre><span class="cm-keyword">import</span> <span class="cm-variable">logging</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">18</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">19</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">rhodecode</span>.<span class="cm-variable">config</span>.<span class="cm-variable">environment</span> <span class="cm-keyword">import</span> <span class="cm-variable">load_environment</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">20</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">rhodecode</span>.<span class="cm-variable">lib</span>.<span class="cm-variable">db_manage</span> <span class="cm-keyword">import</span> <span class="cm-variable">DbManage</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">21</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">rhodecode</span>.<span class="cm-variable">model</span>.<span class="cm-variable">meta</span> <span class="cm-keyword">import</span> <span class="cm-variable">Session</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">22</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">23</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">24</div></div><pre><span class="cm-variable">log</span> = <span class="cm-variable">logging</span>.<span class="cm-variable">getLogger</span>(<span class="cm-variable">__name__</span>) # Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">25</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">26</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">27</div></div><pre><span class="cm-keyword">def</span> <span class="cm-def">setup_app</span>(<span class="cm-variable">command</span>, <span class="cm-variable">conf</span>, <span class="cm-builtin">vars</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">28</div></div><pre> <span class="cm-string">"""Place any commands to setup rhodecode here"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">29</div></div><pre> <span class="cm-variable">dbconf</span> = <span class="cm-variable">conf</span>[<span class="cm-string">'sqlalchemy.db1.url'</span>]</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">30</div></div><pre> <span class="cm-variable">dbmanage</span> = <span class="cm-variable">DbManage</span>(<span class="cm-variable">log_sql</span>=<span class="cm-builtin">True</span>, <span class="cm-variable">dbconf</span>=<span class="cm-variable">dbconf</span>, <span class="cm-variable">root</span>=<span class="cm-variable">conf</span>[<span class="cm-string">'here'</span>],</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">31</div></div><pre> <span class="cm-variable">tests</span>=<span class="cm-builtin">False</span>, <span class="cm-variable">cli_args</span>=<span class="cm-variable">command</span>.<span class="cm-variable">options</span>.<span class="cm-variable">__dict__</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">32</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_tables</span>(<span class="cm-variable">override</span>=<span class="cm-builtin">True</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">33</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">set_db_version</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">34</div></div><pre> <span class="cm-variable">opts</span> = <span class="cm-variable">dbmanage</span>.<span class="cm-variable">config_prompt</span>(<span class="cm-builtin">None</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">35</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_settings</span>(<span class="cm-variable">opts</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">36</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_default_user</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">37</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">admin_prompt</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">38</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_permissions</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">39</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">populate_default_permissions</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">40</div></div><pre> <span class="cm-variable">Session</span>().<span class="cm-variable">commit</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">41</div></div><pre> <span class="cm-variable">load_environment</span>(<span class="cm-variable">conf</span>.<span class="cm-variable">global_conf</span>, <span class="cm-variable">conf</span>.<span class="cm-variable">local_conf</span>, <span class="cm-variable">initial</span>=<span class="cm-builtin">True</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">42</div></div><pre>&nbsp;</pre></div></div><div class="CodeMirror-cursor" style="left: 4px; top: 0px; height: 13px;">&nbsp;</div><div class="CodeMirror-cursor CodeMirror-secondarycursor" style="display: none;">&nbsp;</div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 554px;"></div><div class="CodeMirror-gutters" style="height: 554px;"><div class="CodeMirror-gutter CodeMirror-linenumbers" style="width: 28px;"></div></div></div></div>
736 </textarea><div class="CodeMirror cm-s-default CodeMirror-focused"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 5px; left: 34px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" style="position: absolute; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div class="CodeMirror-hscrollbar" style="left: 29px; min-height: 18px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar" style="display: block; bottom: 0px; min-width: 18px;"><div style="min-width: 1px; height: 554px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="min-width: 579.350463867188px; margin-left: 29px; min-height: 554px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><div style="width: 50px; height: 50px; overflow-x: scroll;"></div></div><div style="position: relative; z-index: 1; display: none;"></div><div class="CodeMirror-code"><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">1</div></div><pre><span class="cm-comment"># -*- coding: utf-8 -*-</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">2</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">3</div></div><pre><span class="cm-comment"># Published under Commercial License.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">4</div></div><pre><span class="cm-comment"># Read the full license text at https://rhodecode.com/licenses.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">5</div></div><pre><span class="cm-string">"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">6</div></div><pre><span class="cm-string">rhodecode.websetup</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">7</div></div><pre><span class="cm-string">~~~~~~~~~~~~~~~~~~</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">8</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">9</div></div><pre><span class="cm-string">Weboperations and setup for rhodecode</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">10</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">11</div></div><pre><span class="cm-string">:created_on: Dec 11, 2010</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">12</div></div><pre><span class="cm-string">:author: marcink</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">13</div></div><pre><span class="cm-string">:copyright: (c) 2013-2015 RhodeCode GmbH.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">14</div></div><pre><span class="cm-string">:license: Commercial License, see LICENSE for more details.</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">15</div></div><pre><span class="cm-string">"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">16</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">17</div></div><pre><span class="cm-keyword">import</span> <span class="cm-variable">logging</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">18</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">19</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">rhodecode</span>.<span class="cm-variable">config</span>.<span class="cm-variable">environment</span> <span class="cm-keyword">import</span> <span class="cm-variable">load_environment</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">20</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">rhodecode</span>.<span class="cm-variable">lib</span>.<span class="cm-variable">db_manage</span> <span class="cm-keyword">import</span> <span class="cm-variable">DbManage</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">21</div></div><pre><span class="cm-keyword">from</span> <span class="cm-variable">rhodecode</span>.<span class="cm-variable">model</span>.<span class="cm-variable">meta</span> <span class="cm-keyword">import</span> <span class="cm-variable">Session</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">22</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">23</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">24</div></div><pre><span class="cm-variable">log</span> = <span class="cm-variable">logging</span>.<span class="cm-variable">getLogger</span>(<span class="cm-variable">__name__</span>) # Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">25</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">26</div></div><pre>&nbsp;</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">27</div></div><pre><span class="cm-keyword">def</span> <span class="cm-def">setup_app</span>(<span class="cm-variable">command</span>, <span class="cm-variable">conf</span>, <span class="cm-builtin">vars</span>):</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">28</div></div><pre> <span class="cm-string">"""Place any commands to setup rhodecode here"""</span></pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">29</div></div><pre> <span class="cm-variable">dbconf</span> = <span class="cm-variable">conf</span>[<span class="cm-string">'sqlalchemy.db1.url'</span>]</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">30</div></div><pre> <span class="cm-variable">dbmanage</span> = <span class="cm-variable">DbManage</span>(<span class="cm-variable">log_sql</span>=<span class="cm-builtin">True</span>, <span class="cm-variable">dbconf</span>=<span class="cm-variable">dbconf</span>, <span class="cm-variable">root</span>=<span class="cm-variable">conf</span>[<span class="cm-string">'here'</span>],</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">31</div></div><pre> <span class="cm-variable">tests</span>=<span class="cm-builtin">False</span>, <span class="cm-variable">cli_args</span>=<span class="cm-variable">command</span>.<span class="cm-variable">options</span>.<span class="cm-variable">__dict__</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">32</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_tables</span>(<span class="cm-variable">override</span>=<span class="cm-builtin">True</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">33</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">set_db_version</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">34</div></div><pre> <span class="cm-variable">opts</span> = <span class="cm-variable">dbmanage</span>.<span class="cm-variable">config_prompt</span>(<span class="cm-builtin">None</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">35</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_settings</span>(<span class="cm-variable">opts</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">36</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_default_user</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">37</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">admin_prompt</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">38</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">create_permissions</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">39</div></div><pre> <span class="cm-variable">dbmanage</span>.<span class="cm-variable">populate_default_permissions</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">40</div></div><pre> <span class="cm-variable">Session</span>().<span class="cm-variable">commit</span>()</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">41</div></div><pre> <span class="cm-variable">load_environment</span>(<span class="cm-variable">conf</span>.<span class="cm-variable">global_conf</span>, <span class="cm-variable">conf</span>.<span class="cm-variable">local_conf</span>, <span class="cm-variable">initial</span>=<span class="cm-builtin">True</span>)</pre></div><div style="position: relative;"><div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;"><div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">42</div></div><pre>&nbsp;</pre></div></div><div class="CodeMirror-cursor" style="left: 4px; top: 0px; height: 13px;">&nbsp;</div><div class="CodeMirror-cursor CodeMirror-secondarycursor" style="display: none;">&nbsp;</div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 554px;"></div><div class="CodeMirror-gutters" style="height: 554px;"><div class="CodeMirror-gutter CodeMirror-linenumbers" style="width: 28px;"></div></div></div></div>
737 <div id="editor_preview"></div>
737 <div id="editor_preview"></div>
738 </div>
738 </div>
739 <div class="message">
739 <div class="message">
740 <label class="codeblock-label">Commit Message</label>
740 <label class="codeblock-label">Commit Message</label>
741 <textarea id="commit" name="message" placeholder="Edited file rhodecode/websetup.py via RhodeCode"></textarea>
741 <textarea id="commit" name="message" placeholder="Edited file rhodecode/websetup.py via RhodeCode"></textarea>
742 </div>
742 </div>
743 </div>
743 </div>
744
744
745
745
746
746
747
747
748
748
749
749
750 <!--
750 <!--
751 Commit with comments
751 Commit with comments
752 -->
752 -->
753
753
754 <h2>Commit with comments</h2>
754 <h2>Commit with comments</h2>
755
755
756 <div class="diff-container" id="diff-container-140360037209920">
756 <div class="diff-container" id="diff-container-140360037209920">
757 <div id="c-4e5ee86997c6-7046e4320b26_target"></div>
757 <div id="c-4e5ee86997c6-7046e4320b26_target"></div>
758 <div id="c-4e5ee86997c6-7046e4320b26" class="diffblock margined comm">
758 <div id="c-4e5ee86997c6-7046e4320b26" class="diffblock margined comm">
759 <div class="code-header">
759 <div class="code-header">
760 <div title="Go back to changed files overview">
760 <div title="Go back to changed files overview">
761 <a href="#changes_box">
761 <a href="#changes_box">
762 <i class="icon-circle-arrow-up"></i>
762 <i class="icon-circle-arrow-up"></i>
763 </a>
763 </a>
764 </div>
764 </div>
765 <div class="changeset_header">
765 <div class="changeset_header">
766 <div class="changeset_file">
766 <div class="changeset_file">
767 <i class="icon-file"></i>
767 <i class="icon-file"></i>
768 <a href="/andersonsantos/rhodecode-dev-fork/files/4e5ee86997c64981d85cf62283af448624e26929/rhodecode/tests/functional/test_compare_local.py">rhodecode/tests/functional/test_compare_local.py</a>
768 <a href="/andersonsantos/rhodecode-dev-fork/files/4e5ee86997c64981d85cf62283af448624e26929/rhodecode/tests/functional/test_compare_local.py">rhodecode/tests/functional/test_compare_local.py</a>
769 </div>
769 </div>
770 <div class="diff-actions">
770 <div class="diff-actions">
771 <a href="/andersonsantos/rhodecode-dev-fork/diff/rhodecode/tests/functional/test_compare_local.py?fulldiff=1&amp;diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=diff&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Show full diff for this file">
771 <a href="/andersonsantos/rhodecode-dev-fork/diff/rhodecode/tests/functional/test_compare_local.py?fulldiff=1&amp;diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=diff&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Show full diff for this file">
772 <img class="icon" src="/images/icons/page_white_go.png">
772 <img class="icon" src="/images/icons/page_white_go.png">
773 </a>
773 </a>
774 <a href="/andersonsantos/rhodecode-dev-fork/diff-2way/rhodecode/tests/functional/test_compare_local.py?fulldiff=1&amp;diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=diff&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Show full side-by-side diff for this file">
774 <a href="/andersonsantos/rhodecode-dev-fork/diff-2way/rhodecode/tests/functional/test_compare_local.py?fulldiff=1&amp;diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=diff&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Show full side-by-side diff for this file">
775 <img class="icon" src="/images/icons/application_double.png">
775 <img class="icon" src="/images/icons/application_double.png">
776 </a>
776 </a>
777 <a href="/andersonsantos/rhodecode-dev-fork/diff/rhodecode/tests/functional/test_compare_local.py?diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=raw&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Raw diff">
777 <a href="/andersonsantos/rhodecode-dev-fork/diff/rhodecode/tests/functional/test_compare_local.py?diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=raw&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Raw diff">
778 <img class="icon" src="/images/icons/page_white.png">
778 <img class="icon" src="/images/icons/page_white.png">
779 </a>
779 </a>
780 <a href="/andersonsantos/rhodecode-dev-fork/diff/rhodecode/tests/functional/test_compare_local.py?diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=download&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Download diff">
780 <a href="/andersonsantos/rhodecode-dev-fork/diff/rhodecode/tests/functional/test_compare_local.py?diff1=682135c2e3958d7c84db06d716efe482bd3ce7c6&amp;diff=download&amp;diff2=4e5ee86997c64981d85cf62283af448624e26929" class="tooltip" title="Download diff">
781 <img class="icon" src="/images/icons/page_save.png">
781 <img class="icon" src="/images/icons/page_save.png">
782 </a>
782 </a>
783 <a class="tooltip" href="/andersonsantos/rhodecode-dev-fork/changeset/4e5ee86997c64981d85cf62283af448624e26929?c-4e5ee86997c6-7046e4320b26=WS%3A1&amp;c-4e5ee86997c6-7046e4320b26=C%3A3#c-4e5ee86997c6-7046e4320b26" title="Ignore white space"><img alt="Ignore white space" class="icon" src="/images/icons/text_strikethrough.png"></a>
783 <a class="tooltip" href="/andersonsantos/rhodecode-dev-fork/changeset/4e5ee86997c64981d85cf62283af448624e26929?c-4e5ee86997c6-7046e4320b26=WS%3A1&amp;c-4e5ee86997c6-7046e4320b26=C%3A3#c-4e5ee86997c6-7046e4320b26" title="Ignore white space"><img alt="Ignore white space" class="icon" src="/images/icons/text_strikethrough.png"></a>
784 <a class="tooltip" href="/andersonsantos/rhodecode-dev-fork/changeset/4e5ee86997c64981d85cf62283af448624e26929?c-4e5ee86997c6-7046e4320b26=C%3A6#c-4e5ee86997c6-7046e4320b26" title="increase diff context to 6 lines"><img alt="increase diff context to 6 lines" class="icon" src="/images/icons/table_add.png"></a>
784 <a class="tooltip" href="/andersonsantos/rhodecode-dev-fork/changeset/4e5ee86997c64981d85cf62283af448624e26929?c-4e5ee86997c6-7046e4320b26=C%3A6#c-4e5ee86997c6-7046e4320b26" title="increase diff context to 6 lines"><img alt="increase diff context to 6 lines" class="icon" src="/images/icons/table_add.png"></a>
785 </div>
785 </div>
786 <span>
786 <span>
787 <label>
787 <label>
788 Show inline comments
788 Show inline comments
789 <input checked="checked" class="show-inline-comments" id="" id_for="c-4e5ee86997c6-7046e4320b26" name="" type="checkbox" value="1">
789 <input checked="checked" class="show-inline-comments" id="" id_for="c-4e5ee86997c6-7046e4320b26" name="" type="checkbox" value="1">
790 </label>
790 </label>
791 </span>
791 </span>
792 </div>
792 </div>
793 </div>
793 </div>
794 <div class="code-body">
794 <div class="code-body">
795 <div class="full_f_path" path="rhodecode/tests/functional/test_compare_local.py"></div>
795 <div class="full_f_path" path="rhodecode/tests/functional/test_compare_local.py"></div>
796 <table class="code-difftable">
796 <table class="code-difftable">
797 <tbody><tr class="line context">
797 <tbody><tr class="line context">
798 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o...">...</a></td>
798 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o...">...</a></td>
799 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n...">...</a></td>
799 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n...">...</a></td>
800 <td class="code ">
800 <td class="code ">
801 <pre>@@ -59,7 +59,7 @@
801 <pre>@@ -59,7 +59,7 @@
802 </pre>
802 </pre>
803 </td>
803 </td>
804 </tr>
804 </tr>
805 <tr class="line unmod">
805 <tr class="line unmod">
806 <td id="rhodecodetestsfunctionaltest_compare_localpy_o59" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o59">59</a></td>
806 <td id="rhodecodetestsfunctionaltest_compare_localpy_o59" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o59">59</a></td>
807 <td id="rhodecodetestsfunctionaltest_compare_localpy_n59" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n59">59</a></td>
807 <td id="rhodecodetestsfunctionaltest_compare_localpy_n59" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n59">59</a></td>
808 <td class="code ">
808 <td class="code ">
809 <pre> 'tag': 'v0.2.0',
809 <pre> 'tag': 'v0.2.0',
810 </pre>
810 </pre>
811 </td>
811 </td>
812 </tr>
812 </tr>
813 <tr class="line unmod">
813 <tr class="line unmod">
814 <td id="rhodecodetestsfunctionaltest_compare_localpy_o60" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o60">60</a></td>
814 <td id="rhodecodetestsfunctionaltest_compare_localpy_o60" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o60">60</a></td>
815 <td id="rhodecodetestsfunctionaltest_compare_localpy_n60" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n60">60</a></td>
815 <td id="rhodecodetestsfunctionaltest_compare_localpy_n60" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n60">60</a></td>
816 <td class="code ">
816 <td class="code ">
817 <pre> 'branch': 'default',
817 <pre> 'branch': 'default',
818 </pre>
818 </pre>
819 </td>
819 </td>
820 </tr>
820 </tr>
821 <tr class="line unmod">
821 <tr class="line unmod">
822 <td id="rhodecodetestsfunctionaltest_compare_localpy_o61" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o61">61</a></td>
822 <td id="rhodecodetestsfunctionaltest_compare_localpy_o61" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o61">61</a></td>
823 <td id="rhodecodetestsfunctionaltest_compare_localpy_n61" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n61">61</a></td>
823 <td id="rhodecodetestsfunctionaltest_compare_localpy_n61" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n61">61</a></td>
824 <td class="code ">
824 <td class="code ">
825 <pre> 'response': # Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.
825 <pre> 'response': # Intentionally long line to show what will happen if this line does not fit onto the screen. It might have some horizontal scrolling applied or some other fancy mechanism to deal with it.
826 </pre>
826 </pre>
827 </td>
827 </td>
828 </tr>
828 </tr>
829 <tr class="line del">
829 <tr class="line del">
830 <td id="rhodecodetestsfunctionaltest_compare_localpy_o62" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o62">62</a></td>
830 <td id="rhodecodetestsfunctionaltest_compare_localpy_o62" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o62">62</a></td>
831 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n"></a></td>
831 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n"></a></td>
832 <td class="code ">
832 <td class="code ">
833 <pre> '147 files changed: 5700 inserted, 10176 deleted'
833 <pre> '147 files changed: 5700 inserted, 10176 deleted'
834 </pre>
834 </pre>
835 </td>
835 </td>
836 </tr>
836 </tr>
837 <tr class="line add">
837 <tr class="line add">
838 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
838 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
839 <td id="rhodecodetestsfunctionaltest_compare_localpy_n62" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n62">62</a></td>
839 <td id="rhodecodetestsfunctionaltest_compare_localpy_n62" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n62">62</a></td>
840 <td class="code ">
840 <td class="code ">
841 <pre><ins> </ins> '147 files changed: 5700 inserted, 10176 deleted'
841 <pre><ins> </ins> '147 files changed: 5700 inserted, 10176 deleted'
842 </pre>
842 </pre>
843 </td>
843 </td>
844 </tr>
844 </tr>
845 <tr class="line unmod">
845 <tr class="line unmod">
846 <td id="rhodecodetestsfunctionaltest_compare_localpy_o63" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o63">63</a></td>
846 <td id="rhodecodetestsfunctionaltest_compare_localpy_o63" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o63">63</a></td>
847 <td id="rhodecodetestsfunctionaltest_compare_localpy_n63" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n63">63</a></td>
847 <td id="rhodecodetestsfunctionaltest_compare_localpy_n63" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n63">63</a></td>
848 <td class="code ">
848 <td class="code ">
849 <pre> },
849 <pre> },
850 </pre>
850 </pre>
851 </td>
851 </td>
852 </tr>
852 </tr>
853 <tr class="line unmod">
853 <tr class="line unmod">
854 <td id="rhodecodetestsfunctionaltest_compare_localpy_o64" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o64">64</a></td>
854 <td id="rhodecodetestsfunctionaltest_compare_localpy_o64" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o64">64</a></td>
855 <td id="rhodecodetestsfunctionaltest_compare_localpy_n64" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n64">64</a></td>
855 <td id="rhodecodetestsfunctionaltest_compare_localpy_n64" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n64">64</a></td>
856 <td class="code ">
856 <td class="code ">
857 <pre> 'git': {
857 <pre> 'git': {
858 </pre>
858 </pre>
859 </td>
859 </td>
860 </tr>
860 </tr>
861 <tr class="line unmod">
861 <tr class="line unmod">
862 <td id="rhodecodetestsfunctionaltest_compare_localpy_o65" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o65">65</a></td>
862 <td id="rhodecodetestsfunctionaltest_compare_localpy_o65" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o65">65</a></td>
863 <td id="rhodecodetestsfunctionaltest_compare_localpy_n65" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n65">65</a></td>
863 <td id="rhodecodetestsfunctionaltest_compare_localpy_n65" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n65">65</a></td>
864 <td class="code ">
864 <td class="code ">
865 <pre> 'tag': 'v0.2.2',
865 <pre> 'tag': 'v0.2.2',
866 </pre>
866 </pre>
867 </td>
867 </td>
868 </tr>
868 </tr>
869 <tr class="line context">
869 <tr class="line context">
870 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o...">...</a></td>
870 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o...">...</a></td>
871 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n...">...</a></td>
871 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n...">...</a></td>
872 <td class="code ">
872 <td class="code ">
873 <pre>@@ -77,9 +77,11 @@
873 <pre>@@ -77,9 +77,11 @@
874 </pre>
874 </pre>
875 </td>
875 </td>
876 </tr>
876 </tr>
877 <tr class="line unmod">
877 <tr class="line unmod">
878 <td id="rhodecodetestsfunctionaltest_compare_localpy_o77" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o77">77</a></td>
878 <td id="rhodecodetestsfunctionaltest_compare_localpy_o77" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o77">77</a></td>
879 <td id="rhodecodetestsfunctionaltest_compare_localpy_n77" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n77">77</a></td>
879 <td id="rhodecodetestsfunctionaltest_compare_localpy_n77" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n77">77</a></td>
880 <td class="code ">
880 <td class="code ">
881 <pre> target_ref=revisions[backend.alias]['tag'],
881 <pre> target_ref=revisions[backend.alias]['tag'],
882 </pre>
882 </pre>
883 </td>
883 </td>
884 </tr>
884 </tr>
885 <tr class="line unmod">
885 <tr class="line unmod">
886 <td id="rhodecodetestsfunctionaltest_compare_localpy_o78" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o78">78</a></td>
886 <td id="rhodecodetestsfunctionaltest_compare_localpy_o78" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o78">78</a></td>
887 <td id="rhodecodetestsfunctionaltest_compare_localpy_n78" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n78">78</a></td>
887 <td id="rhodecodetestsfunctionaltest_compare_localpy_n78" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n78">78</a></td>
888 <td class="code ">
888 <td class="code ">
889 <pre> ))
889 <pre> ))
890 </pre>
890 </pre>
891 </td>
891 </td>
892 </tr><tr id="comment-tr-3754" class="inline-comments"><td></td><td></td><td>
892 </tr><tr id="comment-tr-3754" class="inline-comments"><td></td><td></td><td>
893
893
894 <div class="comment" id="comment-3754" line="n78">
894 <div class="comment" id="comment-3754" line="n78">
895 <div class="comment-wrapp">
895 <div class="comment-wrapp">
896 <div class="meta">
896 <div class="meta">
897 <span class="gravatar">
897 <span class="gravatar">
898 <img src="https://secure.gravatar.com/avatar/72706ebd30734451af9ff3fb59f05ff1?d=identicon&amp;s=40" height="20" width="20">
898 <img src="https://secure.gravatar.com/avatar/72706ebd30734451af9ff3fb59f05ff1?d=identicon&amp;s=40" height="20" width="20">
899 </span>
899 </span>
900 <span class="user">
900 <span class="user">
901 anderson
901 anderson
902 </span>
902 </span>
903 <span class="date">
903 <span class="date">
904 just now |
904 just now |
905 </span>
905 </span>
906 <span class="status-change">
906 <span class="status-change">
907 Comment on commit
907 Comment on commit
908 </span>
908 </span>
909 <a class="permalink" href="#comment-3754"></a>
909 <a class="permalink" href="#comment-3754"></a>
910 </div>
910 </div>
911 <div class="text">
911 <div class="text">
912 <div class="rst-block"><p>commented line
912 <div class="rst-block"><p>commented line
913 with multiple lines</p>
913 with multiple lines</p>
914 </div>
914 </div>
915 </div>
915 </div>
916 </div>
916 </div>
917 </div><div class="add-comment"><span class="btn btn-default">Add another comment</span></div>
917 </div><div class="add-comment"><span class="btn btn-default">Add another comment</span></div>
918
918
919 </td></tr>
919 </td></tr>
920 <tr class="line unmod">
920 <tr class="line unmod">
921 <td id="rhodecodetestsfunctionaltest_compare_localpy_o79" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o79">79</a></td>
921 <td id="rhodecodetestsfunctionaltest_compare_localpy_o79" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o79">79</a></td>
922 <td id="rhodecodetestsfunctionaltest_compare_localpy_n79" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n79">79</a></td>
922 <td id="rhodecodetestsfunctionaltest_compare_localpy_n79" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n79">79</a></td>
923 <td class="code ">
923 <td class="code ">
924 <pre></pre>
924 <pre></pre>
925 </td>
925 </td>
926 </tr>
926 </tr>
927 <tr class="line del form-open hl-comment">
927 <tr class="line del form-open hl-comment">
928 <td id="rhodecodetestsfunctionaltest_compare_localpy_o80" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o80">80</a></td>
928 <td id="rhodecodetestsfunctionaltest_compare_localpy_o80" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o80">80</a></td>
929 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n"></a></td>
929 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n"></a></td>
930 <td class="code ">
930 <td class="code ">
931 <pre> response.mustcontain('%s@%s' % (<del>backend.repo_name,</del>
931 <pre> response.mustcontain('%s@%s' % (<del>backend.repo_name,</del>
932 </pre>
932 </pre>
933 </td>
933 </td>
934 </tr><tr id="comment-tr-undefined" class="comment-form-inline"><td></td><td></td><td>
934 </tr><tr id="comment-tr-undefined" class="comment-form-inline"><td></td><td></td><td>
935 <div class="comment-inline-form ac">
935 <div class="comment-inline-form ac">
936 <div class="overlay"><div class="overlay-text">Submitting...</div></div>
936 <div class="overlay"><div class="overlay-text">Submitting...</div></div>
937 <form action="#" class="inline-form" method="get">
937 <form action="#" class="inline-form" method="get">
938 <div id="edit-container_o80" class="clearfix">
938 <div id="edit-container_o80" class="clearfix">
939 <div class="comment-title pull-left">
939 <div class="comment-title pull-left">
940 Commenting on line o80.
940 Commenting on line o80.
941 </div>
941 </div>
942 <div class="comment-help pull-right">
942 <div class="comment-help pull-right">
943 Comments parsed using <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">RST</a> syntax with <span class="tooltip" title="Use @username inside this text to send notification to this RhodeCode user">@mention</span> support.
943 Comments parsed using <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">RST</a> syntax with <span class="tooltip" title="Use @username inside this text to send notification to this RhodeCode user">@mention</span> support.
944 </div>
944 </div>
945 <div style="clear: both"></div>
945 <div style="clear: both"></div>
946 <textarea id="text_o80" name="text" class="comment-block-ta ac-input" autocomplete="off"></textarea>
946 <textarea id="text_o80" name="text" class="comment-block-ta ac-input" autocomplete="off"></textarea>
947 </div>
947 </div>
948 <div id="preview-container_o80" class="clearfix" style="display: none;">
948 <div id="preview-container_o80" class="clearfix" style="display: none;">
949 <div class="comment-help">
949 <div class="comment-help">
950 Comment preview
950 Comment preview
951 </div>
951 </div>
952 <div id="preview-box_o80" class="preview-box"></div>
952 <div id="preview-box_o80" class="preview-box"></div>
953 </div>
953 </div>
954 <div class="comment-button pull-right">
954 <div class="comment-button pull-right">
955 <input type="hidden" name="f_path" value="rhodecode/tests/functional/test_compare_local.py">
955 <input type="hidden" name="f_path" value="rhodecode/tests/functional/test_compare_local.py">
956 <input type="hidden" name="line" value="o80">
956 <input type="hidden" name="line" value="o80">
957 <div id="preview-btn_o80" class="btn btn-default">Preview</div>
957 <div id="preview-btn_o80" class="btn btn-default">Preview</div>
958 <div id="edit-btn_o80" class="btn" style="display: none;">Edit</div>
958 <div id="edit-btn_o80" class="btn" style="display: none;">Edit</div>
959 <input class="btn btn-success save-inline-form" id="save" name="save" type="submit" value="Comment">
959 <input class="btn btn-success save-inline-form" id="save" name="save" type="submit" value="Comment">
960 </div>
960 </div>
961 <div class="comment-button hide-inline-form-button">
961 <div class="comment-button hide-inline-form-button">
962 <input class="btn hide-inline-form" id="hide-inline-form" name="hide-inline-form" type="reset" value="Cancel">
962 <input class="btn hide-inline-form" id="hide-inline-form" name="hide-inline-form" type="reset" value="Cancel">
963 </div>
963 </div>
964 </form>
964 </form>
965 </div>
965 </div>
966 </td></tr>
966 </td></tr>
967 <tr class="line add">
967 <tr class="line add">
968 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
968 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
969 <td id="rhodecodetestsfunctionaltest_compare_localpy_n80" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n80">80</a></td>
969 <td id="rhodecodetestsfunctionaltest_compare_localpy_n80" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n80">80</a></td>
970 <td class="code ">
970 <td class="code ">
971 <pre> response.mustcontain('%s@%s' % (
971 <pre> response.mustcontain('%s@%s' % (
972 </pre>
972 </pre>
973 </td>
973 </td>
974 </tr>
974 </tr>
975 <tr class="line add">
975 <tr class="line add">
976 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
976 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
977 <td id="rhodecodetestsfunctionaltest_compare_localpy_n81" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n81">81</a></td>
977 <td id="rhodecodetestsfunctionaltest_compare_localpy_n81" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n81">81</a></td>
978 <td class="code ">
978 <td class="code ">
979 <pre> backend.repo_name,
979 <pre> backend.repo_name,
980 </pre>
980 </pre>
981 </td>
981 </td>
982 </tr>
982 </tr>
983 <tr class="line unmod">
983 <tr class="line unmod">
984 <td id="rhodecodetestsfunctionaltest_compare_localpy_o81" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o81">81</a></td>
984 <td id="rhodecodetestsfunctionaltest_compare_localpy_o81" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o81">81</a></td>
985 <td id="rhodecodetestsfunctionaltest_compare_localpy_n82" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n82">82</a></td>
985 <td id="rhodecodetestsfunctionaltest_compare_localpy_n82" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n82">82</a></td>
986 <td class="code ">
986 <td class="code ">
987 <pre> revisions[backend.alias]['branch']))
987 <pre> revisions[backend.alias]['branch']))
988 </pre>
988 </pre>
989 </td>
989 </td>
990 </tr>
990 </tr>
991 <tr class="line del">
991 <tr class="line del">
992 <td id="rhodecodetestsfunctionaltest_compare_localpy_o82" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o82">82</a></td>
992 <td id="rhodecodetestsfunctionaltest_compare_localpy_o82" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o82">82</a></td>
993 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n"></a></td>
993 <td class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n"></a></td>
994 <td class="code ">
994 <td class="code ">
995 <pre> response.mustcontain('%s@%s' % (<del>backend.repo_name,</del>
995 <pre> response.mustcontain('%s@%s' % (<del>backend.repo_name,</del>
996 </pre>
996 </pre>
997 </td>
997 </td>
998 </tr>
998 </tr>
999 <tr class="line add">
999 <tr class="line add">
1000 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
1000 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
1001 <td id="rhodecodetestsfunctionaltest_compare_localpy_n83" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n83">83</a></td>
1001 <td id="rhodecodetestsfunctionaltest_compare_localpy_n83" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n83">83</a></td>
1002 <td class="code ">
1002 <td class="code ">
1003 <pre> response.mustcontain('%s@%s' % (
1003 <pre> response.mustcontain('%s@%s' % (
1004 </pre>
1004 </pre>
1005 </td>
1005 </td>
1006 </tr>
1006 </tr>
1007 <tr class="line add">
1007 <tr class="line add">
1008 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
1008 <td class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o"></a></td>
1009 <td id="rhodecodetestsfunctionaltest_compare_localpy_n84" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n84">84</a></td>
1009 <td id="rhodecodetestsfunctionaltest_compare_localpy_n84" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n84">84</a></td>
1010 <td class="code ">
1010 <td class="code ">
1011 <pre> backend.repo_name,
1011 <pre> backend.repo_name,
1012 </pre>
1012 </pre>
1013 </td>
1013 </td>
1014 </tr>
1014 </tr>
1015 <tr class="line unmod">
1015 <tr class="line unmod">
1016 <td id="rhodecodetestsfunctionaltest_compare_localpy_o83" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o83">83</a></td>
1016 <td id="rhodecodetestsfunctionaltest_compare_localpy_o83" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o83">83</a></td>
1017 <td id="rhodecodetestsfunctionaltest_compare_localpy_n85" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n85">85</a></td>
1017 <td id="rhodecodetestsfunctionaltest_compare_localpy_n85" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n85">85</a></td>
1018 <td class="code ">
1018 <td class="code ">
1019 <pre> revisions[backend.alias]['tag']))
1019 <pre> revisions[backend.alias]['tag']))
1020 </pre>
1020 </pre>
1021 </td>
1021 </td>
1022 </tr>
1022 </tr>
1023 <tr class="line unmod">
1023 <tr class="line unmod">
1024 <td id="rhodecodetestsfunctionaltest_compare_localpy_o84" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o84">84</a></td>
1024 <td id="rhodecodetestsfunctionaltest_compare_localpy_o84" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o84">84</a></td>
1025 <td id="rhodecodetestsfunctionaltest_compare_localpy_n86" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n86">86</a></td>
1025 <td id="rhodecodetestsfunctionaltest_compare_localpy_n86" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n86">86</a></td>
1026 <td class="code ">
1026 <td class="code ">
1027 <pre> response.mustcontain(revisions[backend.alias]['response'])
1027 <pre> response.mustcontain(revisions[backend.alias]['response'])
1028 </pre>
1028 </pre>
1029 </td>
1029 </td>
1030 </tr>
1030 </tr>
1031 <tr class="line unmod">
1031 <tr class="line unmod">
1032 <td id="rhodecodetestsfunctionaltest_compare_localpy_o85" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o85">85</a></td>
1032 <td id="rhodecodetestsfunctionaltest_compare_localpy_o85" class="lineno old"><a href="#rhodecodetestsfunctionaltest_compare_localpy_o85">85</a></td>
1033 <td id="rhodecodetestsfunctionaltest_compare_localpy_n87" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n87">87</a></td>
1033 <td id="rhodecodetestsfunctionaltest_compare_localpy_n87" class="lineno new"><a href="#rhodecodetestsfunctionaltest_compare_localpy_n87">87</a></td>
1034 <td class="code ">
1034 <td class="code ">
1035 <pre></pre>
1035 <pre></pre>
1036 </td>
1036 </td>
1037 </tr>
1037 </tr>
1038 </tbody></table>
1038 </tbody></table>
1039 </div>
1039 </div>
1040 </div>
1040 </div>
1041 </div>
1041 </div>
1042
1042
1043
1043
1044
1044
1045 <!--
1045 <!--
1046 Side-by-side diff
1046 Side-by-side diff
1047 -->
1047 -->
1048
1048
1049 <h2>Side-by-side diff</h2>
1049 <h2>Side-by-side diff</h2>
1050
1050
1051 <div class="box">
1051 <div class="box">
1052 <div class="diff-container" style="overflow-x: hidden">
1052 <div class="diff-container" style="overflow-x: hidden">
1053 <div class="diffblock comm" style="margin:3px; padding:1px">
1053 <div class="diffblock comm" style="margin:3px; padding:1px">
1054 <div class="code-header">
1054 <div class="code-header">
1055 <div class="changeset_header">
1055 <div class="changeset_header">
1056 <div class="changeset_file">
1056 <div class="changeset_file">
1057 <i class="icon-file"></i>
1057 <i class="icon-file"></i>
1058 <a href="/pygments/files/ea295cfb622620f5ba13e226ec531e3fe5296399/tests/test_basic_api.py">tests/test_basic_api.py</a>
1058 <a href="/pygments/files/ea295cfb622620f5ba13e226ec531e3fe5296399/tests/test_basic_api.py">tests/test_basic_api.py</a>
1059 [mode: <span id="selected_mode">python</span>]
1059 [mode: <span id="selected_mode">python</span>]
1060 </div>
1060 </div>
1061 <div class="diff-actions">
1061 <div class="diff-actions">
1062 <a href="/pygments/diff/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff=diff&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;fulldiff=1" class="tooltip" title="Show full diff for this file">
1062 <a href="/pygments/diff/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff=diff&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;fulldiff=1" class="tooltip" title="Show full diff for this file">
1063 <img class="icon" src="/images/icons/page_white_go.png">
1063 <img class="icon" src="/images/icons/page_white_go.png">
1064 </a>
1064 </a>
1065 <a href="/pygments/diff-2way/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff=diff&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;fulldiff=1" class="tooltip" title="Show full side-by-side diff for this file" tt_title="Show full side-by-side diff for this file">
1065 <a href="/pygments/diff-2way/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff=diff&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;fulldiff=1" class="tooltip" title="Show full side-by-side diff for this file" tt_title="Show full side-by-side diff for this file">
1066 <img class="icon" src="/images/icons/application_double.png">
1066 <img class="icon" src="/images/icons/application_double.png">
1067 </a>
1067 </a>
1068 <a href="/pygments/diff/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;diff=raw" class="tooltip" title="Raw diff">
1068 <a href="/pygments/diff/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;diff=raw" class="tooltip" title="Raw diff">
1069 <img class="icon" src="/images/icons/page_white.png">
1069 <img class="icon" src="/images/icons/page_white.png">
1070 </a>
1070 </a>
1071 <a href="/pygments/diff/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;diff=download" class="tooltip" title="Download diff">
1071 <a href="/pygments/diff/tests/test_basic_api.py?diff2=ea295cfb622620f5ba13e226ec531e3fe5296399&amp;diff1=de45f950b669e2d991c4ba512fa6fe450c6616db&amp;diff=download" class="tooltip" title="Download diff">
1072 <img class="icon" src="/images/icons/page_save.png">
1072 <img class="icon" src="/images/icons/page_save.png">
1073 </a>
1073 </a>
1074 <label><input id="ignorews" name="ignorews" type="checkbox" value="1">ignore white space</label>
1074 <label><input id="ignorews" name="ignorews" type="checkbox" value="1">ignore white space</label>
1075 <label><input id="edit_mode" name="edit_mode" type="checkbox" value="1">turn on edit mode</label>
1075 <label><input id="edit_mode" name="edit_mode" type="checkbox" value="1">turn on edit mode</label>
1076
1076
1077 </div>
1077 </div>
1078 <div style="float: right; padding: 0px 10px 0px 0px">
1078 <div style="float: right; padding: 0px 10px 0px 0px">
1079 r1538:de45f950b669 ... r1539:ea295cfb6226
1079 r1538:de45f950b669 ... r1539:ea295cfb6226
1080 </div>
1080 </div>
1081 </div>
1081 </div>
1082 </div>
1082 </div>
1083 <div id="compare"></div>
1083 <div id="compare"></div>
1084 </div>
1084 </div>
1085 </div>
1085 </div>
1086
1086
1087 <script>
1087 <script>
1088 $(document).ready(function () {
1088 $(document).ready(function () {
1089 var example_lines = '1\n2\n3\n4\n5\n6\n7\n8\n9\n \n';
1089 var example_lines = '1\n2\n3\n4\n5\n6\n7\n8\n9\n \n';
1090
1090
1091 $('#compare').mergely({
1091 $('#compare').mergely({
1092 width: 'auto',
1092 width: 'auto',
1093 height: '600',
1093 height: '600',
1094 fgcolor: {a:'#ddffdd',c:'#cccccc',d:'#ffdddd'},
1094 fgcolor: {a:'#ddffdd',c:'#cccccc',d:'#ffdddd'},
1095 bgcolor: '#fff',
1095 bgcolor: '#fff',
1096 viewport: true,
1096 viewport: true,
1097 cmsettings: {mode: 'text/plain', readOnly: true, lineWrapping: false, lineNumbers: true},
1097 cmsettings: {mode: 'text/plain', readOnly: true, lineWrapping: false, lineNumbers: true},
1098 lhs: function(setValue) {
1098 lhs: function(setValue) {
1099 if("False" == "True"){
1099 if("False" == "True"){
1100 setValue('Binary file')
1100 setValue('Binary file')
1101 }
1101 }
1102 else if("MercurialCommit" == "EmptyCommit"){
1102 else if("MercurialCommit" == "EmptyCommit"){
1103 setValue('');
1103 setValue('');
1104 }
1104 }
1105 else{
1105 else{
1106 var left_value = example_lines.slice(0, 10) +
1106 var left_value = example_lines.slice(0, 10) +
1107 '123456789 '.repeat(10) +
1107 '123456789 '.repeat(10) +
1108 '\n'+
1108 '\n'+
1109 example_lines.slice(10, 20);
1109 example_lines.slice(10, 20);
1110 setValue(left_value + example_lines.repeat(9));
1110 setValue(left_value + example_lines.repeat(9));
1111 }
1111 }
1112
1112
1113 },
1113 },
1114 rhs: function(setValue) {
1114 rhs: function(setValue) {
1115 if("False" == "True"){
1115 if("False" == "True"){
1116 setValue('Binary file')
1116 setValue('Binary file')
1117 }
1117 }
1118 else if("MercurialCommit" == "EmptyCommit"){
1118 else if("MercurialCommit" == "EmptyCommit"){
1119 setValue('');
1119 setValue('');
1120 }
1120 }
1121 else{
1121 else{
1122 var right_value = example_lines +
1122 var right_value = example_lines +
1123 example_lines.slice(0, 8) +
1123 example_lines.slice(0, 8) +
1124 'abcdefghi '.repeat(10) +
1124 'abcdefghi '.repeat(10) +
1125 '\n'+
1125 '\n'+
1126 example_lines.slice(8, 20);
1126 example_lines.slice(8, 20);
1127 setValue(right_value + example_lines.repeat(9));
1127 setValue(right_value + example_lines.repeat(9));
1128 }
1128 }
1129 },
1129 },
1130 });
1130 });
1131
1131
1132 var detected_mode = detectCodeMirrorModeFromExt('test_basic_api.py', true);
1132 var detected_mode = detectCodeMirrorModeFromExt('test_basic_api.py', true);
1133 if(detected_mode){
1133 if(detected_mode){
1134 setCodeMirrorMode($('#compare').mergely('cm', 'lhs'), detected_mode);
1134 setCodeMirrorMode($('#compare').mergely('cm', 'lhs'), detected_mode);
1135 setCodeMirrorMode($('#compare').mergely('cm', 'rhs'), detected_mode);
1135 setCodeMirrorMode($('#compare').mergely('cm', 'rhs'), detected_mode);
1136 $('#selected_mode').html(detected_mode);
1136 $('#selected_mode').html(detected_mode);
1137 }
1137 }
1138
1138
1139 $('#ignorews').change(function(e){
1139 $('#ignorews').change(function(e){
1140 var val = e.currentTarget.checked;
1140 var val = e.currentTarget.checked;
1141 $('#compare').mergely('options', {ignorews: val});
1141 $('#compare').mergely('options', {ignorews: val});
1142 $('#compare').mergely('update');
1142 $('#compare').mergely('update');
1143 });
1143 });
1144 $('#edit_mode').change(function(e){
1144 $('#edit_mode').change(function(e){
1145 var val = !e.currentTarget.checked;
1145 var val = !e.currentTarget.checked;
1146 $('#compare').mergely('cm', 'lhs').setOption('readOnly', val);
1146 $('#compare').mergely('cm', 'lhs').setOption('readOnly', val);
1147 $('#compare').mergely('cm', 'rhs').setOption('readOnly', val);
1147 $('#compare').mergely('cm', 'rhs').setOption('readOnly', val);
1148 $('#compare').mergely('update');
1148 $('#compare').mergely('update');
1149 })
1149 })
1150 });
1150 });
1151 </script>
1151 </script>
1152
1152
1153 </div>
1153 </div>
1154
1154
1155 <!-- end examples -->
1155 <!-- end examples -->
1156
1156
1157 </div>
1157 </div>
1158 </div>
1158 </div>
1159 </div>
1159 </div>
1160 </%def>
1160 </%def>
@@ -1,369 +1,369 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title(*args)">
3 <%def name="title(*args)">
4 ${_('{} Files').format(c.repo_name)}
4 ${_('{} Files').format(c.repo_name)}
5 %if hasattr(c,'file'):
5 %if hasattr(c,'file'):
6 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
6 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
7 %endif
7 %endif
8
8
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Files')}
15 ${_('Files')}
16 %if c.file:
16 %if c.file:
17 @ ${h.show_id(c.commit)}
17 @ ${h.show_id(c.commit)}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='files')}
26 ${self.repo_menu(active='files')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30 <script type="text/javascript">
30 <script type="text/javascript">
31 var fileSourcePage = ${c.file_source_page};
31 var fileSourcePage = ${c.file_source_page};
32 var atRef = '${request.GET.get('at', '')}';
32 var atRef = '${request.GET.get('at', '')}';
33
33
34 // global state for fetching metadata
34 // global state for fetching metadata
35 metadataRequest = null;
35 metadataRequest = null;
36
36
37 // global metadata about URL
37 // global metadata about URL
38 filesUrlData = ${h.files_url_data(request)|n};
38 filesUrlData = ${h.files_url_data(request)|n};
39 </script>
39 </script>
40
40
41 <div>
41 <div>
42 <div id="files_data">
42 <div id="files_data">
43 <%include file='files_pjax.mako'/>
43 <%include file='files_pjax.mako'/>
44 </div>
44 </div>
45 </div>
45 </div>
46
46
47 <script type="text/javascript">
47 <script type="text/javascript">
48
48
49 var initFileJS = function () {
49 var initFileJS = function () {
50 var state = getFileState();
50 var state = getFileState();
51
51
52 // select code link event
52 // select code link event
53 $("#hlcode").mouseup(getSelectionLink);
53 $("#hlcode").mouseup(getSelectionLink);
54
54
55 // file history select2 used for history of file, and switch to
55 // file history select2 used for history of file, and switch to
56 var initialCommitData = {
56 var initialCommitData = {
57 at_ref: atRef,
57 at_ref: atRef,
58 id: null,
58 id: null,
59 text: '${c.commit.raw_id}',
59 text: '${c.commit.raw_id}',
60 type: 'sha',
60 type: 'sha',
61 raw_id: '${c.commit.raw_id}',
61 raw_id: '${c.commit.raw_id}',
62 idx: ${c.commit.idx},
62 idx: ${c.commit.idx},
63 files_url: null,
63 files_url: null,
64 };
64 };
65
65
66 // check if we have ref info.
66 // check if we have ref info.
67 var selectedRef = fileTreeRefs[atRef];
67 var selectedRef = fileTreeRefs[atRef];
68 if (selectedRef !== undefined) {
68 if (selectedRef !== undefined) {
69 $.extend(initialCommitData, selectedRef)
69 $.extend(initialCommitData, selectedRef)
70 }
70 }
71
71
72 var loadUrl = pyroutes.url('repo_file_history', {'repo_name': templateContext.repo_name, 'commit_id': state.commit_id,'f_path': state.f_path});
72 var loadUrl = pyroutes.url('repo_file_history', {'repo_name': templateContext.repo_name, 'commit_id': state.commit_id,'f_path': state.f_path});
73 var cacheKey = '__SINGLE_FILE_REFS__';
73 var cacheKey = '__SINGLE_FILE_REFS__';
74 var cachedDataSource = {};
74 var cachedDataSource = {};
75
75
76 var loadRefsData = function (query) {
76 var loadRefsData = function (query) {
77 $.ajax({
77 $.ajax({
78 url: loadUrl,
78 url: loadUrl,
79 data: {},
79 data: {},
80 dataType: 'json',
80 dataType: 'json',
81 type: 'GET',
81 type: 'GET',
82 success: function (data) {
82 success: function (data) {
83 cachedDataSource[cacheKey] = data;
83 cachedDataSource[cacheKey] = data;
84 query.callback({results: data.results});
84 query.callback({results: data.results});
85 }
85 }
86 });
86 });
87 };
87 };
88
88
89 var feedRefsData = function (query, cachedData) {
89 var feedRefsData = function (query, cachedData) {
90 var data = {results: []};
90 var data = {results: []};
91 //filter results
91 //filter results
92 $.each(cachedData.results, function () {
92 $.each(cachedData.results, function () {
93 var section = this.text;
93 var section = this.text;
94 var children = [];
94 var children = [];
95 $.each(this.children, function () {
95 $.each(this.children, function () {
96 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
96 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
97 children.push(this)
97 children.push(this)
98 }
98 }
99 });
99 });
100 data.results.push({
100 data.results.push({
101 'text': section,
101 'text': section,
102 'children': children
102 'children': children
103 })
103 })
104 });
104 });
105
105
106 query.callback(data);
106 query.callback(data);
107 };
107 };
108
108
109 var select2FileHistorySwitcher = function (targetElement, loadUrl, initialData) {
109 var select2FileHistorySwitcher = function (targetElement, loadUrl, initialData) {
110 var formatResult = function (result, container, query) {
110 var formatResult = function (result, container, query) {
111 return formatSelect2SelectionRefs(result);
111 return formatSelect2SelectionRefs(result);
112 };
112 };
113
113
114 var formatSelection = function (data, container) {
114 var formatSelection = function (data, container) {
115 var commit_ref = data;
115 var commit_ref = data;
116
116
117 var tmpl = '';
117 var tmpl = '';
118 if (commit_ref.type === 'sha') {
118 if (commit_ref.type === 'sha') {
119 tmpl = (commit_ref.raw_id || "").substr(0,8);
119 tmpl = (commit_ref.raw_id || "").substr(0,8);
120 } else if (commit_ref.type === 'branch') {
120 } else if (commit_ref.type === 'branch') {
121 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
121 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
122 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
122 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
123 } else if (commit_ref.type === 'tag') {
123 } else if (commit_ref.type === 'tag') {
124 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
124 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
125 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
125 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
126 } else if (commit_ref.type === 'book') {
126 } else if (commit_ref.type === 'book') {
127 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
127 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
128 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
128 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
129 }
129 }
130 var idx = commit_ref.idx || 0;
130 var idx = commit_ref.idx || 0;
131 if (idx !== 0) {
131 if (idx !== 0) {
132 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
132 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
133 }
133 }
134 return tmpl
134 return tmpl
135 };
135 };
136
136
137 $(targetElement).select2({
137 $(targetElement).select2({
138 dropdownAutoWidth: true,
138 dropdownAutoWidth: true,
139 width: "resolve",
139 width: "resolve",
140 containerCssClass: "drop-menu",
140 containerCssClass: "drop-menu",
141 dropdownCssClass: "drop-menu-dropdown",
141 dropdownCssClass: "drop-menu-dropdown",
142 query: function(query) {
142 query: function(query) {
143 var cachedData = cachedDataSource[cacheKey];
143 var cachedData = cachedDataSource[cacheKey];
144 if (cachedData) {
144 if (cachedData) {
145 feedRefsData(query, cachedData)
145 feedRefsData(query, cachedData)
146 } else {
146 } else {
147 loadRefsData(query)
147 loadRefsData(query)
148 }
148 }
149 },
149 },
150 initSelection: function(element, callback) {
150 initSelection: function(element, callback) {
151 callback(initialData);
151 callback(initialData);
152 },
152 },
153 formatResult: formatResult,
153 formatResult: formatResult,
154 formatSelection: formatSelection
154 formatSelection: formatSelection
155 });
155 });
156
156
157 };
157 };
158
158
159 select2FileHistorySwitcher('#file_refs_filter', loadUrl, initialCommitData);
159 select2FileHistorySwitcher('#file_refs_filter', loadUrl, initialCommitData);
160
160
161 $('#file_refs_filter').on('change', function(e) {
161 $('#file_refs_filter').on('change', function(e) {
162 var data = $('#file_refs_filter').select2('data');
162 var data = $('#file_refs_filter').select2('data');
163 var commit_id = data.id;
163 var commit_id = data.id;
164
164
165 if ("${c.annotate}" === "True") {
165 if ("${c.annotate}" === "True") {
166 var url = pyroutes.url('repo_files:annotated',
166 var url = pyroutes.url('repo_files:annotated',
167 {'repo_name': templateContext.repo_name,
167 {'repo_name': templateContext.repo_name,
168 'commit_id': commit_id, 'f_path': state.f_path});
168 'commit_id': commit_id, 'f_path': state.f_path});
169 } else {
169 } else {
170 var url = pyroutes.url('repo_files',
170 var url = pyroutes.url('repo_files',
171 {'repo_name': templateContext.repo_name,
171 {'repo_name': templateContext.repo_name,
172 'commit_id': commit_id, 'f_path': state.f_path});
172 'commit_id': commit_id, 'f_path': state.f_path});
173 }
173 }
174 window.location = url;
174 window.location = url;
175
175
176 });
176 });
177
177
178 // load file short history
178 // load file short history
179 $('#file_history_overview').on('click', function(e) {
179 $('#file_history_overview').on('click', function(e) {
180 e.preventDefault();
180 e.preventDefault();
181 path = state.f_path;
181 path = state.f_path;
182 if (path.indexOf("#") >= 0) {
182 if (path.indexOf("#") >= 0) {
183 path = path.slice(0, path.indexOf("#"));
183 path = path.slice(0, path.indexOf("#"));
184 }
184 }
185 var url = pyroutes.url('repo_changelog_file',
185 var url = pyroutes.url('repo_commits_file',
186 {'repo_name': templateContext.repo_name,
186 {'repo_name': templateContext.repo_name,
187 'commit_id': state.commit_id, 'f_path': path, 'limit': 6});
187 'commit_id': state.commit_id, 'f_path': path, 'limit': 6});
188 $('#file_history_container').show();
188 $('#file_history_container').show();
189 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
189 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
190
190
191 $.pjax({
191 $.pjax({
192 url: url,
192 url: url,
193 container: '#file_history_container',
193 container: '#file_history_container',
194 push: false,
194 push: false,
195 timeout: 5000
195 timeout: 5000
196 });
196 });
197 });
197 });
198
198
199 };
199 };
200
200
201 var initTreeJS = function () {
201 var initTreeJS = function () {
202 var state = getFileState();
202 var state = getFileState();
203 getFilesMetadata();
203 getFilesMetadata();
204
204
205 // fuzzy file filter
205 // fuzzy file filter
206 fileBrowserListeners(state.node_list_url, state.url_base);
206 fileBrowserListeners(state.node_list_url, state.url_base);
207
207
208 // switch to widget
208 // switch to widget
209 var initialCommitData = {
209 var initialCommitData = {
210 at_ref: atRef,
210 at_ref: atRef,
211 id: null,
211 id: null,
212 text: '${c.commit.raw_id}',
212 text: '${c.commit.raw_id}',
213 type: 'sha',
213 type: 'sha',
214 raw_id: '${c.commit.raw_id}',
214 raw_id: '${c.commit.raw_id}',
215 idx: ${c.commit.idx},
215 idx: ${c.commit.idx},
216 files_url: null,
216 files_url: null,
217 };
217 };
218
218
219 // check if we have ref info.
219 // check if we have ref info.
220 var selectedRef = fileTreeRefs[atRef];
220 var selectedRef = fileTreeRefs[atRef];
221 if (selectedRef !== undefined) {
221 if (selectedRef !== undefined) {
222 $.extend(initialCommitData, selectedRef)
222 $.extend(initialCommitData, selectedRef)
223 }
223 }
224
224
225 var loadUrl = pyroutes.url('repo_refs_data', {'repo_name': templateContext.repo_name});
225 var loadUrl = pyroutes.url('repo_refs_data', {'repo_name': templateContext.repo_name});
226 var cacheKey = '__ALL_FILE_REFS__';
226 var cacheKey = '__ALL_FILE_REFS__';
227 var cachedDataSource = {};
227 var cachedDataSource = {};
228
228
229 var loadRefsData = function (query) {
229 var loadRefsData = function (query) {
230 $.ajax({
230 $.ajax({
231 url: loadUrl,
231 url: loadUrl,
232 data: {},
232 data: {},
233 dataType: 'json',
233 dataType: 'json',
234 type: 'GET',
234 type: 'GET',
235 success: function (data) {
235 success: function (data) {
236 cachedDataSource[cacheKey] = data;
236 cachedDataSource[cacheKey] = data;
237 query.callback({results: data.results});
237 query.callback({results: data.results});
238 }
238 }
239 });
239 });
240 };
240 };
241
241
242 var feedRefsData = function (query, cachedData) {
242 var feedRefsData = function (query, cachedData) {
243 var data = {results: []};
243 var data = {results: []};
244 //filter results
244 //filter results
245 $.each(cachedData.results, function () {
245 $.each(cachedData.results, function () {
246 var section = this.text;
246 var section = this.text;
247 var children = [];
247 var children = [];
248 $.each(this.children, function () {
248 $.each(this.children, function () {
249 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
249 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
250 children.push(this)
250 children.push(this)
251 }
251 }
252 });
252 });
253 data.results.push({
253 data.results.push({
254 'text': section,
254 'text': section,
255 'children': children
255 'children': children
256 })
256 })
257 });
257 });
258
258
259 //push the typed in commit idx
259 //push the typed in commit idx
260 if (!isNaN(query.term)) {
260 if (!isNaN(query.term)) {
261 var files_url = pyroutes.url('repo_files',
261 var files_url = pyroutes.url('repo_files',
262 {'repo_name': templateContext.repo_name,
262 {'repo_name': templateContext.repo_name,
263 'commit_id': query.term, 'f_path': state.f_path});
263 'commit_id': query.term, 'f_path': state.f_path});
264
264
265 data.results.push({
265 data.results.push({
266 'text': _gettext('go to numeric commit'),
266 'text': _gettext('go to numeric commit'),
267 'children': [{
267 'children': [{
268 at_ref: null,
268 at_ref: null,
269 id: null,
269 id: null,
270 text: 'r{0}'.format(query.term),
270 text: 'r{0}'.format(query.term),
271 type: 'sha',
271 type: 'sha',
272 raw_id: query.term,
272 raw_id: query.term,
273 idx: query.term,
273 idx: query.term,
274 files_url: files_url,
274 files_url: files_url,
275 }]
275 }]
276 });
276 });
277 }
277 }
278 query.callback(data);
278 query.callback(data);
279 };
279 };
280
280
281 var select2RefFileSwitcher = function (targetElement, loadUrl, initialData) {
281 var select2RefFileSwitcher = function (targetElement, loadUrl, initialData) {
282 var formatResult = function (result, container, query) {
282 var formatResult = function (result, container, query) {
283 return formatSelect2SelectionRefs(result);
283 return formatSelect2SelectionRefs(result);
284 };
284 };
285
285
286 var formatSelection = function (data, container) {
286 var formatSelection = function (data, container) {
287 var commit_ref = data;
287 var commit_ref = data;
288
288
289 var tmpl = '';
289 var tmpl = '';
290 if (commit_ref.type === 'sha') {
290 if (commit_ref.type === 'sha') {
291 tmpl = (commit_ref.raw_id || "").substr(0,8);
291 tmpl = (commit_ref.raw_id || "").substr(0,8);
292 } else if (commit_ref.type === 'branch') {
292 } else if (commit_ref.type === 'branch') {
293 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
293 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
294 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
294 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
295 } else if (commit_ref.type === 'tag') {
295 } else if (commit_ref.type === 'tag') {
296 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
296 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
297 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
297 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
298 } else if (commit_ref.type === 'book') {
298 } else if (commit_ref.type === 'book') {
299 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
299 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
300 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
300 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
301 }
301 }
302
302
303 var idx = commit_ref.idx || 0;
303 var idx = commit_ref.idx || 0;
304 if (idx !== 0) {
304 if (idx !== 0) {
305 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
305 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
306 }
306 }
307 return tmpl
307 return tmpl
308 };
308 };
309
309
310 $(targetElement).select2({
310 $(targetElement).select2({
311 dropdownAutoWidth: true,
311 dropdownAutoWidth: true,
312 width: "resolve",
312 width: "resolve",
313 containerCssClass: "drop-menu",
313 containerCssClass: "drop-menu",
314 dropdownCssClass: "drop-menu-dropdown",
314 dropdownCssClass: "drop-menu-dropdown",
315 query: function(query) {
315 query: function(query) {
316
316
317 var cachedData = cachedDataSource[cacheKey];
317 var cachedData = cachedDataSource[cacheKey];
318 if (cachedData) {
318 if (cachedData) {
319 feedRefsData(query, cachedData)
319 feedRefsData(query, cachedData)
320 } else {
320 } else {
321 loadRefsData(query)
321 loadRefsData(query)
322 }
322 }
323 },
323 },
324 initSelection: function(element, callback) {
324 initSelection: function(element, callback) {
325 callback(initialData);
325 callback(initialData);
326 },
326 },
327 formatResult: formatResult,
327 formatResult: formatResult,
328 formatSelection: formatSelection
328 formatSelection: formatSelection
329 });
329 });
330
330
331 };
331 };
332
332
333 select2RefFileSwitcher('#refs_filter', loadUrl, initialCommitData);
333 select2RefFileSwitcher('#refs_filter', loadUrl, initialCommitData);
334
334
335 $('#refs_filter').on('change', function(e) {
335 $('#refs_filter').on('change', function(e) {
336 var data = $('#refs_filter').select2('data');
336 var data = $('#refs_filter').select2('data');
337 window.location = data.files_url
337 window.location = data.files_url
338 });
338 });
339
339
340 };
340 };
341
341
342 $(document).ready(function() {
342 $(document).ready(function() {
343 timeagoActivate();
343 timeagoActivate();
344
344
345 if ($('#trimmed_message_box').height() < 50) {
345 if ($('#trimmed_message_box').height() < 50) {
346 $('#message_expand').hide();
346 $('#message_expand').hide();
347 }
347 }
348
348
349 $('#message_expand').on('click', function(e) {
349 $('#message_expand').on('click', function(e) {
350 $('#trimmed_message_box').css('max-height', 'none');
350 $('#trimmed_message_box').css('max-height', 'none');
351 $(this).hide();
351 $(this).hide();
352 });
352 });
353
353
354 if (fileSourcePage) {
354 if (fileSourcePage) {
355 initFileJS()
355 initFileJS()
356 } else {
356 } else {
357 initTreeJS()
357 initTreeJS()
358 }
358 }
359
359
360 var search_GET = "${request.GET.get('search','')}";
360 var search_GET = "${request.GET.get('search','')}";
361 if (search_GET === "1") {
361 if (search_GET === "1") {
362 NodeFilter.initFilter();
362 NodeFilter.initFilter();
363 NodeFilter.focus();
363 NodeFilter.focus();
364 }
364 }
365 });
365 });
366
366
367 </script>
367 </script>
368
368
369 </%def> No newline at end of file
369 </%def>
@@ -1,194 +1,194 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 ${_('%s File Edit') % 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()">
15 ${_('Edit file')} @ ${h.show_id(c.commit)}
15 ${_('Edit file')} @ ${h.show_id(c.commit)}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_subnav()">
18 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
19 ${self.repo_menu(active='files')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <% renderer = h.renderer_from_filename(c.f_path)%>
23 <% renderer = h.renderer_from_filename(c.f_path)%>
24 <div class="box">
24 <div class="box">
25 <div class="edit-file-title">
25 <div class="edit-file-title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class="edit-file-fieldset">
28 <div class="edit-file-fieldset">
29 <div class="fieldset">
29 <div class="fieldset">
30 <div id="destination-label" class="left-label">
30 <div id="destination-label" class="left-label">
31 ${_('Path')}:
31 ${_('Path')}:
32 </div>
32 </div>
33 <div class="right-content">
33 <div class="right-content">
34 <div id="specify-custom-path-container">
34 <div id="specify-custom-path-container">
35 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))}</span>
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>
36 </div>
37 </div>
37 </div>
38 </div>
38 </div>
39 </div>
39 </div>
40
40
41 <div class="table">
41 <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)}
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)}
43 <div id="codeblock" class="codeblock" >
43 <div id="codeblock" class="codeblock" >
44 <div class="code-header">
44 <div class="code-header">
45 <div class="stats">
45 <div class="stats">
46 <i class="icon-file"></i>
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>
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>
48 <span class="item">${h.format_byte_size_binary(c.file.size)}</span>
49 <span class="item last">${c.file.mimetype}</span>
49 <span class="item last">${c.file.mimetype}</span>
50 <div class="buttons">
50 <div class="buttons">
51 <a class="btn btn-mini" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">
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')}
52 <i class="icon-time"></i> ${_('history')}
53 </a>
53 </a>
54
54
55 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
55 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
56 % if not c.file.is_binary:
56 % if not c.file.is_binary:
57 %if True:
57 %if True:
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")}
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")}
59 %else:
59 %else:
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")}
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")}
61 %endif
61 %endif
62
62
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)}">
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)}">
64 ${_('raw')}
64 ${_('raw')}
65 </a>
65 </a>
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)}">
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)}">
67 <i class="icon-archive"></i> ${_('download')}
67 <i class="icon-archive"></i> ${_('download')}
68 </a>
68 </a>
69 % endif
69 % endif
70 % endif
70 % endif
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="form">
73 <div class="form">
74 <label for="set_mode">${_('Editing file')}:</label>
74 <label for="set_mode">${_('Editing file')}:</label>
75 ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path}
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}">
76 <input id="filename" type="text" name="filename" value="${c.file.name}">
77
77
78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
79 <label for="line_wrap">${_('line wraps')}</label>
79 <label for="line_wrap">${_('line wraps')}</label>
80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
81
81
82 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
82 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
83 </div>
83 </div>
84 </div>
84 </div>
85 <div id="editor_container">
85 <div id="editor_container">
86 <pre id="editor_pre"></pre>
86 <pre id="editor_pre"></pre>
87 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
87 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
88 <div id="editor_preview" ></div>
88 <div id="editor_preview" ></div>
89 </div>
89 </div>
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="edit-file-fieldset">
93 <div class="edit-file-fieldset">
94 <div class="fieldset">
94 <div class="fieldset">
95 <div id="commit-message-label" class="commit-message-label left-label">
95 <div id="commit-message-label" class="commit-message-label left-label">
96 ${_('Commit Message')}:
96 ${_('Commit Message')}:
97 </div>
97 </div>
98 <div class="right-content">
98 <div class="right-content">
99 <div class="message">
99 <div class="message">
100 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
100 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
101 </div>
101 </div>
102 </div>
102 </div>
103 </div>
103 </div>
104 <div class="pull-right">
104 <div class="pull-right">
105 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
105 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
106 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
106 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
107 </div>
107 </div>
108 </div>
108 </div>
109 ${h.end_form()}
109 ${h.end_form()}
110 </div>
110 </div>
111
111
112 <script type="text/javascript">
112 <script type="text/javascript">
113 $(document).ready(function(){
113 $(document).ready(function(){
114 var renderer = "${renderer}";
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)}";
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);
116 var myCodeMirror = initCodeMirror('editor', reset_url);
117
117
118 var modes_select = $('#set_mode');
118 var modes_select = $('#set_mode');
119 fillCodeMirrorOptions(modes_select);
119 fillCodeMirrorOptions(modes_select);
120
120
121 // try to detect the mode based on the file we edit
121 // try to detect the mode based on the file we edit
122 var mimetype = "${c.file.mimetype}";
122 var mimetype = "${c.file.mimetype}";
123 var detected_mode = detectCodeMirrorMode(
123 var detected_mode = detectCodeMirrorMode(
124 "${c.file.name}", mimetype);
124 "${c.file.name}", mimetype);
125
125
126 if(detected_mode){
126 if(detected_mode){
127 setCodeMirrorMode(myCodeMirror, detected_mode);
127 setCodeMirrorMode(myCodeMirror, detected_mode);
128 $(modes_select).select2("val", mimetype);
128 $(modes_select).select2("val", mimetype);
129 $(modes_select).change();
129 $(modes_select).change();
130 setCodeMirrorMode(myCodeMirror, detected_mode);
130 setCodeMirrorMode(myCodeMirror, detected_mode);
131 }
131 }
132
132
133 var filename_selector = '#filename';
133 var filename_selector = '#filename';
134 var callback = function(filename, mimetype, mode){
134 var callback = function(filename, mimetype, mode){
135 CodeMirrorPreviewEnable(mode);
135 CodeMirrorPreviewEnable(mode);
136 };
136 };
137 // on change of select field set mode
137 // on change of select field set mode
138 setCodeMirrorModeFromSelect(
138 setCodeMirrorModeFromSelect(
139 modes_select, filename_selector, myCodeMirror, callback);
139 modes_select, filename_selector, myCodeMirror, callback);
140
140
141 // on entering the new filename set mode, from given extension
141 // on entering the new filename set mode, from given extension
142 setCodeMirrorModeFromInput(
142 setCodeMirrorModeFromInput(
143 modes_select, filename_selector, myCodeMirror, callback);
143 modes_select, filename_selector, myCodeMirror, callback);
144
144
145 // if the file is renderable set line wraps automatically
145 // if the file is renderable set line wraps automatically
146 if (renderer !== ""){
146 if (renderer !== ""){
147 var line_wrap = 'on';
147 var line_wrap = 'on';
148 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
148 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
149 setCodeMirrorLineWrap(myCodeMirror, true);
149 setCodeMirrorLineWrap(myCodeMirror, true);
150 }
150 }
151 // on select line wraps change the editor
151 // on select line wraps change the editor
152 $('#line_wrap').on('change', function(e){
152 $('#line_wrap').on('change', function(e){
153 var selected = e.currentTarget;
153 var selected = e.currentTarget;
154 var line_wraps = {'on': true, 'off': false}[selected.value];
154 var line_wraps = {'on': true, 'off': false}[selected.value];
155 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
155 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
156 });
156 });
157
157
158 // render preview/edit button
158 // render preview/edit button
159 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
159 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
160 $('#render_preview').removeClass('hidden');
160 $('#render_preview').removeClass('hidden');
161 }
161 }
162 $('#render_preview').on('click', function(e){
162 $('#render_preview').on('click', function(e){
163 if($(this).hasClass('preview')){
163 if($(this).hasClass('preview')){
164 $(this).removeClass('preview');
164 $(this).removeClass('preview');
165 $(this).html("${_('Edit')}");
165 $(this).html("${_('Edit')}");
166 $('#editor_preview').show();
166 $('#editor_preview').show();
167 $(myCodeMirror.getWrapperElement()).hide();
167 $(myCodeMirror.getWrapperElement()).hide();
168
168
169 var possible_renderer = {
169 var possible_renderer = {
170 'rst':'rst',
170 'rst':'rst',
171 'markdown':'markdown',
171 'markdown':'markdown',
172 'gfm': 'markdown'}[myCodeMirror.getMode().name];
172 'gfm': 'markdown'}[myCodeMirror.getMode().name];
173 var _text = myCodeMirror.getValue();
173 var _text = myCodeMirror.getValue();
174 var _renderer = possible_renderer || DEFAULT_RENDERER;
174 var _renderer = possible_renderer || DEFAULT_RENDERER;
175 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
175 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
176 $('#editor_preview').html(_gettext('Loading ...'));
176 $('#editor_preview').html(_gettext('Loading ...'));
177 var url = pyroutes.url('repo_commit_comment_preview',
177 var url = pyroutes.url('repo_commit_comment_preview',
178 {'repo_name': '${c.repo_name}',
178 {'repo_name': '${c.repo_name}',
179 'commit_id': '${c.commit.raw_id}'});
179 'commit_id': '${c.commit.raw_id}'});
180 ajaxPOST(url, post_data, function(o){
180 ajaxPOST(url, post_data, function(o){
181 $('#editor_preview').html(o);
181 $('#editor_preview').html(o);
182 })
182 })
183 }
183 }
184 else{
184 else{
185 $(this).addClass('preview');
185 $(this).addClass('preview');
186 $(this).html("${_('Preview')}");
186 $(this).html("${_('Preview')}");
187 $('#editor_preview').hide();
187 $('#editor_preview').hide();
188 $(myCodeMirror.getWrapperElement()).show();
188 $(myCodeMirror.getWrapperElement()).show();
189 }
189 }
190 });
190 });
191
191
192 })
192 })
193 </script>
193 </script>
194 </%def>
194 </%def>
@@ -1,841 +1,841 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="breadcrumbs_links()">
12 <%def name="breadcrumbs_links()">
13 <span id="pr-title">
13 <span id="pr-title">
14 ${c.pull_request.title}
14 ${c.pull_request.title}
15 %if c.pull_request.is_closed():
15 %if c.pull_request.is_closed():
16 (${_('Closed')})
16 (${_('Closed')})
17 %endif
17 %endif
18 </span>
18 </span>
19 <div id="pr-title-edit" class="input" style="display: none;">
19 <div id="pr-title-edit" class="input" style="display: none;">
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
21 </div>
21 </div>
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_nav()">
24 <%def name="menu_bar_nav()">
25 ${self.menu_items(active='repositories')}
25 ${self.menu_items(active='repositories')}
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_subnav()">
28 <%def name="menu_bar_subnav()">
29 ${self.repo_menu(active='showpullrequest')}
29 ${self.repo_menu(active='showpullrequest')}
30 </%def>
30 </%def>
31
31
32 <%def name="main()">
32 <%def name="main()">
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 </script>
38 </script>
39 <div class="box">
39 <div class="box">
40
40
41 ${self.breadcrumbs()}
41 ${self.breadcrumbs()}
42
42
43 <div class="box pr-summary">
43 <div class="box pr-summary">
44
44
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
47 <div class="pr-details-title">
47 <div class="pr-details-title">
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 %if c.allowed_to_update:
49 %if c.allowed_to_update:
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 % if c.allowed_to_delete:
51 % if c.allowed_to_delete:
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 % else:
56 % else:
57 ${_('Delete')}
57 ${_('Delete')}
58 % endif
58 % endif
59 </div>
59 </div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 %endif
62 %endif
63 </div>
63 </div>
64
64
65 <div id="summary" class="fields pr-details-content">
65 <div id="summary" class="fields pr-details-content">
66 <div class="field">
66 <div class="field">
67 <div class="label-summary">
67 <div class="label-summary">
68 <label>${_('Source')}:</label>
68 <label>${_('Source')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 <div class="pr-origininfo">
71 <div class="pr-origininfo">
72 ## branch link is only valid if it is a branch
72 ## branch link is only valid if it is a branch
73 <span class="tag">
73 <span class="tag">
74 %if c.pull_request.source_ref_parts.type == 'branch':
74 %if c.pull_request.source_ref_parts.type == 'branch':
75 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 %else:
76 %else:
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 %endif
78 %endif
79 </span>
79 </span>
80 <span class="clone-url">
80 <span class="clone-url">
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 </span>
82 </span>
83 <br/>
83 <br/>
84 % if c.ancestor_commit:
84 % if c.ancestor_commit:
85 ${_('Common ancestor')}:
85 ${_('Common ancestor')}:
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
87 % endif
87 % endif
88 </div>
88 </div>
89 %if h.is_hg(c.pull_request.source_repo):
89 %if h.is_hg(c.pull_request.source_repo):
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
91 %elif h.is_git(c.pull_request.source_repo):
91 %elif h.is_git(c.pull_request.source_repo):
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
93 %endif
93 %endif
94
94
95 <div class="">
95 <div class="">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
98 </div>
98 </div>
99
99
100 </div>
100 </div>
101 </div>
101 </div>
102 <div class="field">
102 <div class="field">
103 <div class="label-summary">
103 <div class="label-summary">
104 <label>${_('Target')}:</label>
104 <label>${_('Target')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 <div class="pr-targetinfo">
107 <div class="pr-targetinfo">
108 ## branch link is only valid if it is a branch
108 ## branch link is only valid if it is a branch
109 <span class="tag">
109 <span class="tag">
110 %if c.pull_request.target_ref_parts.type == 'branch':
110 %if c.pull_request.target_ref_parts.type == 'branch':
111 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
112 %else:
112 %else:
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
114 %endif
114 %endif
115 </span>
115 </span>
116 <span class="clone-url">
116 <span class="clone-url">
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
118 </span>
118 </span>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 ## Link to the shadow repository.
123 ## Link to the shadow repository.
124 <div class="field">
124 <div class="field">
125 <div class="label-summary">
125 <div class="label-summary">
126 <label>${_('Merge')}:</label>
126 <label>${_('Merge')}:</label>
127 </div>
127 </div>
128 <div class="input">
128 <div class="input">
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
130 %if h.is_hg(c.pull_request.target_repo):
130 %if h.is_hg(c.pull_request.target_repo):
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
132 %elif h.is_git(c.pull_request.target_repo):
132 %elif h.is_git(c.pull_request.target_repo):
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 %endif
134 %endif
135 <div class="">
135 <div class="">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
138 </div>
138 </div>
139 % else:
139 % else:
140 <div class="">
140 <div class="">
141 ${_('Shadow repository data not available')}.
141 ${_('Shadow repository data not available')}.
142 </div>
142 </div>
143 % endif
143 % endif
144 </div>
144 </div>
145 </div>
145 </div>
146
146
147 <div class="field">
147 <div class="field">
148 <div class="label-summary">
148 <div class="label-summary">
149 <label>${_('Review')}:</label>
149 <label>${_('Review')}:</label>
150 </div>
150 </div>
151 <div class="input">
151 <div class="input">
152 %if c.pull_request_review_status:
152 %if c.pull_request_review_status:
153 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
153 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
154 <span class="changeset-status-lbl tooltip">
154 <span class="changeset-status-lbl tooltip">
155 %if c.pull_request.is_closed():
155 %if c.pull_request.is_closed():
156 ${_('Closed')},
156 ${_('Closed')},
157 %endif
157 %endif
158 ${h.commit_status_lbl(c.pull_request_review_status)}
158 ${h.commit_status_lbl(c.pull_request_review_status)}
159 </span>
159 </span>
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
161 %endif
161 %endif
162 </div>
162 </div>
163 </div>
163 </div>
164 <div class="field">
164 <div class="field">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
166 <label>${_('Description')}:</label>
166 <label>${_('Description')}:</label>
167 </div>
167 </div>
168 <div id="pr-desc" class="input">
168 <div id="pr-desc" class="input">
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
170 </div>
170 </div>
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
174 </div>
174 </div>
175 </div>
175 </div>
176
176
177 <div class="field">
177 <div class="field">
178 <div class="label-summary">
178 <div class="label-summary">
179 <label>${_('Versions')}:</label>
179 <label>${_('Versions')}:</label>
180 </div>
180 </div>
181
181
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
184
184
185 <div class="pr-versions">
185 <div class="pr-versions">
186 % if c.show_version_changes:
186 % if c.show_version_changes:
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
191 data-toggle-off="${_('Hide all versions of this pull request')}">
191 data-toggle-off="${_('Hide all versions of this pull request')}">
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
193 </a>
193 </a>
194 <table>
194 <table>
195 ## SHOW ALL VERSIONS OF PR
195 ## SHOW ALL VERSIONS OF PR
196 <% ver_pr = None %>
196 <% ver_pr = None %>
197
197
198 % for data in reversed(list(enumerate(c.versions, 1))):
198 % for data in reversed(list(enumerate(c.versions, 1))):
199 <% ver_pos = data[0] %>
199 <% ver_pos = data[0] %>
200 <% ver = data[1] %>
200 <% ver = data[1] %>
201 <% ver_pr = ver.pull_request_version_id %>
201 <% ver_pr = ver.pull_request_version_id %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
203
203
204 <tr class="version-pr" style="display: ${display_row}">
204 <tr class="version-pr" style="display: ${display_row}">
205 <td>
205 <td>
206 <code>
206 <code>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
208 </code>
208 </code>
209 </td>
209 </td>
210 <td>
210 <td>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 </td>
213 </td>
214 <td>
214 <td>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
216 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
216 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
217 </div>
217 </div>
218 </td>
218 </td>
219 <td>
219 <td>
220 % if c.at_version_num != ver_pr:
220 % if c.at_version_num != ver_pr:
221 <i class="icon-comment"></i>
221 <i class="icon-comment"></i>
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
224 </code>
224 </code>
225 % endif
225 % endif
226 </td>
226 </td>
227 <td>
227 <td>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
229 </td>
229 </td>
230 <td>
230 <td>
231 ${h.age_component(ver.updated_on, time_is_local=True)}
231 ${h.age_component(ver.updated_on, time_is_local=True)}
232 </td>
232 </td>
233 </tr>
233 </tr>
234 % endfor
234 % endfor
235
235
236 <tr>
236 <tr>
237 <td colspan="6">
237 <td colspan="6">
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
239 data-label-text-locked="${_('select versions to show changes')}"
239 data-label-text-locked="${_('select versions to show changes')}"
240 data-label-text-diff="${_('show changes between versions')}"
240 data-label-text-diff="${_('show changes between versions')}"
241 data-label-text-show="${_('show pull request for this version')}"
241 data-label-text-show="${_('show pull request for this version')}"
242 >
242 >
243 ${_('select versions to show changes')}
243 ${_('select versions to show changes')}
244 </button>
244 </button>
245 </td>
245 </td>
246 </tr>
246 </tr>
247
247
248 ## show comment/inline comments summary
248 ## show comment/inline comments summary
249 <%def name="comments_summary()">
249 <%def name="comments_summary()">
250 <tr>
250 <tr>
251 <td colspan="6" class="comments-summary-td">
251 <td colspan="6" class="comments-summary-td">
252
252
253 % if c.at_version:
253 % if c.at_version:
254 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
254 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
255 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
255 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
256 ${_('Comments at this version')}:
256 ${_('Comments at this version')}:
257 % else:
257 % else:
258 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
258 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
259 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
259 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
260 ${_('Comments for this pull request')}:
260 ${_('Comments for this pull request')}:
261 % endif
261 % endif
262
262
263
263
264 %if general_comm_count_ver:
264 %if general_comm_count_ver:
265 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
265 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
266 %else:
266 %else:
267 ${_("%d General ") % general_comm_count_ver}
267 ${_("%d General ") % general_comm_count_ver}
268 %endif
268 %endif
269
269
270 %if inline_comm_count_ver:
270 %if inline_comm_count_ver:
271 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
271 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
272 %else:
272 %else:
273 , ${_("%d Inline") % inline_comm_count_ver}
273 , ${_("%d Inline") % inline_comm_count_ver}
274 %endif
274 %endif
275
275
276 %if outdated_comm_count_ver:
276 %if outdated_comm_count_ver:
277 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
277 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
278 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
278 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
279 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
279 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
280 %else:
280 %else:
281 , ${_("%d Outdated") % outdated_comm_count_ver}
281 , ${_("%d Outdated") % outdated_comm_count_ver}
282 %endif
282 %endif
283 </td>
283 </td>
284 </tr>
284 </tr>
285 </%def>
285 </%def>
286 ${comments_summary()}
286 ${comments_summary()}
287 </table>
287 </table>
288 % else:
288 % else:
289 <div class="input">
289 <div class="input">
290 ${_('Pull request versions not available')}.
290 ${_('Pull request versions not available')}.
291 </div>
291 </div>
292 <div>
292 <div>
293 <table>
293 <table>
294 ${comments_summary()}
294 ${comments_summary()}
295 </table>
295 </table>
296 </div>
296 </div>
297 % endif
297 % endif
298 </div>
298 </div>
299 </div>
299 </div>
300
300
301 <div id="pr-save" class="field" style="display: none;">
301 <div id="pr-save" class="field" style="display: none;">
302 <div class="label-summary"></div>
302 <div class="label-summary"></div>
303 <div class="input">
303 <div class="input">
304 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
304 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
305 </div>
305 </div>
306 </div>
306 </div>
307 </div>
307 </div>
308 </div>
308 </div>
309 <div>
309 <div>
310 ## AUTHOR
310 ## AUTHOR
311 <div class="reviewers-title block-right">
311 <div class="reviewers-title block-right">
312 <div class="pr-details-title">
312 <div class="pr-details-title">
313 ${_('Author of this pull request')}
313 ${_('Author of this pull request')}
314 </div>
314 </div>
315 </div>
315 </div>
316 <div class="block-right pr-details-content reviewers">
316 <div class="block-right pr-details-content reviewers">
317 <ul class="group_members">
317 <ul class="group_members">
318 <li>
318 <li>
319 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
319 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
320 </li>
320 </li>
321 </ul>
321 </ul>
322 </div>
322 </div>
323
323
324 ## REVIEW RULES
324 ## REVIEW RULES
325 <div id="review_rules" style="display: none" class="reviewers-title block-right">
325 <div id="review_rules" style="display: none" class="reviewers-title block-right">
326 <div class="pr-details-title">
326 <div class="pr-details-title">
327 ${_('Reviewer rules')}
327 ${_('Reviewer rules')}
328 %if c.allowed_to_update:
328 %if c.allowed_to_update:
329 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
329 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
330 %endif
330 %endif
331 </div>
331 </div>
332 <div class="pr-reviewer-rules">
332 <div class="pr-reviewer-rules">
333 ## review rules will be appended here, by default reviewers logic
333 ## review rules will be appended here, by default reviewers logic
334 </div>
334 </div>
335 <input id="review_data" type="hidden" name="review_data" value="">
335 <input id="review_data" type="hidden" name="review_data" value="">
336 </div>
336 </div>
337
337
338 ## REVIEWERS
338 ## REVIEWERS
339 <div class="reviewers-title block-right">
339 <div class="reviewers-title block-right">
340 <div class="pr-details-title">
340 <div class="pr-details-title">
341 ${_('Pull request reviewers')}
341 ${_('Pull request reviewers')}
342 %if c.allowed_to_update:
342 %if c.allowed_to_update:
343 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
343 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
344 %endif
344 %endif
345 </div>
345 </div>
346 </div>
346 </div>
347 <div id="reviewers" class="block-right pr-details-content reviewers">
347 <div id="reviewers" class="block-right pr-details-content reviewers">
348
348
349 ## members redering block
349 ## members redering block
350 <input type="hidden" name="__start__" value="review_members:sequence">
350 <input type="hidden" name="__start__" value="review_members:sequence">
351 <ul id="review_members" class="group_members">
351 <ul id="review_members" class="group_members">
352
352
353 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
353 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
354 <script>
354 <script>
355 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
355 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
356 var status = "${(status[0][1].status if status else 'not_reviewed')}";
356 var status = "${(status[0][1].status if status else 'not_reviewed')}";
357 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
357 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
358 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
358 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
359
359
360 var entry = renderTemplate('reviewMemberEntry', {
360 var entry = renderTemplate('reviewMemberEntry', {
361 'member': member,
361 'member': member,
362 'mandatory': member.mandatory,
362 'mandatory': member.mandatory,
363 'reasons': member.reasons,
363 'reasons': member.reasons,
364 'allowed_to_update': allowed_to_update,
364 'allowed_to_update': allowed_to_update,
365 'review_status': status,
365 'review_status': status,
366 'review_status_label': status_lbl,
366 'review_status_label': status_lbl,
367 'user_group': member.user_group,
367 'user_group': member.user_group,
368 'create': false
368 'create': false
369 });
369 });
370 $('#review_members').append(entry)
370 $('#review_members').append(entry)
371 </script>
371 </script>
372
372
373 % endfor
373 % endfor
374
374
375 </ul>
375 </ul>
376 <input type="hidden" name="__end__" value="review_members:sequence">
376 <input type="hidden" name="__end__" value="review_members:sequence">
377 ## end members redering block
377 ## end members redering block
378
378
379 %if not c.pull_request.is_closed():
379 %if not c.pull_request.is_closed():
380 <div id="add_reviewer" class="ac" style="display: none;">
380 <div id="add_reviewer" class="ac" style="display: none;">
381 %if c.allowed_to_update:
381 %if c.allowed_to_update:
382 % if not c.forbid_adding_reviewers:
382 % if not c.forbid_adding_reviewers:
383 <div id="add_reviewer_input" class="reviewer_ac">
383 <div id="add_reviewer_input" class="reviewer_ac">
384 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
384 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
385 <div id="reviewers_container"></div>
385 <div id="reviewers_container"></div>
386 </div>
386 </div>
387 % endif
387 % endif
388 <div class="pull-right">
388 <div class="pull-right">
389 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
389 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
390 </div>
390 </div>
391 %endif
391 %endif
392 </div>
392 </div>
393 %endif
393 %endif
394 </div>
394 </div>
395 </div>
395 </div>
396 </div>
396 </div>
397 <div class="box">
397 <div class="box">
398 ##DIFF
398 ##DIFF
399 <div class="table" >
399 <div class="table" >
400 <div id="changeset_compare_view_content">
400 <div id="changeset_compare_view_content">
401 ##CS
401 ##CS
402 % if c.missing_requirements:
402 % if c.missing_requirements:
403 <div class="box">
403 <div class="box">
404 <div class="alert alert-warning">
404 <div class="alert alert-warning">
405 <div>
405 <div>
406 <strong>${_('Missing requirements:')}</strong>
406 <strong>${_('Missing requirements:')}</strong>
407 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
407 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
408 </div>
408 </div>
409 </div>
409 </div>
410 </div>
410 </div>
411 % elif c.missing_commits:
411 % elif c.missing_commits:
412 <div class="box">
412 <div class="box">
413 <div class="alert alert-warning">
413 <div class="alert alert-warning">
414 <div>
414 <div>
415 <strong>${_('Missing commits')}:</strong>
415 <strong>${_('Missing commits')}:</strong>
416 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
416 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
417 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
417 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
418 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
418 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
419 </div>
419 </div>
420 </div>
420 </div>
421 </div>
421 </div>
422 % endif
422 % endif
423
423
424 <div class="compare_view_commits_title">
424 <div class="compare_view_commits_title">
425 % if not c.compare_mode:
425 % if not c.compare_mode:
426
426
427 % if c.at_version_pos:
427 % if c.at_version_pos:
428 <h4>
428 <h4>
429 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
429 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
430 </h4>
430 </h4>
431 % endif
431 % endif
432
432
433 <div class="pull-left">
433 <div class="pull-left">
434 <div class="btn-group">
434 <div class="btn-group">
435 <a
435 <a
436 class="btn"
436 class="btn"
437 href="#"
437 href="#"
438 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
438 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
439 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
439 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
440 </a>
440 </a>
441 <a
441 <a
442 class="btn"
442 class="btn"
443 href="#"
443 href="#"
444 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
444 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
445 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
445 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
446 </a>
446 </a>
447 </div>
447 </div>
448 </div>
448 </div>
449
449
450 <div class="pull-right">
450 <div class="pull-right">
451 % if c.allowed_to_update and not c.pull_request.is_closed():
451 % if c.allowed_to_update and not c.pull_request.is_closed():
452 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
452 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
453 % else:
453 % else:
454 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
454 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
455 % endif
455 % endif
456
456
457 </div>
457 </div>
458 % endif
458 % endif
459 </div>
459 </div>
460
460
461 % if not c.missing_commits:
461 % if not c.missing_commits:
462 % if c.compare_mode:
462 % if c.compare_mode:
463 % if c.at_version:
463 % if c.at_version:
464 <h4>
464 <h4>
465 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
465 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
466 </h4>
466 </h4>
467
467
468 <div class="subtitle-compare">
468 <div class="subtitle-compare">
469 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
469 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
470 </div>
470 </div>
471
471
472 <div class="container">
472 <div class="container">
473 <table class="rctable compare_view_commits">
473 <table class="rctable compare_view_commits">
474 <tr>
474 <tr>
475 <th></th>
475 <th></th>
476 <th>${_('Time')}</th>
476 <th>${_('Time')}</th>
477 <th>${_('Author')}</th>
477 <th>${_('Author')}</th>
478 <th>${_('Commit')}</th>
478 <th>${_('Commit')}</th>
479 <th></th>
479 <th></th>
480 <th>${_('Description')}</th>
480 <th>${_('Description')}</th>
481 </tr>
481 </tr>
482
482
483 % for c_type, commit in c.commit_changes:
483 % for c_type, commit in c.commit_changes:
484 % if c_type in ['a', 'r']:
484 % if c_type in ['a', 'r']:
485 <%
485 <%
486 if c_type == 'a':
486 if c_type == 'a':
487 cc_title = _('Commit added in displayed changes')
487 cc_title = _('Commit added in displayed changes')
488 elif c_type == 'r':
488 elif c_type == 'r':
489 cc_title = _('Commit removed in displayed changes')
489 cc_title = _('Commit removed in displayed changes')
490 else:
490 else:
491 cc_title = ''
491 cc_title = ''
492 %>
492 %>
493 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
493 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
494 <td>
494 <td>
495 <div class="commit-change-indicator color-${c_type}-border">
495 <div class="commit-change-indicator color-${c_type}-border">
496 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
496 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
497 ${c_type.upper()}
497 ${c_type.upper()}
498 </div>
498 </div>
499 </div>
499 </div>
500 </td>
500 </td>
501 <td class="td-time">
501 <td class="td-time">
502 ${h.age_component(commit.date)}
502 ${h.age_component(commit.date)}
503 </td>
503 </td>
504 <td class="td-user">
504 <td class="td-user">
505 ${base.gravatar_with_user(commit.author, 16)}
505 ${base.gravatar_with_user(commit.author, 16)}
506 </td>
506 </td>
507 <td class="td-hash">
507 <td class="td-hash">
508 <code>
508 <code>
509 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
509 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
510 r${commit.idx}:${h.short_id(commit.raw_id)}
510 r${commit.idx}:${h.short_id(commit.raw_id)}
511 </a>
511 </a>
512 ${h.hidden('revisions', commit.raw_id)}
512 ${h.hidden('revisions', commit.raw_id)}
513 </code>
513 </code>
514 </td>
514 </td>
515 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
515 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
516 <i class="icon-expand-linked"></i>
516 <i class="icon-expand-linked"></i>
517 </td>
517 </td>
518 <td class="mid td-description">
518 <td class="mid td-description">
519 <div class="log-container truncate-wrap">
519 <div class="log-container truncate-wrap">
520 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
520 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
521 </div>
521 </div>
522 </td>
522 </td>
523 </tr>
523 </tr>
524 % endif
524 % endif
525 % endfor
525 % endfor
526 </table>
526 </table>
527 </div>
527 </div>
528
528
529 % endif
529 % endif
530
530
531 % else:
531 % else:
532 <%include file="/compare/compare_commits.mako" />
532 <%include file="/compare/compare_commits.mako" />
533 % endif
533 % endif
534
534
535 <div class="cs_files">
535 <div class="cs_files">
536 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
536 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
537
537
538 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
538 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
539
539
540 % if c.range_diff_on:
540 % if c.range_diff_on:
541 % for commit in c.commit_ranges:
541 % for commit in c.commit_ranges:
542 ${cbdiffs.render_diffset(
542 ${cbdiffs.render_diffset(
543 c.changes[commit.raw_id],
543 c.changes[commit.raw_id],
544 commit=commit, use_comments=True,
544 commit=commit, use_comments=True,
545 collapse_when_files_over=5,
545 collapse_when_files_over=5,
546 disable_new_comments=True,
546 disable_new_comments=True,
547 deleted_files_comments=c.deleted_files_comments,
547 deleted_files_comments=c.deleted_files_comments,
548 inline_comments=c.inline_comments)}
548 inline_comments=c.inline_comments)}
549 % endfor
549 % endfor
550 % else:
550 % else:
551 ${cbdiffs.render_diffset(
551 ${cbdiffs.render_diffset(
552 c.diffset, use_comments=True,
552 c.diffset, use_comments=True,
553 collapse_when_files_over=30,
553 collapse_when_files_over=30,
554 disable_new_comments=not c.allowed_to_comment,
554 disable_new_comments=not c.allowed_to_comment,
555 deleted_files_comments=c.deleted_files_comments,
555 deleted_files_comments=c.deleted_files_comments,
556 inline_comments=c.inline_comments)}
556 inline_comments=c.inline_comments)}
557 % endif
557 % endif
558
558
559 </div>
559 </div>
560 % else:
560 % else:
561 ## skipping commits we need to clear the view for missing commits
561 ## skipping commits we need to clear the view for missing commits
562 <div style="clear:both;"></div>
562 <div style="clear:both;"></div>
563 % endif
563 % endif
564
564
565 </div>
565 </div>
566 </div>
566 </div>
567
567
568 ## template for inline comment form
568 ## template for inline comment form
569 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
569 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
570
570
571 ## render general comments
571 ## render general comments
572
572
573 <div id="comment-tr-show">
573 <div id="comment-tr-show">
574 <div class="comment">
574 <div class="comment">
575 % if general_outdated_comm_count_ver:
575 % if general_outdated_comm_count_ver:
576 <div class="meta">
576 <div class="meta">
577 % if general_outdated_comm_count_ver == 1:
577 % if general_outdated_comm_count_ver == 1:
578 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
578 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
579 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
579 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
580 % else:
580 % else:
581 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
581 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
582 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
582 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
583 % endif
583 % endif
584 </div>
584 </div>
585 % endif
585 % endif
586 </div>
586 </div>
587 </div>
587 </div>
588
588
589 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
589 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
590
590
591 % if not c.pull_request.is_closed():
591 % if not c.pull_request.is_closed():
592 ## merge status, and merge action
592 ## merge status, and merge action
593 <div class="pull-request-merge">
593 <div class="pull-request-merge">
594 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
594 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
595 </div>
595 </div>
596
596
597 ## main comment form and it status
597 ## main comment form and it status
598 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
598 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
599 pull_request_id=c.pull_request.pull_request_id),
599 pull_request_id=c.pull_request.pull_request_id),
600 c.pull_request_review_status,
600 c.pull_request_review_status,
601 is_pull_request=True, change_status=c.allowed_to_change_status)}
601 is_pull_request=True, change_status=c.allowed_to_change_status)}
602 %endif
602 %endif
603
603
604 <script type="text/javascript">
604 <script type="text/javascript">
605 if (location.hash) {
605 if (location.hash) {
606 var result = splitDelimitedHash(location.hash);
606 var result = splitDelimitedHash(location.hash);
607 var line = $('html').find(result.loc);
607 var line = $('html').find(result.loc);
608 // show hidden comments if we use location.hash
608 // show hidden comments if we use location.hash
609 if (line.hasClass('comment-general')) {
609 if (line.hasClass('comment-general')) {
610 $(line).show();
610 $(line).show();
611 } else if (line.hasClass('comment-inline')) {
611 } else if (line.hasClass('comment-inline')) {
612 $(line).show();
612 $(line).show();
613 var $cb = $(line).closest('.cb');
613 var $cb = $(line).closest('.cb');
614 $cb.removeClass('cb-collapsed')
614 $cb.removeClass('cb-collapsed')
615 }
615 }
616 if (line.length > 0){
616 if (line.length > 0){
617 offsetScroll(line, 70);
617 offsetScroll(line, 70);
618 }
618 }
619 }
619 }
620
620
621 versionController = new VersionController();
621 versionController = new VersionController();
622 versionController.init();
622 versionController.init();
623
623
624 reviewersController = new ReviewersController();
624 reviewersController = new ReviewersController();
625 commitsController = new CommitsController();
625 commitsController = new CommitsController();
626
626
627 $(function(){
627 $(function(){
628
628
629 // custom code mirror
629 // custom code mirror
630 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
630 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
631
631
632 var PRDetails = {
632 var PRDetails = {
633 editButton: $('#open_edit_pullrequest'),
633 editButton: $('#open_edit_pullrequest'),
634 closeButton: $('#close_edit_pullrequest'),
634 closeButton: $('#close_edit_pullrequest'),
635 deleteButton: $('#delete_pullrequest'),
635 deleteButton: $('#delete_pullrequest'),
636 viewFields: $('#pr-desc, #pr-title'),
636 viewFields: $('#pr-desc, #pr-title'),
637 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
637 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
638
638
639 init: function() {
639 init: function() {
640 var that = this;
640 var that = this;
641 this.editButton.on('click', function(e) { that.edit(); });
641 this.editButton.on('click', function(e) { that.edit(); });
642 this.closeButton.on('click', function(e) { that.view(); });
642 this.closeButton.on('click', function(e) { that.view(); });
643 },
643 },
644
644
645 edit: function(event) {
645 edit: function(event) {
646 this.viewFields.hide();
646 this.viewFields.hide();
647 this.editButton.hide();
647 this.editButton.hide();
648 this.deleteButton.hide();
648 this.deleteButton.hide();
649 this.closeButton.show();
649 this.closeButton.show();
650 this.editFields.show();
650 this.editFields.show();
651 codeMirrorInstance.refresh();
651 codeMirrorInstance.refresh();
652 },
652 },
653
653
654 view: function(event) {
654 view: function(event) {
655 this.editButton.show();
655 this.editButton.show();
656 this.deleteButton.show();
656 this.deleteButton.show();
657 this.editFields.hide();
657 this.editFields.hide();
658 this.closeButton.hide();
658 this.closeButton.hide();
659 this.viewFields.show();
659 this.viewFields.show();
660 }
660 }
661 };
661 };
662
662
663 var ReviewersPanel = {
663 var ReviewersPanel = {
664 editButton: $('#open_edit_reviewers'),
664 editButton: $('#open_edit_reviewers'),
665 closeButton: $('#close_edit_reviewers'),
665 closeButton: $('#close_edit_reviewers'),
666 addButton: $('#add_reviewer'),
666 addButton: $('#add_reviewer'),
667 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
667 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
668
668
669 init: function() {
669 init: function() {
670 var self = this;
670 var self = this;
671 this.editButton.on('click', function(e) { self.edit(); });
671 this.editButton.on('click', function(e) { self.edit(); });
672 this.closeButton.on('click', function(e) { self.close(); });
672 this.closeButton.on('click', function(e) { self.close(); });
673 },
673 },
674
674
675 edit: function(event) {
675 edit: function(event) {
676 this.editButton.hide();
676 this.editButton.hide();
677 this.closeButton.show();
677 this.closeButton.show();
678 this.addButton.show();
678 this.addButton.show();
679 this.removeButtons.css('visibility', 'visible');
679 this.removeButtons.css('visibility', 'visible');
680 // review rules
680 // review rules
681 reviewersController.loadReviewRules(
681 reviewersController.loadReviewRules(
682 ${c.pull_request.reviewer_data_json | n});
682 ${c.pull_request.reviewer_data_json | n});
683 },
683 },
684
684
685 close: function(event) {
685 close: function(event) {
686 this.editButton.show();
686 this.editButton.show();
687 this.closeButton.hide();
687 this.closeButton.hide();
688 this.addButton.hide();
688 this.addButton.hide();
689 this.removeButtons.css('visibility', 'hidden');
689 this.removeButtons.css('visibility', 'hidden');
690 // hide review rules
690 // hide review rules
691 reviewersController.hideReviewRules()
691 reviewersController.hideReviewRules()
692 }
692 }
693 };
693 };
694
694
695 PRDetails.init();
695 PRDetails.init();
696 ReviewersPanel.init();
696 ReviewersPanel.init();
697
697
698 showOutdated = function(self){
698 showOutdated = function(self){
699 $('.comment-inline.comment-outdated').show();
699 $('.comment-inline.comment-outdated').show();
700 $('.filediff-outdated').show();
700 $('.filediff-outdated').show();
701 $('.showOutdatedComments').hide();
701 $('.showOutdatedComments').hide();
702 $('.hideOutdatedComments').show();
702 $('.hideOutdatedComments').show();
703 };
703 };
704
704
705 hideOutdated = function(self){
705 hideOutdated = function(self){
706 $('.comment-inline.comment-outdated').hide();
706 $('.comment-inline.comment-outdated').hide();
707 $('.filediff-outdated').hide();
707 $('.filediff-outdated').hide();
708 $('.hideOutdatedComments').hide();
708 $('.hideOutdatedComments').hide();
709 $('.showOutdatedComments').show();
709 $('.showOutdatedComments').show();
710 };
710 };
711
711
712 refreshMergeChecks = function(){
712 refreshMergeChecks = function(){
713 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
713 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
714 $('.pull-request-merge').css('opacity', 0.3);
714 $('.pull-request-merge').css('opacity', 0.3);
715 $('.action-buttons-extra').css('opacity', 0.3);
715 $('.action-buttons-extra').css('opacity', 0.3);
716
716
717 $('.pull-request-merge').load(
717 $('.pull-request-merge').load(
718 loadUrl, function() {
718 loadUrl, function() {
719 $('.pull-request-merge').css('opacity', 1);
719 $('.pull-request-merge').css('opacity', 1);
720
720
721 $('.action-buttons-extra').css('opacity', 1);
721 $('.action-buttons-extra').css('opacity', 1);
722 injectCloseAction();
722 injectCloseAction();
723 }
723 }
724 );
724 );
725 };
725 };
726
726
727 injectCloseAction = function() {
727 injectCloseAction = function() {
728 var closeAction = $('#close-pull-request-action').html();
728 var closeAction = $('#close-pull-request-action').html();
729 var $actionButtons = $('.action-buttons-extra');
729 var $actionButtons = $('.action-buttons-extra');
730 // clear the action before
730 // clear the action before
731 $actionButtons.html("");
731 $actionButtons.html("");
732 $actionButtons.html(closeAction);
732 $actionButtons.html(closeAction);
733 };
733 };
734
734
735 closePullRequest = function (status) {
735 closePullRequest = function (status) {
736 // inject closing flag
736 // inject closing flag
737 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
737 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
738 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
738 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
739 $(generalCommentForm.submitForm).submit();
739 $(generalCommentForm.submitForm).submit();
740 };
740 };
741
741
742 $('#show-outdated-comments').on('click', function(e){
742 $('#show-outdated-comments').on('click', function(e){
743 var button = $(this);
743 var button = $(this);
744 var outdated = $('.comment-outdated');
744 var outdated = $('.comment-outdated');
745
745
746 if (button.html() === "(Show)") {
746 if (button.html() === "(Show)") {
747 button.html("(Hide)");
747 button.html("(Hide)");
748 outdated.show();
748 outdated.show();
749 } else {
749 } else {
750 button.html("(Show)");
750 button.html("(Show)");
751 outdated.hide();
751 outdated.hide();
752 }
752 }
753 });
753 });
754
754
755 $('.show-inline-comments').on('change', function(e){
755 $('.show-inline-comments').on('change', function(e){
756 var show = 'none';
756 var show = 'none';
757 var target = e.currentTarget;
757 var target = e.currentTarget;
758 if(target.checked){
758 if(target.checked){
759 show = ''
759 show = ''
760 }
760 }
761 var boxid = $(target).attr('id_for');
761 var boxid = $(target).attr('id_for');
762 var comments = $('#{0} .inline-comments'.format(boxid));
762 var comments = $('#{0} .inline-comments'.format(boxid));
763 var fn_display = function(idx){
763 var fn_display = function(idx){
764 $(this).css('display', show);
764 $(this).css('display', show);
765 };
765 };
766 $(comments).each(fn_display);
766 $(comments).each(fn_display);
767 var btns = $('#{0} .inline-comments-button'.format(boxid));
767 var btns = $('#{0} .inline-comments-button'.format(boxid));
768 $(btns).each(fn_display);
768 $(btns).each(fn_display);
769 });
769 });
770
770
771 $('#merge_pull_request_form').submit(function() {
771 $('#merge_pull_request_form').submit(function() {
772 if (!$('#merge_pull_request').attr('disabled')) {
772 if (!$('#merge_pull_request').attr('disabled')) {
773 $('#merge_pull_request').attr('disabled', 'disabled');
773 $('#merge_pull_request').attr('disabled', 'disabled');
774 }
774 }
775 return true;
775 return true;
776 });
776 });
777
777
778 $('#edit_pull_request').on('click', function(e){
778 $('#edit_pull_request').on('click', function(e){
779 var title = $('#pr-title-input').val();
779 var title = $('#pr-title-input').val();
780 var description = codeMirrorInstance.getValue();
780 var description = codeMirrorInstance.getValue();
781 var renderer = $('#pr-renderer-input').val();
781 var renderer = $('#pr-renderer-input').val();
782 editPullRequest(
782 editPullRequest(
783 "${c.repo_name}", "${c.pull_request.pull_request_id}",
783 "${c.repo_name}", "${c.pull_request.pull_request_id}",
784 title, description, renderer);
784 title, description, renderer);
785 });
785 });
786
786
787 $('#update_pull_request').on('click', function(e){
787 $('#update_pull_request').on('click', function(e){
788 $(this).attr('disabled', 'disabled');
788 $(this).attr('disabled', 'disabled');
789 $(this).addClass('disabled');
789 $(this).addClass('disabled');
790 $(this).html(_gettext('Saving...'));
790 $(this).html(_gettext('Saving...'));
791 reviewersController.updateReviewers(
791 reviewersController.updateReviewers(
792 "${c.repo_name}", "${c.pull_request.pull_request_id}");
792 "${c.repo_name}", "${c.pull_request.pull_request_id}");
793 });
793 });
794
794
795 $('#update_commits').on('click', function(e){
795 $('#update_commits').on('click', function(e){
796 var isDisabled = !$(e.currentTarget).attr('disabled');
796 var isDisabled = !$(e.currentTarget).attr('disabled');
797 $(e.currentTarget).attr('disabled', 'disabled');
797 $(e.currentTarget).attr('disabled', 'disabled');
798 $(e.currentTarget).addClass('disabled');
798 $(e.currentTarget).addClass('disabled');
799 $(e.currentTarget).removeClass('btn-primary');
799 $(e.currentTarget).removeClass('btn-primary');
800 $(e.currentTarget).text(_gettext('Updating...'));
800 $(e.currentTarget).text(_gettext('Updating...'));
801 if(isDisabled){
801 if(isDisabled){
802 updateCommits(
802 updateCommits(
803 "${c.repo_name}", "${c.pull_request.pull_request_id}");
803 "${c.repo_name}", "${c.pull_request.pull_request_id}");
804 }
804 }
805 });
805 });
806 // fixing issue with caches on firefox
806 // fixing issue with caches on firefox
807 $('#update_commits').removeAttr("disabled");
807 $('#update_commits').removeAttr("disabled");
808
808
809 $('.show-inline-comments').on('click', function(e){
809 $('.show-inline-comments').on('click', function(e){
810 var boxid = $(this).attr('data-comment-id');
810 var boxid = $(this).attr('data-comment-id');
811 var button = $(this);
811 var button = $(this);
812
812
813 if(button.hasClass("comments-visible")) {
813 if(button.hasClass("comments-visible")) {
814 $('#{0} .inline-comments'.format(boxid)).each(function(index){
814 $('#{0} .inline-comments'.format(boxid)).each(function(index){
815 $(this).hide();
815 $(this).hide();
816 });
816 });
817 button.removeClass("comments-visible");
817 button.removeClass("comments-visible");
818 } else {
818 } else {
819 $('#{0} .inline-comments'.format(boxid)).each(function(index){
819 $('#{0} .inline-comments'.format(boxid)).each(function(index){
820 $(this).show();
820 $(this).show();
821 });
821 });
822 button.addClass("comments-visible");
822 button.addClass("comments-visible");
823 }
823 }
824 });
824 });
825
825
826 // register submit callback on commentForm form to track TODOs
826 // register submit callback on commentForm form to track TODOs
827 window.commentFormGlobalSubmitSuccessCallback = function(){
827 window.commentFormGlobalSubmitSuccessCallback = function(){
828 refreshMergeChecks();
828 refreshMergeChecks();
829 };
829 };
830 // initial injection
830 // initial injection
831 injectCloseAction();
831 injectCloseAction();
832
832
833 ReviewerAutoComplete('#user');
833 ReviewerAutoComplete('#user');
834
834
835 })
835 })
836 </script>
836 </script>
837
837
838 </div>
838 </div>
839 </div>
839 </div>
840
840
841 </%def>
841 </%def>
@@ -1,180 +1,180 b''
1 <%namespace name="search" file="/search/search.mako"/>
1 <%namespace name="search" file="/search/search.mako"/>
2
2
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
4 % if has_matched_content:
4 % if has_matched_content:
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
6 % else:
6 % else:
7 ${_('No content matched')} <br/>
7 ${_('No content matched')} <br/>
8 % endif
8 % endif
9
9
10 %if len(matching_lines) > shown_matching_lines:
10 %if len(matching_lines) > shown_matching_lines:
11 <a href="${url}">
11 <a href="${url}">
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
13 </a>
13 </a>
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <div class="search-results">
17 <div class="search-results">
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
19
19
20 %for entry in c.formatted_results:
20 %for entry in c.formatted_results:
21
21
22 <%
22 <%
23 file_content = entry['content_highlight'] or entry['content']
23 file_content = entry['content_highlight'] or entry['content']
24 mimetype = entry.get('mimetype')
24 mimetype = entry.get('mimetype')
25 filepath = entry.get('path')
25 filepath = entry.get('path')
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
28
28
29 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
29 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
30 terms = c.cur_query
30 terms = c.cur_query
31
31
32 if c.searcher.is_es_6:
32 if c.searcher.is_es_6:
33 # use empty terms so we default to markers usage
33 # use empty terms so we default to markers usage
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
35 else:
35 else:
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
37
37
38 shown_matching_lines = 0
38 shown_matching_lines = 0
39 lines_of_interest = set()
39 lines_of_interest = set()
40 for line_number in matching_lines:
40 for line_number in matching_lines:
41 if len(lines_of_interest) < max_lines:
41 if len(lines_of_interest) < max_lines:
42 lines_of_interest |= set(range(
42 lines_of_interest |= set(range(
43 max(line_number - line_context, 0),
43 max(line_number - line_context, 0),
44 min(line_number + line_context, total_lines + 1)))
44 min(line_number + line_context, total_lines + 1)))
45 shown_matching_lines += 1
45 shown_matching_lines += 1
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
47
47
48 html_formatter = h.SearchContentCodeHtmlFormatter(
48 html_formatter = h.SearchContentCodeHtmlFormatter(
49 linenos=True,
49 linenos=True,
50 cssclass="code-highlight",
50 cssclass="code-highlight",
51 url=match_file_url,
51 url=match_file_url,
52 query_terms=terms,
52 query_terms=terms,
53 only_line_numbers=lines_of_interest
53 only_line_numbers=lines_of_interest
54 )
54 )
55
55
56 has_matched_content = len(lines_of_interest) >= 1
56 has_matched_content = len(lines_of_interest) >= 1
57
57
58 %>
58 %>
59 ## search results are additionally filtered, and this check is just a safe gate
59 ## search results are additionally filtered, and this check is just a safe gate
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
61 <div id="codeblock" class="codeblock">
61 <div id="codeblock" class="codeblock">
62 <div class="codeblock-header">
62 <div class="codeblock-header">
63 <h1>
63 <h1>
64 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
64 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
65 ${search.repo_icon(repo_type)}
65 ${search.repo_icon(repo_type)}
66 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
66 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
67 </h1>
67 </h1>
68 ## level 1
68 ## level 1
69 <div class="file-container">
69 <div class="file-container">
70
70
71 <div class="pull-left">
71 <div class="pull-left">
72 <span class="stats-filename">
72 <span class="stats-filename">
73 <strong>
73 <strong>
74 <i class="icon-file-text"></i>
74 <i class="icon-file-text"></i>
75 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
75 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
76 </strong>
76 </strong>
77 </span>
77 </span>
78 <span class="item last">
78 <span class="item last">
79 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${entry['f_path']}" title="${_('Copy the full path')}"></i>
79 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${entry['f_path']}" title="${_('Copy the full path')}"></i>
80 </span>
80 </span>
81 </div>
81 </div>
82
82
83 <div class="pull-right">
83 <div class="pull-right">
84 <div class="buttons">
84 <div class="buttons">
85 <a id="file_history_overview_full" href="${h.route_path('repo_changelog_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
85 <a id="file_history_overview_full" href="${h.route_path('repo_commits_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
86 ${_('Show Full History')}
86 ${_('Show Full History')}
87 </a>
87 </a>
88 | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
88 | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
89 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
89 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
90 | ${h.link_to(_('Download'), h.route_path('repo_file_download',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
90 | ${h.link_to(_('Download'), h.route_path('repo_file_download',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 </div>
94 </div>
95 ## level 2
95 ## level 2
96 <div class="file-container">
96 <div class="file-container">
97
97
98 <div class="pull-left">
98 <div class="pull-left">
99 <span class="stats-first-item">
99 <span class="stats-first-item">
100 %if entry.get('lines'):
100 %if entry.get('lines'):
101 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
101 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
102 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
102 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
103 %endif
103 %endif
104 </span>
104 </span>
105
105
106 <span>
106 <span>
107 %if entry.get('size'):
107 %if entry.get('size'):
108 | ${h.format_byte_size_binary(entry['size'])}
108 | ${h.format_byte_size_binary(entry['size'])}
109 %endif
109 %endif
110 </span>
110 </span>
111
111
112 <span>
112 <span>
113 %if entry.get('mimetype'):
113 %if entry.get('mimetype'):
114 | ${entry.get('mimetype', "unknown mimetype")}
114 | ${entry.get('mimetype', "unknown mimetype")}
115 %endif
115 %endif
116 </span>
116 </span>
117 </div>
117 </div>
118
118
119 <div class="pull-right">
119 <div class="pull-right">
120 <div class="search-tags">
120 <div class="search-tags">
121
121
122 <% repo_group = entry.get('repository_group')%>
122 <% repo_group = entry.get('repository_group')%>
123 ## hiden if in repo group view
123 ## hiden if in repo group view
124 % if repo_group and not c.repo_group_name:
124 % if repo_group and not c.repo_group_name:
125 <span class="tag tag8">
125 <span class="tag tag8">
126 ${search.repo_group_icon()}
126 ${search.repo_group_icon()}
127 <a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
127 <a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
128 </span>
128 </span>
129 % endif
129 % endif
130 ## hiden if in repo view
130 ## hiden if in repo view
131 % if not c.repo_name:
131 % if not c.repo_name:
132 <span class="tag tag8">
132 <span class="tag tag8">
133 ${search.repo_icon(repo_type)}
133 ${search.repo_icon(repo_type)}
134 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
134 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
135 </span>
135 </span>
136 % endif
136 % endif
137 </div>
137 </div>
138 </div>
138 </div>
139
139
140 </div>
140 </div>
141
141
142 </div>
142 </div>
143 <div class="code-body search-code-body">
143 <div class="code-body search-code-body">
144
144
145 ${highlight_text_file(
145 ${highlight_text_file(
146 has_matched_content=has_matched_content,
146 has_matched_content=has_matched_content,
147 file_content=file_content,
147 file_content=file_content,
148 lexer=lexer,
148 lexer=lexer,
149 html_formatter=html_formatter,
149 html_formatter=html_formatter,
150 matching_lines=matching_lines,
150 matching_lines=matching_lines,
151 shown_matching_lines=shown_matching_lines,
151 shown_matching_lines=shown_matching_lines,
152 url=match_file_url,
152 url=match_file_url,
153 use_hl_filter=c.searcher.is_es_6
153 use_hl_filter=c.searcher.is_es_6
154 )}
154 )}
155 </div>
155 </div>
156
156
157 </div>
157 </div>
158 % endif
158 % endif
159 %endfor
159 %endfor
160 </div>
160 </div>
161 %if c.cur_query and c.formatted_results:
161 %if c.cur_query and c.formatted_results:
162 <div class="pagination-wh pagination-left" >
162 <div class="pagination-wh pagination-left" >
163 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
163 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
164 </div>
164 </div>
165 %endif
165 %endif
166
166
167 %if c.cur_query:
167 %if c.cur_query:
168 <script type="text/javascript">
168 <script type="text/javascript">
169 $(function(){
169 $(function(){
170 $(".search-code-body").mark(
170 $(".search-code-body").mark(
171 "${query_mark}",
171 "${query_mark}",
172 {
172 {
173 "className": 'match',
173 "className": 'match',
174 "accuracy": "complementary",
174 "accuracy": "complementary",
175 "ignorePunctuation": ":._(){}[]!'+=".split("")
175 "ignorePunctuation": ":._(){}[]!'+=".split("")
176 }
176 }
177 );
177 );
178 })
178 })
179 </script>
179 </script>
180 %endif
180 %endif
@@ -1,252 +1,252 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
3 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
4 <span class="branchtag tag">
4 <span class="branchtag tag">
5 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
5 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
6 <i class="icon-branch"></i>
6 <i class="icon-branch"></i>
7 % if len(branches) == 1:
7 % if len(branches) == 1:
8 <span>${len(branches)}</span> ${_('Branch')}
8 <span>${len(branches)}</span> ${_('Branch')}
9 % else:
9 % else:
10 <span>${len(branches)}</span> ${_('Branches')}
10 <span>${len(branches)}</span> ${_('Branches')}
11 % endif
11 % endif
12 </a>
12 </a>
13 </span>
13 </span>
14
14
15 %if closed_branches:
15 %if closed_branches:
16 <span class="branchtag tag">
16 <span class="branchtag tag">
17 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-branch"></i>
18 <i class="icon-branch"></i>
19 % if len(closed_branches) == 1:
19 % if len(closed_branches) == 1:
20 <span>${len(closed_branches)}</span> ${_('Closed Branch')}
20 <span>${len(closed_branches)}</span> ${_('Closed Branch')}
21 % else:
21 % else:
22 <span>${len(closed_branches)}</span> ${_('Closed Branches')}
22 <span>${len(closed_branches)}</span> ${_('Closed Branches')}
23 % endif
23 % endif
24 </a>
24 </a>
25 </span>
25 </span>
26 %endif
26 %endif
27
27
28 <span class="tagtag tag">
28 <span class="tagtag tag">
29 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
29 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
30 <i class="icon-tag"></i>
30 <i class="icon-tag"></i>
31 % if len(tags) == 1:
31 % if len(tags) == 1:
32 <span>${len(tags)}</span> ${_('Tag')}
32 <span>${len(tags)}</span> ${_('Tag')}
33 % else:
33 % else:
34 <span>${len(tags)}</span> ${_('Tags')}
34 <span>${len(tags)}</span> ${_('Tags')}
35 % endif
35 % endif
36 </a>
36 </a>
37 </span>
37 </span>
38
38
39 %if bookmarks:
39 %if bookmarks:
40 <span class="booktag tag">
40 <span class="booktag tag">
41 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
41 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
42 <i class="icon-bookmark"></i>
42 <i class="icon-bookmark"></i>
43 % if len(bookmarks) == 1:
43 % if len(bookmarks) == 1:
44 <span>${len(bookmarks)}</span> ${_('Bookmark')}
44 <span>${len(bookmarks)}</span> ${_('Bookmark')}
45 % else:
45 % else:
46 <span>${len(bookmarks)}</span> ${_('Bookmarks')}
46 <span>${len(bookmarks)}</span> ${_('Bookmarks')}
47 % endif
47 % endif
48 </a>
48 </a>
49 </span>
49 </span>
50 %endif
50 %endif
51 </%def>
51 </%def>
52
52
53 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
53 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
54 <% summary = lambda n:{False:'summary-short'}.get(n) %>
54 <% summary = lambda n:{False:'summary-short'}.get(n) %>
55
55
56 <div id="summary-menu-stats" class="summary-detail">
56 <div id="summary-menu-stats" class="summary-detail">
57 <div class="fieldset">
57 <div class="fieldset">
58 <div class="left-content">
58 <div class="left-content">
59 <div class="left-clone">
59 <div class="left-clone">
60 <select id="clone_option" name="clone_option">
60 <select id="clone_option" name="clone_option">
61 <option value="http" selected="selected">HTTP</option>
61 <option value="http" selected="selected">HTTP</option>
62 <option value="http_id">HTTP UID</option>
62 <option value="http_id">HTTP UID</option>
63 % if c.ssh_enabled:
63 % if c.ssh_enabled:
64 <option value="ssh">SSH</option>
64 <option value="ssh">SSH</option>
65 % endif
65 % endif
66 </select>
66 </select>
67 </div>
67 </div>
68
68
69 <div class="right-clone">
69 <div class="right-clone">
70 <%
70 <%
71 maybe_disabled = ''
71 maybe_disabled = ''
72 if h.is_svn_without_proxy(c.rhodecode_db_repo):
72 if h.is_svn_without_proxy(c.rhodecode_db_repo):
73 maybe_disabled = 'disabled'
73 maybe_disabled = 'disabled'
74 %>
74 %>
75
75
76 <span id="clone_option_http">
76 <span id="clone_option_http">
77 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url}"/>
77 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url}"/>
78 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
78 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
79 </span>
79 </span>
80
80
81 <span style="display: none;" id="clone_option_http_id">
81 <span style="display: none;" id="clone_option_http_id">
82 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_id}"/>
82 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_id}"/>
83 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}"></i>
83 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}"></i>
84 </span>
84 </span>
85
85
86 <span style="display: none;" id="clone_option_ssh">
86 <span style="display: none;" id="clone_option_ssh">
87 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_ssh}"/>
87 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_ssh}"/>
88 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_ssh}" title="${_('Copy the clone by ssh url')}"></i>
88 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_ssh}" title="${_('Copy the clone by ssh url')}"></i>
89 </span>
89 </span>
90
90
91 % if maybe_disabled:
91 % if maybe_disabled:
92 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
92 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
93 % endif
93 % endif
94 </div>
94 </div>
95 </div>
95 </div>
96
96
97 <div class="right-content">
97 <div class="right-content">
98 <div class="commit-info">
98 <div class="commit-info">
99 <div class="tags">
99 <div class="tags">
100 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
100 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
101 % if c.rhodecode_repo:
101 % if c.rhodecode_repo:
102 ${refs_counters(
102 ${refs_counters(
103 c.rhodecode_repo.branches,
103 c.rhodecode_repo.branches,
104 c.rhodecode_repo.branches_closed,
104 c.rhodecode_repo.branches_closed,
105 c.rhodecode_repo.tags,
105 c.rhodecode_repo.tags,
106 c.rhodecode_repo.bookmarks)}
106 c.rhodecode_repo.bookmarks)}
107 % else:
107 % else:
108 ## missing requirements can make c.rhodecode_repo None
108 ## missing requirements can make c.rhodecode_repo None
109 ${refs_counters([], [], [], [])}
109 ${refs_counters([], [], [], [])}
110 % endif
110 % endif
111
111
112 ## commits
112 ## commits
113 <span class="tag">
113 <span class="tag">
114 % if commit_rev == -1:
114 % if commit_rev == -1:
115 <i class="icon-tag"></i>
115 <i class="icon-tag"></i>
116 % if commit_rev == -1:
116 % if commit_rev == -1:
117 <span>0</span> ${_('Commit')}
117 <span>0</span> ${_('Commit')}
118 % else:
118 % else:
119 <span>0</span> ${_('Commits')}
119 <span>0</span> ${_('Commits')}
120 % endif
120 % endif
121 % else:
121 % else:
122 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
122 <a href="${h.route_path('repo_commits', repo_name=c.repo_name)}">
123 <i class="icon-tag"></i>
123 <i class="icon-tag"></i>
124 % if commit_rev == 1:
124 % if commit_rev == 1:
125 <span>${commit_rev}</span> ${_('Commit')}
125 <span>${commit_rev}</span> ${_('Commit')}
126 % else:
126 % else:
127 <span>${commit_rev}</span> ${_('Commits')}
127 <span>${commit_rev}</span> ${_('Commits')}
128 % endif
128 % endif
129 </a>
129 </a>
130 % endif
130 % endif
131 </span>
131 </span>
132
132
133 ## forks
133 ## forks
134 <span class="tag">
134 <span class="tag">
135 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
135 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
136 <i class="icon-code-fork"></i>
136 <i class="icon-code-fork"></i>
137 <span>${c.repository_forks}</span> ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>
137 <span>${c.repository_forks}</span> ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>
138 </span>
138 </span>
139 </div>
139 </div>
140 </div>
140 </div>
141 </div>
141 </div>
142 </div>
142 </div>
143 ## owner, description, downloads, statistics
143 ## owner, description, downloads, statistics
144
144
145 ## Owner
145 ## Owner
146 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
146 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
147 <div class="left-label-summary">
147 <div class="left-label-summary">
148 <p>${_('Owner')}</p>
148 <p>${_('Owner')}</p>
149 <div class="right-label-summary">
149 <div class="right-label-summary">
150 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email, 16)}
150 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email, 16)}
151 </div>
151 </div>
152
152
153 </div>
153 </div>
154 </div>
154 </div>
155
155
156 ## Description
156 ## Description
157 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
157 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
158 <div class="left-label-summary">
158 <div class="left-label-summary">
159 <p>${_('Description')}</p>
159 <p>${_('Description')}</p>
160
160
161 <div class="right-label-summary input ${summary(c.show_stats)}">
161 <div class="right-label-summary input ${summary(c.show_stats)}">
162 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
162 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
163 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
163 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
164 </div>
164 </div>
165 </div>
165 </div>
166 </div>
166 </div>
167
167
168 ## Downloads
168 ## Downloads
169 % if show_downloads:
169 % if show_downloads:
170 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
170 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
171 <div class="left-label-summary">
171 <div class="left-label-summary">
172 <p>${_('Downloads')}</p>
172 <p>${_('Downloads')}</p>
173
173
174 <div class="right-label-summary input ${summary(c.show_stats)} downloads">
174 <div class="right-label-summary input ${summary(c.show_stats)} downloads">
175 % if c.rhodecode_repo and len(c.rhodecode_repo.commit_ids) == 0:
175 % if c.rhodecode_repo and len(c.rhodecode_repo.commit_ids) == 0:
176 <span class="disabled">
176 <span class="disabled">
177 ${_('There are no downloads yet')}
177 ${_('There are no downloads yet')}
178 </span>
178 </span>
179 % elif not c.enable_downloads:
179 % elif not c.enable_downloads:
180 <span class="disabled">
180 <span class="disabled">
181 ${_('Downloads are disabled for this repository')}.
181 ${_('Downloads are disabled for this repository')}.
182 </span>
182 </span>
183 % if c.is_super_admin:
183 % if c.is_super_admin:
184 ${h.link_to(_('Enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
184 ${h.link_to(_('Enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
185 % endif
185 % endif
186 % else:
186 % else:
187 <span class="enabled">
187 <span class="enabled">
188 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
188 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
189 <i class="icon-archive"></i> tip.zip
189 <i class="icon-archive"></i> tip.zip
190 ## replaced by some JS on select
190 ## replaced by some JS on select
191 </a>
191 </a>
192 </span>
192 </span>
193 ${h.hidden('download_options')}
193 ${h.hidden('download_options')}
194 % endif
194 % endif
195 </div>
195 </div>
196 </div>
196 </div>
197 </div>
197 </div>
198 % endif
198 % endif
199
199
200 ## Repo size
200 ## Repo size
201 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
201 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
202 <div class="left-label-summary">
202 <div class="left-label-summary">
203 <p>${_('Repository size')}</p>
203 <p>${_('Repository size')}</p>
204
204
205 <div class="right-label-summary">
205 <div class="right-label-summary">
206 <div class="tags">
206 <div class="tags">
207 ## repo size
207 ## repo size
208 % if commit_rev == -1:
208 % if commit_rev == -1:
209 <span class="stats-bullet">0 B</span>
209 <span class="stats-bullet">0 B</span>
210 % else:
210 % else:
211 <span>
211 <span>
212 <a href="#showSize" onclick="calculateSize(); $(this).hide(); return false" id="show-repo-size">Show repository size</a>
212 <a href="#showSize" onclick="calculateSize(); $(this).hide(); return false" id="show-repo-size">Show repository size</a>
213 </span>
213 </span>
214 <span class="stats-bullet" id="repo_size_container" style="display:none">
214 <span class="stats-bullet" id="repo_size_container" style="display:none">
215 ${_('Calculating Repository Size...')}
215 ${_('Calculating Repository Size...')}
216 </span>
216 </span>
217 % endif
217 % endif
218 </div>
218 </div>
219 </div>
219 </div>
220 </div>
220 </div>
221 </div>
221 </div>
222
222
223 ## Statistics
223 ## Statistics
224 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
224 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
225 <div class="left-label-summary">
225 <div class="left-label-summary">
226 <p>${_('Code Statistics')}</p>
226 <p>${_('Code Statistics')}</p>
227
227
228 <div class="right-label-summary input ${summary(c.show_stats)} statistics">
228 <div class="right-label-summary input ${summary(c.show_stats)} statistics">
229 % if c.show_stats:
229 % if c.show_stats:
230 <div id="lang_stats" class="enabled">
230 <div id="lang_stats" class="enabled">
231 <a href="#showSize" onclick="calculateSize(); $('#show-repo-size').hide(); $(this).hide(); return false" id="show-repo-size">Show code statistics</a>
231 <a href="#showSize" onclick="calculateSize(); $('#show-repo-size').hide(); $(this).hide(); return false" id="show-repo-size">Show code statistics</a>
232 </div>
232 </div>
233 % else:
233 % else:
234 <span class="disabled">
234 <span class="disabled">
235 ${_('Statistics are disabled for this repository')}.
235 ${_('Statistics are disabled for this repository')}.
236 </span>
236 </span>
237 % if c.is_super_admin:
237 % if c.is_super_admin:
238 ${h.link_to(_('Enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
238 ${h.link_to(_('Enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
239 % endif
239 % endif
240 % endif
240 % endif
241 </div>
241 </div>
242
242
243 </div>
243 </div>
244 </div>
244 </div>
245
245
246
246
247 </div><!--end summary-detail-->
247 </div><!--end summary-detail-->
248
248
249 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
249 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
250 ${_('Show More')}
250 ${_('Show More')}
251 </div>
251 </div>
252 </%def>
252 </%def>
@@ -1,151 +1,151 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 %if c.repo_commits:
3 %if c.repo_commits:
4 <table class="rctable repo_summary table_disp">
4 <table class="rctable repo_summary table_disp">
5 <tr>
5 <tr>
6
6
7 <th class="status"></th>
7 <th class="status"></th>
8 <th>${_('Commit')}</th>
8 <th>${_('Commit')}</th>
9 <th>${_('Commit message')}</th>
9 <th>${_('Commit message')}</th>
10 <th>${_('Age')}</th>
10 <th>${_('Age')}</th>
11 <th>${_('Author')}</th>
11 <th>${_('Author')}</th>
12 <th colspan="2">${_('Refs')}</th>
12 <th colspan="2">${_('Refs')}</th>
13 </tr>
13 </tr>
14
14
15 ## to speed up lookups cache some functions before the loop
15 ## to speed up lookups cache some functions before the loop
16 <%
16 <%
17 active_patterns = h.get_active_pattern_entries(c.repo_name)
17 active_patterns = h.get_active_pattern_entries(c.repo_name)
18 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
18 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
19 %>
19 %>
20 %for cnt,cs in enumerate(c.repo_commits):
20 %for cnt,cs in enumerate(c.repo_commits):
21 <tr class="parity${cnt%2}">
21 <tr class="parity${cnt%2}">
22
22
23 <td class="td-status">
23 <td class="td-status">
24 %if c.statuses.get(cs.raw_id):
24 %if c.statuses.get(cs.raw_id):
25 <div class="changeset-status-ico shortlog">
25 <div class="changeset-status-ico shortlog">
26 %if c.statuses.get(cs.raw_id)[2]:
26 %if c.statuses.get(cs.raw_id)[2]:
27 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
27 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
28 <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div>
28 <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div>
29 </a>
29 </a>
30 %else:
30 %else:
31 <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(cs.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
31 <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(cs.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
32 <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div>
32 <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div>
33 </a>
33 </a>
34 %endif
34 %endif
35 </div>
35 </div>
36 %else:
36 %else:
37 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
37 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
38 %endif
38 %endif
39 </td>
39 </td>
40 <td class="td-commit">
40 <td class="td-commit">
41 <code>
41 <code>
42 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=cs.raw_id)}">${h.show_id(cs)}</a>
42 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=cs.raw_id)}">${h.show_id(cs)}</a>
43 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${cs.raw_id}" title="${_('Copy the full commit id')}"></i>
43 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${cs.raw_id}" title="${_('Copy the full commit id')}"></i>
44 </code>
44 </code>
45 </td>
45 </td>
46
46
47 <td class="td-description mid">
47 <td class="td-description mid">
48 <div class="log-container truncate-wrap">
48 <div class="log-container truncate-wrap">
49 <div class="message truncate" id="c-${cs.raw_id}">${urlify_commit_message(cs.message, c.repo_name)}</div>
49 <div class="message truncate" id="c-${cs.raw_id}">${urlify_commit_message(cs.message, c.repo_name)}</div>
50 </div>
50 </div>
51 </td>
51 </td>
52
52
53 <td class="td-time">
53 <td class="td-time">
54 ${h.age_component(cs.date)}
54 ${h.age_component(cs.date)}
55 </td>
55 </td>
56 <td class="td-user author">
56 <td class="td-user author">
57 ${base.gravatar_with_user(cs.author)}
57 ${base.gravatar_with_user(cs.author)}
58 </td>
58 </td>
59
59
60 <td class="td-tags">
60 <td class="td-tags">
61 <div class="autoexpand">
61 <div class="autoexpand">
62 %if h.is_hg(c.rhodecode_repo):
62 %if h.is_hg(c.rhodecode_repo):
63 %for book in cs.bookmarks:
63 %for book in cs.bookmarks:
64 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
64 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
65 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
65 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
66 </span>
66 </span>
67 %endfor
67 %endfor
68 %endif
68 %endif
69 ## tags
69 ## tags
70 %for tag in cs.tags:
70 %for tag in cs.tags:
71 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
71 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
72 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
72 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
73 </span>
73 </span>
74 %endfor
74 %endfor
75
75
76 ## branch
76 ## branch
77 %if cs.branch:
77 %if cs.branch:
78 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
78 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
79 <a href="${h.route_path('repo_changelog',repo_name=c.repo_name,_query=dict(branch=cs.branch))}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
79 <a href="${h.route_path('repo_commits',repo_name=c.repo_name,_query=dict(branch=cs.branch))}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
80 </span>
80 </span>
81 %endif
81 %endif
82 </div>
82 </div>
83 </td>
83 </td>
84 <td class="td-comments">
84 <td class="td-comments">
85 <% cs_comments = c.comments.get(cs.raw_id,[]) %>
85 <% cs_comments = c.comments.get(cs.raw_id,[]) %>
86 % if cs_comments:
86 % if cs_comments:
87 <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % cs_comments[0].comment_id)}">
87 <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % cs_comments[0].comment_id)}">
88 <i class="icon-comment"></i> ${len(cs_comments)}
88 <i class="icon-comment"></i> ${len(cs_comments)}
89 </a>
89 </a>
90 % else:
90 % else:
91 <i class="icon-comment"></i> ${len(cs_comments)}
91 <i class="icon-comment"></i> ${len(cs_comments)}
92 % endif
92 % endif
93 </td>
93 </td>
94 </tr>
94 </tr>
95 %endfor
95 %endfor
96
96
97 </table>
97 </table>
98
98
99 <script type="text/javascript">
99 <script type="text/javascript">
100 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 5000, scrollTo: false, push: false});
100 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 5000, scrollTo: false, push: false});
101 $(document).on('pjax:success', function(){ timeagoActivate(); });
101 $(document).on('pjax:success', function(){ timeagoActivate(); });
102 $(document).on('pjax:timeout', function(event) {
102 $(document).on('pjax:timeout', function(event) {
103 // Prevent default timeout redirection behavior
103 // Prevent default timeout redirection behavior
104 event.preventDefault()
104 event.preventDefault()
105 })
105 })
106
106
107 </script>
107 </script>
108
108
109 <div class="pagination-wh pagination-left">
109 <div class="pagination-wh pagination-left">
110 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
110 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
111 </div>
111 </div>
112 %else:
112 %else:
113
113
114 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
114 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
115 <div class="quick_start">
115 <div class="quick_start">
116 <div class="fieldset">
116 <div class="fieldset">
117 <p><b>${_('Add or upload files directly via RhodeCode:')}</b></p>
117 <p><b>${_('Add or upload files directly via RhodeCode:')}</b></p>
118 <div class="pull-left">
118 <div class="pull-left">
119 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Add New File')}</a>
119 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Add New File')}</a>
120 </div>
120 </div>
121 <div class="pull-left">
121 <div class="pull-left">
122 <a href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Upload New File')}</a>
122 <a href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Upload New File')}</a>
123 </div>
123 </div>
124 %endif
124 %endif
125 </div>
125 </div>
126
126
127 %if not h.is_svn(c.rhodecode_repo):
127 %if not h.is_svn(c.rhodecode_repo):
128 <div class="fieldset">
128 <div class="fieldset">
129 <p><b>${_('Push new repo:')}</b></p>
129 <p><b>${_('Push new repo:')}</b></p>
130 <pre>
130 <pre>
131 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
131 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
132 ${c.rhodecode_repo.alias} add README # add first file
132 ${c.rhodecode_repo.alias} add README # add first file
133 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
133 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
134 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
134 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
135 </pre>
135 </pre>
136 </div>
136 </div>
137
137
138 <div class="fieldset">
138 <div class="fieldset">
139 <p><b>${_('Existing repository?')}</b></p>
139 <p><b>${_('Existing repository?')}</b></p>
140 <pre>
140 <pre>
141 %if h.is_git(c.rhodecode_repo):
141 %if h.is_git(c.rhodecode_repo):
142 git remote add origin ${c.clone_repo_url}
142 git remote add origin ${c.clone_repo_url}
143 git push -u origin master
143 git push -u origin master
144 %else:
144 %else:
145 hg push ${c.clone_repo_url}
145 hg push ${c.clone_repo_url}
146 %endif
146 %endif
147 </pre>
147 </pre>
148 </div>
148 </div>
149 %endif
149 %endif
150 </div>
150 </div>
151 %endif
151 %endif
@@ -1,592 +1,596 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 import datetime
21 import datetime
22 import time
22 import time
23
23
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.vcs.backends.base import (
26 from rhodecode.lib.vcs.backends.base import (
27 CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit)
27 CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit)
28 from rhodecode.lib.vcs.exceptions import (
28 from rhodecode.lib.vcs.exceptions import (
29 BranchDoesNotExistError, CommitDoesNotExistError,
29 BranchDoesNotExistError, CommitDoesNotExistError,
30 RepositoryError, EmptyRepositoryError)
30 RepositoryError, EmptyRepositoryError)
31 from rhodecode.lib.vcs.nodes import (
31 from rhodecode.lib.vcs.nodes import (
32 FileNode, AddedFileNodesGenerator,
32 FileNode, AddedFileNodesGenerator,
33 ChangedFileNodesGenerator, RemovedFileNodesGenerator)
33 ChangedFileNodesGenerator, RemovedFileNodesGenerator)
34 from rhodecode.tests import get_new_dir
34 from rhodecode.tests import get_new_dir
35 from rhodecode.tests.vcs.conftest import BackendTestMixin
35 from rhodecode.tests.vcs.conftest import BackendTestMixin
36
36
37
37
38 class TestBaseChangeset:
38 class TestBaseChangeset(object):
39
39
40 def test_is_deprecated(self):
40 def test_is_deprecated(self):
41 from rhodecode.lib.vcs.backends.base import BaseChangeset
41 from rhodecode.lib.vcs.backends.base import BaseChangeset
42 pytest.deprecated_call(BaseChangeset)
42 pytest.deprecated_call(BaseChangeset)
43
43
44
44
45 class TestEmptyCommit(object):
45 class TestEmptyCommit(object):
46
46
47 def test_branch_without_alias_returns_none(self):
47 def test_branch_without_alias_returns_none(self):
48 commit = EmptyCommit()
48 commit = EmptyCommit()
49 assert commit.branch is None
49 assert commit.branch is None
50
50
51
51
52 @pytest.mark.usefixtures("vcs_repository_support")
52 @pytest.mark.usefixtures("vcs_repository_support")
53 class TestCommitsInNonEmptyRepo(BackendTestMixin):
53 class TestCommitsInNonEmptyRepo(BackendTestMixin):
54 recreate_repo_per_test = True
54 recreate_repo_per_test = True
55
55
56 @classmethod
56 @classmethod
57 def _get_commits(cls):
57 def _get_commits(cls):
58 start_date = datetime.datetime(2010, 1, 1, 20)
58 start_date = datetime.datetime(2010, 1, 1, 20)
59 for x in xrange(5):
59 for x in xrange(5):
60 yield {
60 yield {
61 'message': 'Commit %d' % x,
61 'message': 'Commit %d' % x,
62 'author': 'Joe Doe <joe.doe@example.com>',
62 'author': 'Joe Doe <joe.doe@example.com>',
63 'date': start_date + datetime.timedelta(hours=12 * x),
63 'date': start_date + datetime.timedelta(hours=12 * x),
64 'added': [
64 'added': [
65 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
65 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
66 ],
66 ],
67 }
67 }
68
68
69 def test_walk_returns_empty_list_in_case_of_file(self):
69 def test_walk_returns_empty_list_in_case_of_file(self):
70 result = list(self.tip.walk('file_0.txt'))
70 result = list(self.tip.walk('file_0.txt'))
71 assert result == []
71 assert result == []
72
72
73 @pytest.mark.backends("git", "hg")
73 @pytest.mark.backends("git", "hg")
74 def test_new_branch(self):
74 def test_new_branch(self):
75 self.imc.add(FileNode('docs/index.txt',
75 self.imc.add(FileNode('docs/index.txt',
76 content='Documentation\n'))
76 content='Documentation\n'))
77 foobar_tip = self.imc.commit(
77 foobar_tip = self.imc.commit(
78 message=u'New branch: foobar',
78 message=u'New branch: foobar',
79 author=u'joe',
79 author=u'joe',
80 branch='foobar',
80 branch='foobar',
81 )
81 )
82 assert 'foobar' in self.repo.branches
82 assert 'foobar' in self.repo.branches
83 assert foobar_tip.branch == 'foobar'
83 assert foobar_tip.branch == 'foobar'
84 # 'foobar' should be the only branch that contains the new commit
84 # 'foobar' should be the only branch that contains the new commit
85 branch = self.repo.branches.values()
85 branch = self.repo.branches.values()
86 assert branch[0] != branch[1]
86 assert branch[0] != branch[1]
87
87
88 @pytest.mark.backends("git", "hg")
88 @pytest.mark.backends("git", "hg")
89 def test_new_head_in_default_branch(self):
89 def test_new_head_in_default_branch(self):
90 tip = self.repo.get_commit()
90 tip = self.repo.get_commit()
91 self.imc.add(FileNode('docs/index.txt',
91 self.imc.add(FileNode('docs/index.txt',
92 content='Documentation\n'))
92 content='Documentation\n'))
93 foobar_tip = self.imc.commit(
93 foobar_tip = self.imc.commit(
94 message=u'New branch: foobar',
94 message=u'New branch: foobar',
95 author=u'joe',
95 author=u'joe',
96 branch='foobar',
96 branch='foobar',
97 parents=[tip],
97 parents=[tip],
98 )
98 )
99 self.imc.change(FileNode('docs/index.txt',
99 self.imc.change(FileNode('docs/index.txt',
100 content='Documentation\nand more...\n'))
100 content='Documentation\nand more...\n'))
101 newtip = self.imc.commit(
101 newtip = self.imc.commit(
102 message=u'At default branch',
102 message=u'At default branch',
103 author=u'joe',
103 author=u'joe',
104 branch=foobar_tip.branch,
104 branch=foobar_tip.branch,
105 parents=[foobar_tip],
105 parents=[foobar_tip],
106 )
106 )
107
107
108 newest_tip = self.imc.commit(
108 newest_tip = self.imc.commit(
109 message=u'Merged with %s' % foobar_tip.raw_id,
109 message=u'Merged with %s' % foobar_tip.raw_id,
110 author=u'joe',
110 author=u'joe',
111 branch=self.backend_class.DEFAULT_BRANCH_NAME,
111 branch=self.backend_class.DEFAULT_BRANCH_NAME,
112 parents=[newtip, foobar_tip],
112 parents=[newtip, foobar_tip],
113 )
113 )
114
114
115 assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
115 assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
116
116
117 @pytest.mark.backends("git", "hg")
117 @pytest.mark.backends("git", "hg")
118 def test_get_commits_respects_branch_name(self):
118 def test_get_commits_respects_branch_name(self):
119 """
119 """
120 * e1930d0 (HEAD, master) Back in default branch
120 * e1930d0 (HEAD, master) Back in default branch
121 | * e1930d0 (docs) New Branch: docs2
121 | * e1930d0 (docs) New Branch: docs2
122 | * dcc14fa New branch: docs
122 | * dcc14fa New branch: docs
123 |/
123 |/
124 * e63c41a Initial commit
124 * e63c41a Initial commit
125 ...
125 ...
126 * 624d3db Commit 0
126 * 624d3db Commit 0
127
127
128 :return:
128 :return:
129 """
129 """
130 DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME
130 DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME
131 TEST_BRANCH = 'docs'
131 TEST_BRANCH = 'docs'
132 org_tip = self.repo.get_commit()
132 org_tip = self.repo.get_commit()
133
133
134 self.imc.add(FileNode('readme.txt', content='Document\n'))
134 self.imc.add(FileNode('readme.txt', content='Document\n'))
135 initial = self.imc.commit(
135 initial = self.imc.commit(
136 message=u'Initial commit',
136 message=u'Initial commit',
137 author=u'joe',
137 author=u'joe',
138 parents=[org_tip],
138 parents=[org_tip],
139 branch=DEFAULT_BRANCH,)
139 branch=DEFAULT_BRANCH,)
140
140
141 self.imc.add(FileNode('newdoc.txt', content='foobar\n'))
141 self.imc.add(FileNode('newdoc.txt', content='foobar\n'))
142 docs_branch_commit1 = self.imc.commit(
142 docs_branch_commit1 = self.imc.commit(
143 message=u'New branch: docs',
143 message=u'New branch: docs',
144 author=u'joe',
144 author=u'joe',
145 parents=[initial],
145 parents=[initial],
146 branch=TEST_BRANCH,)
146 branch=TEST_BRANCH,)
147
147
148 self.imc.add(FileNode('newdoc2.txt', content='foobar2\n'))
148 self.imc.add(FileNode('newdoc2.txt', content='foobar2\n'))
149 docs_branch_commit2 = self.imc.commit(
149 docs_branch_commit2 = self.imc.commit(
150 message=u'New branch: docs2',
150 message=u'New branch: docs2',
151 author=u'joe',
151 author=u'joe',
152 parents=[docs_branch_commit1],
152 parents=[docs_branch_commit1],
153 branch=TEST_BRANCH,)
153 branch=TEST_BRANCH,)
154
154
155 self.imc.add(FileNode('newfile', content='hello world\n'))
155 self.imc.add(FileNode('newfile', content='hello world\n'))
156 self.imc.commit(
156 self.imc.commit(
157 message=u'Back in default branch',
157 message=u'Back in default branch',
158 author=u'joe',
158 author=u'joe',
159 parents=[initial],
159 parents=[initial],
160 branch=DEFAULT_BRANCH,)
160 branch=DEFAULT_BRANCH,)
161
161
162 default_branch_commits = self.repo.get_commits(branch_name=DEFAULT_BRANCH)
162 default_branch_commits = self.repo.get_commits(branch_name=DEFAULT_BRANCH)
163 assert docs_branch_commit1 not in list(default_branch_commits)
163 assert docs_branch_commit1 not in list(default_branch_commits)
164 assert docs_branch_commit2 not in list(default_branch_commits)
164 assert docs_branch_commit2 not in list(default_branch_commits)
165
165
166 docs_branch_commits = self.repo.get_commits(
166 docs_branch_commits = self.repo.get_commits(
167 start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1],
167 start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1],
168 branch_name=TEST_BRANCH)
168 branch_name=TEST_BRANCH)
169 assert docs_branch_commit1 in list(docs_branch_commits)
169 assert docs_branch_commit1 in list(docs_branch_commits)
170 assert docs_branch_commit2 in list(docs_branch_commits)
170 assert docs_branch_commit2 in list(docs_branch_commits)
171
171
172 @pytest.mark.backends("svn")
172 @pytest.mark.backends("svn")
173 def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn):
173 def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn):
174 repo = vcsbackend_svn['svn-simple-layout']
174 repo = vcsbackend_svn['svn-simple-layout']
175 commits = repo.get_commits(branch_name='trunk')
175 commits = repo.get_commits(branch_name='trunk')
176 commit_indexes = [c.idx for c in commits]
176 commit_indexes = [c.idx for c in commits]
177 assert commit_indexes == [1, 2, 3, 7, 12, 15]
177 assert commit_indexes == [1, 2, 3, 7, 12, 15]
178
178
179 def test_get_commit_by_index(self):
180 for idx in [1, 2, 3, 4]:
181 assert idx == self.repo.get_commit(commit_idx=idx).idx
182
179 def test_get_commit_by_branch(self):
183 def test_get_commit_by_branch(self):
180 for branch, commit_id in self.repo.branches.iteritems():
184 for branch, commit_id in self.repo.branches.iteritems():
181 assert commit_id == self.repo.get_commit(branch).raw_id
185 assert commit_id == self.repo.get_commit(branch).raw_id
182
186
183 def test_get_commit_by_tag(self):
187 def test_get_commit_by_tag(self):
184 for tag, commit_id in self.repo.tags.iteritems():
188 for tag, commit_id in self.repo.tags.iteritems():
185 assert commit_id == self.repo.get_commit(tag).raw_id
189 assert commit_id == self.repo.get_commit(tag).raw_id
186
190
187 def test_get_commit_parents(self):
191 def test_get_commit_parents(self):
188 repo = self.repo
192 repo = self.repo
189 for test_idx in [1, 2, 3]:
193 for test_idx in [1, 2, 3]:
190 commit = repo.get_commit(commit_idx=test_idx - 1)
194 commit = repo.get_commit(commit_idx=test_idx - 1)
191 assert [commit] == repo.get_commit(commit_idx=test_idx).parents
195 assert [commit] == repo.get_commit(commit_idx=test_idx).parents
192
196
193 def test_get_commit_children(self):
197 def test_get_commit_children(self):
194 repo = self.repo
198 repo = self.repo
195 for test_idx in [1, 2, 3]:
199 for test_idx in [1, 2, 3]:
196 commit = repo.get_commit(commit_idx=test_idx + 1)
200 commit = repo.get_commit(commit_idx=test_idx + 1)
197 assert [commit] == repo.get_commit(commit_idx=test_idx).children
201 assert [commit] == repo.get_commit(commit_idx=test_idx).children
198
202
199
203
200 @pytest.mark.usefixtures("vcs_repository_support")
204 @pytest.mark.usefixtures("vcs_repository_support")
201 class TestCommits(BackendTestMixin):
205 class TestCommits(BackendTestMixin):
202 recreate_repo_per_test = False
206 recreate_repo_per_test = False
203
207
204 @classmethod
208 @classmethod
205 def _get_commits(cls):
209 def _get_commits(cls):
206 start_date = datetime.datetime(2010, 1, 1, 20)
210 start_date = datetime.datetime(2010, 1, 1, 20)
207 for x in xrange(5):
211 for x in xrange(5):
208 yield {
212 yield {
209 'message': u'Commit %d' % x,
213 'message': u'Commit %d' % x,
210 'author': u'Joe Doe <joe.doe@example.com>',
214 'author': u'Joe Doe <joe.doe@example.com>',
211 'date': start_date + datetime.timedelta(hours=12 * x),
215 'date': start_date + datetime.timedelta(hours=12 * x),
212 'added': [
216 'added': [
213 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
217 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
214 ],
218 ],
215 }
219 }
216
220
217 def test_simple(self):
221 def test_simple(self):
218 tip = self.repo.get_commit()
222 tip = self.repo.get_commit()
219 assert tip.date, datetime.datetime(2010, 1, 3 == 20)
223 assert tip.date, datetime.datetime(2010, 1, 3 == 20)
220
224
221 def test_simple_serialized_commit(self):
225 def test_simple_serialized_commit(self):
222 tip = self.repo.get_commit()
226 tip = self.repo.get_commit()
223 # json.dumps(tip) uses .__json__() method
227 # json.dumps(tip) uses .__json__() method
224 data = tip.__json__()
228 data = tip.__json__()
225 assert 'branch' in data
229 assert 'branch' in data
226 assert data['revision']
230 assert data['revision']
227
231
228 def test_retrieve_tip(self):
232 def test_retrieve_tip(self):
229 tip = self.repo.get_commit('tip')
233 tip = self.repo.get_commit('tip')
230 assert tip == self.repo.get_commit()
234 assert tip == self.repo.get_commit()
231
235
232 def test_invalid(self):
236 def test_invalid(self):
233 with pytest.raises(CommitDoesNotExistError):
237 with pytest.raises(CommitDoesNotExistError):
234 self.repo.get_commit(commit_idx=123456789)
238 self.repo.get_commit(commit_idx=123456789)
235
239
236 def test_idx(self):
240 def test_idx(self):
237 commit = self.repo[0]
241 commit = self.repo[0]
238 assert commit.idx == 0
242 assert commit.idx == 0
239
243
240 def test_negative_idx(self):
244 def test_negative_idx(self):
241 commit = self.repo.get_commit(commit_idx=-1)
245 commit = self.repo.get_commit(commit_idx=-1)
242 assert commit.idx >= 0
246 assert commit.idx >= 0
243
247
244 def test_revision_is_deprecated(self):
248 def test_revision_is_deprecated(self):
245 def get_revision(commit):
249 def get_revision(commit):
246 return commit.revision
250 return commit.revision
247
251
248 commit = self.repo[0]
252 commit = self.repo[0]
249 pytest.deprecated_call(get_revision, commit)
253 pytest.deprecated_call(get_revision, commit)
250
254
251 def test_size(self):
255 def test_size(self):
252 tip = self.repo.get_commit()
256 tip = self.repo.get_commit()
253 size = 5 * len('Foobar N') # Size of 5 files
257 size = 5 * len('Foobar N') # Size of 5 files
254 assert tip.size == size
258 assert tip.size == size
255
259
256 def test_size_at_commit(self):
260 def test_size_at_commit(self):
257 tip = self.repo.get_commit()
261 tip = self.repo.get_commit()
258 size = 5 * len('Foobar N') # Size of 5 files
262 size = 5 * len('Foobar N') # Size of 5 files
259 assert self.repo.size_at_commit(tip.raw_id) == size
263 assert self.repo.size_at_commit(tip.raw_id) == size
260
264
261 def test_size_at_first_commit(self):
265 def test_size_at_first_commit(self):
262 commit = self.repo[0]
266 commit = self.repo[0]
263 size = len('Foobar N') # Size of 1 file
267 size = len('Foobar N') # Size of 1 file
264 assert self.repo.size_at_commit(commit.raw_id) == size
268 assert self.repo.size_at_commit(commit.raw_id) == size
265
269
266 def test_author(self):
270 def test_author(self):
267 tip = self.repo.get_commit()
271 tip = self.repo.get_commit()
268 assert_text_equal(tip.author, u'Joe Doe <joe.doe@example.com>')
272 assert_text_equal(tip.author, u'Joe Doe <joe.doe@example.com>')
269
273
270 def test_author_name(self):
274 def test_author_name(self):
271 tip = self.repo.get_commit()
275 tip = self.repo.get_commit()
272 assert_text_equal(tip.author_name, u'Joe Doe')
276 assert_text_equal(tip.author_name, u'Joe Doe')
273
277
274 def test_author_email(self):
278 def test_author_email(self):
275 tip = self.repo.get_commit()
279 tip = self.repo.get_commit()
276 assert_text_equal(tip.author_email, u'joe.doe@example.com')
280 assert_text_equal(tip.author_email, u'joe.doe@example.com')
277
281
278 def test_message(self):
282 def test_message(self):
279 tip = self.repo.get_commit()
283 tip = self.repo.get_commit()
280 assert_text_equal(tip.message, u'Commit 4')
284 assert_text_equal(tip.message, u'Commit 4')
281
285
282 def test_diff(self):
286 def test_diff(self):
283 tip = self.repo.get_commit()
287 tip = self.repo.get_commit()
284 diff = tip.diff()
288 diff = tip.diff()
285 assert "+Foobar 4" in diff.raw
289 assert "+Foobar 4" in diff.raw
286
290
287 def test_prev(self):
291 def test_prev(self):
288 tip = self.repo.get_commit()
292 tip = self.repo.get_commit()
289 prev_commit = tip.prev()
293 prev_commit = tip.prev()
290 assert prev_commit.message == 'Commit 3'
294 assert prev_commit.message == 'Commit 3'
291
295
292 def test_prev_raises_on_first_commit(self):
296 def test_prev_raises_on_first_commit(self):
293 commit = self.repo.get_commit(commit_idx=0)
297 commit = self.repo.get_commit(commit_idx=0)
294 with pytest.raises(CommitDoesNotExistError):
298 with pytest.raises(CommitDoesNotExistError):
295 commit.prev()
299 commit.prev()
296
300
297 def test_prev_works_on_second_commit_issue_183(self):
301 def test_prev_works_on_second_commit_issue_183(self):
298 commit = self.repo.get_commit(commit_idx=1)
302 commit = self.repo.get_commit(commit_idx=1)
299 prev_commit = commit.prev()
303 prev_commit = commit.prev()
300 assert prev_commit.idx == 0
304 assert prev_commit.idx == 0
301
305
302 def test_next(self):
306 def test_next(self):
303 commit = self.repo.get_commit(commit_idx=2)
307 commit = self.repo.get_commit(commit_idx=2)
304 next_commit = commit.next()
308 next_commit = commit.next()
305 assert next_commit.message == 'Commit 3'
309 assert next_commit.message == 'Commit 3'
306
310
307 def test_next_raises_on_tip(self):
311 def test_next_raises_on_tip(self):
308 commit = self.repo.get_commit()
312 commit = self.repo.get_commit()
309 with pytest.raises(CommitDoesNotExistError):
313 with pytest.raises(CommitDoesNotExistError):
310 commit.next()
314 commit.next()
311
315
312 def test_get_path_commit(self):
316 def test_get_path_commit(self):
313 commit = self.repo.get_commit()
317 commit = self.repo.get_commit()
314 commit.get_path_commit('file_4.txt')
318 commit.get_path_commit('file_4.txt')
315 assert commit.message == 'Commit 4'
319 assert commit.message == 'Commit 4'
316
320
317 def test_get_filenodes_generator(self):
321 def test_get_filenodes_generator(self):
318 tip = self.repo.get_commit()
322 tip = self.repo.get_commit()
319 filepaths = [node.path for node in tip.get_filenodes_generator()]
323 filepaths = [node.path for node in tip.get_filenodes_generator()]
320 assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
324 assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
321
325
322 def test_get_file_annotate(self):
326 def test_get_file_annotate(self):
323 file_added_commit = self.repo.get_commit(commit_idx=3)
327 file_added_commit = self.repo.get_commit(commit_idx=3)
324 annotations = list(file_added_commit.get_file_annotate('file_3.txt'))
328 annotations = list(file_added_commit.get_file_annotate('file_3.txt'))
325
329
326 line_no, commit_id, commit_loader, line = annotations[0]
330 line_no, commit_id, commit_loader, line = annotations[0]
327
331
328 assert line_no == 1
332 assert line_no == 1
329 assert commit_id == file_added_commit.raw_id
333 assert commit_id == file_added_commit.raw_id
330 assert commit_loader() == file_added_commit
334 assert commit_loader() == file_added_commit
331 assert 'Foobar 3' in line
335 assert 'Foobar 3' in line
332
336
333 def test_get_file_annotate_does_not_exist(self):
337 def test_get_file_annotate_does_not_exist(self):
334 file_added_commit = self.repo.get_commit(commit_idx=2)
338 file_added_commit = self.repo.get_commit(commit_idx=2)
335 # TODO: Should use a specific exception class here?
339 # TODO: Should use a specific exception class here?
336 with pytest.raises(Exception):
340 with pytest.raises(Exception):
337 list(file_added_commit.get_file_annotate('file_3.txt'))
341 list(file_added_commit.get_file_annotate('file_3.txt'))
338
342
339 def test_get_file_annotate_tip(self):
343 def test_get_file_annotate_tip(self):
340 tip = self.repo.get_commit()
344 tip = self.repo.get_commit()
341 commit = self.repo.get_commit(commit_idx=3)
345 commit = self.repo.get_commit(commit_idx=3)
342 expected_values = list(commit.get_file_annotate('file_3.txt'))
346 expected_values = list(commit.get_file_annotate('file_3.txt'))
343 annotations = list(tip.get_file_annotate('file_3.txt'))
347 annotations = list(tip.get_file_annotate('file_3.txt'))
344
348
345 # Note: Skip index 2 because the loader function is not the same
349 # Note: Skip index 2 because the loader function is not the same
346 for idx in (0, 1, 3):
350 for idx in (0, 1, 3):
347 assert annotations[0][idx] == expected_values[0][idx]
351 assert annotations[0][idx] == expected_values[0][idx]
348
352
349 def test_get_commits_is_ordered_by_date(self):
353 def test_get_commits_is_ordered_by_date(self):
350 commits = self.repo.get_commits()
354 commits = self.repo.get_commits()
351 assert isinstance(commits, CollectionGenerator)
355 assert isinstance(commits, CollectionGenerator)
352 assert len(commits) == 0 or len(commits) != 0
356 assert len(commits) == 0 or len(commits) != 0
353 commits = list(commits)
357 commits = list(commits)
354 ordered_by_date = sorted(commits, key=lambda commit: commit.date)
358 ordered_by_date = sorted(commits, key=lambda commit: commit.date)
355 assert commits == ordered_by_date
359 assert commits == ordered_by_date
356
360
357 def test_get_commits_respects_start(self):
361 def test_get_commits_respects_start(self):
358 second_id = self.repo.commit_ids[1]
362 second_id = self.repo.commit_ids[1]
359 commits = self.repo.get_commits(start_id=second_id)
363 commits = self.repo.get_commits(start_id=second_id)
360 assert isinstance(commits, CollectionGenerator)
364 assert isinstance(commits, CollectionGenerator)
361 commits = list(commits)
365 commits = list(commits)
362 assert len(commits) == 4
366 assert len(commits) == 4
363
367
364 def test_get_commits_includes_start_commit(self):
368 def test_get_commits_includes_start_commit(self):
365 second_id = self.repo.commit_ids[1]
369 second_id = self.repo.commit_ids[1]
366 commits = self.repo.get_commits(start_id=second_id)
370 commits = self.repo.get_commits(start_id=second_id)
367 assert isinstance(commits, CollectionGenerator)
371 assert isinstance(commits, CollectionGenerator)
368 commits = list(commits)
372 commits = list(commits)
369 assert commits[0].raw_id == second_id
373 assert commits[0].raw_id == second_id
370
374
371 def test_get_commits_respects_end(self):
375 def test_get_commits_respects_end(self):
372 second_id = self.repo.commit_ids[1]
376 second_id = self.repo.commit_ids[1]
373 commits = self.repo.get_commits(end_id=second_id)
377 commits = self.repo.get_commits(end_id=second_id)
374 assert isinstance(commits, CollectionGenerator)
378 assert isinstance(commits, CollectionGenerator)
375 commits = list(commits)
379 commits = list(commits)
376 assert commits[-1].raw_id == second_id
380 assert commits[-1].raw_id == second_id
377 assert len(commits) == 2
381 assert len(commits) == 2
378
382
379 def test_get_commits_respects_both_start_and_end(self):
383 def test_get_commits_respects_both_start_and_end(self):
380 second_id = self.repo.commit_ids[1]
384 second_id = self.repo.commit_ids[1]
381 third_id = self.repo.commit_ids[2]
385 third_id = self.repo.commit_ids[2]
382 commits = self.repo.get_commits(start_id=second_id, end_id=third_id)
386 commits = self.repo.get_commits(start_id=second_id, end_id=third_id)
383 assert isinstance(commits, CollectionGenerator)
387 assert isinstance(commits, CollectionGenerator)
384 commits = list(commits)
388 commits = list(commits)
385 assert len(commits) == 2
389 assert len(commits) == 2
386
390
387 def test_get_commits_on_empty_repo_raises_EmptyRepository_error(self):
391 def test_get_commits_on_empty_repo_raises_EmptyRepository_error(self):
388 repo_path = get_new_dir(str(time.time()))
392 repo_path = get_new_dir(str(time.time()))
389 repo = self.Backend(repo_path, create=True)
393 repo = self.Backend(repo_path, create=True)
390
394
391 with pytest.raises(EmptyRepositoryError):
395 with pytest.raises(EmptyRepositoryError):
392 list(repo.get_commits(start_id='foobar'))
396 list(repo.get_commits(start_id='foobar'))
393
397
394 def test_get_commits_respects_hidden(self):
398 def test_get_commits_respects_hidden(self):
395 commits = self.repo.get_commits(show_hidden=True)
399 commits = self.repo.get_commits(show_hidden=True)
396 assert isinstance(commits, CollectionGenerator)
400 assert isinstance(commits, CollectionGenerator)
397 assert len(commits) == 5
401 assert len(commits) == 5
398
402
399 def test_get_commits_includes_end_commit(self):
403 def test_get_commits_includes_end_commit(self):
400 second_id = self.repo.commit_ids[1]
404 second_id = self.repo.commit_ids[1]
401 commits = self.repo.get_commits(end_id=second_id)
405 commits = self.repo.get_commits(end_id=second_id)
402 assert isinstance(commits, CollectionGenerator)
406 assert isinstance(commits, CollectionGenerator)
403 assert len(commits) == 2
407 assert len(commits) == 2
404 commits = list(commits)
408 commits = list(commits)
405 assert commits[-1].raw_id == second_id
409 assert commits[-1].raw_id == second_id
406
410
407 def test_get_commits_respects_start_date(self):
411 def test_get_commits_respects_start_date(self):
408 start_date = datetime.datetime(2010, 1, 2)
412 start_date = datetime.datetime(2010, 1, 2)
409 commits = self.repo.get_commits(start_date=start_date)
413 commits = self.repo.get_commits(start_date=start_date)
410 assert isinstance(commits, CollectionGenerator)
414 assert isinstance(commits, CollectionGenerator)
411 # Should be 4 commits after 2010-01-02 00:00:00
415 # Should be 4 commits after 2010-01-02 00:00:00
412 assert len(commits) == 4
416 assert len(commits) == 4
413 for c in commits:
417 for c in commits:
414 assert c.date >= start_date
418 assert c.date >= start_date
415
419
416 def test_get_commits_respects_start_date_with_branch(self):
420 def test_get_commits_respects_start_date_with_branch(self):
417 start_date = datetime.datetime(2010, 1, 2)
421 start_date = datetime.datetime(2010, 1, 2)
418 commits = self.repo.get_commits(
422 commits = self.repo.get_commits(
419 start_date=start_date, branch_name=self.repo.DEFAULT_BRANCH_NAME)
423 start_date=start_date, branch_name=self.repo.DEFAULT_BRANCH_NAME)
420 assert isinstance(commits, CollectionGenerator)
424 assert isinstance(commits, CollectionGenerator)
421 # Should be 4 commits after 2010-01-02 00:00:00
425 # Should be 4 commits after 2010-01-02 00:00:00
422 assert len(commits) == 4
426 assert len(commits) == 4
423 for c in commits:
427 for c in commits:
424 assert c.date >= start_date
428 assert c.date >= start_date
425
429
426 def test_get_commits_respects_start_date_and_end_date(self):
430 def test_get_commits_respects_start_date_and_end_date(self):
427 start_date = datetime.datetime(2010, 1, 2)
431 start_date = datetime.datetime(2010, 1, 2)
428 end_date = datetime.datetime(2010, 1, 3)
432 end_date = datetime.datetime(2010, 1, 3)
429 commits = self.repo.get_commits(start_date=start_date,
433 commits = self.repo.get_commits(start_date=start_date,
430 end_date=end_date)
434 end_date=end_date)
431 assert isinstance(commits, CollectionGenerator)
435 assert isinstance(commits, CollectionGenerator)
432 assert len(commits) == 2
436 assert len(commits) == 2
433 for c in commits:
437 for c in commits:
434 assert c.date >= start_date
438 assert c.date >= start_date
435 assert c.date <= end_date
439 assert c.date <= end_date
436
440
437 def test_get_commits_respects_end_date(self):
441 def test_get_commits_respects_end_date(self):
438 end_date = datetime.datetime(2010, 1, 2)
442 end_date = datetime.datetime(2010, 1, 2)
439 commits = self.repo.get_commits(end_date=end_date)
443 commits = self.repo.get_commits(end_date=end_date)
440 assert isinstance(commits, CollectionGenerator)
444 assert isinstance(commits, CollectionGenerator)
441 assert len(commits) == 1
445 assert len(commits) == 1
442 for c in commits:
446 for c in commits:
443 assert c.date <= end_date
447 assert c.date <= end_date
444
448
445 def test_get_commits_respects_reverse(self):
449 def test_get_commits_respects_reverse(self):
446 commits = self.repo.get_commits() # no longer reverse support
450 commits = self.repo.get_commits() # no longer reverse support
447 assert isinstance(commits, CollectionGenerator)
451 assert isinstance(commits, CollectionGenerator)
448 assert len(commits) == 5
452 assert len(commits) == 5
449 commit_ids = reversed([c.raw_id for c in commits])
453 commit_ids = reversed([c.raw_id for c in commits])
450 assert list(commit_ids) == list(reversed(self.repo.commit_ids))
454 assert list(commit_ids) == list(reversed(self.repo.commit_ids))
451
455
452 def test_get_commits_slice_generator(self):
456 def test_get_commits_slice_generator(self):
453 commits = self.repo.get_commits(
457 commits = self.repo.get_commits(
454 branch_name=self.repo.DEFAULT_BRANCH_NAME)
458 branch_name=self.repo.DEFAULT_BRANCH_NAME)
455 assert isinstance(commits, CollectionGenerator)
459 assert isinstance(commits, CollectionGenerator)
456 commit_slice = list(commits[1:3])
460 commit_slice = list(commits[1:3])
457 assert len(commit_slice) == 2
461 assert len(commit_slice) == 2
458
462
459 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self):
463 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self):
460 with pytest.raises(CommitDoesNotExistError):
464 with pytest.raises(CommitDoesNotExistError):
461 list(self.repo.get_commits(start_id='foobar'))
465 list(self.repo.get_commits(start_id='foobar'))
462
466
463 def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self):
467 def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self):
464 with pytest.raises(CommitDoesNotExistError):
468 with pytest.raises(CommitDoesNotExistError):
465 list(self.repo.get_commits(end_id='foobar'))
469 list(self.repo.get_commits(end_id='foobar'))
466
470
467 def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self):
471 def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self):
468 with pytest.raises(BranchDoesNotExistError):
472 with pytest.raises(BranchDoesNotExistError):
469 list(self.repo.get_commits(branch_name='foobar'))
473 list(self.repo.get_commits(branch_name='foobar'))
470
474
471 def test_get_commits_raise_repositoryerror_for_wrong_start_end(self):
475 def test_get_commits_raise_repositoryerror_for_wrong_start_end(self):
472 start_id = self.repo.commit_ids[-1]
476 start_id = self.repo.commit_ids[-1]
473 end_id = self.repo.commit_ids[0]
477 end_id = self.repo.commit_ids[0]
474 with pytest.raises(RepositoryError):
478 with pytest.raises(RepositoryError):
475 list(self.repo.get_commits(start_id=start_id, end_id=end_id))
479 list(self.repo.get_commits(start_id=start_id, end_id=end_id))
476
480
477 def test_get_commits_raises_for_numerical_ids(self):
481 def test_get_commits_raises_for_numerical_ids(self):
478 with pytest.raises(TypeError):
482 with pytest.raises(TypeError):
479 self.repo.get_commits(start_id=1, end_id=2)
483 self.repo.get_commits(start_id=1, end_id=2)
480
484
481 def test_commit_equality(self):
485 def test_commit_equality(self):
482 commit1 = self.repo.get_commit(self.repo.commit_ids[0])
486 commit1 = self.repo.get_commit(self.repo.commit_ids[0])
483 commit2 = self.repo.get_commit(self.repo.commit_ids[1])
487 commit2 = self.repo.get_commit(self.repo.commit_ids[1])
484
488
485 assert commit1 == commit1
489 assert commit1 == commit1
486 assert commit2 == commit2
490 assert commit2 == commit2
487 assert commit1 != commit2
491 assert commit1 != commit2
488 assert commit2 != commit1
492 assert commit2 != commit1
489 assert commit1 != None
493 assert commit1 != None
490 assert None != commit1
494 assert None != commit1
491 assert 1 != commit1
495 assert 1 != commit1
492 assert 'string' != commit1
496 assert 'string' != commit1
493
497
494
498
495 @pytest.mark.parametrize("filename, expected", [
499 @pytest.mark.parametrize("filename, expected", [
496 ("README.rst", False),
500 ("README.rst", False),
497 ("README", True),
501 ("README", True),
498 ])
502 ])
499 def test_commit_is_link(vcsbackend, filename, expected):
503 def test_commit_is_link(vcsbackend, filename, expected):
500 commit = vcsbackend.repo.get_commit()
504 commit = vcsbackend.repo.get_commit()
501 link_status = commit.is_link(filename)
505 link_status = commit.is_link(filename)
502 assert link_status is expected
506 assert link_status is expected
503
507
504
508
505 @pytest.mark.usefixtures("vcs_repository_support")
509 @pytest.mark.usefixtures("vcs_repository_support")
506 class TestCommitsChanges(BackendTestMixin):
510 class TestCommitsChanges(BackendTestMixin):
507 recreate_repo_per_test = False
511 recreate_repo_per_test = False
508
512
509 @classmethod
513 @classmethod
510 def _get_commits(cls):
514 def _get_commits(cls):
511 return [
515 return [
512 {
516 {
513 'message': u'Initial',
517 'message': u'Initial',
514 'author': u'Joe Doe <joe.doe@example.com>',
518 'author': u'Joe Doe <joe.doe@example.com>',
515 'date': datetime.datetime(2010, 1, 1, 20),
519 'date': datetime.datetime(2010, 1, 1, 20),
516 'added': [
520 'added': [
517 FileNode('foo/bar', content='foo'),
521 FileNode('foo/bar', content='foo'),
518 FileNode('foo/bał', content='foo'),
522 FileNode('foo/bał', content='foo'),
519 FileNode('foobar', content='foo'),
523 FileNode('foobar', content='foo'),
520 FileNode('qwe', content='foo'),
524 FileNode('qwe', content='foo'),
521 ],
525 ],
522 },
526 },
523 {
527 {
524 'message': u'Massive changes',
528 'message': u'Massive changes',
525 'author': u'Joe Doe <joe.doe@example.com>',
529 'author': u'Joe Doe <joe.doe@example.com>',
526 'date': datetime.datetime(2010, 1, 1, 22),
530 'date': datetime.datetime(2010, 1, 1, 22),
527 'added': [FileNode('fallout', content='War never changes')],
531 'added': [FileNode('fallout', content='War never changes')],
528 'changed': [
532 'changed': [
529 FileNode('foo/bar', content='baz'),
533 FileNode('foo/bar', content='baz'),
530 FileNode('foobar', content='baz'),
534 FileNode('foobar', content='baz'),
531 ],
535 ],
532 'removed': [FileNode('qwe')],
536 'removed': [FileNode('qwe')],
533 },
537 },
534 ]
538 ]
535
539
536 def test_initial_commit(self, local_dt_to_utc):
540 def test_initial_commit(self, local_dt_to_utc):
537 commit = self.repo.get_commit(commit_idx=0)
541 commit = self.repo.get_commit(commit_idx=0)
538 assert set(commit.added) == set([
542 assert set(commit.added) == set([
539 commit.get_node('foo/bar'),
543 commit.get_node('foo/bar'),
540 commit.get_node('foo/bał'),
544 commit.get_node('foo/bał'),
541 commit.get_node('foobar'),
545 commit.get_node('foobar'),
542 commit.get_node('qwe'),
546 commit.get_node('qwe'),
543 ])
547 ])
544 assert set(commit.changed) == set()
548 assert set(commit.changed) == set()
545 assert set(commit.removed) == set()
549 assert set(commit.removed) == set()
546 assert set(commit.affected_files) == set(
550 assert set(commit.affected_files) == set(
547 ['foo/bar', 'foo/bał', 'foobar', 'qwe'])
551 ['foo/bar', 'foo/bał', 'foobar', 'qwe'])
548 assert commit.date == local_dt_to_utc(
552 assert commit.date == local_dt_to_utc(
549 datetime.datetime(2010, 1, 1, 20, 0))
553 datetime.datetime(2010, 1, 1, 20, 0))
550
554
551 def test_head_added(self):
555 def test_head_added(self):
552 commit = self.repo.get_commit()
556 commit = self.repo.get_commit()
553 assert isinstance(commit.added, AddedFileNodesGenerator)
557 assert isinstance(commit.added, AddedFileNodesGenerator)
554 assert set(commit.added) == set([commit.get_node('fallout')])
558 assert set(commit.added) == set([commit.get_node('fallout')])
555 assert isinstance(commit.changed, ChangedFileNodesGenerator)
559 assert isinstance(commit.changed, ChangedFileNodesGenerator)
556 assert set(commit.changed) == set([
560 assert set(commit.changed) == set([
557 commit.get_node('foo/bar'),
561 commit.get_node('foo/bar'),
558 commit.get_node('foobar'),
562 commit.get_node('foobar'),
559 ])
563 ])
560 assert isinstance(commit.removed, RemovedFileNodesGenerator)
564 assert isinstance(commit.removed, RemovedFileNodesGenerator)
561 assert len(commit.removed) == 1
565 assert len(commit.removed) == 1
562 assert list(commit.removed)[0].path == 'qwe'
566 assert list(commit.removed)[0].path == 'qwe'
563
567
564 def test_get_filemode(self):
568 def test_get_filemode(self):
565 commit = self.repo.get_commit()
569 commit = self.repo.get_commit()
566 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar')
570 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar')
567
571
568 def test_get_filemode_non_ascii(self):
572 def test_get_filemode_non_ascii(self):
569 commit = self.repo.get_commit()
573 commit = self.repo.get_commit()
570 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał')
574 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał')
571 assert FILEMODE_DEFAULT == commit.get_file_mode(u'foo/bał')
575 assert FILEMODE_DEFAULT == commit.get_file_mode(u'foo/bał')
572
576
573 def test_get_path_history(self):
577 def test_get_path_history(self):
574 commit = self.repo.get_commit()
578 commit = self.repo.get_commit()
575 history = commit.get_path_history('foo/bar')
579 history = commit.get_path_history('foo/bar')
576 assert len(history) == 2
580 assert len(history) == 2
577
581
578 def test_get_path_history_with_limit(self):
582 def test_get_path_history_with_limit(self):
579 commit = self.repo.get_commit()
583 commit = self.repo.get_commit()
580 history = commit.get_path_history('foo/bar', limit=1)
584 history = commit.get_path_history('foo/bar', limit=1)
581 assert len(history) == 1
585 assert len(history) == 1
582
586
583 def test_get_path_history_first_commit(self):
587 def test_get_path_history_first_commit(self):
584 commit = self.repo[0]
588 commit = self.repo[0]
585 history = commit.get_path_history('foo/bar')
589 history = commit.get_path_history('foo/bar')
586 assert len(history) == 1
590 assert len(history) == 1
587
591
588
592
589 def assert_text_equal(expected, given):
593 def assert_text_equal(expected, given):
590 assert expected == given
594 assert expected == given
591 assert isinstance(expected, unicode)
595 assert isinstance(expected, unicode)
592 assert isinstance(given, unicode)
596 assert isinstance(given, unicode)
General Comments 0
You need to be logged in to leave comments. Login now