##// END OF EJS Templates
hovercards: allow hovercards on parsed !PR patterns....
dan -
r4046:a792f9c3 default
parent child Browse files
Show More
@@ -0,0 +1,26 b''
1 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
4 % if c.can_view_pr:
5 <div class="pr-hovercard-header">
6 <div class="pull-left tagtag">
7 ${c.pull_request.status}
8 </div>
9 <div class="pr-hovercard-user">
10 ${_('Created')}: ${h.format_date(c.pull_request.created_on)}
11 </div>
12 </div>
13
14 <div class="pr-hovercard-title">
15 <h3><a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">!${c.pull_request.pull_request_id}</a> - ${c.pull_request.title}</h3>
16 </div>
17
18 <div class="pr-hovercard-footer">
19 ${_('repo')}: ${c.pull_request.target_repo.repo_name}
20 </div>
21 % else:
22 ## user cannot view this PR we just show the generic info, without any exposed data
23 <div class="pr-hovercard-title">
24 <h3>${_('Pull Request')} !${c.pull_request.pull_request_id}</h3>
25 </div>
26 % endif No newline at end of file
@@ -1,37 +1,41 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2018-2019 RhodeCode GmbH
3 # Copyright (C) 2018-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 def includeme(config):
22 def includeme(config):
23
23
24 config.add_route(
24 config.add_route(
25 name='hovercard_user',
25 name='hovercard_user',
26 pattern='/_hovercard/user/{user_id}')
26 pattern='/_hovercard/user/{user_id}')
27
27
28 config.add_route(
28 config.add_route(
29 name='hovercard_user_group',
29 name='hovercard_user_group',
30 pattern='/_hovercard/user_group/{user_group_id}')
30 pattern='/_hovercard/user_group/{user_group_id}')
31
31
32 config.add_route(
32 config.add_route(
33 name='hovercard_pull_request',
34 pattern='/_hovercard/pull_request/{pull_request_id}')
35
36 config.add_route(
33 name='hovercard_repo_commit',
37 name='hovercard_repo_commit',
34 pattern='/_hovercard/commit/{repo_name:.*?[^/]}/{commit_id}', repo_route=True)
38 pattern='/_hovercard/commit/{repo_name:.*?[^/]}/{commit_id}', repo_route=True)
35
39
36 # Scan module for configuration decorators.
40 # Scan module for configuration decorators.
37 config.scan('.views', ignore='.tests')
41 config.scan('.views', ignore='.tests')
@@ -1,90 +1,103 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, RepoAppView
27 from rhodecode.apps._base import BaseAppView, RepoAppView
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, CSRFRequired,
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired,
31 HasRepoPermissionAnyDecorator)
31 HasRepoPermissionAnyDecorator)
32 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
32 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
33 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.index import searcher_from_config
34 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
37 from rhodecode.model.db import (
37 from rhodecode.model.db import (
38 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
38 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup, PullRequest)
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.repo_group import RepoGroupModel
41 from rhodecode.model.scm import RepoGroupList, RepoList
41 from rhodecode.model.scm import RepoGroupList, RepoList
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.model.user_group import UserGroupModel
43 from rhodecode.model.user_group import UserGroupModel
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class HoverCardsView(BaseAppView):
48 class HoverCardsView(BaseAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 c = self._get_local_tmpl_context()
51 c = self._get_local_tmpl_context()
52 return c
52 return c
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @view_config(
55 @view_config(
56 route_name='hovercard_user', request_method='GET', xhr=True,
56 route_name='hovercard_user', request_method='GET', xhr=True,
57 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
57 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
58 def hovercard_user(self):
58 def hovercard_user(self):
59 c = self.load_default_context()
59 c = self.load_default_context()
60 user_id = self.request.matchdict['user_id']
60 user_id = self.request.matchdict['user_id']
61 c.user = User.get_or_404(user_id)
61 c.user = User.get_or_404(user_id)
62 return self._get_template_context(c)
62 return self._get_template_context(c)
63
63
64 @LoginRequired()
64 @LoginRequired()
65 @view_config(
65 @view_config(
66 route_name='hovercard_user_group', request_method='GET', xhr=True,
66 route_name='hovercard_user_group', request_method='GET', xhr=True,
67 renderer='rhodecode:templates/hovercards/hovercard_user_group.mako')
67 renderer='rhodecode:templates/hovercards/hovercard_user_group.mako')
68 def hovercard_user_group(self):
68 def hovercard_user_group(self):
69 c = self.load_default_context()
69 c = self.load_default_context()
70 user_group_id = self.request.matchdict['user_group_id']
70 user_group_id = self.request.matchdict['user_group_id']
71 c.user_group = UserGroup.get_or_404(user_group_id)
71 c.user_group = UserGroup.get_or_404(user_group_id)
72 return self._get_template_context(c)
72 return self._get_template_context(c)
73
73
74 @LoginRequired()
75 @view_config(
76 route_name='hovercard_pull_request', request_method='GET', xhr=True,
77 renderer='rhodecode:templates/hovercards/hovercard_pull_request.mako')
78 def hovercard_pull_request(self):
79 c = self.load_default_context()
80 c.pull_request = PullRequest.get_or_404(
81 self.request.matchdict['pull_request_id'])
82 perms = ['repository.read', 'repository.write', 'repository.admin']
83 c.can_view_pr = h.HasRepoPermissionAny(*perms)(
84 c.pull_request.target_repo.repo_name)
85 return self._get_template_context(c)
86
74
87
75 class HoverCardsRepoView(RepoAppView):
88 class HoverCardsRepoView(RepoAppView):
76 def load_default_context(self):
89 def load_default_context(self):
77 c = self._get_local_tmpl_context()
90 c = self._get_local_tmpl_context()
78 return c
91 return c
79
92
80 @LoginRequired()
93 @LoginRequired()
81 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin')
94 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin')
82 @view_config(
95 @view_config(
83 route_name='hovercard_repo_commit', request_method='GET', xhr=True,
96 route_name='hovercard_repo_commit', request_method='GET', xhr=True,
84 renderer='rhodecode:templates/hovercards/hovercard_repo_commit.mako')
97 renderer='rhodecode:templates/hovercards/hovercard_repo_commit.mako')
85 def hovercard_repo_commit(self):
98 def hovercard_repo_commit(self):
86 c = self.load_default_context()
99 c = self.load_default_context()
87 commit_id = self.request.matchdict['commit_id']
100 commit_id = self.request.matchdict['commit_id']
88 pre_load = ['author', 'branch', 'date', 'message']
101 pre_load = ['author', 'branch', 'date', 'message']
89 c.commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id, pre_load=pre_load)
102 c.commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id, pre_load=pre_load)
90 return self._get_template_context(c)
103 return self._get_template_context(c)
@@ -1,2107 +1,2125 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 (
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 str2bool, safe_unicode, safe_str,
79 AttributeDict, safe_int, md5, md5_safe
79 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
80 AttributeDict, safe_int, md5, md5_safe, get_host_info)
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89 from rhodecode.model.settings import IssueTrackerSettingsModel
89
90
90
91
91 log = logging.getLogger(__name__)
92 log = logging.getLogger(__name__)
92
93
93
94
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
97
97
98
98 def asset(path, ver=None, **kwargs):
99 def asset(path, ver=None, **kwargs):
99 """
100 """
100 Helper to generate a static asset file path for rhodecode assets
101 Helper to generate a static asset file path for rhodecode assets
101
102
102 eg. h.asset('images/image.png', ver='3923')
103 eg. h.asset('images/image.png', ver='3923')
103
104
104 :param path: path of asset
105 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
106 :param ver: optional version query param to append as ?ver=
106 """
107 """
107 request = get_current_request()
108 request = get_current_request()
108 query = {}
109 query = {}
109 query.update(kwargs)
110 query.update(kwargs)
110 if ver:
111 if ver:
111 query = {'ver': ver}
112 query = {'ver': ver}
112 return request.static_path(
113 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
114 'rhodecode:public/{}'.format(path), _query=query)
114
115
115
116
116 default_html_escape_table = {
117 default_html_escape_table = {
117 ord('&'): u'&amp;',
118 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
119 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
120 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
121 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
122 ord("'"): u'&#39;',
122 }
123 }
123
124
124
125
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
127 """Produce entities within text."""
127 return text.translate(html_escape_table)
128 return text.translate(html_escape_table)
128
129
129
130
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
132 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
133 Truncate string ``s`` at the first occurrence of ``sub``.
133
134
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
136 """
136 suffix_if_chopped = suffix_if_chopped or ''
137 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
138 pos = s.find(sub)
138 if pos == -1:
139 if pos == -1:
139 return s
140 return s
140
141
141 if inclusive:
142 if inclusive:
142 pos += len(sub)
143 pos += len(sub)
143
144
144 chopped = s[:pos]
145 chopped = s[:pos]
145 left = s[pos:].strip()
146 left = s[pos:].strip()
146
147
147 if left and suffix_if_chopped:
148 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
149 chopped += suffix_if_chopped
149
150
150 return chopped
151 return chopped
151
152
152
153
153 def shorter(text, size=20, prefix=False):
154 def shorter(text, size=20, prefix=False):
154 postfix = '...'
155 postfix = '...'
155 if len(text) > size:
156 if len(text) > size:
156 if prefix:
157 if prefix:
157 # shorten in front
158 # shorten in front
158 return postfix + text[-(size - len(postfix)):]
159 return postfix + text[-(size - len(postfix)):]
159 else:
160 else:
160 return text[:size - len(postfix)] + postfix
161 return text[:size - len(postfix)] + postfix
161 return text
162 return text
162
163
163
164
164 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
165 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
165 """
166 """
166 Reset button
167 Reset button
167 """
168 """
168 _set_input_attrs(attrs, type, name, value)
169 _set_input_attrs(attrs, type, name, value)
169 _set_id_attr(attrs, id, name)
170 _set_id_attr(attrs, id, name)
170 convert_boolean_attrs(attrs, ["disabled"])
171 convert_boolean_attrs(attrs, ["disabled"])
171 return HTML.input(**attrs)
172 return HTML.input(**attrs)
172
173
173 reset = _reset
174 reset = _reset
174 safeid = _make_safe_id_component
175 safeid = _make_safe_id_component
175
176
176
177
177 def branding(name, length=40):
178 def branding(name, length=40):
178 return truncate(name, length, indicator="")
179 return truncate(name, length, indicator="")
179
180
180
181
181 def FID(raw_id, path):
182 def FID(raw_id, path):
182 """
183 """
183 Creates a unique ID for filenode based on it's hash of path and commit
184 Creates a unique ID for filenode based on it's hash of path and commit
184 it's safe to use in urls
185 it's safe to use in urls
185
186
186 :param raw_id:
187 :param raw_id:
187 :param path:
188 :param path:
188 """
189 """
189
190
190 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
191 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
191
192
192
193
193 class _GetError(object):
194 class _GetError(object):
194 """Get error from form_errors, and represent it as span wrapped error
195 """Get error from form_errors, and represent it as span wrapped error
195 message
196 message
196
197
197 :param field_name: field to fetch errors for
198 :param field_name: field to fetch errors for
198 :param form_errors: form errors dict
199 :param form_errors: form errors dict
199 """
200 """
200
201
201 def __call__(self, field_name, form_errors):
202 def __call__(self, field_name, form_errors):
202 tmpl = """<span class="error_msg">%s</span>"""
203 tmpl = """<span class="error_msg">%s</span>"""
203 if form_errors and field_name in form_errors:
204 if form_errors and field_name in form_errors:
204 return literal(tmpl % form_errors.get(field_name))
205 return literal(tmpl % form_errors.get(field_name))
205
206
206
207
207 get_error = _GetError()
208 get_error = _GetError()
208
209
209
210
210 class _ToolTip(object):
211 class _ToolTip(object):
211
212
212 def __call__(self, tooltip_title, trim_at=50):
213 def __call__(self, tooltip_title, trim_at=50):
213 """
214 """
214 Special function just to wrap our text into nice formatted
215 Special function just to wrap our text into nice formatted
215 autowrapped text
216 autowrapped text
216
217
217 :param tooltip_title:
218 :param tooltip_title:
218 """
219 """
219 tooltip_title = escape(tooltip_title)
220 tooltip_title = escape(tooltip_title)
220 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
221 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
221 return tooltip_title
222 return tooltip_title
222
223
223
224
224 tooltip = _ToolTip()
225 tooltip = _ToolTip()
225
226
226 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
227 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
227
228
228
229
229 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
230 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
230 if isinstance(file_path, str):
231 if isinstance(file_path, str):
231 file_path = safe_unicode(file_path)
232 file_path = safe_unicode(file_path)
232
233
233 route_qry = {'at': at_ref} if at_ref else None
234 route_qry = {'at': at_ref} if at_ref else None
234
235
235 # first segment is a `..` link to repo files
236 # first segment is a `..` link to repo files
236 root_name = literal(u'<i class="icon-home"></i>')
237 root_name = literal(u'<i class="icon-home"></i>')
237 url_segments = [
238 url_segments = [
238 link_to(
239 link_to(
239 root_name,
240 root_name,
240 route_path(
241 route_path(
241 'repo_files',
242 'repo_files',
242 repo_name=repo_name,
243 repo_name=repo_name,
243 commit_id=commit_id,
244 commit_id=commit_id,
244 f_path='',
245 f_path='',
245 _query=route_qry),
246 _query=route_qry),
246 )]
247 )]
247
248
248 path_segments = file_path.split('/')
249 path_segments = file_path.split('/')
249 last_cnt = len(path_segments) - 1
250 last_cnt = len(path_segments) - 1
250 for cnt, segment in enumerate(path_segments):
251 for cnt, segment in enumerate(path_segments):
251 if not segment:
252 if not segment:
252 continue
253 continue
253 segment_html = escape(segment)
254 segment_html = escape(segment)
254
255
255 last_item = cnt == last_cnt
256 last_item = cnt == last_cnt
256
257
257 if last_item and linkify_last_item is False:
258 if last_item and linkify_last_item is False:
258 # plain version
259 # plain version
259 url_segments.append(segment_html)
260 url_segments.append(segment_html)
260 else:
261 else:
261 url_segments.append(
262 url_segments.append(
262 link_to(
263 link_to(
263 segment_html,
264 segment_html,
264 route_path(
265 route_path(
265 'repo_files',
266 'repo_files',
266 repo_name=repo_name,
267 repo_name=repo_name,
267 commit_id=commit_id,
268 commit_id=commit_id,
268 f_path='/'.join(path_segments[:cnt + 1]),
269 f_path='/'.join(path_segments[:cnt + 1]),
269 _query=route_qry),
270 _query=route_qry),
270 ))
271 ))
271
272
272 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
273 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
273 if limit_items and len(limited_url_segments) < len(url_segments):
274 if limit_items and len(limited_url_segments) < len(url_segments):
274 url_segments = limited_url_segments
275 url_segments = limited_url_segments
275
276
276 full_path = file_path
277 full_path = file_path
277 icon = files_icon.format(escape(full_path))
278 icon = files_icon.format(escape(full_path))
278 if file_path == '':
279 if file_path == '':
279 return root_name
280 return root_name
280 else:
281 else:
281 return literal(' / '.join(url_segments) + icon)
282 return literal(' / '.join(url_segments) + icon)
282
283
283
284
284 def files_url_data(request):
285 def files_url_data(request):
285 matchdict = request.matchdict
286 matchdict = request.matchdict
286
287
287 if 'f_path' not in matchdict:
288 if 'f_path' not in matchdict:
288 matchdict['f_path'] = ''
289 matchdict['f_path'] = ''
289
290
290 if 'commit_id' not in matchdict:
291 if 'commit_id' not in matchdict:
291 matchdict['commit_id'] = 'tip'
292 matchdict['commit_id'] = 'tip'
292
293
293 return json.dumps(matchdict)
294 return json.dumps(matchdict)
294
295
295
296
296 def code_highlight(code, lexer, formatter, use_hl_filter=False):
297 def code_highlight(code, lexer, formatter, use_hl_filter=False):
297 """
298 """
298 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
299 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
299
300
300 If ``outfile`` is given and a valid file object (an object
301 If ``outfile`` is given and a valid file object (an object
301 with a ``write`` method), the result will be written to it, otherwise
302 with a ``write`` method), the result will be written to it, otherwise
302 it is returned as a string.
303 it is returned as a string.
303 """
304 """
304 if use_hl_filter:
305 if use_hl_filter:
305 # add HL filter
306 # add HL filter
306 from rhodecode.lib.index import search_utils
307 from rhodecode.lib.index import search_utils
307 lexer.add_filter(search_utils.ElasticSearchHLFilter())
308 lexer.add_filter(search_utils.ElasticSearchHLFilter())
308 return pygments.format(pygments.lex(code, lexer), formatter)
309 return pygments.format(pygments.lex(code, lexer), formatter)
309
310
310
311
311 class CodeHtmlFormatter(HtmlFormatter):
312 class CodeHtmlFormatter(HtmlFormatter):
312 """
313 """
313 My code Html Formatter for source codes
314 My code Html Formatter for source codes
314 """
315 """
315
316
316 def wrap(self, source, outfile):
317 def wrap(self, source, outfile):
317 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
318 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
318
319
319 def _wrap_code(self, source):
320 def _wrap_code(self, source):
320 for cnt, it in enumerate(source):
321 for cnt, it in enumerate(source):
321 i, t = it
322 i, t = it
322 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
323 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
323 yield i, t
324 yield i, t
324
325
325 def _wrap_tablelinenos(self, inner):
326 def _wrap_tablelinenos(self, inner):
326 dummyoutfile = StringIO.StringIO()
327 dummyoutfile = StringIO.StringIO()
327 lncount = 0
328 lncount = 0
328 for t, line in inner:
329 for t, line in inner:
329 if t:
330 if t:
330 lncount += 1
331 lncount += 1
331 dummyoutfile.write(line)
332 dummyoutfile.write(line)
332
333
333 fl = self.linenostart
334 fl = self.linenostart
334 mw = len(str(lncount + fl - 1))
335 mw = len(str(lncount + fl - 1))
335 sp = self.linenospecial
336 sp = self.linenospecial
336 st = self.linenostep
337 st = self.linenostep
337 la = self.lineanchors
338 la = self.lineanchors
338 aln = self.anchorlinenos
339 aln = self.anchorlinenos
339 nocls = self.noclasses
340 nocls = self.noclasses
340 if sp:
341 if sp:
341 lines = []
342 lines = []
342
343
343 for i in range(fl, fl + lncount):
344 for i in range(fl, fl + lncount):
344 if i % st == 0:
345 if i % st == 0:
345 if i % sp == 0:
346 if i % sp == 0:
346 if aln:
347 if aln:
347 lines.append('<a href="#%s%d" class="special">%*d</a>' %
348 lines.append('<a href="#%s%d" class="special">%*d</a>' %
348 (la, i, mw, i))
349 (la, i, mw, i))
349 else:
350 else:
350 lines.append('<span class="special">%*d</span>' % (mw, i))
351 lines.append('<span class="special">%*d</span>' % (mw, i))
351 else:
352 else:
352 if aln:
353 if aln:
353 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
354 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
354 else:
355 else:
355 lines.append('%*d' % (mw, i))
356 lines.append('%*d' % (mw, i))
356 else:
357 else:
357 lines.append('')
358 lines.append('')
358 ls = '\n'.join(lines)
359 ls = '\n'.join(lines)
359 else:
360 else:
360 lines = []
361 lines = []
361 for i in range(fl, fl + lncount):
362 for i in range(fl, fl + lncount):
362 if i % st == 0:
363 if i % st == 0:
363 if aln:
364 if aln:
364 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
365 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
365 else:
366 else:
366 lines.append('%*d' % (mw, i))
367 lines.append('%*d' % (mw, i))
367 else:
368 else:
368 lines.append('')
369 lines.append('')
369 ls = '\n'.join(lines)
370 ls = '\n'.join(lines)
370
371
371 # in case you wonder about the seemingly redundant <div> here: since the
372 # in case you wonder about the seemingly redundant <div> here: since the
372 # content in the other cell also is wrapped in a div, some browsers in
373 # content in the other cell also is wrapped in a div, some browsers in
373 # some configurations seem to mess up the formatting...
374 # some configurations seem to mess up the formatting...
374 if nocls:
375 if nocls:
375 yield 0, ('<table class="%stable">' % self.cssclass +
376 yield 0, ('<table class="%stable">' % self.cssclass +
376 '<tr><td><div class="linenodiv" '
377 '<tr><td><div class="linenodiv" '
377 'style="background-color: #f0f0f0; padding-right: 10px">'
378 'style="background-color: #f0f0f0; padding-right: 10px">'
378 '<pre style="line-height: 125%">' +
379 '<pre style="line-height: 125%">' +
379 ls + '</pre></div></td><td id="hlcode" class="code">')
380 ls + '</pre></div></td><td id="hlcode" class="code">')
380 else:
381 else:
381 yield 0, ('<table class="%stable">' % self.cssclass +
382 yield 0, ('<table class="%stable">' % self.cssclass +
382 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
383 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
383 ls + '</pre></div></td><td id="hlcode" class="code">')
384 ls + '</pre></div></td><td id="hlcode" class="code">')
384 yield 0, dummyoutfile.getvalue()
385 yield 0, dummyoutfile.getvalue()
385 yield 0, '</td></tr></table>'
386 yield 0, '</td></tr></table>'
386
387
387
388
388 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
389 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
389 def __init__(self, **kw):
390 def __init__(self, **kw):
390 # only show these line numbers if set
391 # only show these line numbers if set
391 self.only_lines = kw.pop('only_line_numbers', [])
392 self.only_lines = kw.pop('only_line_numbers', [])
392 self.query_terms = kw.pop('query_terms', [])
393 self.query_terms = kw.pop('query_terms', [])
393 self.max_lines = kw.pop('max_lines', 5)
394 self.max_lines = kw.pop('max_lines', 5)
394 self.line_context = kw.pop('line_context', 3)
395 self.line_context = kw.pop('line_context', 3)
395 self.url = kw.pop('url', None)
396 self.url = kw.pop('url', None)
396
397
397 super(CodeHtmlFormatter, self).__init__(**kw)
398 super(CodeHtmlFormatter, self).__init__(**kw)
398
399
399 def _wrap_code(self, source):
400 def _wrap_code(self, source):
400 for cnt, it in enumerate(source):
401 for cnt, it in enumerate(source):
401 i, t = it
402 i, t = it
402 t = '<pre>%s</pre>' % t
403 t = '<pre>%s</pre>' % t
403 yield i, t
404 yield i, t
404
405
405 def _wrap_tablelinenos(self, inner):
406 def _wrap_tablelinenos(self, inner):
406 yield 0, '<table class="code-highlight %stable">' % self.cssclass
407 yield 0, '<table class="code-highlight %stable">' % self.cssclass
407
408
408 last_shown_line_number = 0
409 last_shown_line_number = 0
409 current_line_number = 1
410 current_line_number = 1
410
411
411 for t, line in inner:
412 for t, line in inner:
412 if not t:
413 if not t:
413 yield t, line
414 yield t, line
414 continue
415 continue
415
416
416 if current_line_number in self.only_lines:
417 if current_line_number in self.only_lines:
417 if last_shown_line_number + 1 != current_line_number:
418 if last_shown_line_number + 1 != current_line_number:
418 yield 0, '<tr>'
419 yield 0, '<tr>'
419 yield 0, '<td class="line">...</td>'
420 yield 0, '<td class="line">...</td>'
420 yield 0, '<td id="hlcode" class="code"></td>'
421 yield 0, '<td id="hlcode" class="code"></td>'
421 yield 0, '</tr>'
422 yield 0, '</tr>'
422
423
423 yield 0, '<tr>'
424 yield 0, '<tr>'
424 if self.url:
425 if self.url:
425 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
426 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
426 self.url, current_line_number, current_line_number)
427 self.url, current_line_number, current_line_number)
427 else:
428 else:
428 yield 0, '<td class="line"><a href="">%i</a></td>' % (
429 yield 0, '<td class="line"><a href="">%i</a></td>' % (
429 current_line_number)
430 current_line_number)
430 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
431 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
431 yield 0, '</tr>'
432 yield 0, '</tr>'
432
433
433 last_shown_line_number = current_line_number
434 last_shown_line_number = current_line_number
434
435
435 current_line_number += 1
436 current_line_number += 1
436
437
437 yield 0, '</table>'
438 yield 0, '</table>'
438
439
439
440
440 def hsv_to_rgb(h, s, v):
441 def hsv_to_rgb(h, s, v):
441 """ Convert hsv color values to rgb """
442 """ Convert hsv color values to rgb """
442
443
443 if s == 0.0:
444 if s == 0.0:
444 return v, v, v
445 return v, v, v
445 i = int(h * 6.0) # XXX assume int() truncates!
446 i = int(h * 6.0) # XXX assume int() truncates!
446 f = (h * 6.0) - i
447 f = (h * 6.0) - i
447 p = v * (1.0 - s)
448 p = v * (1.0 - s)
448 q = v * (1.0 - s * f)
449 q = v * (1.0 - s * f)
449 t = v * (1.0 - s * (1.0 - f))
450 t = v * (1.0 - s * (1.0 - f))
450 i = i % 6
451 i = i % 6
451 if i == 0:
452 if i == 0:
452 return v, t, p
453 return v, t, p
453 if i == 1:
454 if i == 1:
454 return q, v, p
455 return q, v, p
455 if i == 2:
456 if i == 2:
456 return p, v, t
457 return p, v, t
457 if i == 3:
458 if i == 3:
458 return p, q, v
459 return p, q, v
459 if i == 4:
460 if i == 4:
460 return t, p, v
461 return t, p, v
461 if i == 5:
462 if i == 5:
462 return v, p, q
463 return v, p, q
463
464
464
465
465 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
466 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
466 """
467 """
467 Generator for getting n of evenly distributed colors using
468 Generator for getting n of evenly distributed colors using
468 hsv color and golden ratio. It always return same order of colors
469 hsv color and golden ratio. It always return same order of colors
469
470
470 :param n: number of colors to generate
471 :param n: number of colors to generate
471 :param saturation: saturation of returned colors
472 :param saturation: saturation of returned colors
472 :param lightness: lightness of returned colors
473 :param lightness: lightness of returned colors
473 :returns: RGB tuple
474 :returns: RGB tuple
474 """
475 """
475
476
476 golden_ratio = 0.618033988749895
477 golden_ratio = 0.618033988749895
477 h = 0.22717784590367374
478 h = 0.22717784590367374
478
479
479 for _ in xrange(n):
480 for _ in xrange(n):
480 h += golden_ratio
481 h += golden_ratio
481 h %= 1
482 h %= 1
482 HSV_tuple = [h, saturation, lightness]
483 HSV_tuple = [h, saturation, lightness]
483 RGB_tuple = hsv_to_rgb(*HSV_tuple)
484 RGB_tuple = hsv_to_rgb(*HSV_tuple)
484 yield map(lambda x: str(int(x * 256)), RGB_tuple)
485 yield map(lambda x: str(int(x * 256)), RGB_tuple)
485
486
486
487
487 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
488 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
488 """
489 """
489 Returns a function which when called with an argument returns a unique
490 Returns a function which when called with an argument returns a unique
490 color for that argument, eg.
491 color for that argument, eg.
491
492
492 :param n: number of colors to generate
493 :param n: number of colors to generate
493 :param saturation: saturation of returned colors
494 :param saturation: saturation of returned colors
494 :param lightness: lightness of returned colors
495 :param lightness: lightness of returned colors
495 :returns: css RGB string
496 :returns: css RGB string
496
497
497 >>> color_hash = color_hasher()
498 >>> color_hash = color_hasher()
498 >>> color_hash('hello')
499 >>> color_hash('hello')
499 'rgb(34, 12, 59)'
500 'rgb(34, 12, 59)'
500 >>> color_hash('hello')
501 >>> color_hash('hello')
501 'rgb(34, 12, 59)'
502 'rgb(34, 12, 59)'
502 >>> color_hash('other')
503 >>> color_hash('other')
503 'rgb(90, 224, 159)'
504 'rgb(90, 224, 159)'
504 """
505 """
505
506
506 color_dict = {}
507 color_dict = {}
507 cgenerator = unique_color_generator(
508 cgenerator = unique_color_generator(
508 saturation=saturation, lightness=lightness)
509 saturation=saturation, lightness=lightness)
509
510
510 def get_color_string(thing):
511 def get_color_string(thing):
511 if thing in color_dict:
512 if thing in color_dict:
512 col = color_dict[thing]
513 col = color_dict[thing]
513 else:
514 else:
514 col = color_dict[thing] = cgenerator.next()
515 col = color_dict[thing] = cgenerator.next()
515 return "rgb(%s)" % (', '.join(col))
516 return "rgb(%s)" % (', '.join(col))
516
517
517 return get_color_string
518 return get_color_string
518
519
519
520
520 def get_lexer_safe(mimetype=None, filepath=None):
521 def get_lexer_safe(mimetype=None, filepath=None):
521 """
522 """
522 Tries to return a relevant pygments lexer using mimetype/filepath name,
523 Tries to return a relevant pygments lexer using mimetype/filepath name,
523 defaulting to plain text if none could be found
524 defaulting to plain text if none could be found
524 """
525 """
525 lexer = None
526 lexer = None
526 try:
527 try:
527 if mimetype:
528 if mimetype:
528 lexer = get_lexer_for_mimetype(mimetype)
529 lexer = get_lexer_for_mimetype(mimetype)
529 if not lexer:
530 if not lexer:
530 lexer = get_lexer_for_filename(filepath)
531 lexer = get_lexer_for_filename(filepath)
531 except pygments.util.ClassNotFound:
532 except pygments.util.ClassNotFound:
532 pass
533 pass
533
534
534 if not lexer:
535 if not lexer:
535 lexer = get_lexer_by_name('text')
536 lexer = get_lexer_by_name('text')
536
537
537 return lexer
538 return lexer
538
539
539
540
540 def get_lexer_for_filenode(filenode):
541 def get_lexer_for_filenode(filenode):
541 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
542 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
542 return lexer
543 return lexer
543
544
544
545
545 def pygmentize(filenode, **kwargs):
546 def pygmentize(filenode, **kwargs):
546 """
547 """
547 pygmentize function using pygments
548 pygmentize function using pygments
548
549
549 :param filenode:
550 :param filenode:
550 """
551 """
551 lexer = get_lexer_for_filenode(filenode)
552 lexer = get_lexer_for_filenode(filenode)
552 return literal(code_highlight(filenode.content, lexer,
553 return literal(code_highlight(filenode.content, lexer,
553 CodeHtmlFormatter(**kwargs)))
554 CodeHtmlFormatter(**kwargs)))
554
555
555
556
556 def is_following_repo(repo_name, user_id):
557 def is_following_repo(repo_name, user_id):
557 from rhodecode.model.scm import ScmModel
558 from rhodecode.model.scm import ScmModel
558 return ScmModel().is_following_repo(repo_name, user_id)
559 return ScmModel().is_following_repo(repo_name, user_id)
559
560
560
561
561 class _Message(object):
562 class _Message(object):
562 """A message returned by ``Flash.pop_messages()``.
563 """A message returned by ``Flash.pop_messages()``.
563
564
564 Converting the message to a string returns the message text. Instances
565 Converting the message to a string returns the message text. Instances
565 also have the following attributes:
566 also have the following attributes:
566
567
567 * ``message``: the message text.
568 * ``message``: the message text.
568 * ``category``: the category specified when the message was created.
569 * ``category``: the category specified when the message was created.
569 """
570 """
570
571
571 def __init__(self, category, message):
572 def __init__(self, category, message):
572 self.category = category
573 self.category = category
573 self.message = message
574 self.message = message
574
575
575 def __str__(self):
576 def __str__(self):
576 return self.message
577 return self.message
577
578
578 __unicode__ = __str__
579 __unicode__ = __str__
579
580
580 def __html__(self):
581 def __html__(self):
581 return escape(safe_unicode(self.message))
582 return escape(safe_unicode(self.message))
582
583
583
584
584 class Flash(object):
585 class Flash(object):
585 # List of allowed categories. If None, allow any category.
586 # List of allowed categories. If None, allow any category.
586 categories = ["warning", "notice", "error", "success"]
587 categories = ["warning", "notice", "error", "success"]
587
588
588 # Default category if none is specified.
589 # Default category if none is specified.
589 default_category = "notice"
590 default_category = "notice"
590
591
591 def __init__(self, session_key="flash", categories=None,
592 def __init__(self, session_key="flash", categories=None,
592 default_category=None):
593 default_category=None):
593 """
594 """
594 Instantiate a ``Flash`` object.
595 Instantiate a ``Flash`` object.
595
596
596 ``session_key`` is the key to save the messages under in the user's
597 ``session_key`` is the key to save the messages under in the user's
597 session.
598 session.
598
599
599 ``categories`` is an optional list which overrides the default list
600 ``categories`` is an optional list which overrides the default list
600 of categories.
601 of categories.
601
602
602 ``default_category`` overrides the default category used for messages
603 ``default_category`` overrides the default category used for messages
603 when none is specified.
604 when none is specified.
604 """
605 """
605 self.session_key = session_key
606 self.session_key = session_key
606 if categories is not None:
607 if categories is not None:
607 self.categories = categories
608 self.categories = categories
608 if default_category is not None:
609 if default_category is not None:
609 self.default_category = default_category
610 self.default_category = default_category
610 if self.categories and self.default_category not in self.categories:
611 if self.categories and self.default_category not in self.categories:
611 raise ValueError(
612 raise ValueError(
612 "unrecognized default category %r" % (self.default_category,))
613 "unrecognized default category %r" % (self.default_category,))
613
614
614 def pop_messages(self, session=None, request=None):
615 def pop_messages(self, session=None, request=None):
615 """
616 """
616 Return all accumulated messages and delete them from the session.
617 Return all accumulated messages and delete them from the session.
617
618
618 The return value is a list of ``Message`` objects.
619 The return value is a list of ``Message`` objects.
619 """
620 """
620 messages = []
621 messages = []
621
622
622 if not session:
623 if not session:
623 if not request:
624 if not request:
624 request = get_current_request()
625 request = get_current_request()
625 session = request.session
626 session = request.session
626
627
627 # Pop the 'old' pylons flash messages. They are tuples of the form
628 # Pop the 'old' pylons flash messages. They are tuples of the form
628 # (category, message)
629 # (category, message)
629 for cat, msg in session.pop(self.session_key, []):
630 for cat, msg in session.pop(self.session_key, []):
630 messages.append(_Message(cat, msg))
631 messages.append(_Message(cat, msg))
631
632
632 # Pop the 'new' pyramid flash messages for each category as list
633 # Pop the 'new' pyramid flash messages for each category as list
633 # of strings.
634 # of strings.
634 for cat in self.categories:
635 for cat in self.categories:
635 for msg in session.pop_flash(queue=cat):
636 for msg in session.pop_flash(queue=cat):
636 messages.append(_Message(cat, msg))
637 messages.append(_Message(cat, msg))
637 # Map messages from the default queue to the 'notice' category.
638 # Map messages from the default queue to the 'notice' category.
638 for msg in session.pop_flash():
639 for msg in session.pop_flash():
639 messages.append(_Message('notice', msg))
640 messages.append(_Message('notice', msg))
640
641
641 session.save()
642 session.save()
642 return messages
643 return messages
643
644
644 def json_alerts(self, session=None, request=None):
645 def json_alerts(self, session=None, request=None):
645 payloads = []
646 payloads = []
646 messages = flash.pop_messages(session=session, request=request)
647 messages = flash.pop_messages(session=session, request=request)
647 if messages:
648 if messages:
648 for message in messages:
649 for message in messages:
649 subdata = {}
650 subdata = {}
650 if hasattr(message.message, 'rsplit'):
651 if hasattr(message.message, 'rsplit'):
651 flash_data = message.message.rsplit('|DELIM|', 1)
652 flash_data = message.message.rsplit('|DELIM|', 1)
652 org_message = flash_data[0]
653 org_message = flash_data[0]
653 if len(flash_data) > 1:
654 if len(flash_data) > 1:
654 subdata = json.loads(flash_data[1])
655 subdata = json.loads(flash_data[1])
655 else:
656 else:
656 org_message = message.message
657 org_message = message.message
657 payloads.append({
658 payloads.append({
658 'message': {
659 'message': {
659 'message': u'{}'.format(org_message),
660 'message': u'{}'.format(org_message),
660 'level': message.category,
661 'level': message.category,
661 'force': True,
662 'force': True,
662 'subdata': subdata
663 'subdata': subdata
663 }
664 }
664 })
665 })
665 return json.dumps(payloads)
666 return json.dumps(payloads)
666
667
667 def __call__(self, message, category=None, ignore_duplicate=True,
668 def __call__(self, message, category=None, ignore_duplicate=True,
668 session=None, request=None):
669 session=None, request=None):
669
670
670 if not session:
671 if not session:
671 if not request:
672 if not request:
672 request = get_current_request()
673 request = get_current_request()
673 session = request.session
674 session = request.session
674
675
675 session.flash(
676 session.flash(
676 message, queue=category, allow_duplicate=not ignore_duplicate)
677 message, queue=category, allow_duplicate=not ignore_duplicate)
677
678
678
679
679 flash = Flash()
680 flash = Flash()
680
681
681 #==============================================================================
682 #==============================================================================
682 # SCM FILTERS available via h.
683 # SCM FILTERS available via h.
683 #==============================================================================
684 #==============================================================================
684 from rhodecode.lib.vcs.utils import author_name, author_email
685 from rhodecode.lib.vcs.utils import author_name, author_email
685 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
686 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
686 from rhodecode.model.db import User, ChangesetStatus
687 from rhodecode.model.db import User, ChangesetStatus
687
688
688 capitalize = lambda x: x.capitalize()
689 capitalize = lambda x: x.capitalize()
689 email = author_email
690 email = author_email
690 short_id = lambda x: x[:12]
691 short_id = lambda x: x[:12]
691 hide_credentials = lambda x: ''.join(credentials_filter(x))
692 hide_credentials = lambda x: ''.join(credentials_filter(x))
692
693
693
694
694 import pytz
695 import pytz
695 import tzlocal
696 import tzlocal
696 local_timezone = tzlocal.get_localzone()
697 local_timezone = tzlocal.get_localzone()
697
698
698
699
699 def age_component(datetime_iso, value=None, time_is_local=False):
700 def age_component(datetime_iso, value=None, time_is_local=False):
700 title = value or format_date(datetime_iso)
701 title = value or format_date(datetime_iso)
701 tzinfo = '+00:00'
702 tzinfo = '+00:00'
702
703
703 # detect if we have a timezone info, otherwise, add it
704 # detect if we have a timezone info, otherwise, add it
704 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
705 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
705 force_timezone = os.environ.get('RC_TIMEZONE', '')
706 force_timezone = os.environ.get('RC_TIMEZONE', '')
706 if force_timezone:
707 if force_timezone:
707 force_timezone = pytz.timezone(force_timezone)
708 force_timezone = pytz.timezone(force_timezone)
708 timezone = force_timezone or local_timezone
709 timezone = force_timezone or local_timezone
709 offset = timezone.localize(datetime_iso).strftime('%z')
710 offset = timezone.localize(datetime_iso).strftime('%z')
710 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
711 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
711
712
712 return literal(
713 return literal(
713 '<time class="timeago tooltip" '
714 '<time class="timeago tooltip" '
714 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
715 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
715 datetime_iso, title, tzinfo))
716 datetime_iso, title, tzinfo))
716
717
717
718
718 def _shorten_commit_id(commit_id, commit_len=None):
719 def _shorten_commit_id(commit_id, commit_len=None):
719 if commit_len is None:
720 if commit_len is None:
720 request = get_current_request()
721 request = get_current_request()
721 commit_len = request.call_context.visual.show_sha_length
722 commit_len = request.call_context.visual.show_sha_length
722 return commit_id[:commit_len]
723 return commit_id[:commit_len]
723
724
724
725
725 def show_id(commit, show_idx=None, commit_len=None):
726 def show_id(commit, show_idx=None, commit_len=None):
726 """
727 """
727 Configurable function that shows ID
728 Configurable function that shows ID
728 by default it's r123:fffeeefffeee
729 by default it's r123:fffeeefffeee
729
730
730 :param commit: commit instance
731 :param commit: commit instance
731 """
732 """
732 if show_idx is None:
733 if show_idx is None:
733 request = get_current_request()
734 request = get_current_request()
734 show_idx = request.call_context.visual.show_revision_number
735 show_idx = request.call_context.visual.show_revision_number
735
736
736 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
737 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
737 if show_idx:
738 if show_idx:
738 return 'r%s:%s' % (commit.idx, raw_id)
739 return 'r%s:%s' % (commit.idx, raw_id)
739 else:
740 else:
740 return '%s' % (raw_id, )
741 return '%s' % (raw_id, )
741
742
742
743
743 def format_date(date):
744 def format_date(date):
744 """
745 """
745 use a standardized formatting for dates used in RhodeCode
746 use a standardized formatting for dates used in RhodeCode
746
747
747 :param date: date/datetime object
748 :param date: date/datetime object
748 :return: formatted date
749 :return: formatted date
749 """
750 """
750
751
751 if date:
752 if date:
752 _fmt = "%a, %d %b %Y %H:%M:%S"
753 _fmt = "%a, %d %b %Y %H:%M:%S"
753 return safe_unicode(date.strftime(_fmt))
754 return safe_unicode(date.strftime(_fmt))
754
755
755 return u""
756 return u""
756
757
757
758
758 class _RepoChecker(object):
759 class _RepoChecker(object):
759
760
760 def __init__(self, backend_alias):
761 def __init__(self, backend_alias):
761 self._backend_alias = backend_alias
762 self._backend_alias = backend_alias
762
763
763 def __call__(self, repository):
764 def __call__(self, repository):
764 if hasattr(repository, 'alias'):
765 if hasattr(repository, 'alias'):
765 _type = repository.alias
766 _type = repository.alias
766 elif hasattr(repository, 'repo_type'):
767 elif hasattr(repository, 'repo_type'):
767 _type = repository.repo_type
768 _type = repository.repo_type
768 else:
769 else:
769 _type = repository
770 _type = repository
770 return _type == self._backend_alias
771 return _type == self._backend_alias
771
772
772
773
773 is_git = _RepoChecker('git')
774 is_git = _RepoChecker('git')
774 is_hg = _RepoChecker('hg')
775 is_hg = _RepoChecker('hg')
775 is_svn = _RepoChecker('svn')
776 is_svn = _RepoChecker('svn')
776
777
777
778
778 def get_repo_type_by_name(repo_name):
779 def get_repo_type_by_name(repo_name):
779 repo = Repository.get_by_repo_name(repo_name)
780 repo = Repository.get_by_repo_name(repo_name)
780 if repo:
781 if repo:
781 return repo.repo_type
782 return repo.repo_type
782
783
783
784
784 def is_svn_without_proxy(repository):
785 def is_svn_without_proxy(repository):
785 if is_svn(repository):
786 if is_svn(repository):
786 from rhodecode.model.settings import VcsSettingsModel
787 from rhodecode.model.settings import VcsSettingsModel
787 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
788 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
788 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
789 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
789 return False
790 return False
790
791
791
792
792 def discover_user(author):
793 def discover_user(author):
793 """
794 """
794 Tries to discover RhodeCode User based on the autho string. Author string
795 Tries to discover RhodeCode User based on the autho string. Author string
795 is typically `FirstName LastName <email@address.com>`
796 is typically `FirstName LastName <email@address.com>`
796 """
797 """
797
798
798 # if author is already an instance use it for extraction
799 # if author is already an instance use it for extraction
799 if isinstance(author, User):
800 if isinstance(author, User):
800 return author
801 return author
801
802
802 # Valid email in the attribute passed, see if they're in the system
803 # Valid email in the attribute passed, see if they're in the system
803 _email = author_email(author)
804 _email = author_email(author)
804 if _email != '':
805 if _email != '':
805 user = User.get_by_email(_email, case_insensitive=True, cache=True)
806 user = User.get_by_email(_email, case_insensitive=True, cache=True)
806 if user is not None:
807 if user is not None:
807 return user
808 return user
808
809
809 # Maybe it's a username, we try to extract it and fetch by username ?
810 # Maybe it's a username, we try to extract it and fetch by username ?
810 _author = author_name(author)
811 _author = author_name(author)
811 user = User.get_by_username(_author, case_insensitive=True, cache=True)
812 user = User.get_by_username(_author, case_insensitive=True, cache=True)
812 if user is not None:
813 if user is not None:
813 return user
814 return user
814
815
815 return None
816 return None
816
817
817
818
818 def email_or_none(author):
819 def email_or_none(author):
819 # extract email from the commit string
820 # extract email from the commit string
820 _email = author_email(author)
821 _email = author_email(author)
821
822
822 # If we have an email, use it, otherwise
823 # If we have an email, use it, otherwise
823 # see if it contains a username we can get an email from
824 # see if it contains a username we can get an email from
824 if _email != '':
825 if _email != '':
825 return _email
826 return _email
826 else:
827 else:
827 user = User.get_by_username(
828 user = User.get_by_username(
828 author_name(author), case_insensitive=True, cache=True)
829 author_name(author), case_insensitive=True, cache=True)
829
830
830 if user is not None:
831 if user is not None:
831 return user.email
832 return user.email
832
833
833 # No valid email, not a valid user in the system, none!
834 # No valid email, not a valid user in the system, none!
834 return None
835 return None
835
836
836
837
837 def link_to_user(author, length=0, **kwargs):
838 def link_to_user(author, length=0, **kwargs):
838 user = discover_user(author)
839 user = discover_user(author)
839 # user can be None, but if we have it already it means we can re-use it
840 # user can be None, but if we have it already it means we can re-use it
840 # in the person() function, so we save 1 intensive-query
841 # in the person() function, so we save 1 intensive-query
841 if user:
842 if user:
842 author = user
843 author = user
843
844
844 display_person = person(author, 'username_or_name_or_email')
845 display_person = person(author, 'username_or_name_or_email')
845 if length:
846 if length:
846 display_person = shorter(display_person, length)
847 display_person = shorter(display_person, length)
847
848
848 if user:
849 if user:
849 return link_to(
850 return link_to(
850 escape(display_person),
851 escape(display_person),
851 route_path('user_profile', username=user.username),
852 route_path('user_profile', username=user.username),
852 **kwargs)
853 **kwargs)
853 else:
854 else:
854 return escape(display_person)
855 return escape(display_person)
855
856
856
857
857 def link_to_group(users_group_name, **kwargs):
858 def link_to_group(users_group_name, **kwargs):
858 return link_to(
859 return link_to(
859 escape(users_group_name),
860 escape(users_group_name),
860 route_path('user_group_profile', user_group_name=users_group_name),
861 route_path('user_group_profile', user_group_name=users_group_name),
861 **kwargs)
862 **kwargs)
862
863
863
864
864 def person(author, show_attr="username_and_name"):
865 def person(author, show_attr="username_and_name"):
865 user = discover_user(author)
866 user = discover_user(author)
866 if user:
867 if user:
867 return getattr(user, show_attr)
868 return getattr(user, show_attr)
868 else:
869 else:
869 _author = author_name(author)
870 _author = author_name(author)
870 _email = email(author)
871 _email = email(author)
871 return _author or _email
872 return _author or _email
872
873
873
874
874 def author_string(email):
875 def author_string(email):
875 if email:
876 if email:
876 user = User.get_by_email(email, case_insensitive=True, cache=True)
877 user = User.get_by_email(email, case_insensitive=True, cache=True)
877 if user:
878 if user:
878 if user.first_name or user.last_name:
879 if user.first_name or user.last_name:
879 return '%s %s &lt;%s&gt;' % (
880 return '%s %s &lt;%s&gt;' % (
880 user.first_name, user.last_name, email)
881 user.first_name, user.last_name, email)
881 else:
882 else:
882 return email
883 return email
883 else:
884 else:
884 return email
885 return email
885 else:
886 else:
886 return None
887 return None
887
888
888
889
889 def person_by_id(id_, show_attr="username_and_name"):
890 def person_by_id(id_, show_attr="username_and_name"):
890 # attr to return from fetched user
891 # attr to return from fetched user
891 person_getter = lambda usr: getattr(usr, show_attr)
892 person_getter = lambda usr: getattr(usr, show_attr)
892
893
893 #maybe it's an ID ?
894 #maybe it's an ID ?
894 if str(id_).isdigit() or isinstance(id_, int):
895 if str(id_).isdigit() or isinstance(id_, int):
895 id_ = int(id_)
896 id_ = int(id_)
896 user = User.get(id_)
897 user = User.get(id_)
897 if user is not None:
898 if user is not None:
898 return person_getter(user)
899 return person_getter(user)
899 return id_
900 return id_
900
901
901
902
902 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
903 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
903 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
904 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
904 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
905 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
905
906
906
907
907 tags_paterns = OrderedDict((
908 tags_paterns = OrderedDict((
908 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
909 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
909 '<div class="metatag" tag="lang">\\2</div>')),
910 '<div class="metatag" tag="lang">\\2</div>')),
910
911
911 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
912 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
912 '<div class="metatag" tag="see">see: \\1 </div>')),
913 '<div class="metatag" tag="see">see: \\1 </div>')),
913
914
914 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
915 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
915 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
916 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
916
917
917 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
918 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
918 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
919 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
919
920
920 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
921 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
921 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
922 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
922
923
923 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
924 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
924 '<div class="metatag" tag="state \\1">\\1</div>')),
925 '<div class="metatag" tag="state \\1">\\1</div>')),
925
926
926 # label in grey
927 # label in grey
927 ('label', (re.compile(r'\[([a-z]+)\]'),
928 ('label', (re.compile(r'\[([a-z]+)\]'),
928 '<div class="metatag" tag="label">\\1</div>')),
929 '<div class="metatag" tag="label">\\1</div>')),
929
930
930 # generic catch all in grey
931 # generic catch all in grey
931 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
932 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
932 '<div class="metatag" tag="generic">\\1</div>')),
933 '<div class="metatag" tag="generic">\\1</div>')),
933 ))
934 ))
934
935
935
936
936 def extract_metatags(value):
937 def extract_metatags(value):
937 """
938 """
938 Extract supported meta-tags from given text value
939 Extract supported meta-tags from given text value
939 """
940 """
940 tags = []
941 tags = []
941 if not value:
942 if not value:
942 return tags, ''
943 return tags, ''
943
944
944 for key, val in tags_paterns.items():
945 for key, val in tags_paterns.items():
945 pat, replace_html = val
946 pat, replace_html = val
946 tags.extend([(key, x.group()) for x in pat.finditer(value)])
947 tags.extend([(key, x.group()) for x in pat.finditer(value)])
947 value = pat.sub('', value)
948 value = pat.sub('', value)
948
949
949 return tags, value
950 return tags, value
950
951
951
952
952 def style_metatag(tag_type, value):
953 def style_metatag(tag_type, value):
953 """
954 """
954 converts tags from value into html equivalent
955 converts tags from value into html equivalent
955 """
956 """
956 if not value:
957 if not value:
957 return ''
958 return ''
958
959
959 html_value = value
960 html_value = value
960 tag_data = tags_paterns.get(tag_type)
961 tag_data = tags_paterns.get(tag_type)
961 if tag_data:
962 if tag_data:
962 pat, replace_html = tag_data
963 pat, replace_html = tag_data
963 # convert to plain `unicode` instead of a markup tag to be used in
964 # convert to plain `unicode` instead of a markup tag to be used in
964 # regex expressions. safe_unicode doesn't work here
965 # regex expressions. safe_unicode doesn't work here
965 html_value = pat.sub(replace_html, unicode(value))
966 html_value = pat.sub(replace_html, unicode(value))
966
967
967 return html_value
968 return html_value
968
969
969
970
970 def bool2icon(value, show_at_false=True):
971 def bool2icon(value, show_at_false=True):
971 """
972 """
972 Returns boolean value of a given value, represented as html element with
973 Returns boolean value of a given value, represented as html element with
973 classes that will represent icons
974 classes that will represent icons
974
975
975 :param value: given value to convert to html node
976 :param value: given value to convert to html node
976 """
977 """
977
978
978 if value: # does bool conversion
979 if value: # does bool conversion
979 return HTML.tag('i', class_="icon-true", title='True')
980 return HTML.tag('i', class_="icon-true", title='True')
980 else: # not true as bool
981 else: # not true as bool
981 if show_at_false:
982 if show_at_false:
982 return HTML.tag('i', class_="icon-false", title='False')
983 return HTML.tag('i', class_="icon-false", title='False')
983 return HTML.tag('i')
984 return HTML.tag('i')
984
985
985 #==============================================================================
986 #==============================================================================
986 # PERMS
987 # PERMS
987 #==============================================================================
988 #==============================================================================
988 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
989 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
989 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
990 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
990 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
991 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
991 csrf_token_key
992 csrf_token_key
992
993
993
994
994 #==============================================================================
995 #==============================================================================
995 # GRAVATAR URL
996 # GRAVATAR URL
996 #==============================================================================
997 #==============================================================================
997 class InitialsGravatar(object):
998 class InitialsGravatar(object):
998 def __init__(self, email_address, first_name, last_name, size=30,
999 def __init__(self, email_address, first_name, last_name, size=30,
999 background=None, text_color='#fff'):
1000 background=None, text_color='#fff'):
1000 self.size = size
1001 self.size = size
1001 self.first_name = first_name
1002 self.first_name = first_name
1002 self.last_name = last_name
1003 self.last_name = last_name
1003 self.email_address = email_address
1004 self.email_address = email_address
1004 self.background = background or self.str2color(email_address)
1005 self.background = background or self.str2color(email_address)
1005 self.text_color = text_color
1006 self.text_color = text_color
1006
1007
1007 def get_color_bank(self):
1008 def get_color_bank(self):
1008 """
1009 """
1009 returns a predefined list of colors that gravatars can use.
1010 returns a predefined list of colors that gravatars can use.
1010 Those are randomized distinct colors that guarantee readability and
1011 Those are randomized distinct colors that guarantee readability and
1011 uniqueness.
1012 uniqueness.
1012
1013
1013 generated with: http://phrogz.net/css/distinct-colors.html
1014 generated with: http://phrogz.net/css/distinct-colors.html
1014 """
1015 """
1015 return [
1016 return [
1016 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1017 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1017 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1018 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1018 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1019 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1019 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1020 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1020 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1021 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1021 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1022 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1022 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1023 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1023 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1024 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1024 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1025 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1025 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1026 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1026 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1027 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1027 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1028 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1028 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1029 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1029 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1030 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1030 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1031 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1031 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1032 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1032 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1033 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1033 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1034 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1034 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1035 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1035 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1036 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1036 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1037 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1037 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1038 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1038 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1039 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1039 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1040 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1040 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1041 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1041 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1042 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1042 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1043 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1043 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1044 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1044 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1045 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1045 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1046 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1046 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1047 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1047 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1048 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1048 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1049 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1049 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1050 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1050 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1051 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1051 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1052 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1052 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1053 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1053 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1054 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1054 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1055 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1055 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1056 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1056 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1057 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1057 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1058 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1058 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1059 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1059 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1060 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1060 '#4f8c46', '#368dd9', '#5c0073'
1061 '#4f8c46', '#368dd9', '#5c0073'
1061 ]
1062 ]
1062
1063
1063 def rgb_to_hex_color(self, rgb_tuple):
1064 def rgb_to_hex_color(self, rgb_tuple):
1064 """
1065 """
1065 Converts an rgb_tuple passed to an hex color.
1066 Converts an rgb_tuple passed to an hex color.
1066
1067
1067 :param rgb_tuple: tuple with 3 ints represents rgb color space
1068 :param rgb_tuple: tuple with 3 ints represents rgb color space
1068 """
1069 """
1069 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1070 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1070
1071
1071 def email_to_int_list(self, email_str):
1072 def email_to_int_list(self, email_str):
1072 """
1073 """
1073 Get every byte of the hex digest value of email and turn it to integer.
1074 Get every byte of the hex digest value of email and turn it to integer.
1074 It's going to be always between 0-255
1075 It's going to be always between 0-255
1075 """
1076 """
1076 digest = md5_safe(email_str.lower())
1077 digest = md5_safe(email_str.lower())
1077 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1078 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1078
1079
1079 def pick_color_bank_index(self, email_str, color_bank):
1080 def pick_color_bank_index(self, email_str, color_bank):
1080 return self.email_to_int_list(email_str)[0] % len(color_bank)
1081 return self.email_to_int_list(email_str)[0] % len(color_bank)
1081
1082
1082 def str2color(self, email_str):
1083 def str2color(self, email_str):
1083 """
1084 """
1084 Tries to map in a stable algorithm an email to color
1085 Tries to map in a stable algorithm an email to color
1085
1086
1086 :param email_str:
1087 :param email_str:
1087 """
1088 """
1088 color_bank = self.get_color_bank()
1089 color_bank = self.get_color_bank()
1089 # pick position (module it's length so we always find it in the
1090 # pick position (module it's length so we always find it in the
1090 # bank even if it's smaller than 256 values
1091 # bank even if it's smaller than 256 values
1091 pos = self.pick_color_bank_index(email_str, color_bank)
1092 pos = self.pick_color_bank_index(email_str, color_bank)
1092 return color_bank[pos]
1093 return color_bank[pos]
1093
1094
1094 def normalize_email(self, email_address):
1095 def normalize_email(self, email_address):
1095 import unicodedata
1096 import unicodedata
1096 # default host used to fill in the fake/missing email
1097 # default host used to fill in the fake/missing email
1097 default_host = u'localhost'
1098 default_host = u'localhost'
1098
1099
1099 if not email_address:
1100 if not email_address:
1100 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1101 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1101
1102
1102 email_address = safe_unicode(email_address)
1103 email_address = safe_unicode(email_address)
1103
1104
1104 if u'@' not in email_address:
1105 if u'@' not in email_address:
1105 email_address = u'%s@%s' % (email_address, default_host)
1106 email_address = u'%s@%s' % (email_address, default_host)
1106
1107
1107 if email_address.endswith(u'@'):
1108 if email_address.endswith(u'@'):
1108 email_address = u'%s%s' % (email_address, default_host)
1109 email_address = u'%s%s' % (email_address, default_host)
1109
1110
1110 email_address = unicodedata.normalize('NFKD', email_address)\
1111 email_address = unicodedata.normalize('NFKD', email_address)\
1111 .encode('ascii', 'ignore')
1112 .encode('ascii', 'ignore')
1112 return email_address
1113 return email_address
1113
1114
1114 def get_initials(self):
1115 def get_initials(self):
1115 """
1116 """
1116 Returns 2 letter initials calculated based on the input.
1117 Returns 2 letter initials calculated based on the input.
1117 The algorithm picks first given email address, and takes first letter
1118 The algorithm picks first given email address, and takes first letter
1118 of part before @, and then the first letter of server name. In case
1119 of part before @, and then the first letter of server name. In case
1119 the part before @ is in a format of `somestring.somestring2` it replaces
1120 the part before @ is in a format of `somestring.somestring2` it replaces
1120 the server letter with first letter of somestring2
1121 the server letter with first letter of somestring2
1121
1122
1122 In case function was initialized with both first and lastname, this
1123 In case function was initialized with both first and lastname, this
1123 overrides the extraction from email by first letter of the first and
1124 overrides the extraction from email by first letter of the first and
1124 last name. We add special logic to that functionality, In case Full name
1125 last name. We add special logic to that functionality, In case Full name
1125 is compound, like Guido Von Rossum, we use last part of the last name
1126 is compound, like Guido Von Rossum, we use last part of the last name
1126 (Von Rossum) picking `R`.
1127 (Von Rossum) picking `R`.
1127
1128
1128 Function also normalizes the non-ascii characters to they ascii
1129 Function also normalizes the non-ascii characters to they ascii
1129 representation, eg Δ„ => A
1130 representation, eg Δ„ => A
1130 """
1131 """
1131 import unicodedata
1132 import unicodedata
1132 # replace non-ascii to ascii
1133 # replace non-ascii to ascii
1133 first_name = unicodedata.normalize(
1134 first_name = unicodedata.normalize(
1134 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1135 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1135 last_name = unicodedata.normalize(
1136 last_name = unicodedata.normalize(
1136 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1137 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1137
1138
1138 # do NFKD encoding, and also make sure email has proper format
1139 # do NFKD encoding, and also make sure email has proper format
1139 email_address = self.normalize_email(self.email_address)
1140 email_address = self.normalize_email(self.email_address)
1140
1141
1141 # first push the email initials
1142 # first push the email initials
1142 prefix, server = email_address.split('@', 1)
1143 prefix, server = email_address.split('@', 1)
1143
1144
1144 # check if prefix is maybe a 'first_name.last_name' syntax
1145 # check if prefix is maybe a 'first_name.last_name' syntax
1145 _dot_split = prefix.rsplit('.', 1)
1146 _dot_split = prefix.rsplit('.', 1)
1146 if len(_dot_split) == 2 and _dot_split[1]:
1147 if len(_dot_split) == 2 and _dot_split[1]:
1147 initials = [_dot_split[0][0], _dot_split[1][0]]
1148 initials = [_dot_split[0][0], _dot_split[1][0]]
1148 else:
1149 else:
1149 initials = [prefix[0], server[0]]
1150 initials = [prefix[0], server[0]]
1150
1151
1151 # then try to replace either first_name or last_name
1152 # then try to replace either first_name or last_name
1152 fn_letter = (first_name or " ")[0].strip()
1153 fn_letter = (first_name or " ")[0].strip()
1153 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1154 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1154
1155
1155 if fn_letter:
1156 if fn_letter:
1156 initials[0] = fn_letter
1157 initials[0] = fn_letter
1157
1158
1158 if ln_letter:
1159 if ln_letter:
1159 initials[1] = ln_letter
1160 initials[1] = ln_letter
1160
1161
1161 return ''.join(initials).upper()
1162 return ''.join(initials).upper()
1162
1163
1163 def get_img_data_by_type(self, font_family, img_type):
1164 def get_img_data_by_type(self, font_family, img_type):
1164 default_user = """
1165 default_user = """
1165 <svg xmlns="http://www.w3.org/2000/svg"
1166 <svg xmlns="http://www.w3.org/2000/svg"
1166 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1167 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1167 viewBox="-15 -10 439.165 429.164"
1168 viewBox="-15 -10 439.165 429.164"
1168
1169
1169 xml:space="preserve"
1170 xml:space="preserve"
1170 style="background:{background};" >
1171 style="background:{background};" >
1171
1172
1172 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1173 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1173 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1174 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1174 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1175 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1175 168.596,153.916,216.671,
1176 168.596,153.916,216.671,
1176 204.583,216.671z" fill="{text_color}"/>
1177 204.583,216.671z" fill="{text_color}"/>
1177 <path d="M407.164,374.717L360.88,
1178 <path d="M407.164,374.717L360.88,
1178 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1179 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1179 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1180 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1180 15.366-44.203,23.488-69.076,23.488c-24.877,
1181 15.366-44.203,23.488-69.076,23.488c-24.877,
1181 0-48.762-8.122-69.078-23.488
1182 0-48.762-8.122-69.078-23.488
1182 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1183 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1183 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1184 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1184 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1185 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1185 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1186 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1186 19.402-10.527 C409.699,390.129,
1187 19.402-10.527 C409.699,390.129,
1187 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1188 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1188 </svg>""".format(
1189 </svg>""".format(
1189 size=self.size,
1190 size=self.size,
1190 background='#979797', # @grey4
1191 background='#979797', # @grey4
1191 text_color=self.text_color,
1192 text_color=self.text_color,
1192 font_family=font_family)
1193 font_family=font_family)
1193
1194
1194 return {
1195 return {
1195 "default_user": default_user
1196 "default_user": default_user
1196 }[img_type]
1197 }[img_type]
1197
1198
1198 def get_img_data(self, svg_type=None):
1199 def get_img_data(self, svg_type=None):
1199 """
1200 """
1200 generates the svg metadata for image
1201 generates the svg metadata for image
1201 """
1202 """
1202 fonts = [
1203 fonts = [
1203 '-apple-system',
1204 '-apple-system',
1204 'BlinkMacSystemFont',
1205 'BlinkMacSystemFont',
1205 'Segoe UI',
1206 'Segoe UI',
1206 'Roboto',
1207 'Roboto',
1207 'Oxygen-Sans',
1208 'Oxygen-Sans',
1208 'Ubuntu',
1209 'Ubuntu',
1209 'Cantarell',
1210 'Cantarell',
1210 'Helvetica Neue',
1211 'Helvetica Neue',
1211 'sans-serif'
1212 'sans-serif'
1212 ]
1213 ]
1213 font_family = ','.join(fonts)
1214 font_family = ','.join(fonts)
1214 if svg_type:
1215 if svg_type:
1215 return self.get_img_data_by_type(font_family, svg_type)
1216 return self.get_img_data_by_type(font_family, svg_type)
1216
1217
1217 initials = self.get_initials()
1218 initials = self.get_initials()
1218 img_data = """
1219 img_data = """
1219 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1220 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1220 width="{size}" height="{size}"
1221 width="{size}" height="{size}"
1221 style="width: 100%; height: 100%; background-color: {background}"
1222 style="width: 100%; height: 100%; background-color: {background}"
1222 viewBox="0 0 {size} {size}">
1223 viewBox="0 0 {size} {size}">
1223 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1224 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1224 pointer-events="auto" fill="{text_color}"
1225 pointer-events="auto" fill="{text_color}"
1225 font-family="{font_family}"
1226 font-family="{font_family}"
1226 style="font-weight: 400; font-size: {f_size}px;">{text}
1227 style="font-weight: 400; font-size: {f_size}px;">{text}
1227 </text>
1228 </text>
1228 </svg>""".format(
1229 </svg>""".format(
1229 size=self.size,
1230 size=self.size,
1230 f_size=self.size/2.05, # scale the text inside the box nicely
1231 f_size=self.size/2.05, # scale the text inside the box nicely
1231 background=self.background,
1232 background=self.background,
1232 text_color=self.text_color,
1233 text_color=self.text_color,
1233 text=initials.upper(),
1234 text=initials.upper(),
1234 font_family=font_family)
1235 font_family=font_family)
1235
1236
1236 return img_data
1237 return img_data
1237
1238
1238 def generate_svg(self, svg_type=None):
1239 def generate_svg(self, svg_type=None):
1239 img_data = self.get_img_data(svg_type)
1240 img_data = self.get_img_data(svg_type)
1240 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1241 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1241
1242
1242
1243
1243 def initials_gravatar(email_address, first_name, last_name, size=30):
1244 def initials_gravatar(email_address, first_name, last_name, size=30):
1244 svg_type = None
1245 svg_type = None
1245 if email_address == User.DEFAULT_USER_EMAIL:
1246 if email_address == User.DEFAULT_USER_EMAIL:
1246 svg_type = 'default_user'
1247 svg_type = 'default_user'
1247 klass = InitialsGravatar(email_address, first_name, last_name, size)
1248 klass = InitialsGravatar(email_address, first_name, last_name, size)
1248 return klass.generate_svg(svg_type=svg_type)
1249 return klass.generate_svg(svg_type=svg_type)
1249
1250
1250
1251
1251 def gravatar_url(email_address, size=30, request=None):
1252 def gravatar_url(email_address, size=30, request=None):
1252 request = get_current_request()
1253 request = get_current_request()
1253 _use_gravatar = request.call_context.visual.use_gravatar
1254 _use_gravatar = request.call_context.visual.use_gravatar
1254 _gravatar_url = request.call_context.visual.gravatar_url
1255 _gravatar_url = request.call_context.visual.gravatar_url
1255
1256
1256 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1257 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1257
1258
1258 email_address = email_address or User.DEFAULT_USER_EMAIL
1259 email_address = email_address or User.DEFAULT_USER_EMAIL
1259 if isinstance(email_address, unicode):
1260 if isinstance(email_address, unicode):
1260 # hashlib crashes on unicode items
1261 # hashlib crashes on unicode items
1261 email_address = safe_str(email_address)
1262 email_address = safe_str(email_address)
1262
1263
1263 # empty email or default user
1264 # empty email or default user
1264 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1265 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1265 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1266 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1266
1267
1267 if _use_gravatar:
1268 if _use_gravatar:
1268 # TODO: Disuse pyramid thread locals. Think about another solution to
1269 # TODO: Disuse pyramid thread locals. Think about another solution to
1269 # get the host and schema here.
1270 # get the host and schema here.
1270 request = get_current_request()
1271 request = get_current_request()
1271 tmpl = safe_str(_gravatar_url)
1272 tmpl = safe_str(_gravatar_url)
1272 tmpl = tmpl.replace('{email}', email_address)\
1273 tmpl = tmpl.replace('{email}', email_address)\
1273 .replace('{md5email}', md5_safe(email_address.lower())) \
1274 .replace('{md5email}', md5_safe(email_address.lower())) \
1274 .replace('{netloc}', request.host)\
1275 .replace('{netloc}', request.host)\
1275 .replace('{scheme}', request.scheme)\
1276 .replace('{scheme}', request.scheme)\
1276 .replace('{size}', safe_str(size))
1277 .replace('{size}', safe_str(size))
1277 return tmpl
1278 return tmpl
1278 else:
1279 else:
1279 return initials_gravatar(email_address, '', '', size=size)
1280 return initials_gravatar(email_address, '', '', size=size)
1280
1281
1281
1282
1282 class Page(_Page):
1283 class Page(_Page):
1283 """
1284 """
1284 Custom pager to match rendering style with paginator
1285 Custom pager to match rendering style with paginator
1285 """
1286 """
1286
1287
1287 def _get_pos(self, cur_page, max_page, items):
1288 def _get_pos(self, cur_page, max_page, items):
1288 edge = (items / 2) + 1
1289 edge = (items / 2) + 1
1289 if (cur_page <= edge):
1290 if (cur_page <= edge):
1290 radius = max(items / 2, items - cur_page)
1291 radius = max(items / 2, items - cur_page)
1291 elif (max_page - cur_page) < edge:
1292 elif (max_page - cur_page) < edge:
1292 radius = (items - 1) - (max_page - cur_page)
1293 radius = (items - 1) - (max_page - cur_page)
1293 else:
1294 else:
1294 radius = items / 2
1295 radius = items / 2
1295
1296
1296 left = max(1, (cur_page - (radius)))
1297 left = max(1, (cur_page - (radius)))
1297 right = min(max_page, cur_page + (radius))
1298 right = min(max_page, cur_page + (radius))
1298 return left, cur_page, right
1299 return left, cur_page, right
1299
1300
1300 def _range(self, regexp_match):
1301 def _range(self, regexp_match):
1301 """
1302 """
1302 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1303 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1303
1304
1304 Arguments:
1305 Arguments:
1305
1306
1306 regexp_match
1307 regexp_match
1307 A "re" (regular expressions) match object containing the
1308 A "re" (regular expressions) match object containing the
1308 radius of linked pages around the current page in
1309 radius of linked pages around the current page in
1309 regexp_match.group(1) as a string
1310 regexp_match.group(1) as a string
1310
1311
1311 This function is supposed to be called as a callable in
1312 This function is supposed to be called as a callable in
1312 re.sub.
1313 re.sub.
1313
1314
1314 """
1315 """
1315 radius = int(regexp_match.group(1))
1316 radius = int(regexp_match.group(1))
1316
1317
1317 # Compute the first and last page number within the radius
1318 # Compute the first and last page number within the radius
1318 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1319 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1319 # -> leftmost_page = 5
1320 # -> leftmost_page = 5
1320 # -> rightmost_page = 9
1321 # -> rightmost_page = 9
1321 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1322 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1322 self.last_page,
1323 self.last_page,
1323 (radius * 2) + 1)
1324 (radius * 2) + 1)
1324 nav_items = []
1325 nav_items = []
1325
1326
1326 # Create a link to the first page (unless we are on the first page
1327 # Create a link to the first page (unless we are on the first page
1327 # or there would be no need to insert '..' spacers)
1328 # or there would be no need to insert '..' spacers)
1328 if self.page != self.first_page and self.first_page < leftmost_page:
1329 if self.page != self.first_page and self.first_page < leftmost_page:
1329 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1330 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1330
1331
1331 # Insert dots if there are pages between the first page
1332 # Insert dots if there are pages between the first page
1332 # and the currently displayed page range
1333 # and the currently displayed page range
1333 if leftmost_page - self.first_page > 1:
1334 if leftmost_page - self.first_page > 1:
1334 # Wrap in a SPAN tag if nolink_attr is set
1335 # Wrap in a SPAN tag if nolink_attr is set
1335 text = '..'
1336 text = '..'
1336 if self.dotdot_attr:
1337 if self.dotdot_attr:
1337 text = HTML.span(c=text, **self.dotdot_attr)
1338 text = HTML.span(c=text, **self.dotdot_attr)
1338 nav_items.append(text)
1339 nav_items.append(text)
1339
1340
1340 for thispage in xrange(leftmost_page, rightmost_page + 1):
1341 for thispage in xrange(leftmost_page, rightmost_page + 1):
1341 # Hilight the current page number and do not use a link
1342 # Hilight the current page number and do not use a link
1342 if thispage == self.page:
1343 if thispage == self.page:
1343 text = '%s' % (thispage,)
1344 text = '%s' % (thispage,)
1344 # Wrap in a SPAN tag if nolink_attr is set
1345 # Wrap in a SPAN tag if nolink_attr is set
1345 if self.curpage_attr:
1346 if self.curpage_attr:
1346 text = HTML.span(c=text, **self.curpage_attr)
1347 text = HTML.span(c=text, **self.curpage_attr)
1347 nav_items.append(text)
1348 nav_items.append(text)
1348 # Otherwise create just a link to that page
1349 # Otherwise create just a link to that page
1349 else:
1350 else:
1350 text = '%s' % (thispage,)
1351 text = '%s' % (thispage,)
1351 nav_items.append(self._pagerlink(thispage, text))
1352 nav_items.append(self._pagerlink(thispage, text))
1352
1353
1353 # Insert dots if there are pages between the displayed
1354 # Insert dots if there are pages between the displayed
1354 # page numbers and the end of the page range
1355 # page numbers and the end of the page range
1355 if self.last_page - rightmost_page > 1:
1356 if self.last_page - rightmost_page > 1:
1356 text = '..'
1357 text = '..'
1357 # Wrap in a SPAN tag if nolink_attr is set
1358 # Wrap in a SPAN tag if nolink_attr is set
1358 if self.dotdot_attr:
1359 if self.dotdot_attr:
1359 text = HTML.span(c=text, **self.dotdot_attr)
1360 text = HTML.span(c=text, **self.dotdot_attr)
1360 nav_items.append(text)
1361 nav_items.append(text)
1361
1362
1362 # Create a link to the very last page (unless we are on the last
1363 # Create a link to the very last page (unless we are on the last
1363 # page or there would be no need to insert '..' spacers)
1364 # page or there would be no need to insert '..' spacers)
1364 if self.page != self.last_page and rightmost_page < self.last_page:
1365 if self.page != self.last_page and rightmost_page < self.last_page:
1365 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1366 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1366
1367
1367 ## prerender links
1368 ## prerender links
1368 #_page_link = url.current()
1369 #_page_link = url.current()
1369 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1370 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1370 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1371 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1371 return self.separator.join(nav_items)
1372 return self.separator.join(nav_items)
1372
1373
1373 def pager(self, format='~2~', page_param='page', partial_param='partial',
1374 def pager(self, format='~2~', page_param='page', partial_param='partial',
1374 show_if_single_page=False, separator=' ', onclick=None,
1375 show_if_single_page=False, separator=' ', onclick=None,
1375 symbol_first='<<', symbol_last='>>',
1376 symbol_first='<<', symbol_last='>>',
1376 symbol_previous='<', symbol_next='>',
1377 symbol_previous='<', symbol_next='>',
1377 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1378 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1378 curpage_attr={'class': 'pager_curpage'},
1379 curpage_attr={'class': 'pager_curpage'},
1379 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1380 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1380
1381
1381 self.curpage_attr = curpage_attr
1382 self.curpage_attr = curpage_attr
1382 self.separator = separator
1383 self.separator = separator
1383 self.pager_kwargs = kwargs
1384 self.pager_kwargs = kwargs
1384 self.page_param = page_param
1385 self.page_param = page_param
1385 self.partial_param = partial_param
1386 self.partial_param = partial_param
1386 self.onclick = onclick
1387 self.onclick = onclick
1387 self.link_attr = link_attr
1388 self.link_attr = link_attr
1388 self.dotdot_attr = dotdot_attr
1389 self.dotdot_attr = dotdot_attr
1389
1390
1390 # Don't show navigator if there is no more than one page
1391 # Don't show navigator if there is no more than one page
1391 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1392 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1392 return ''
1393 return ''
1393
1394
1394 from string import Template
1395 from string import Template
1395 # Replace ~...~ in token format by range of pages
1396 # Replace ~...~ in token format by range of pages
1396 result = re.sub(r'~(\d+)~', self._range, format)
1397 result = re.sub(r'~(\d+)~', self._range, format)
1397
1398
1398 # Interpolate '%' variables
1399 # Interpolate '%' variables
1399 result = Template(result).safe_substitute({
1400 result = Template(result).safe_substitute({
1400 'first_page': self.first_page,
1401 'first_page': self.first_page,
1401 'last_page': self.last_page,
1402 'last_page': self.last_page,
1402 'page': self.page,
1403 'page': self.page,
1403 'page_count': self.page_count,
1404 'page_count': self.page_count,
1404 'items_per_page': self.items_per_page,
1405 'items_per_page': self.items_per_page,
1405 'first_item': self.first_item,
1406 'first_item': self.first_item,
1406 'last_item': self.last_item,
1407 'last_item': self.last_item,
1407 'item_count': self.item_count,
1408 'item_count': self.item_count,
1408 'link_first': self.page > self.first_page and \
1409 'link_first': self.page > self.first_page and \
1409 self._pagerlink(self.first_page, symbol_first) or '',
1410 self._pagerlink(self.first_page, symbol_first) or '',
1410 'link_last': self.page < self.last_page and \
1411 'link_last': self.page < self.last_page and \
1411 self._pagerlink(self.last_page, symbol_last) or '',
1412 self._pagerlink(self.last_page, symbol_last) or '',
1412 'link_previous': self.previous_page and \
1413 'link_previous': self.previous_page and \
1413 self._pagerlink(self.previous_page, symbol_previous) \
1414 self._pagerlink(self.previous_page, symbol_previous) \
1414 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1415 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1415 'link_next': self.next_page and \
1416 'link_next': self.next_page and \
1416 self._pagerlink(self.next_page, symbol_next) \
1417 self._pagerlink(self.next_page, symbol_next) \
1417 or HTML.span(symbol_next, class_="pg-next disabled")
1418 or HTML.span(symbol_next, class_="pg-next disabled")
1418 })
1419 })
1419
1420
1420 return literal(result)
1421 return literal(result)
1421
1422
1422
1423
1423 #==============================================================================
1424 #==============================================================================
1424 # REPO PAGER, PAGER FOR REPOSITORY
1425 # REPO PAGER, PAGER FOR REPOSITORY
1425 #==============================================================================
1426 #==============================================================================
1426 class RepoPage(Page):
1427 class RepoPage(Page):
1427
1428
1428 def __init__(self, collection, page=1, items_per_page=20,
1429 def __init__(self, collection, page=1, items_per_page=20,
1429 item_count=None, url=None, **kwargs):
1430 item_count=None, url=None, **kwargs):
1430
1431
1431 """Create a "RepoPage" instance. special pager for paging
1432 """Create a "RepoPage" instance. special pager for paging
1432 repository
1433 repository
1433 """
1434 """
1434 self._url_generator = url
1435 self._url_generator = url
1435
1436
1436 # Safe the kwargs class-wide so they can be used in the pager() method
1437 # Safe the kwargs class-wide so they can be used in the pager() method
1437 self.kwargs = kwargs
1438 self.kwargs = kwargs
1438
1439
1439 # Save a reference to the collection
1440 # Save a reference to the collection
1440 self.original_collection = collection
1441 self.original_collection = collection
1441
1442
1442 self.collection = collection
1443 self.collection = collection
1443
1444
1444 # The self.page is the number of the current page.
1445 # The self.page is the number of the current page.
1445 # The first page has the number 1!
1446 # The first page has the number 1!
1446 try:
1447 try:
1447 self.page = int(page) # make it int() if we get it as a string
1448 self.page = int(page) # make it int() if we get it as a string
1448 except (ValueError, TypeError):
1449 except (ValueError, TypeError):
1449 self.page = 1
1450 self.page = 1
1450
1451
1451 self.items_per_page = items_per_page
1452 self.items_per_page = items_per_page
1452
1453
1453 # Unless the user tells us how many items the collections has
1454 # Unless the user tells us how many items the collections has
1454 # we calculate that ourselves.
1455 # we calculate that ourselves.
1455 if item_count is not None:
1456 if item_count is not None:
1456 self.item_count = item_count
1457 self.item_count = item_count
1457 else:
1458 else:
1458 self.item_count = len(self.collection)
1459 self.item_count = len(self.collection)
1459
1460
1460 # Compute the number of the first and last available page
1461 # Compute the number of the first and last available page
1461 if self.item_count > 0:
1462 if self.item_count > 0:
1462 self.first_page = 1
1463 self.first_page = 1
1463 self.page_count = int(math.ceil(float(self.item_count) /
1464 self.page_count = int(math.ceil(float(self.item_count) /
1464 self.items_per_page))
1465 self.items_per_page))
1465 self.last_page = self.first_page + self.page_count - 1
1466 self.last_page = self.first_page + self.page_count - 1
1466
1467
1467 # Make sure that the requested page number is the range of
1468 # Make sure that the requested page number is the range of
1468 # valid pages
1469 # valid pages
1469 if self.page > self.last_page:
1470 if self.page > self.last_page:
1470 self.page = self.last_page
1471 self.page = self.last_page
1471 elif self.page < self.first_page:
1472 elif self.page < self.first_page:
1472 self.page = self.first_page
1473 self.page = self.first_page
1473
1474
1474 # Note: the number of items on this page can be less than
1475 # Note: the number of items on this page can be less than
1475 # items_per_page if the last page is not full
1476 # items_per_page if the last page is not full
1476 self.first_item = max(0, (self.item_count) - (self.page *
1477 self.first_item = max(0, (self.item_count) - (self.page *
1477 items_per_page))
1478 items_per_page))
1478 self.last_item = ((self.item_count - 1) - items_per_page *
1479 self.last_item = ((self.item_count - 1) - items_per_page *
1479 (self.page - 1))
1480 (self.page - 1))
1480
1481
1481 self.items = list(self.collection[self.first_item:self.last_item + 1])
1482 self.items = list(self.collection[self.first_item:self.last_item + 1])
1482
1483
1483 # Links to previous and next page
1484 # Links to previous and next page
1484 if self.page > self.first_page:
1485 if self.page > self.first_page:
1485 self.previous_page = self.page - 1
1486 self.previous_page = self.page - 1
1486 else:
1487 else:
1487 self.previous_page = None
1488 self.previous_page = None
1488
1489
1489 if self.page < self.last_page:
1490 if self.page < self.last_page:
1490 self.next_page = self.page + 1
1491 self.next_page = self.page + 1
1491 else:
1492 else:
1492 self.next_page = None
1493 self.next_page = None
1493
1494
1494 # No items available
1495 # No items available
1495 else:
1496 else:
1496 self.first_page = None
1497 self.first_page = None
1497 self.page_count = 0
1498 self.page_count = 0
1498 self.last_page = None
1499 self.last_page = None
1499 self.first_item = None
1500 self.first_item = None
1500 self.last_item = None
1501 self.last_item = None
1501 self.previous_page = None
1502 self.previous_page = None
1502 self.next_page = None
1503 self.next_page = None
1503 self.items = []
1504 self.items = []
1504
1505
1505 # This is a subclass of the 'list' type. Initialise the list now.
1506 # This is a subclass of the 'list' type. Initialise the list now.
1506 list.__init__(self, reversed(self.items))
1507 list.__init__(self, reversed(self.items))
1507
1508
1508
1509
1509 def breadcrumb_repo_link(repo):
1510 def breadcrumb_repo_link(repo):
1510 """
1511 """
1511 Makes a breadcrumbs path link to repo
1512 Makes a breadcrumbs path link to repo
1512
1513
1513 ex::
1514 ex::
1514 group >> subgroup >> repo
1515 group >> subgroup >> repo
1515
1516
1516 :param repo: a Repository instance
1517 :param repo: a Repository instance
1517 """
1518 """
1518
1519
1519 path = [
1520 path = [
1520 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1521 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1521 title='last change:{}'.format(format_date(group.last_commit_change)))
1522 title='last change:{}'.format(format_date(group.last_commit_change)))
1522 for group in repo.groups_with_parents
1523 for group in repo.groups_with_parents
1523 ] + [
1524 ] + [
1524 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1525 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1525 title='last change:{}'.format(format_date(repo.last_commit_change)))
1526 title='last change:{}'.format(format_date(repo.last_commit_change)))
1526 ]
1527 ]
1527
1528
1528 return literal(' &raquo; '.join(path))
1529 return literal(' &raquo; '.join(path))
1529
1530
1530
1531
1531 def breadcrumb_repo_group_link(repo_group):
1532 def breadcrumb_repo_group_link(repo_group):
1532 """
1533 """
1533 Makes a breadcrumbs path link to repo
1534 Makes a breadcrumbs path link to repo
1534
1535
1535 ex::
1536 ex::
1536 group >> subgroup
1537 group >> subgroup
1537
1538
1538 :param repo_group: a Repository Group instance
1539 :param repo_group: a Repository Group instance
1539 """
1540 """
1540
1541
1541 path = [
1542 path = [
1542 link_to(group.name,
1543 link_to(group.name,
1543 route_path('repo_group_home', repo_group_name=group.group_name),
1544 route_path('repo_group_home', repo_group_name=group.group_name),
1544 title='last change:{}'.format(format_date(group.last_commit_change)))
1545 title='last change:{}'.format(format_date(group.last_commit_change)))
1545 for group in repo_group.parents
1546 for group in repo_group.parents
1546 ] + [
1547 ] + [
1547 link_to(repo_group.name,
1548 link_to(repo_group.name,
1548 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1549 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1549 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1550 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1550 ]
1551 ]
1551
1552
1552 return literal(' &raquo; '.join(path))
1553 return literal(' &raquo; '.join(path))
1553
1554
1554
1555
1555 def format_byte_size_binary(file_size):
1556 def format_byte_size_binary(file_size):
1556 """
1557 """
1557 Formats file/folder sizes to standard.
1558 Formats file/folder sizes to standard.
1558 """
1559 """
1559 if file_size is None:
1560 if file_size is None:
1560 file_size = 0
1561 file_size = 0
1561
1562
1562 formatted_size = format_byte_size(file_size, binary=True)
1563 formatted_size = format_byte_size(file_size, binary=True)
1563 return formatted_size
1564 return formatted_size
1564
1565
1565
1566
1566 def urlify_text(text_, safe=True):
1567 def urlify_text(text_, safe=True):
1567 """
1568 """
1568 Extrac urls from text and make html links out of them
1569 Extrac urls from text and make html links out of them
1569
1570
1570 :param text_:
1571 :param text_:
1571 """
1572 """
1572
1573
1573 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1574 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1574 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1575 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1575
1576
1576 def url_func(match_obj):
1577 def url_func(match_obj):
1577 url_full = match_obj.groups()[0]
1578 url_full = match_obj.groups()[0]
1578 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1579 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1579 _newtext = url_pat.sub(url_func, text_)
1580 _newtext = url_pat.sub(url_func, text_)
1580 if safe:
1581 if safe:
1581 return literal(_newtext)
1582 return literal(_newtext)
1582 return _newtext
1583 return _newtext
1583
1584
1584
1585
1585 def urlify_commits(text_, repository):
1586 def urlify_commits(text_, repository):
1586 """
1587 """
1587 Extract commit ids from text and make link from them
1588 Extract commit ids from text and make link from them
1588
1589
1589 :param text_:
1590 :param text_:
1590 :param repository: repo name to build the URL with
1591 :param repository: repo name to build the URL with
1591 """
1592 """
1592
1593
1593 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1594 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1594
1595
1595 def url_func(match_obj):
1596 def url_func(match_obj):
1596 commit_id = match_obj.groups()[1]
1597 commit_id = match_obj.groups()[1]
1597 pref = match_obj.groups()[0]
1598 pref = match_obj.groups()[0]
1598 suf = match_obj.groups()[2]
1599 suf = match_obj.groups()[2]
1599
1600
1600 tmpl = (
1601 tmpl = (
1601 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1602 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1602 '%(commit_id)s</a>%(suf)s'
1603 '%(commit_id)s</a>%(suf)s'
1603 )
1604 )
1604 return tmpl % {
1605 return tmpl % {
1605 'pref': pref,
1606 'pref': pref,
1606 'cls': 'revision-link',
1607 'cls': 'revision-link',
1607 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1608 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1608 'commit_id': commit_id,
1609 'commit_id': commit_id,
1609 'suf': suf
1610 'suf': suf
1610 }
1611 }
1611
1612
1612 newtext = URL_PAT.sub(url_func, text_)
1613 newtext = URL_PAT.sub(url_func, text_)
1613
1614
1614 return newtext
1615 return newtext
1615
1616
1616
1617
1617 def _process_url_func(match_obj, repo_name, uid, entry,
1618 def _process_url_func(match_obj, repo_name, uid, entry,
1618 return_raw_data=False, link_format='html'):
1619 return_raw_data=False, link_format='html'):
1619 pref = ''
1620 pref = ''
1620 if match_obj.group().startswith(' '):
1621 if match_obj.group().startswith(' '):
1621 pref = ' '
1622 pref = ' '
1622
1623
1623 issue_id = ''.join(match_obj.groups())
1624 issue_id = ''.join(match_obj.groups())
1624
1625
1625 if link_format == 'html':
1626 if link_format == 'html':
1626 tmpl = (
1627 tmpl = (
1627 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1628 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1628 '%(issue-prefix)s%(id-repr)s'
1629 '%(issue-prefix)s%(id-repr)s'
1629 '</a>')
1630 '</a>')
1630 elif link_format == 'html+hovercard':
1631 elif link_format == 'html+hovercard':
1631 tmpl = (
1632 tmpl = (
1632 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1633 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1633 '%(issue-prefix)s%(id-repr)s'
1634 '%(issue-prefix)s%(id-repr)s'
1634 '</a>')
1635 '</a>')
1635 elif link_format == 'rst':
1636 elif link_format in ['rst', 'rst+hovercard']:
1636 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1637 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1637 elif link_format == 'markdown':
1638 elif link_format in ['markdown', 'markdown+hovercard']:
1638 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1639 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1639 else:
1640 else:
1640 raise ValueError('Bad link_format:{}'.format(link_format))
1641 raise ValueError('Bad link_format:{}'.format(link_format))
1641
1642
1642 (repo_name_cleaned,
1643 (repo_name_cleaned,
1643 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1644 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1644
1645
1645 # variables replacement
1646 # variables replacement
1646 named_vars = {
1647 named_vars = {
1647 'id': issue_id,
1648 'id': issue_id,
1648 'repo': repo_name,
1649 'repo': repo_name,
1649 'repo_name': repo_name_cleaned,
1650 'repo_name': repo_name_cleaned,
1650 'group_name': parent_group_name,
1651 'group_name': parent_group_name,
1652 # set dummy keys so we always have them
1653 'hostname': '',
1654 'netloc': '',
1655 'scheme': ''
1651 }
1656 }
1657
1658 request = get_current_request()
1659 if request:
1660 # exposes, hostname, netloc, scheme
1661 host_data = get_host_info(request)
1662 named_vars.update(host_data)
1663
1652 # named regex variables
1664 # named regex variables
1653 named_vars.update(match_obj.groupdict())
1665 named_vars.update(match_obj.groupdict())
1654 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1666 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1655 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1667 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1668 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1656
1669
1657 def quote_cleaner(input_str):
1670 def quote_cleaner(input_str):
1658 """Remove quotes as it's HTML"""
1671 """Remove quotes as it's HTML"""
1659 return input_str.replace('"', '')
1672 return input_str.replace('"', '')
1660
1673
1661 data = {
1674 data = {
1662 'pref': pref,
1675 'pref': pref,
1663 'cls': quote_cleaner('issue-tracker-link'),
1676 'cls': quote_cleaner('issue-tracker-link'),
1664 'url': quote_cleaner(_url),
1677 'url': quote_cleaner(_url),
1665 'id-repr': issue_id,
1678 'id-repr': issue_id,
1666 'issue-prefix': entry['pref'],
1679 'issue-prefix': entry['pref'],
1667 'serv': entry['url'],
1680 'serv': entry['url'],
1668 'title': desc,
1681 'title': desc,
1669 'hovercard_url': ''
1682 'hovercard_url': hovercard_url
1670 }
1683 }
1684
1671 if return_raw_data:
1685 if return_raw_data:
1672 return {
1686 return {
1673 'id': issue_id,
1687 'id': issue_id,
1674 'url': _url
1688 'url': _url
1675 }
1689 }
1676 return tmpl % data
1690 return tmpl % data
1677
1691
1678
1692
1679 def get_active_pattern_entries(repo_name):
1693 def get_active_pattern_entries(repo_name):
1680 repo = None
1694 repo = None
1681 if repo_name:
1695 if repo_name:
1682 # Retrieving repo_name to avoid invalid repo_name to explode on
1696 # Retrieving repo_name to avoid invalid repo_name to explode on
1683 # IssueTrackerSettingsModel but still passing invalid name further down
1697 # IssueTrackerSettingsModel but still passing invalid name further down
1684 repo = Repository.get_by_repo_name(repo_name, cache=True)
1698 repo = Repository.get_by_repo_name(repo_name, cache=True)
1685
1699
1686 settings_model = IssueTrackerSettingsModel(repo=repo)
1700 settings_model = IssueTrackerSettingsModel(repo=repo)
1687 active_entries = settings_model.get_settings(cache=True)
1701 active_entries = settings_model.get_settings(cache=True)
1688 return active_entries
1702 return active_entries
1689
1703
1690
1704
1691 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1705 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1692
1706
1693 allowed_formats = ['html', 'rst', 'markdown']
1707 allowed_formats = ['html', 'rst', 'markdown',
1708 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1694 if link_format not in allowed_formats:
1709 if link_format not in allowed_formats:
1695 raise ValueError('Link format can be only one of:{} got {}'.format(
1710 raise ValueError('Link format can be only one of:{} got {}'.format(
1696 allowed_formats, link_format))
1711 allowed_formats, link_format))
1697
1712
1698 active_entries = active_entries or get_active_pattern_entries(repo_name)
1713 active_entries = active_entries or get_active_pattern_entries(repo_name)
1699 issues_data = []
1714 issues_data = []
1700 new_text = text_string
1715 new_text = text_string
1701
1716
1702 log.debug('Got %s entries to process', len(active_entries))
1717 log.debug('Got %s entries to process', len(active_entries))
1703 for uid, entry in active_entries.items():
1718 for uid, entry in active_entries.items():
1704 log.debug('found issue tracker entry with uid %s', uid)
1719 log.debug('found issue tracker entry with uid %s', uid)
1705
1720
1706 if not (entry['pat'] and entry['url']):
1721 if not (entry['pat'] and entry['url']):
1707 log.debug('skipping due to missing data')
1722 log.debug('skipping due to missing data')
1708 continue
1723 continue
1709
1724
1710 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1725 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1711 uid, entry['pat'], entry['url'], entry['pref'])
1726 uid, entry['pat'], entry['url'], entry['pref'])
1712
1727
1713 try:
1728 try:
1714 pattern = re.compile(r'%s' % entry['pat'])
1729 pattern = re.compile(r'%s' % entry['pat'])
1715 except re.error:
1730 except re.error:
1716 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1731 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1717 continue
1732 continue
1718
1733
1719 data_func = partial(
1734 data_func = partial(
1720 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1735 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1721 return_raw_data=True)
1736 return_raw_data=True)
1722
1737
1723 for match_obj in pattern.finditer(text_string):
1738 for match_obj in pattern.finditer(text_string):
1724 issues_data.append(data_func(match_obj))
1739 issues_data.append(data_func(match_obj))
1725
1740
1726 url_func = partial(
1741 url_func = partial(
1727 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1742 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1728 link_format=link_format)
1743 link_format=link_format)
1729
1744
1730 new_text = pattern.sub(url_func, new_text)
1745 new_text = pattern.sub(url_func, new_text)
1731 log.debug('processed prefix:uid `%s`', uid)
1746 log.debug('processed prefix:uid `%s`', uid)
1732
1747
1733 # finally use global replace, eg !123 -> pr-link, those will not catch
1748 # finally use global replace, eg !123 -> pr-link, those will not catch
1734 # if already similar pattern exists
1749 # if already similar pattern exists
1750 server_url = '${scheme}://${netloc}'
1735 pr_entry = {
1751 pr_entry = {
1736 'pref': '!',
1752 'pref': '!',
1737 'url': '/_admin/pull-requests/${id}',
1753 'url': server_url + '/_admin/pull-requests/${id}',
1738 'desc': 'Pull Request !${id}'
1754 'desc': 'Pull Request !${id}',
1755 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1739 }
1756 }
1740 pr_url_func = partial(
1757 pr_url_func = partial(
1741 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None)
1758 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1759 link_format=link_format+'+hovercard')
1742 new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text)
1760 new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text)
1743 log.debug('processed !pr pattern')
1761 log.debug('processed !pr pattern')
1744
1762
1745 return new_text, issues_data
1763 return new_text, issues_data
1746
1764
1747
1765
1748 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1766 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1749 """
1767 """
1750 Parses given text message and makes proper links.
1768 Parses given text message and makes proper links.
1751 issues are linked to given issue-server, and rest is a commit link
1769 issues are linked to given issue-server, and rest is a commit link
1752
1770
1753 :param commit_text:
1771 :param commit_text:
1754 :param repository:
1772 :param repository:
1755 """
1773 """
1756 def escaper(_text):
1774 def escaper(_text):
1757 return _text.replace('<', '&lt;').replace('>', '&gt;')
1775 return _text.replace('<', '&lt;').replace('>', '&gt;')
1758
1776
1759 new_text = escaper(commit_text)
1777 new_text = escaper(commit_text)
1760
1778
1761 # extract http/https links and make them real urls
1779 # extract http/https links and make them real urls
1762 new_text = urlify_text(new_text, safe=False)
1780 new_text = urlify_text(new_text, safe=False)
1763
1781
1764 # urlify commits - extract commit ids and make link out of them, if we have
1782 # urlify commits - extract commit ids and make link out of them, if we have
1765 # the scope of repository present.
1783 # the scope of repository present.
1766 if repository:
1784 if repository:
1767 new_text = urlify_commits(new_text, repository)
1785 new_text = urlify_commits(new_text, repository)
1768
1786
1769 # process issue tracker patterns
1787 # process issue tracker patterns
1770 new_text, issues = process_patterns(new_text, repository or '',
1788 new_text, issues = process_patterns(new_text, repository or '',
1771 active_entries=active_pattern_entries)
1789 active_entries=active_pattern_entries)
1772
1790
1773 return literal(new_text)
1791 return literal(new_text)
1774
1792
1775
1793
1776 def render_binary(repo_name, file_obj):
1794 def render_binary(repo_name, file_obj):
1777 """
1795 """
1778 Choose how to render a binary file
1796 Choose how to render a binary file
1779 """
1797 """
1780
1798
1781 filename = file_obj.name
1799 filename = file_obj.name
1782
1800
1783 # images
1801 # images
1784 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1802 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1785 if fnmatch.fnmatch(filename, pat=ext):
1803 if fnmatch.fnmatch(filename, pat=ext):
1786 alt = escape(filename)
1804 alt = escape(filename)
1787 src = route_path(
1805 src = route_path(
1788 'repo_file_raw', repo_name=repo_name,
1806 'repo_file_raw', repo_name=repo_name,
1789 commit_id=file_obj.commit.raw_id,
1807 commit_id=file_obj.commit.raw_id,
1790 f_path=file_obj.path)
1808 f_path=file_obj.path)
1791 return literal(
1809 return literal(
1792 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1810 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1793
1811
1794
1812
1795 def renderer_from_filename(filename, exclude=None):
1813 def renderer_from_filename(filename, exclude=None):
1796 """
1814 """
1797 choose a renderer based on filename, this works only for text based files
1815 choose a renderer based on filename, this works only for text based files
1798 """
1816 """
1799
1817
1800 # ipython
1818 # ipython
1801 for ext in ['*.ipynb']:
1819 for ext in ['*.ipynb']:
1802 if fnmatch.fnmatch(filename, pat=ext):
1820 if fnmatch.fnmatch(filename, pat=ext):
1803 return 'jupyter'
1821 return 'jupyter'
1804
1822
1805 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1823 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1806 if is_markup:
1824 if is_markup:
1807 return is_markup
1825 return is_markup
1808 return None
1826 return None
1809
1827
1810
1828
1811 def render(source, renderer='rst', mentions=False, relative_urls=None,
1829 def render(source, renderer='rst', mentions=False, relative_urls=None,
1812 repo_name=None):
1830 repo_name=None):
1813
1831
1814 def maybe_convert_relative_links(html_source):
1832 def maybe_convert_relative_links(html_source):
1815 if relative_urls:
1833 if relative_urls:
1816 return relative_links(html_source, relative_urls)
1834 return relative_links(html_source, relative_urls)
1817 return html_source
1835 return html_source
1818
1836
1819 if renderer == 'plain':
1837 if renderer == 'plain':
1820 return literal(
1838 return literal(
1821 MarkupRenderer.plain(source, leading_newline=False))
1839 MarkupRenderer.plain(source, leading_newline=False))
1822
1840
1823 elif renderer == 'rst':
1841 elif renderer == 'rst':
1824 if repo_name:
1842 if repo_name:
1825 # process patterns on comments if we pass in repo name
1843 # process patterns on comments if we pass in repo name
1826 source, issues = process_patterns(
1844 source, issues = process_patterns(
1827 source, repo_name, link_format='rst')
1845 source, repo_name, link_format='rst')
1828
1846
1829 return literal(
1847 return literal(
1830 '<div class="rst-block">%s</div>' %
1848 '<div class="rst-block">%s</div>' %
1831 maybe_convert_relative_links(
1849 maybe_convert_relative_links(
1832 MarkupRenderer.rst(source, mentions=mentions)))
1850 MarkupRenderer.rst(source, mentions=mentions)))
1833
1851
1834 elif renderer == 'markdown':
1852 elif renderer == 'markdown':
1835 if repo_name:
1853 if repo_name:
1836 # process patterns on comments if we pass in repo name
1854 # process patterns on comments if we pass in repo name
1837 source, issues = process_patterns(
1855 source, issues = process_patterns(
1838 source, repo_name, link_format='markdown')
1856 source, repo_name, link_format='markdown')
1839
1857
1840 return literal(
1858 return literal(
1841 '<div class="markdown-block">%s</div>' %
1859 '<div class="markdown-block">%s</div>' %
1842 maybe_convert_relative_links(
1860 maybe_convert_relative_links(
1843 MarkupRenderer.markdown(source, flavored=True,
1861 MarkupRenderer.markdown(source, flavored=True,
1844 mentions=mentions)))
1862 mentions=mentions)))
1845
1863
1846 elif renderer == 'jupyter':
1864 elif renderer == 'jupyter':
1847 return literal(
1865 return literal(
1848 '<div class="ipynb">%s</div>' %
1866 '<div class="ipynb">%s</div>' %
1849 maybe_convert_relative_links(
1867 maybe_convert_relative_links(
1850 MarkupRenderer.jupyter(source)))
1868 MarkupRenderer.jupyter(source)))
1851
1869
1852 # None means just show the file-source
1870 # None means just show the file-source
1853 return None
1871 return None
1854
1872
1855
1873
1856 def commit_status(repo, commit_id):
1874 def commit_status(repo, commit_id):
1857 return ChangesetStatusModel().get_status(repo, commit_id)
1875 return ChangesetStatusModel().get_status(repo, commit_id)
1858
1876
1859
1877
1860 def commit_status_lbl(commit_status):
1878 def commit_status_lbl(commit_status):
1861 return dict(ChangesetStatus.STATUSES).get(commit_status)
1879 return dict(ChangesetStatus.STATUSES).get(commit_status)
1862
1880
1863
1881
1864 def commit_time(repo_name, commit_id):
1882 def commit_time(repo_name, commit_id):
1865 repo = Repository.get_by_repo_name(repo_name)
1883 repo = Repository.get_by_repo_name(repo_name)
1866 commit = repo.get_commit(commit_id=commit_id)
1884 commit = repo.get_commit(commit_id=commit_id)
1867 return commit.date
1885 return commit.date
1868
1886
1869
1887
1870 def get_permission_name(key):
1888 def get_permission_name(key):
1871 return dict(Permission.PERMS).get(key)
1889 return dict(Permission.PERMS).get(key)
1872
1890
1873
1891
1874 def journal_filter_help(request):
1892 def journal_filter_help(request):
1875 _ = request.translate
1893 _ = request.translate
1876 from rhodecode.lib.audit_logger import ACTIONS
1894 from rhodecode.lib.audit_logger import ACTIONS
1877 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1895 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1878
1896
1879 return _(
1897 return _(
1880 'Example filter terms:\n' +
1898 'Example filter terms:\n' +
1881 ' repository:vcs\n' +
1899 ' repository:vcs\n' +
1882 ' username:marcin\n' +
1900 ' username:marcin\n' +
1883 ' username:(NOT marcin)\n' +
1901 ' username:(NOT marcin)\n' +
1884 ' action:*push*\n' +
1902 ' action:*push*\n' +
1885 ' ip:127.0.0.1\n' +
1903 ' ip:127.0.0.1\n' +
1886 ' date:20120101\n' +
1904 ' date:20120101\n' +
1887 ' date:[20120101100000 TO 20120102]\n' +
1905 ' date:[20120101100000 TO 20120102]\n' +
1888 '\n' +
1906 '\n' +
1889 'Actions: {actions}\n' +
1907 'Actions: {actions}\n' +
1890 '\n' +
1908 '\n' +
1891 'Generate wildcards using \'*\' character:\n' +
1909 'Generate wildcards using \'*\' character:\n' +
1892 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1910 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1893 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1911 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1894 '\n' +
1912 '\n' +
1895 'Optional AND / OR operators in queries\n' +
1913 'Optional AND / OR operators in queries\n' +
1896 ' "repository:vcs OR repository:test"\n' +
1914 ' "repository:vcs OR repository:test"\n' +
1897 ' "username:test AND repository:test*"\n'
1915 ' "username:test AND repository:test*"\n'
1898 ).format(actions=actions)
1916 ).format(actions=actions)
1899
1917
1900
1918
1901 def not_mapped_error(repo_name):
1919 def not_mapped_error(repo_name):
1902 from rhodecode.translation import _
1920 from rhodecode.translation import _
1903 flash(_('%s repository is not mapped to db perhaps'
1921 flash(_('%s repository is not mapped to db perhaps'
1904 ' it was created or renamed from the filesystem'
1922 ' it was created or renamed from the filesystem'
1905 ' please run the application again'
1923 ' please run the application again'
1906 ' in order to rescan repositories') % repo_name, category='error')
1924 ' in order to rescan repositories') % repo_name, category='error')
1907
1925
1908
1926
1909 def ip_range(ip_addr):
1927 def ip_range(ip_addr):
1910 from rhodecode.model.db import UserIpMap
1928 from rhodecode.model.db import UserIpMap
1911 s, e = UserIpMap._get_ip_range(ip_addr)
1929 s, e = UserIpMap._get_ip_range(ip_addr)
1912 return '%s - %s' % (s, e)
1930 return '%s - %s' % (s, e)
1913
1931
1914
1932
1915 def form(url, method='post', needs_csrf_token=True, **attrs):
1933 def form(url, method='post', needs_csrf_token=True, **attrs):
1916 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1934 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1917 if method.lower() != 'get' and needs_csrf_token:
1935 if method.lower() != 'get' and needs_csrf_token:
1918 raise Exception(
1936 raise Exception(
1919 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1937 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1920 'CSRF token. If the endpoint does not require such token you can ' +
1938 'CSRF token. If the endpoint does not require such token you can ' +
1921 'explicitly set the parameter needs_csrf_token to false.')
1939 'explicitly set the parameter needs_csrf_token to false.')
1922
1940
1923 return wh_form(url, method=method, **attrs)
1941 return wh_form(url, method=method, **attrs)
1924
1942
1925
1943
1926 def secure_form(form_url, method="POST", multipart=False, **attrs):
1944 def secure_form(form_url, method="POST", multipart=False, **attrs):
1927 """Start a form tag that points the action to an url. This
1945 """Start a form tag that points the action to an url. This
1928 form tag will also include the hidden field containing
1946 form tag will also include the hidden field containing
1929 the auth token.
1947 the auth token.
1930
1948
1931 The url options should be given either as a string, or as a
1949 The url options should be given either as a string, or as a
1932 ``url()`` function. The method for the form defaults to POST.
1950 ``url()`` function. The method for the form defaults to POST.
1933
1951
1934 Options:
1952 Options:
1935
1953
1936 ``multipart``
1954 ``multipart``
1937 If set to True, the enctype is set to "multipart/form-data".
1955 If set to True, the enctype is set to "multipart/form-data".
1938 ``method``
1956 ``method``
1939 The method to use when submitting the form, usually either
1957 The method to use when submitting the form, usually either
1940 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1958 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1941 hidden input with name _method is added to simulate the verb
1959 hidden input with name _method is added to simulate the verb
1942 over POST.
1960 over POST.
1943
1961
1944 """
1962 """
1945 from webhelpers.pylonslib.secure_form import insecure_form
1963 from webhelpers.pylonslib.secure_form import insecure_form
1946
1964
1947 if 'request' in attrs:
1965 if 'request' in attrs:
1948 session = attrs['request'].session
1966 session = attrs['request'].session
1949 del attrs['request']
1967 del attrs['request']
1950 else:
1968 else:
1951 raise ValueError(
1969 raise ValueError(
1952 'Calling this form requires request= to be passed as argument')
1970 'Calling this form requires request= to be passed as argument')
1953
1971
1954 form = insecure_form(form_url, method, multipart, **attrs)
1972 form = insecure_form(form_url, method, multipart, **attrs)
1955 token = literal(
1973 token = literal(
1956 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1974 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1957 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1975 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1958
1976
1959 return literal("%s\n%s" % (form, token))
1977 return literal("%s\n%s" % (form, token))
1960
1978
1961
1979
1962 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1980 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1963 select_html = select(name, selected, options, **attrs)
1981 select_html = select(name, selected, options, **attrs)
1964
1982
1965 select2 = """
1983 select2 = """
1966 <script>
1984 <script>
1967 $(document).ready(function() {
1985 $(document).ready(function() {
1968 $('#%s').select2({
1986 $('#%s').select2({
1969 containerCssClass: 'drop-menu %s',
1987 containerCssClass: 'drop-menu %s',
1970 dropdownCssClass: 'drop-menu-dropdown',
1988 dropdownCssClass: 'drop-menu-dropdown',
1971 dropdownAutoWidth: true%s
1989 dropdownAutoWidth: true%s
1972 });
1990 });
1973 });
1991 });
1974 </script>
1992 </script>
1975 """
1993 """
1976
1994
1977 filter_option = """,
1995 filter_option = """,
1978 minimumResultsForSearch: -1
1996 minimumResultsForSearch: -1
1979 """
1997 """
1980 input_id = attrs.get('id') or name
1998 input_id = attrs.get('id') or name
1981 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1999 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1982 filter_enabled = "" if enable_filter else filter_option
2000 filter_enabled = "" if enable_filter else filter_option
1983 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
2001 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1984
2002
1985 return literal(select_html+select_script)
2003 return literal(select_html+select_script)
1986
2004
1987
2005
1988 def get_visual_attr(tmpl_context_var, attr_name):
2006 def get_visual_attr(tmpl_context_var, attr_name):
1989 """
2007 """
1990 A safe way to get a variable from visual variable of template context
2008 A safe way to get a variable from visual variable of template context
1991
2009
1992 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
2010 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1993 :param attr_name: name of the attribute we fetch from the c.visual
2011 :param attr_name: name of the attribute we fetch from the c.visual
1994 """
2012 """
1995 visual = getattr(tmpl_context_var, 'visual', None)
2013 visual = getattr(tmpl_context_var, 'visual', None)
1996 if not visual:
2014 if not visual:
1997 return
2015 return
1998 else:
2016 else:
1999 return getattr(visual, attr_name, None)
2017 return getattr(visual, attr_name, None)
2000
2018
2001
2019
2002 def get_last_path_part(file_node):
2020 def get_last_path_part(file_node):
2003 if not file_node.path:
2021 if not file_node.path:
2004 return u'/'
2022 return u'/'
2005
2023
2006 path = safe_unicode(file_node.path.split('/')[-1])
2024 path = safe_unicode(file_node.path.split('/')[-1])
2007 return u'../' + path
2025 return u'../' + path
2008
2026
2009
2027
2010 def route_url(*args, **kwargs):
2028 def route_url(*args, **kwargs):
2011 """
2029 """
2012 Wrapper around pyramids `route_url` (fully qualified url) function.
2030 Wrapper around pyramids `route_url` (fully qualified url) function.
2013 """
2031 """
2014 req = get_current_request()
2032 req = get_current_request()
2015 return req.route_url(*args, **kwargs)
2033 return req.route_url(*args, **kwargs)
2016
2034
2017
2035
2018 def route_path(*args, **kwargs):
2036 def route_path(*args, **kwargs):
2019 """
2037 """
2020 Wrapper around pyramids `route_path` function.
2038 Wrapper around pyramids `route_path` function.
2021 """
2039 """
2022 req = get_current_request()
2040 req = get_current_request()
2023 return req.route_path(*args, **kwargs)
2041 return req.route_path(*args, **kwargs)
2024
2042
2025
2043
2026 def route_path_or_none(*args, **kwargs):
2044 def route_path_or_none(*args, **kwargs):
2027 try:
2045 try:
2028 return route_path(*args, **kwargs)
2046 return route_path(*args, **kwargs)
2029 except KeyError:
2047 except KeyError:
2030 return None
2048 return None
2031
2049
2032
2050
2033 def current_route_path(request, **kw):
2051 def current_route_path(request, **kw):
2034 new_args = request.GET.mixed()
2052 new_args = request.GET.mixed()
2035 new_args.update(kw)
2053 new_args.update(kw)
2036 return request.current_route_path(_query=new_args)
2054 return request.current_route_path(_query=new_args)
2037
2055
2038
2056
2039 def curl_api_example(method, args):
2057 def curl_api_example(method, args):
2040 args_json = json.dumps(OrderedDict([
2058 args_json = json.dumps(OrderedDict([
2041 ('id', 1),
2059 ('id', 1),
2042 ('auth_token', 'SECRET'),
2060 ('auth_token', 'SECRET'),
2043 ('method', method),
2061 ('method', method),
2044 ('args', args)
2062 ('args', args)
2045 ]))
2063 ]))
2046
2064
2047 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
2065 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
2048 api_url=route_url('apiv2'),
2066 api_url=route_url('apiv2'),
2049 args_json=args_json
2067 args_json=args_json
2050 )
2068 )
2051
2069
2052
2070
2053 def api_call_example(method, args):
2071 def api_call_example(method, args):
2054 """
2072 """
2055 Generates an API call example via CURL
2073 Generates an API call example via CURL
2056 """
2074 """
2057 curl_call = curl_api_example(method, args)
2075 curl_call = curl_api_example(method, args)
2058
2076
2059 return literal(
2077 return literal(
2060 curl_call +
2078 curl_call +
2061 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2079 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2062 "and needs to be of `api calls` role."
2080 "and needs to be of `api calls` role."
2063 .format(token_url=route_url('my_account_auth_tokens')))
2081 .format(token_url=route_url('my_account_auth_tokens')))
2064
2082
2065
2083
2066 def notification_description(notification, request):
2084 def notification_description(notification, request):
2067 """
2085 """
2068 Generate notification human readable description based on notification type
2086 Generate notification human readable description based on notification type
2069 """
2087 """
2070 from rhodecode.model.notification import NotificationModel
2088 from rhodecode.model.notification import NotificationModel
2071 return NotificationModel().make_description(
2089 return NotificationModel().make_description(
2072 notification, translate=request.translate)
2090 notification, translate=request.translate)
2073
2091
2074
2092
2075 def go_import_header(request, db_repo=None):
2093 def go_import_header(request, db_repo=None):
2076 """
2094 """
2077 Creates a header for go-import functionality in Go Lang
2095 Creates a header for go-import functionality in Go Lang
2078 """
2096 """
2079
2097
2080 if not db_repo:
2098 if not db_repo:
2081 return
2099 return
2082 if 'go-get' not in request.GET:
2100 if 'go-get' not in request.GET:
2083 return
2101 return
2084
2102
2085 clone_url = db_repo.clone_url()
2103 clone_url = db_repo.clone_url()
2086 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2104 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2087 # we have a repo and go-get flag,
2105 # we have a repo and go-get flag,
2088 return literal('<meta name="go-import" content="{} {} {}">'.format(
2106 return literal('<meta name="go-import" content="{} {} {}">'.format(
2089 prefix, db_repo.repo_type, clone_url))
2107 prefix, db_repo.repo_type, clone_url))
2090
2108
2091
2109
2092 def reviewer_as_json(*args, **kwargs):
2110 def reviewer_as_json(*args, **kwargs):
2093 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2111 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2094 return _reviewer_as_json(*args, **kwargs)
2112 return _reviewer_as_json(*args, **kwargs)
2095
2113
2096
2114
2097 def get_repo_view_type(request):
2115 def get_repo_view_type(request):
2098 route_name = request.matched_route.name
2116 route_name = request.matched_route.name
2099 route_to_view_type = {
2117 route_to_view_type = {
2100 'repo_changelog': 'commits',
2118 'repo_changelog': 'commits',
2101 'repo_commits': 'commits',
2119 'repo_commits': 'commits',
2102 'repo_files': 'files',
2120 'repo_files': 'files',
2103 'repo_summary': 'summary',
2121 'repo_summary': 'summary',
2104 'repo_commit': 'commit'
2122 'repo_commit': 'commit'
2105 }
2123 }
2106
2124
2107 return route_to_view_type.get(route_name)
2125 return route_to_view_type.get(route_name)
@@ -1,1070 +1,1090 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Some simple helper functions
23 Some simple helper functions
24 """
24 """
25
25
26 import collections
26 import collections
27 import datetime
27 import datetime
28 import dateutil.relativedelta
28 import dateutil.relativedelta
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import re
31 import re
32 import sys
32 import sys
33 import time
33 import time
34 import urllib
34 import urllib
35 import urlobject
35 import urlobject
36 import uuid
36 import uuid
37 import getpass
37 import getpass
38 from functools import update_wrapper, partial
38 from functools import update_wrapper, partial
39
39
40 import pygments.lexers
40 import pygments.lexers
41 import sqlalchemy
41 import sqlalchemy
42 import sqlalchemy.engine.url
42 import sqlalchemy.engine.url
43 import sqlalchemy.exc
43 import sqlalchemy.exc
44 import sqlalchemy.sql
44 import sqlalchemy.sql
45 import webob
45 import webob
46 import pyramid.threadlocal
46 import pyramid.threadlocal
47 from pyramid import compat
47 from pyramid import compat
48 from pyramid.settings import asbool
48 from pyramid.settings import asbool
49
49
50 import rhodecode
50 import rhodecode
51 from rhodecode.translation import _, _pluralize
51 from rhodecode.translation import _, _pluralize
52
52
53
53
54 def md5(s):
54 def md5(s):
55 return hashlib.md5(s).hexdigest()
55 return hashlib.md5(s).hexdigest()
56
56
57
57
58 def md5_safe(s):
58 def md5_safe(s):
59 return md5(safe_str(s))
59 return md5(safe_str(s))
60
60
61
61
62 def sha1(s):
62 def sha1(s):
63 return hashlib.sha1(s).hexdigest()
63 return hashlib.sha1(s).hexdigest()
64
64
65
65
66 def sha1_safe(s):
66 def sha1_safe(s):
67 return sha1(safe_str(s))
67 return sha1(safe_str(s))
68
68
69
69
70 def __get_lem(extra_mapping=None):
70 def __get_lem(extra_mapping=None):
71 """
71 """
72 Get language extension map based on what's inside pygments lexers
72 Get language extension map based on what's inside pygments lexers
73 """
73 """
74 d = collections.defaultdict(lambda: [])
74 d = collections.defaultdict(lambda: [])
75
75
76 def __clean(s):
76 def __clean(s):
77 s = s.lstrip('*')
77 s = s.lstrip('*')
78 s = s.lstrip('.')
78 s = s.lstrip('.')
79
79
80 if s.find('[') != -1:
80 if s.find('[') != -1:
81 exts = []
81 exts = []
82 start, stop = s.find('['), s.find(']')
82 start, stop = s.find('['), s.find(']')
83
83
84 for suffix in s[start + 1:stop]:
84 for suffix in s[start + 1:stop]:
85 exts.append(s[:s.find('[')] + suffix)
85 exts.append(s[:s.find('[')] + suffix)
86 return [e.lower() for e in exts]
86 return [e.lower() for e in exts]
87 else:
87 else:
88 return [s.lower()]
88 return [s.lower()]
89
89
90 for lx, t in sorted(pygments.lexers.LEXERS.items()):
90 for lx, t in sorted(pygments.lexers.LEXERS.items()):
91 m = map(__clean, t[-2])
91 m = map(__clean, t[-2])
92 if m:
92 if m:
93 m = reduce(lambda x, y: x + y, m)
93 m = reduce(lambda x, y: x + y, m)
94 for ext in m:
94 for ext in m:
95 desc = lx.replace('Lexer', '')
95 desc = lx.replace('Lexer', '')
96 d[ext].append(desc)
96 d[ext].append(desc)
97
97
98 data = dict(d)
98 data = dict(d)
99
99
100 extra_mapping = extra_mapping or {}
100 extra_mapping = extra_mapping or {}
101 if extra_mapping:
101 if extra_mapping:
102 for k, v in extra_mapping.items():
102 for k, v in extra_mapping.items():
103 if k not in data:
103 if k not in data:
104 # register new mapping2lexer
104 # register new mapping2lexer
105 data[k] = [v]
105 data[k] = [v]
106
106
107 return data
107 return data
108
108
109
109
110 def str2bool(_str):
110 def str2bool(_str):
111 """
111 """
112 returns True/False value from given string, it tries to translate the
112 returns True/False value from given string, it tries to translate the
113 string into boolean
113 string into boolean
114
114
115 :param _str: string value to translate into boolean
115 :param _str: string value to translate into boolean
116 :rtype: boolean
116 :rtype: boolean
117 :returns: boolean from given string
117 :returns: boolean from given string
118 """
118 """
119 if _str is None:
119 if _str is None:
120 return False
120 return False
121 if _str in (True, False):
121 if _str in (True, False):
122 return _str
122 return _str
123 _str = str(_str).strip().lower()
123 _str = str(_str).strip().lower()
124 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
124 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
125
125
126
126
127 def aslist(obj, sep=None, strip=True):
127 def aslist(obj, sep=None, strip=True):
128 """
128 """
129 Returns given string separated by sep as list
129 Returns given string separated by sep as list
130
130
131 :param obj:
131 :param obj:
132 :param sep:
132 :param sep:
133 :param strip:
133 :param strip:
134 """
134 """
135 if isinstance(obj, (basestring,)):
135 if isinstance(obj, (basestring,)):
136 lst = obj.split(sep)
136 lst = obj.split(sep)
137 if strip:
137 if strip:
138 lst = [v.strip() for v in lst]
138 lst = [v.strip() for v in lst]
139 return lst
139 return lst
140 elif isinstance(obj, (list, tuple)):
140 elif isinstance(obj, (list, tuple)):
141 return obj
141 return obj
142 elif obj is None:
142 elif obj is None:
143 return []
143 return []
144 else:
144 else:
145 return [obj]
145 return [obj]
146
146
147
147
148 def convert_line_endings(line, mode):
148 def convert_line_endings(line, mode):
149 """
149 """
150 Converts a given line "line end" accordingly to given mode
150 Converts a given line "line end" accordingly to given mode
151
151
152 Available modes are::
152 Available modes are::
153 0 - Unix
153 0 - Unix
154 1 - Mac
154 1 - Mac
155 2 - DOS
155 2 - DOS
156
156
157 :param line: given line to convert
157 :param line: given line to convert
158 :param mode: mode to convert to
158 :param mode: mode to convert to
159 :rtype: str
159 :rtype: str
160 :return: converted line according to mode
160 :return: converted line according to mode
161 """
161 """
162 if mode == 0:
162 if mode == 0:
163 line = line.replace('\r\n', '\n')
163 line = line.replace('\r\n', '\n')
164 line = line.replace('\r', '\n')
164 line = line.replace('\r', '\n')
165 elif mode == 1:
165 elif mode == 1:
166 line = line.replace('\r\n', '\r')
166 line = line.replace('\r\n', '\r')
167 line = line.replace('\n', '\r')
167 line = line.replace('\n', '\r')
168 elif mode == 2:
168 elif mode == 2:
169 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
169 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
170 return line
170 return line
171
171
172
172
173 def detect_mode(line, default):
173 def detect_mode(line, default):
174 """
174 """
175 Detects line break for given line, if line break couldn't be found
175 Detects line break for given line, if line break couldn't be found
176 given default value is returned
176 given default value is returned
177
177
178 :param line: str line
178 :param line: str line
179 :param default: default
179 :param default: default
180 :rtype: int
180 :rtype: int
181 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
181 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
182 """
182 """
183 if line.endswith('\r\n'):
183 if line.endswith('\r\n'):
184 return 2
184 return 2
185 elif line.endswith('\n'):
185 elif line.endswith('\n'):
186 return 0
186 return 0
187 elif line.endswith('\r'):
187 elif line.endswith('\r'):
188 return 1
188 return 1
189 else:
189 else:
190 return default
190 return default
191
191
192
192
193 def safe_int(val, default=None):
193 def safe_int(val, default=None):
194 """
194 """
195 Returns int() of val if val is not convertable to int use default
195 Returns int() of val if val is not convertable to int use default
196 instead
196 instead
197
197
198 :param val:
198 :param val:
199 :param default:
199 :param default:
200 """
200 """
201
201
202 try:
202 try:
203 val = int(val)
203 val = int(val)
204 except (ValueError, TypeError):
204 except (ValueError, TypeError):
205 val = default
205 val = default
206
206
207 return val
207 return val
208
208
209
209
210 def safe_unicode(str_, from_encoding=None):
210 def safe_unicode(str_, from_encoding=None):
211 """
211 """
212 safe unicode function. Does few trick to turn str_ into unicode
212 safe unicode function. Does few trick to turn str_ into unicode
213
213
214 In case of UnicodeDecode error, we try to return it with encoding detected
214 In case of UnicodeDecode error, we try to return it with encoding detected
215 by chardet library if it fails fallback to unicode with errors replaced
215 by chardet library if it fails fallback to unicode with errors replaced
216
216
217 :param str_: string to decode
217 :param str_: string to decode
218 :rtype: unicode
218 :rtype: unicode
219 :returns: unicode object
219 :returns: unicode object
220 """
220 """
221 if isinstance(str_, unicode):
221 if isinstance(str_, unicode):
222 return str_
222 return str_
223
223
224 if not from_encoding:
224 if not from_encoding:
225 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
225 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
226 'utf8'), sep=',')
226 'utf8'), sep=',')
227 from_encoding = DEFAULT_ENCODINGS
227 from_encoding = DEFAULT_ENCODINGS
228
228
229 if not isinstance(from_encoding, (list, tuple)):
229 if not isinstance(from_encoding, (list, tuple)):
230 from_encoding = [from_encoding]
230 from_encoding = [from_encoding]
231
231
232 try:
232 try:
233 return unicode(str_)
233 return unicode(str_)
234 except UnicodeDecodeError:
234 except UnicodeDecodeError:
235 pass
235 pass
236
236
237 for enc in from_encoding:
237 for enc in from_encoding:
238 try:
238 try:
239 return unicode(str_, enc)
239 return unicode(str_, enc)
240 except UnicodeDecodeError:
240 except UnicodeDecodeError:
241 pass
241 pass
242
242
243 try:
243 try:
244 import chardet
244 import chardet
245 encoding = chardet.detect(str_)['encoding']
245 encoding = chardet.detect(str_)['encoding']
246 if encoding is None:
246 if encoding is None:
247 raise Exception()
247 raise Exception()
248 return str_.decode(encoding)
248 return str_.decode(encoding)
249 except (ImportError, UnicodeDecodeError, Exception):
249 except (ImportError, UnicodeDecodeError, Exception):
250 return unicode(str_, from_encoding[0], 'replace')
250 return unicode(str_, from_encoding[0], 'replace')
251
251
252
252
253 def safe_str(unicode_, to_encoding=None):
253 def safe_str(unicode_, to_encoding=None):
254 """
254 """
255 safe str function. Does few trick to turn unicode_ into string
255 safe str function. Does few trick to turn unicode_ into string
256
256
257 In case of UnicodeEncodeError, we try to return it with encoding detected
257 In case of UnicodeEncodeError, we try to return it with encoding detected
258 by chardet library if it fails fallback to string with errors replaced
258 by chardet library if it fails fallback to string with errors replaced
259
259
260 :param unicode_: unicode to encode
260 :param unicode_: unicode to encode
261 :rtype: str
261 :rtype: str
262 :returns: str object
262 :returns: str object
263 """
263 """
264
264
265 # if it's not basestr cast to str
265 # if it's not basestr cast to str
266 if not isinstance(unicode_, compat.string_types):
266 if not isinstance(unicode_, compat.string_types):
267 return str(unicode_)
267 return str(unicode_)
268
268
269 if isinstance(unicode_, str):
269 if isinstance(unicode_, str):
270 return unicode_
270 return unicode_
271
271
272 if not to_encoding:
272 if not to_encoding:
273 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
273 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
274 'utf8'), sep=',')
274 'utf8'), sep=',')
275 to_encoding = DEFAULT_ENCODINGS
275 to_encoding = DEFAULT_ENCODINGS
276
276
277 if not isinstance(to_encoding, (list, tuple)):
277 if not isinstance(to_encoding, (list, tuple)):
278 to_encoding = [to_encoding]
278 to_encoding = [to_encoding]
279
279
280 for enc in to_encoding:
280 for enc in to_encoding:
281 try:
281 try:
282 return unicode_.encode(enc)
282 return unicode_.encode(enc)
283 except UnicodeEncodeError:
283 except UnicodeEncodeError:
284 pass
284 pass
285
285
286 try:
286 try:
287 import chardet
287 import chardet
288 encoding = chardet.detect(unicode_)['encoding']
288 encoding = chardet.detect(unicode_)['encoding']
289 if encoding is None:
289 if encoding is None:
290 raise UnicodeEncodeError()
290 raise UnicodeEncodeError()
291
291
292 return unicode_.encode(encoding)
292 return unicode_.encode(encoding)
293 except (ImportError, UnicodeEncodeError):
293 except (ImportError, UnicodeEncodeError):
294 return unicode_.encode(to_encoding[0], 'replace')
294 return unicode_.encode(to_encoding[0], 'replace')
295
295
296
296
297 def remove_suffix(s, suffix):
297 def remove_suffix(s, suffix):
298 if s.endswith(suffix):
298 if s.endswith(suffix):
299 s = s[:-1 * len(suffix)]
299 s = s[:-1 * len(suffix)]
300 return s
300 return s
301
301
302
302
303 def remove_prefix(s, prefix):
303 def remove_prefix(s, prefix):
304 if s.startswith(prefix):
304 if s.startswith(prefix):
305 s = s[len(prefix):]
305 s = s[len(prefix):]
306 return s
306 return s
307
307
308
308
309 def find_calling_context(ignore_modules=None):
309 def find_calling_context(ignore_modules=None):
310 """
310 """
311 Look through the calling stack and return the frame which called
311 Look through the calling stack and return the frame which called
312 this function and is part of core module ( ie. rhodecode.* )
312 this function and is part of core module ( ie. rhodecode.* )
313
313
314 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
314 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
315 """
315 """
316
316
317 ignore_modules = ignore_modules or []
317 ignore_modules = ignore_modules or []
318
318
319 f = sys._getframe(2)
319 f = sys._getframe(2)
320 while f.f_back is not None:
320 while f.f_back is not None:
321 name = f.f_globals.get('__name__')
321 name = f.f_globals.get('__name__')
322 if name and name.startswith(__name__.split('.')[0]):
322 if name and name.startswith(__name__.split('.')[0]):
323 if name not in ignore_modules:
323 if name not in ignore_modules:
324 return f
324 return f
325 f = f.f_back
325 f = f.f_back
326 return None
326 return None
327
327
328
328
329 def ping_connection(connection, branch):
329 def ping_connection(connection, branch):
330 if branch:
330 if branch:
331 # "branch" refers to a sub-connection of a connection,
331 # "branch" refers to a sub-connection of a connection,
332 # we don't want to bother pinging on these.
332 # we don't want to bother pinging on these.
333 return
333 return
334
334
335 # turn off "close with result". This flag is only used with
335 # turn off "close with result". This flag is only used with
336 # "connectionless" execution, otherwise will be False in any case
336 # "connectionless" execution, otherwise will be False in any case
337 save_should_close_with_result = connection.should_close_with_result
337 save_should_close_with_result = connection.should_close_with_result
338 connection.should_close_with_result = False
338 connection.should_close_with_result = False
339
339
340 try:
340 try:
341 # run a SELECT 1. use a core select() so that
341 # run a SELECT 1. use a core select() so that
342 # the SELECT of a scalar value without a table is
342 # the SELECT of a scalar value without a table is
343 # appropriately formatted for the backend
343 # appropriately formatted for the backend
344 connection.scalar(sqlalchemy.sql.select([1]))
344 connection.scalar(sqlalchemy.sql.select([1]))
345 except sqlalchemy.exc.DBAPIError as err:
345 except sqlalchemy.exc.DBAPIError as err:
346 # catch SQLAlchemy's DBAPIError, which is a wrapper
346 # catch SQLAlchemy's DBAPIError, which is a wrapper
347 # for the DBAPI's exception. It includes a .connection_invalidated
347 # for the DBAPI's exception. It includes a .connection_invalidated
348 # attribute which specifies if this connection is a "disconnect"
348 # attribute which specifies if this connection is a "disconnect"
349 # condition, which is based on inspection of the original exception
349 # condition, which is based on inspection of the original exception
350 # by the dialect in use.
350 # by the dialect in use.
351 if err.connection_invalidated:
351 if err.connection_invalidated:
352 # run the same SELECT again - the connection will re-validate
352 # run the same SELECT again - the connection will re-validate
353 # itself and establish a new connection. The disconnect detection
353 # itself and establish a new connection. The disconnect detection
354 # here also causes the whole connection pool to be invalidated
354 # here also causes the whole connection pool to be invalidated
355 # so that all stale connections are discarded.
355 # so that all stale connections are discarded.
356 connection.scalar(sqlalchemy.sql.select([1]))
356 connection.scalar(sqlalchemy.sql.select([1]))
357 else:
357 else:
358 raise
358 raise
359 finally:
359 finally:
360 # restore "close with result"
360 # restore "close with result"
361 connection.should_close_with_result = save_should_close_with_result
361 connection.should_close_with_result = save_should_close_with_result
362
362
363
363
364 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
364 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
365 """Custom engine_from_config functions."""
365 """Custom engine_from_config functions."""
366 log = logging.getLogger('sqlalchemy.engine')
366 log = logging.getLogger('sqlalchemy.engine')
367 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
367 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
368 debug = asbool(configuration.get('debug'))
368 debug = asbool(configuration.get('debug'))
369
369
370 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
370 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
371
371
372 def color_sql(sql):
372 def color_sql(sql):
373 color_seq = '\033[1;33m' # This is yellow: code 33
373 color_seq = '\033[1;33m' # This is yellow: code 33
374 normal = '\x1b[0m'
374 normal = '\x1b[0m'
375 return ''.join([color_seq, sql, normal])
375 return ''.join([color_seq, sql, normal])
376
376
377 if use_ping_connection:
377 if use_ping_connection:
378 log.debug('Adding ping_connection on the engine config.')
378 log.debug('Adding ping_connection on the engine config.')
379 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
379 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
380
380
381 if debug:
381 if debug:
382 # attach events only for debug configuration
382 # attach events only for debug configuration
383 def before_cursor_execute(conn, cursor, statement,
383 def before_cursor_execute(conn, cursor, statement,
384 parameters, context, executemany):
384 parameters, context, executemany):
385 setattr(conn, 'query_start_time', time.time())
385 setattr(conn, 'query_start_time', time.time())
386 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
386 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
387 calling_context = find_calling_context(ignore_modules=[
387 calling_context = find_calling_context(ignore_modules=[
388 'rhodecode.lib.caching_query',
388 'rhodecode.lib.caching_query',
389 'rhodecode.model.settings',
389 'rhodecode.model.settings',
390 ])
390 ])
391 if calling_context:
391 if calling_context:
392 log.info(color_sql('call context %s:%s' % (
392 log.info(color_sql('call context %s:%s' % (
393 calling_context.f_code.co_filename,
393 calling_context.f_code.co_filename,
394 calling_context.f_lineno,
394 calling_context.f_lineno,
395 )))
395 )))
396
396
397 def after_cursor_execute(conn, cursor, statement,
397 def after_cursor_execute(conn, cursor, statement,
398 parameters, context, executemany):
398 parameters, context, executemany):
399 delattr(conn, 'query_start_time')
399 delattr(conn, 'query_start_time')
400
400
401 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
401 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
402 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
402 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
403
403
404 return engine
404 return engine
405
405
406
406
407 def get_encryption_key(config):
407 def get_encryption_key(config):
408 secret = config.get('rhodecode.encrypted_values.secret')
408 secret = config.get('rhodecode.encrypted_values.secret')
409 default = config['beaker.session.secret']
409 default = config['beaker.session.secret']
410 return secret or default
410 return secret or default
411
411
412
412
413 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
413 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
414 short_format=False):
414 short_format=False):
415 """
415 """
416 Turns a datetime into an age string.
416 Turns a datetime into an age string.
417 If show_short_version is True, this generates a shorter string with
417 If show_short_version is True, this generates a shorter string with
418 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
418 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
419
419
420 * IMPORTANT*
420 * IMPORTANT*
421 Code of this function is written in special way so it's easier to
421 Code of this function is written in special way so it's easier to
422 backport it to javascript. If you mean to update it, please also update
422 backport it to javascript. If you mean to update it, please also update
423 `jquery.timeago-extension.js` file
423 `jquery.timeago-extension.js` file
424
424
425 :param prevdate: datetime object
425 :param prevdate: datetime object
426 :param now: get current time, if not define we use
426 :param now: get current time, if not define we use
427 `datetime.datetime.now()`
427 `datetime.datetime.now()`
428 :param show_short_version: if it should approximate the date and
428 :param show_short_version: if it should approximate the date and
429 return a shorter string
429 return a shorter string
430 :param show_suffix:
430 :param show_suffix:
431 :param short_format: show short format, eg 2D instead of 2 days
431 :param short_format: show short format, eg 2D instead of 2 days
432 :rtype: unicode
432 :rtype: unicode
433 :returns: unicode words describing age
433 :returns: unicode words describing age
434 """
434 """
435
435
436 def _get_relative_delta(now, prevdate):
436 def _get_relative_delta(now, prevdate):
437 base = dateutil.relativedelta.relativedelta(now, prevdate)
437 base = dateutil.relativedelta.relativedelta(now, prevdate)
438 return {
438 return {
439 'year': base.years,
439 'year': base.years,
440 'month': base.months,
440 'month': base.months,
441 'day': base.days,
441 'day': base.days,
442 'hour': base.hours,
442 'hour': base.hours,
443 'minute': base.minutes,
443 'minute': base.minutes,
444 'second': base.seconds,
444 'second': base.seconds,
445 }
445 }
446
446
447 def _is_leap_year(year):
447 def _is_leap_year(year):
448 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
448 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
449
449
450 def get_month(prevdate):
450 def get_month(prevdate):
451 return prevdate.month
451 return prevdate.month
452
452
453 def get_year(prevdate):
453 def get_year(prevdate):
454 return prevdate.year
454 return prevdate.year
455
455
456 now = now or datetime.datetime.now()
456 now = now or datetime.datetime.now()
457 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
457 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
458 deltas = {}
458 deltas = {}
459 future = False
459 future = False
460
460
461 if prevdate > now:
461 if prevdate > now:
462 now_old = now
462 now_old = now
463 now = prevdate
463 now = prevdate
464 prevdate = now_old
464 prevdate = now_old
465 future = True
465 future = True
466 if future:
466 if future:
467 prevdate = prevdate.replace(microsecond=0)
467 prevdate = prevdate.replace(microsecond=0)
468 # Get date parts deltas
468 # Get date parts deltas
469 for part in order:
469 for part in order:
470 rel_delta = _get_relative_delta(now, prevdate)
470 rel_delta = _get_relative_delta(now, prevdate)
471 deltas[part] = rel_delta[part]
471 deltas[part] = rel_delta[part]
472
472
473 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
473 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
474 # not 1 hour, -59 minutes and -59 seconds)
474 # not 1 hour, -59 minutes and -59 seconds)
475 offsets = [[5, 60], [4, 60], [3, 24]]
475 offsets = [[5, 60], [4, 60], [3, 24]]
476 for element in offsets: # seconds, minutes, hours
476 for element in offsets: # seconds, minutes, hours
477 num = element[0]
477 num = element[0]
478 length = element[1]
478 length = element[1]
479
479
480 part = order[num]
480 part = order[num]
481 carry_part = order[num - 1]
481 carry_part = order[num - 1]
482
482
483 if deltas[part] < 0:
483 if deltas[part] < 0:
484 deltas[part] += length
484 deltas[part] += length
485 deltas[carry_part] -= 1
485 deltas[carry_part] -= 1
486
486
487 # Same thing for days except that the increment depends on the (variable)
487 # Same thing for days except that the increment depends on the (variable)
488 # number of days in the month
488 # number of days in the month
489 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
489 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
490 if deltas['day'] < 0:
490 if deltas['day'] < 0:
491 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
491 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
492 deltas['day'] += 29
492 deltas['day'] += 29
493 else:
493 else:
494 deltas['day'] += month_lengths[get_month(prevdate) - 1]
494 deltas['day'] += month_lengths[get_month(prevdate) - 1]
495
495
496 deltas['month'] -= 1
496 deltas['month'] -= 1
497
497
498 if deltas['month'] < 0:
498 if deltas['month'] < 0:
499 deltas['month'] += 12
499 deltas['month'] += 12
500 deltas['year'] -= 1
500 deltas['year'] -= 1
501
501
502 # Format the result
502 # Format the result
503 if short_format:
503 if short_format:
504 fmt_funcs = {
504 fmt_funcs = {
505 'year': lambda d: u'%dy' % d,
505 'year': lambda d: u'%dy' % d,
506 'month': lambda d: u'%dm' % d,
506 'month': lambda d: u'%dm' % d,
507 'day': lambda d: u'%dd' % d,
507 'day': lambda d: u'%dd' % d,
508 'hour': lambda d: u'%dh' % d,
508 'hour': lambda d: u'%dh' % d,
509 'minute': lambda d: u'%dmin' % d,
509 'minute': lambda d: u'%dmin' % d,
510 'second': lambda d: u'%dsec' % d,
510 'second': lambda d: u'%dsec' % d,
511 }
511 }
512 else:
512 else:
513 fmt_funcs = {
513 fmt_funcs = {
514 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
514 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
515 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
515 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
516 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
516 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
517 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
517 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
518 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
518 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
519 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
519 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
520 }
520 }
521
521
522 i = 0
522 i = 0
523 for part in order:
523 for part in order:
524 value = deltas[part]
524 value = deltas[part]
525 if value != 0:
525 if value != 0:
526
526
527 if i < 5:
527 if i < 5:
528 sub_part = order[i + 1]
528 sub_part = order[i + 1]
529 sub_value = deltas[sub_part]
529 sub_value = deltas[sub_part]
530 else:
530 else:
531 sub_value = 0
531 sub_value = 0
532
532
533 if sub_value == 0 or show_short_version:
533 if sub_value == 0 or show_short_version:
534 _val = fmt_funcs[part](value)
534 _val = fmt_funcs[part](value)
535 if future:
535 if future:
536 if show_suffix:
536 if show_suffix:
537 return _(u'in ${ago}', mapping={'ago': _val})
537 return _(u'in ${ago}', mapping={'ago': _val})
538 else:
538 else:
539 return _(_val)
539 return _(_val)
540
540
541 else:
541 else:
542 if show_suffix:
542 if show_suffix:
543 return _(u'${ago} ago', mapping={'ago': _val})
543 return _(u'${ago} ago', mapping={'ago': _val})
544 else:
544 else:
545 return _(_val)
545 return _(_val)
546
546
547 val = fmt_funcs[part](value)
547 val = fmt_funcs[part](value)
548 val_detail = fmt_funcs[sub_part](sub_value)
548 val_detail = fmt_funcs[sub_part](sub_value)
549 mapping = {'val': val, 'detail': val_detail}
549 mapping = {'val': val, 'detail': val_detail}
550
550
551 if short_format:
551 if short_format:
552 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
552 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
553 if show_suffix:
553 if show_suffix:
554 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
554 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
555 if future:
555 if future:
556 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
556 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
557 else:
557 else:
558 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
558 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
559 if show_suffix:
559 if show_suffix:
560 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
560 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
561 if future:
561 if future:
562 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
562 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
563
563
564 return datetime_tmpl
564 return datetime_tmpl
565 i += 1
565 i += 1
566 return _(u'just now')
566 return _(u'just now')
567
567
568
568
569 def age_from_seconds(seconds):
569 def age_from_seconds(seconds):
570 seconds = safe_int(seconds) or 0
570 seconds = safe_int(seconds) or 0
571 prevdate = time_to_datetime(time.time() + seconds)
571 prevdate = time_to_datetime(time.time() + seconds)
572 return age(prevdate, show_suffix=False, show_short_version=True)
572 return age(prevdate, show_suffix=False, show_short_version=True)
573
573
574
574
575 def cleaned_uri(uri):
575 def cleaned_uri(uri):
576 """
576 """
577 Quotes '[' and ']' from uri if there is only one of them.
577 Quotes '[' and ']' from uri if there is only one of them.
578 according to RFC3986 we cannot use such chars in uri
578 according to RFC3986 we cannot use such chars in uri
579 :param uri:
579 :param uri:
580 :return: uri without this chars
580 :return: uri without this chars
581 """
581 """
582 return urllib.quote(uri, safe='@$:/')
582 return urllib.quote(uri, safe='@$:/')
583
583
584
584
585 def uri_filter(uri):
585 def uri_filter(uri):
586 """
586 """
587 Removes user:password from given url string
587 Removes user:password from given url string
588
588
589 :param uri:
589 :param uri:
590 :rtype: unicode
590 :rtype: unicode
591 :returns: filtered list of strings
591 :returns: filtered list of strings
592 """
592 """
593 if not uri:
593 if not uri:
594 return ''
594 return ''
595
595
596 proto = ''
596 proto = ''
597
597
598 for pat in ('https://', 'http://'):
598 for pat in ('https://', 'http://'):
599 if uri.startswith(pat):
599 if uri.startswith(pat):
600 uri = uri[len(pat):]
600 uri = uri[len(pat):]
601 proto = pat
601 proto = pat
602 break
602 break
603
603
604 # remove passwords and username
604 # remove passwords and username
605 uri = uri[uri.find('@') + 1:]
605 uri = uri[uri.find('@') + 1:]
606
606
607 # get the port
607 # get the port
608 cred_pos = uri.find(':')
608 cred_pos = uri.find(':')
609 if cred_pos == -1:
609 if cred_pos == -1:
610 host, port = uri, None
610 host, port = uri, None
611 else:
611 else:
612 host, port = uri[:cred_pos], uri[cred_pos + 1:]
612 host, port = uri[:cred_pos], uri[cred_pos + 1:]
613
613
614 return filter(None, [proto, host, port])
614 return filter(None, [proto, host, port])
615
615
616
616
617 def credentials_filter(uri):
617 def credentials_filter(uri):
618 """
618 """
619 Returns a url with removed credentials
619 Returns a url with removed credentials
620
620
621 :param uri:
621 :param uri:
622 """
622 """
623
623
624 uri = uri_filter(uri)
624 uri = uri_filter(uri)
625 # check if we have port
625 # check if we have port
626 if len(uri) > 2 and uri[2]:
626 if len(uri) > 2 and uri[2]:
627 uri[2] = ':' + uri[2]
627 uri[2] = ':' + uri[2]
628
628
629 return ''.join(uri)
629 return ''.join(uri)
630
630
631
631
632 def get_host_info(request):
633 """
634 Generate host info, to obtain full url e.g https://server.com
635 use this
636 `{scheme}://{netloc}`
637 """
638 if not request:
639 return {}
640
641 qualified_home_url = request.route_url('home')
642 parsed_url = urlobject.URLObject(qualified_home_url)
643 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
644
645 return {
646 'scheme': parsed_url.scheme,
647 'netloc': parsed_url.netloc+decoded_path,
648 'hostname': parsed_url.hostname,
649 }
650
651
632 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
652 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
633 qualifed_home_url = request.route_url('home')
653 qualified_home_url = request.route_url('home')
634 parsed_url = urlobject.URLObject(qualifed_home_url)
654 parsed_url = urlobject.URLObject(qualified_home_url)
635 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
655 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
636
656
637 args = {
657 args = {
638 'scheme': parsed_url.scheme,
658 'scheme': parsed_url.scheme,
639 'user': '',
659 'user': '',
640 'sys_user': getpass.getuser(),
660 'sys_user': getpass.getuser(),
641 # path if we use proxy-prefix
661 # path if we use proxy-prefix
642 'netloc': parsed_url.netloc+decoded_path,
662 'netloc': parsed_url.netloc+decoded_path,
643 'hostname': parsed_url.hostname,
663 'hostname': parsed_url.hostname,
644 'prefix': decoded_path,
664 'prefix': decoded_path,
645 'repo': repo_name,
665 'repo': repo_name,
646 'repoid': str(repo_id)
666 'repoid': str(repo_id)
647 }
667 }
648 args.update(override)
668 args.update(override)
649 args['user'] = urllib.quote(safe_str(args['user']))
669 args['user'] = urllib.quote(safe_str(args['user']))
650
670
651 for k, v in args.items():
671 for k, v in args.items():
652 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
672 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
653
673
654 # remove leading @ sign if it's present. Case of empty user
674 # remove leading @ sign if it's present. Case of empty user
655 url_obj = urlobject.URLObject(uri_tmpl)
675 url_obj = urlobject.URLObject(uri_tmpl)
656 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
676 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
657
677
658 return safe_unicode(url)
678 return safe_unicode(url)
659
679
660
680
661 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
681 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
662 """
682 """
663 Safe version of get_commit if this commit doesn't exists for a
683 Safe version of get_commit if this commit doesn't exists for a
664 repository it returns a Dummy one instead
684 repository it returns a Dummy one instead
665
685
666 :param repo: repository instance
686 :param repo: repository instance
667 :param commit_id: commit id as str
687 :param commit_id: commit id as str
668 :param pre_load: optional list of commit attributes to load
688 :param pre_load: optional list of commit attributes to load
669 """
689 """
670 # TODO(skreft): remove these circular imports
690 # TODO(skreft): remove these circular imports
671 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
691 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
672 from rhodecode.lib.vcs.exceptions import RepositoryError
692 from rhodecode.lib.vcs.exceptions import RepositoryError
673 if not isinstance(repo, BaseRepository):
693 if not isinstance(repo, BaseRepository):
674 raise Exception('You must pass an Repository '
694 raise Exception('You must pass an Repository '
675 'object as first argument got %s', type(repo))
695 'object as first argument got %s', type(repo))
676
696
677 try:
697 try:
678 commit = repo.get_commit(
698 commit = repo.get_commit(
679 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
699 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
680 except (RepositoryError, LookupError):
700 except (RepositoryError, LookupError):
681 commit = EmptyCommit()
701 commit = EmptyCommit()
682 return commit
702 return commit
683
703
684
704
685 def datetime_to_time(dt):
705 def datetime_to_time(dt):
686 if dt:
706 if dt:
687 return time.mktime(dt.timetuple())
707 return time.mktime(dt.timetuple())
688
708
689
709
690 def time_to_datetime(tm):
710 def time_to_datetime(tm):
691 if tm:
711 if tm:
692 if isinstance(tm, compat.string_types):
712 if isinstance(tm, compat.string_types):
693 try:
713 try:
694 tm = float(tm)
714 tm = float(tm)
695 except ValueError:
715 except ValueError:
696 return
716 return
697 return datetime.datetime.fromtimestamp(tm)
717 return datetime.datetime.fromtimestamp(tm)
698
718
699
719
700 def time_to_utcdatetime(tm):
720 def time_to_utcdatetime(tm):
701 if tm:
721 if tm:
702 if isinstance(tm, compat.string_types):
722 if isinstance(tm, compat.string_types):
703 try:
723 try:
704 tm = float(tm)
724 tm = float(tm)
705 except ValueError:
725 except ValueError:
706 return
726 return
707 return datetime.datetime.utcfromtimestamp(tm)
727 return datetime.datetime.utcfromtimestamp(tm)
708
728
709
729
710 MENTIONS_REGEX = re.compile(
730 MENTIONS_REGEX = re.compile(
711 # ^@ or @ without any special chars in front
731 # ^@ or @ without any special chars in front
712 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
732 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
713 # main body starts with letter, then can be . - _
733 # main body starts with letter, then can be . - _
714 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
734 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
715 re.VERBOSE | re.MULTILINE)
735 re.VERBOSE | re.MULTILINE)
716
736
717
737
718 def extract_mentioned_users(s):
738 def extract_mentioned_users(s):
719 """
739 """
720 Returns unique usernames from given string s that have @mention
740 Returns unique usernames from given string s that have @mention
721
741
722 :param s: string to get mentions
742 :param s: string to get mentions
723 """
743 """
724 usrs = set()
744 usrs = set()
725 for username in MENTIONS_REGEX.findall(s):
745 for username in MENTIONS_REGEX.findall(s):
726 usrs.add(username)
746 usrs.add(username)
727
747
728 return sorted(list(usrs), key=lambda k: k.lower())
748 return sorted(list(usrs), key=lambda k: k.lower())
729
749
730
750
731 class AttributeDictBase(dict):
751 class AttributeDictBase(dict):
732 def __getstate__(self):
752 def __getstate__(self):
733 odict = self.__dict__ # get attribute dictionary
753 odict = self.__dict__ # get attribute dictionary
734 return odict
754 return odict
735
755
736 def __setstate__(self, dict):
756 def __setstate__(self, dict):
737 self.__dict__ = dict
757 self.__dict__ = dict
738
758
739 __setattr__ = dict.__setitem__
759 __setattr__ = dict.__setitem__
740 __delattr__ = dict.__delitem__
760 __delattr__ = dict.__delitem__
741
761
742
762
743 class StrictAttributeDict(AttributeDictBase):
763 class StrictAttributeDict(AttributeDictBase):
744 """
764 """
745 Strict Version of Attribute dict which raises an Attribute error when
765 Strict Version of Attribute dict which raises an Attribute error when
746 requested attribute is not set
766 requested attribute is not set
747 """
767 """
748 def __getattr__(self, attr):
768 def __getattr__(self, attr):
749 try:
769 try:
750 return self[attr]
770 return self[attr]
751 except KeyError:
771 except KeyError:
752 raise AttributeError('%s object has no attribute %s' % (
772 raise AttributeError('%s object has no attribute %s' % (
753 self.__class__, attr))
773 self.__class__, attr))
754
774
755
775
756 class AttributeDict(AttributeDictBase):
776 class AttributeDict(AttributeDictBase):
757 def __getattr__(self, attr):
777 def __getattr__(self, attr):
758 return self.get(attr, None)
778 return self.get(attr, None)
759
779
760
780
761
781
762 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
782 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
763 def __init__(self, default_factory=None, *args, **kwargs):
783 def __init__(self, default_factory=None, *args, **kwargs):
764 # in python3 you can omit the args to super
784 # in python3 you can omit the args to super
765 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
785 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
766 self.default_factory = default_factory
786 self.default_factory = default_factory
767
787
768
788
769 def fix_PATH(os_=None):
789 def fix_PATH(os_=None):
770 """
790 """
771 Get current active python path, and append it to PATH variable to fix
791 Get current active python path, and append it to PATH variable to fix
772 issues of subprocess calls and different python versions
792 issues of subprocess calls and different python versions
773 """
793 """
774 if os_ is None:
794 if os_ is None:
775 import os
795 import os
776 else:
796 else:
777 os = os_
797 os = os_
778
798
779 cur_path = os.path.split(sys.executable)[0]
799 cur_path = os.path.split(sys.executable)[0]
780 if not os.environ['PATH'].startswith(cur_path):
800 if not os.environ['PATH'].startswith(cur_path):
781 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
801 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
782
802
783
803
784 def obfuscate_url_pw(engine):
804 def obfuscate_url_pw(engine):
785 _url = engine or ''
805 _url = engine or ''
786 try:
806 try:
787 _url = sqlalchemy.engine.url.make_url(engine)
807 _url = sqlalchemy.engine.url.make_url(engine)
788 if _url.password:
808 if _url.password:
789 _url.password = 'XXXXX'
809 _url.password = 'XXXXX'
790 except Exception:
810 except Exception:
791 pass
811 pass
792 return unicode(_url)
812 return unicode(_url)
793
813
794
814
795 def get_server_url(environ):
815 def get_server_url(environ):
796 req = webob.Request(environ)
816 req = webob.Request(environ)
797 return req.host_url + req.script_name
817 return req.host_url + req.script_name
798
818
799
819
800 def unique_id(hexlen=32):
820 def unique_id(hexlen=32):
801 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
821 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
802 return suuid(truncate_to=hexlen, alphabet=alphabet)
822 return suuid(truncate_to=hexlen, alphabet=alphabet)
803
823
804
824
805 def suuid(url=None, truncate_to=22, alphabet=None):
825 def suuid(url=None, truncate_to=22, alphabet=None):
806 """
826 """
807 Generate and return a short URL safe UUID.
827 Generate and return a short URL safe UUID.
808
828
809 If the url parameter is provided, set the namespace to the provided
829 If the url parameter is provided, set the namespace to the provided
810 URL and generate a UUID.
830 URL and generate a UUID.
811
831
812 :param url to get the uuid for
832 :param url to get the uuid for
813 :truncate_to: truncate the basic 22 UUID to shorter version
833 :truncate_to: truncate the basic 22 UUID to shorter version
814
834
815 The IDs won't be universally unique any longer, but the probability of
835 The IDs won't be universally unique any longer, but the probability of
816 a collision will still be very low.
836 a collision will still be very low.
817 """
837 """
818 # Define our alphabet.
838 # Define our alphabet.
819 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
839 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
820
840
821 # If no URL is given, generate a random UUID.
841 # If no URL is given, generate a random UUID.
822 if url is None:
842 if url is None:
823 unique_id = uuid.uuid4().int
843 unique_id = uuid.uuid4().int
824 else:
844 else:
825 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
845 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
826
846
827 alphabet_length = len(_ALPHABET)
847 alphabet_length = len(_ALPHABET)
828 output = []
848 output = []
829 while unique_id > 0:
849 while unique_id > 0:
830 digit = unique_id % alphabet_length
850 digit = unique_id % alphabet_length
831 output.append(_ALPHABET[digit])
851 output.append(_ALPHABET[digit])
832 unique_id = int(unique_id / alphabet_length)
852 unique_id = int(unique_id / alphabet_length)
833 return "".join(output)[:truncate_to]
853 return "".join(output)[:truncate_to]
834
854
835
855
836 def get_current_rhodecode_user(request=None):
856 def get_current_rhodecode_user(request=None):
837 """
857 """
838 Gets rhodecode user from request
858 Gets rhodecode user from request
839 """
859 """
840 pyramid_request = request or pyramid.threadlocal.get_current_request()
860 pyramid_request = request or pyramid.threadlocal.get_current_request()
841
861
842 # web case
862 # web case
843 if pyramid_request and hasattr(pyramid_request, 'user'):
863 if pyramid_request and hasattr(pyramid_request, 'user'):
844 return pyramid_request.user
864 return pyramid_request.user
845
865
846 # api case
866 # api case
847 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
867 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
848 return pyramid_request.rpc_user
868 return pyramid_request.rpc_user
849
869
850 return None
870 return None
851
871
852
872
853 def action_logger_generic(action, namespace=''):
873 def action_logger_generic(action, namespace=''):
854 """
874 """
855 A generic logger for actions useful to the system overview, tries to find
875 A generic logger for actions useful to the system overview, tries to find
856 an acting user for the context of the call otherwise reports unknown user
876 an acting user for the context of the call otherwise reports unknown user
857
877
858 :param action: logging message eg 'comment 5 deleted'
878 :param action: logging message eg 'comment 5 deleted'
859 :param type: string
879 :param type: string
860
880
861 :param namespace: namespace of the logging message eg. 'repo.comments'
881 :param namespace: namespace of the logging message eg. 'repo.comments'
862 :param type: string
882 :param type: string
863
883
864 """
884 """
865
885
866 logger_name = 'rhodecode.actions'
886 logger_name = 'rhodecode.actions'
867
887
868 if namespace:
888 if namespace:
869 logger_name += '.' + namespace
889 logger_name += '.' + namespace
870
890
871 log = logging.getLogger(logger_name)
891 log = logging.getLogger(logger_name)
872
892
873 # get a user if we can
893 # get a user if we can
874 user = get_current_rhodecode_user()
894 user = get_current_rhodecode_user()
875
895
876 logfunc = log.info
896 logfunc = log.info
877
897
878 if not user:
898 if not user:
879 user = '<unknown user>'
899 user = '<unknown user>'
880 logfunc = log.warning
900 logfunc = log.warning
881
901
882 logfunc('Logging action by {}: {}'.format(user, action))
902 logfunc('Logging action by {}: {}'.format(user, action))
883
903
884
904
885 def escape_split(text, sep=',', maxsplit=-1):
905 def escape_split(text, sep=',', maxsplit=-1):
886 r"""
906 r"""
887 Allows for escaping of the separator: e.g. arg='foo\, bar'
907 Allows for escaping of the separator: e.g. arg='foo\, bar'
888
908
889 It should be noted that the way bash et. al. do command line parsing, those
909 It should be noted that the way bash et. al. do command line parsing, those
890 single quotes are required.
910 single quotes are required.
891 """
911 """
892 escaped_sep = r'\%s' % sep
912 escaped_sep = r'\%s' % sep
893
913
894 if escaped_sep not in text:
914 if escaped_sep not in text:
895 return text.split(sep, maxsplit)
915 return text.split(sep, maxsplit)
896
916
897 before, _mid, after = text.partition(escaped_sep)
917 before, _mid, after = text.partition(escaped_sep)
898 startlist = before.split(sep, maxsplit) # a regular split is fine here
918 startlist = before.split(sep, maxsplit) # a regular split is fine here
899 unfinished = startlist[-1]
919 unfinished = startlist[-1]
900 startlist = startlist[:-1]
920 startlist = startlist[:-1]
901
921
902 # recurse because there may be more escaped separators
922 # recurse because there may be more escaped separators
903 endlist = escape_split(after, sep, maxsplit)
923 endlist = escape_split(after, sep, maxsplit)
904
924
905 # finish building the escaped value. we use endlist[0] becaue the first
925 # finish building the escaped value. we use endlist[0] becaue the first
906 # part of the string sent in recursion is the rest of the escaped value.
926 # part of the string sent in recursion is the rest of the escaped value.
907 unfinished += sep + endlist[0]
927 unfinished += sep + endlist[0]
908
928
909 return startlist + [unfinished] + endlist[1:] # put together all the parts
929 return startlist + [unfinished] + endlist[1:] # put together all the parts
910
930
911
931
912 class OptionalAttr(object):
932 class OptionalAttr(object):
913 """
933 """
914 Special Optional Option that defines other attribute. Example::
934 Special Optional Option that defines other attribute. Example::
915
935
916 def test(apiuser, userid=Optional(OAttr('apiuser')):
936 def test(apiuser, userid=Optional(OAttr('apiuser')):
917 user = Optional.extract(userid)
937 user = Optional.extract(userid)
918 # calls
938 # calls
919
939
920 """
940 """
921
941
922 def __init__(self, attr_name):
942 def __init__(self, attr_name):
923 self.attr_name = attr_name
943 self.attr_name = attr_name
924
944
925 def __repr__(self):
945 def __repr__(self):
926 return '<OptionalAttr:%s>' % self.attr_name
946 return '<OptionalAttr:%s>' % self.attr_name
927
947
928 def __call__(self):
948 def __call__(self):
929 return self
949 return self
930
950
931
951
932 # alias
952 # alias
933 OAttr = OptionalAttr
953 OAttr = OptionalAttr
934
954
935
955
936 class Optional(object):
956 class Optional(object):
937 """
957 """
938 Defines an optional parameter::
958 Defines an optional parameter::
939
959
940 param = param.getval() if isinstance(param, Optional) else param
960 param = param.getval() if isinstance(param, Optional) else param
941 param = param() if isinstance(param, Optional) else param
961 param = param() if isinstance(param, Optional) else param
942
962
943 is equivalent of::
963 is equivalent of::
944
964
945 param = Optional.extract(param)
965 param = Optional.extract(param)
946
966
947 """
967 """
948
968
949 def __init__(self, type_):
969 def __init__(self, type_):
950 self.type_ = type_
970 self.type_ = type_
951
971
952 def __repr__(self):
972 def __repr__(self):
953 return '<Optional:%s>' % self.type_.__repr__()
973 return '<Optional:%s>' % self.type_.__repr__()
954
974
955 def __call__(self):
975 def __call__(self):
956 return self.getval()
976 return self.getval()
957
977
958 def getval(self):
978 def getval(self):
959 """
979 """
960 returns value from this Optional instance
980 returns value from this Optional instance
961 """
981 """
962 if isinstance(self.type_, OAttr):
982 if isinstance(self.type_, OAttr):
963 # use params name
983 # use params name
964 return self.type_.attr_name
984 return self.type_.attr_name
965 return self.type_
985 return self.type_
966
986
967 @classmethod
987 @classmethod
968 def extract(cls, val):
988 def extract(cls, val):
969 """
989 """
970 Extracts value from Optional() instance
990 Extracts value from Optional() instance
971
991
972 :param val:
992 :param val:
973 :return: original value if it's not Optional instance else
993 :return: original value if it's not Optional instance else
974 value of instance
994 value of instance
975 """
995 """
976 if isinstance(val, cls):
996 if isinstance(val, cls):
977 return val.getval()
997 return val.getval()
978 return val
998 return val
979
999
980
1000
981 def glob2re(pat):
1001 def glob2re(pat):
982 """
1002 """
983 Translate a shell PATTERN to a regular expression.
1003 Translate a shell PATTERN to a regular expression.
984
1004
985 There is no way to quote meta-characters.
1005 There is no way to quote meta-characters.
986 """
1006 """
987
1007
988 i, n = 0, len(pat)
1008 i, n = 0, len(pat)
989 res = ''
1009 res = ''
990 while i < n:
1010 while i < n:
991 c = pat[i]
1011 c = pat[i]
992 i = i+1
1012 i = i+1
993 if c == '*':
1013 if c == '*':
994 #res = res + '.*'
1014 #res = res + '.*'
995 res = res + '[^/]*'
1015 res = res + '[^/]*'
996 elif c == '?':
1016 elif c == '?':
997 #res = res + '.'
1017 #res = res + '.'
998 res = res + '[^/]'
1018 res = res + '[^/]'
999 elif c == '[':
1019 elif c == '[':
1000 j = i
1020 j = i
1001 if j < n and pat[j] == '!':
1021 if j < n and pat[j] == '!':
1002 j = j+1
1022 j = j+1
1003 if j < n and pat[j] == ']':
1023 if j < n and pat[j] == ']':
1004 j = j+1
1024 j = j+1
1005 while j < n and pat[j] != ']':
1025 while j < n and pat[j] != ']':
1006 j = j+1
1026 j = j+1
1007 if j >= n:
1027 if j >= n:
1008 res = res + '\\['
1028 res = res + '\\['
1009 else:
1029 else:
1010 stuff = pat[i:j].replace('\\','\\\\')
1030 stuff = pat[i:j].replace('\\','\\\\')
1011 i = j+1
1031 i = j+1
1012 if stuff[0] == '!':
1032 if stuff[0] == '!':
1013 stuff = '^' + stuff[1:]
1033 stuff = '^' + stuff[1:]
1014 elif stuff[0] == '^':
1034 elif stuff[0] == '^':
1015 stuff = '\\' + stuff
1035 stuff = '\\' + stuff
1016 res = '%s[%s]' % (res, stuff)
1036 res = '%s[%s]' % (res, stuff)
1017 else:
1037 else:
1018 res = res + re.escape(c)
1038 res = res + re.escape(c)
1019 return res + '\Z(?ms)'
1039 return res + '\Z(?ms)'
1020
1040
1021
1041
1022 def parse_byte_string(size_str):
1042 def parse_byte_string(size_str):
1023 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1043 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1024 if not match:
1044 if not match:
1025 raise ValueError('Given size:%s is invalid, please make sure '
1045 raise ValueError('Given size:%s is invalid, please make sure '
1026 'to use format of <num>(MB|KB)' % size_str)
1046 'to use format of <num>(MB|KB)' % size_str)
1027
1047
1028 _parts = match.groups()
1048 _parts = match.groups()
1029 num, type_ = _parts
1049 num, type_ = _parts
1030 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1050 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1031
1051
1032
1052
1033 class CachedProperty(object):
1053 class CachedProperty(object):
1034 """
1054 """
1035 Lazy Attributes. With option to invalidate the cache by running a method
1055 Lazy Attributes. With option to invalidate the cache by running a method
1036
1056
1037 class Foo():
1057 class Foo():
1038
1058
1039 @CachedProperty
1059 @CachedProperty
1040 def heavy_func():
1060 def heavy_func():
1041 return 'super-calculation'
1061 return 'super-calculation'
1042
1062
1043 foo = Foo()
1063 foo = Foo()
1044 foo.heavy_func() # first computions
1064 foo.heavy_func() # first computions
1045 foo.heavy_func() # fetch from cache
1065 foo.heavy_func() # fetch from cache
1046 foo._invalidate_prop_cache('heavy_func')
1066 foo._invalidate_prop_cache('heavy_func')
1047 # at this point calling foo.heavy_func() will be re-computed
1067 # at this point calling foo.heavy_func() will be re-computed
1048 """
1068 """
1049
1069
1050 def __init__(self, func, func_name=None):
1070 def __init__(self, func, func_name=None):
1051
1071
1052 if func_name is None:
1072 if func_name is None:
1053 func_name = func.__name__
1073 func_name = func.__name__
1054 self.data = (func, func_name)
1074 self.data = (func, func_name)
1055 update_wrapper(self, func)
1075 update_wrapper(self, func)
1056
1076
1057 def __get__(self, inst, class_):
1077 def __get__(self, inst, class_):
1058 if inst is None:
1078 if inst is None:
1059 return self
1079 return self
1060
1080
1061 func, func_name = self.data
1081 func, func_name = self.data
1062 value = func(inst)
1082 value = func(inst)
1063 inst.__dict__[func_name] = value
1083 inst.__dict__[func_name] = value
1064 if '_invalidate_prop_cache' not in inst.__dict__:
1084 if '_invalidate_prop_cache' not in inst.__dict__:
1065 inst.__dict__['_invalidate_prop_cache'] = partial(
1085 inst.__dict__['_invalidate_prop_cache'] = partial(
1066 self._invalidate_prop_cache, inst)
1086 self._invalidate_prop_cache, inst)
1067 return value
1087 return value
1068
1088
1069 def _invalidate_prop_cache(self, inst, name):
1089 def _invalidate_prop_cache(self, inst, name):
1070 inst.__dict__.pop(name, None)
1090 inst.__dict__.pop(name, None)
@@ -1,90 +1,94 b''
1 //--- RESETS ---//
1 //--- RESETS ---//
2 :focus { outline: none; }
2 :focus { outline: none; }
3 a { cursor: pointer; }
3 a { cursor: pointer; }
4
4
5 //--- clearfix --//
5 //--- clearfix --//
6 .clearfix {
6 .clearfix {
7 &:before,
7 &:before,
8 &:after {
8 &:after {
9 content:"";
9 content:"";
10 width: 100%;
10 width: 100%;
11 clear: both;
11 clear: both;
12 float: left;
12 float: left;
13 }
13 }
14 }
14 }
15
15
16 .clearinner:after { /* clears all floating divs inside a block */
16 .clearinner:after { /* clears all floating divs inside a block */
17 content: "";
17 content: "";
18 display: table;
18 display: table;
19 clear: both;
19 clear: both;
20 }
20 }
21
21
22 .js-template { /* mark a template for javascript use */
22 .js-template { /* mark a template for javascript use */
23 display: none;
23 display: none;
24 }
24 }
25
25
26 .linebreak {
26 .linebreak {
27 display: block;
27 display: block;
28 }
28 }
29
29
30 .clear-both {
31 clear: both;
32 }
33
30 .pull-right {
34 .pull-right {
31 float: right !important;
35 float: right !important;
32 }
36 }
33
37
34 .pull-left {
38 .pull-left {
35 float: left !important;
39 float: left !important;
36 }
40 }
37
41
38 .block-left {
42 .block-left {
39 float: left;
43 float: left;
40 }
44 }
41
45
42 .block-right {
46 .block-right {
43 float: right;
47 float: right;
44 clear: right;
48 clear: right;
45
49
46 li {
50 li {
47 list-style-type: none;
51 list-style-type: none;
48 }
52 }
49 }
53 }
50
54
51 //--- DEVICE-SPECIFIC CLASSES ---------------//
55 //--- DEVICE-SPECIFIC CLASSES ---------------//
52 //regular tablet and up
56 //regular tablet and up
53 @media (min-width:768px) {
57 @media (min-width:768px) {
54 .no-mobile {
58 .no-mobile {
55 display: block;
59 display: block;
56 }
60 }
57 .mobile-only {
61 .mobile-only {
58 display: none;
62 display: none;
59 }
63 }
60 }
64 }
61 //small tablet and phone
65 //small tablet and phone
62 @media (max-width:767px) {
66 @media (max-width:767px) {
63 .mobile-only {
67 .mobile-only {
64 display: block;
68 display: block;
65 }
69 }
66 .no-mobile {
70 .no-mobile {
67 display: none;
71 display: none;
68 }
72 }
69 }
73 }
70
74
71 //--- STICKY FOOTER ---//
75 //--- STICKY FOOTER ---//
72 html, body {
76 html, body {
73 height: 100%;
77 height: 100%;
74 margin: 0;
78 margin: 0;
75 }
79 }
76 .outerwrapper {
80 .outerwrapper {
77 height: 100%;
81 height: 100%;
78 min-height: 100%;
82 min-height: 100%;
79 margin: 0;
83 margin: 0;
80 padding-bottom: 3em; /* must be equal to footer height */
84 padding-bottom: 3em; /* must be equal to footer height */
81 }
85 }
82 .outerwrapper:after{
86 .outerwrapper:after{
83 content:" ";
87 content:" ";
84 }
88 }
85 #footer {
89 #footer {
86 clear: both;
90 clear: both;
87 position: relative;
91 position: relative;
88 height: 3em; /* footer height */
92 height: 3em; /* footer height */
89 margin: -3em 0 0; /* must be equal to footer height */
93 margin: -3em 0 0; /* must be equal to footer height */
90 }
94 }
@@ -1,2897 +1,2913 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'variables';
8 @import 'variables';
9 @import 'bootstrap-variables';
9 @import 'bootstrap-variables';
10 @import 'form-bootstrap';
10 @import 'form-bootstrap';
11 @import 'codemirror';
11 @import 'codemirror';
12 @import 'legacy_code_styles';
12 @import 'legacy_code_styles';
13 @import 'readme-box';
13 @import 'readme-box';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29 @import 'tooltips';
29 @import 'tooltips';
30
30
31 //--- BASE ------------------//
31 //--- BASE ------------------//
32 .noscript-error {
32 .noscript-error {
33 top: 0;
33 top: 0;
34 left: 0;
34 left: 0;
35 width: 100%;
35 width: 100%;
36 z-index: 101;
36 z-index: 101;
37 text-align: center;
37 text-align: center;
38 font-size: 120%;
38 font-size: 120%;
39 color: white;
39 color: white;
40 background-color: @alert2;
40 background-color: @alert2;
41 padding: 5px 0 5px 0;
41 padding: 5px 0 5px 0;
42 font-weight: @text-semibold-weight;
42 font-weight: @text-semibold-weight;
43 font-family: @text-semibold;
43 font-family: @text-semibold;
44 }
44 }
45
45
46 html {
46 html {
47 display: table;
47 display: table;
48 height: 100%;
48 height: 100%;
49 width: 100%;
49 width: 100%;
50 }
50 }
51
51
52 body {
52 body {
53 display: table-cell;
53 display: table-cell;
54 width: 100%;
54 width: 100%;
55 }
55 }
56
56
57 //--- LAYOUT ------------------//
57 //--- LAYOUT ------------------//
58
58
59 .hidden{
59 .hidden{
60 display: none !important;
60 display: none !important;
61 }
61 }
62
62
63 .box{
63 .box{
64 float: left;
64 float: left;
65 width: 100%;
65 width: 100%;
66 }
66 }
67
67
68 .browser-header {
68 .browser-header {
69 clear: both;
69 clear: both;
70 }
70 }
71 .main {
71 .main {
72 clear: both;
72 clear: both;
73 padding:0 0 @pagepadding;
73 padding:0 0 @pagepadding;
74 height: auto;
74 height: auto;
75
75
76 &:after { //clearfix
76 &:after { //clearfix
77 content:"";
77 content:"";
78 clear:both;
78 clear:both;
79 width:100%;
79 width:100%;
80 display:block;
80 display:block;
81 }
81 }
82 }
82 }
83
83
84 .action-link{
84 .action-link{
85 margin-left: @padding;
85 margin-left: @padding;
86 padding-left: @padding;
86 padding-left: @padding;
87 border-left: @border-thickness solid @border-default-color;
87 border-left: @border-thickness solid @border-default-color;
88 }
88 }
89
89
90 input + .action-link, .action-link.first{
90 input + .action-link, .action-link.first{
91 border-left: none;
91 border-left: none;
92 }
92 }
93
93
94 .action-link.last{
94 .action-link.last{
95 margin-right: @padding;
95 margin-right: @padding;
96 padding-right: @padding;
96 padding-right: @padding;
97 }
97 }
98
98
99 .action-link.active,
99 .action-link.active,
100 .action-link.active a{
100 .action-link.active a{
101 color: @grey4;
101 color: @grey4;
102 }
102 }
103
103
104 .action-link.disabled {
104 .action-link.disabled {
105 color: @grey4;
105 color: @grey4;
106 cursor: inherit;
106 cursor: inherit;
107 }
107 }
108
108
109 .clipboard-action {
109 .clipboard-action {
110 cursor: pointer;
110 cursor: pointer;
111 color: @grey4;
111 color: @grey4;
112 margin-left: 5px;
112 margin-left: 5px;
113
113
114 &:hover {
114 &:hover {
115 color: @grey2;
115 color: @grey2;
116 }
116 }
117 }
117 }
118
118
119 ul.simple-list{
119 ul.simple-list{
120 list-style: none;
120 list-style: none;
121 margin: 0;
121 margin: 0;
122 padding: 0;
122 padding: 0;
123 }
123 }
124
124
125 .main-content {
125 .main-content {
126 padding-bottom: @pagepadding;
126 padding-bottom: @pagepadding;
127 }
127 }
128
128
129 .wide-mode-wrapper {
129 .wide-mode-wrapper {
130 max-width:4000px !important;
130 max-width:4000px !important;
131 }
131 }
132
132
133 .wrapper {
133 .wrapper {
134 position: relative;
134 position: relative;
135 max-width: @wrapper-maxwidth;
135 max-width: @wrapper-maxwidth;
136 margin: 0 auto;
136 margin: 0 auto;
137 }
137 }
138
138
139 #content {
139 #content {
140 clear: both;
140 clear: both;
141 padding: 0 @contentpadding;
141 padding: 0 @contentpadding;
142 }
142 }
143
143
144 .advanced-settings-fields{
144 .advanced-settings-fields{
145 input{
145 input{
146 margin-left: @textmargin;
146 margin-left: @textmargin;
147 margin-right: @padding/2;
147 margin-right: @padding/2;
148 }
148 }
149 }
149 }
150
150
151 .cs_files_title {
151 .cs_files_title {
152 margin: @pagepadding 0 0;
152 margin: @pagepadding 0 0;
153 }
153 }
154
154
155 input.inline[type="file"] {
155 input.inline[type="file"] {
156 display: inline;
156 display: inline;
157 }
157 }
158
158
159 .error_page {
159 .error_page {
160 margin: 10% auto;
160 margin: 10% auto;
161
161
162 h1 {
162 h1 {
163 color: @grey2;
163 color: @grey2;
164 }
164 }
165
165
166 .alert {
166 .alert {
167 margin: @padding 0;
167 margin: @padding 0;
168 }
168 }
169
169
170 .error-branding {
170 .error-branding {
171 color: @grey4;
171 color: @grey4;
172 font-weight: @text-semibold-weight;
172 font-weight: @text-semibold-weight;
173 font-family: @text-semibold;
173 font-family: @text-semibold;
174 }
174 }
175
175
176 .error_message {
176 .error_message {
177 font-family: @text-regular;
177 font-family: @text-regular;
178 }
178 }
179
179
180 .sidebar {
180 .sidebar {
181 min-height: 275px;
181 min-height: 275px;
182 margin: 0;
182 margin: 0;
183 padding: 0 0 @sidebarpadding @sidebarpadding;
183 padding: 0 0 @sidebarpadding @sidebarpadding;
184 border: none;
184 border: none;
185 }
185 }
186
186
187 .main-content {
187 .main-content {
188 position: relative;
188 position: relative;
189 margin: 0 @sidebarpadding @sidebarpadding;
189 margin: 0 @sidebarpadding @sidebarpadding;
190 padding: 0 0 0 @sidebarpadding;
190 padding: 0 0 0 @sidebarpadding;
191 border-left: @border-thickness solid @grey5;
191 border-left: @border-thickness solid @grey5;
192
192
193 @media (max-width:767px) {
193 @media (max-width:767px) {
194 clear: both;
194 clear: both;
195 width: 100%;
195 width: 100%;
196 margin: 0;
196 margin: 0;
197 border: none;
197 border: none;
198 }
198 }
199 }
199 }
200
200
201 .inner-column {
201 .inner-column {
202 float: left;
202 float: left;
203 width: 29.75%;
203 width: 29.75%;
204 min-height: 150px;
204 min-height: 150px;
205 margin: @sidebarpadding 2% 0 0;
205 margin: @sidebarpadding 2% 0 0;
206 padding: 0 2% 0 0;
206 padding: 0 2% 0 0;
207 border-right: @border-thickness solid @grey5;
207 border-right: @border-thickness solid @grey5;
208
208
209 @media (max-width:767px) {
209 @media (max-width:767px) {
210 clear: both;
210 clear: both;
211 width: 100%;
211 width: 100%;
212 border: none;
212 border: none;
213 }
213 }
214
214
215 ul {
215 ul {
216 padding-left: 1.25em;
216 padding-left: 1.25em;
217 }
217 }
218
218
219 &:last-child {
219 &:last-child {
220 margin: @sidebarpadding 0 0;
220 margin: @sidebarpadding 0 0;
221 border: none;
221 border: none;
222 }
222 }
223
223
224 h4 {
224 h4 {
225 margin: 0 0 @padding;
225 margin: 0 0 @padding;
226 font-weight: @text-semibold-weight;
226 font-weight: @text-semibold-weight;
227 font-family: @text-semibold;
227 font-family: @text-semibold;
228 }
228 }
229 }
229 }
230 }
230 }
231 .error-page-logo {
231 .error-page-logo {
232 width: 130px;
232 width: 130px;
233 height: 160px;
233 height: 160px;
234 }
234 }
235
235
236 // HEADER
236 // HEADER
237 .header {
237 .header {
238
238
239 // TODO: johbo: Fix login pages, so that they work without a min-height
239 // TODO: johbo: Fix login pages, so that they work without a min-height
240 // for the header and then remove the min-height. I chose a smaller value
240 // for the header and then remove the min-height. I chose a smaller value
241 // intentionally here to avoid rendering issues in the main navigation.
241 // intentionally here to avoid rendering issues in the main navigation.
242 min-height: 49px;
242 min-height: 49px;
243 min-width: 1024px;
243 min-width: 1024px;
244
244
245 position: relative;
245 position: relative;
246 vertical-align: bottom;
246 vertical-align: bottom;
247 padding: 0 @header-padding;
247 padding: 0 @header-padding;
248 background-color: @grey1;
248 background-color: @grey1;
249 color: @grey5;
249 color: @grey5;
250
250
251 .title {
251 .title {
252 overflow: visible;
252 overflow: visible;
253 }
253 }
254
254
255 &:before,
255 &:before,
256 &:after {
256 &:after {
257 content: "";
257 content: "";
258 clear: both;
258 clear: both;
259 width: 100%;
259 width: 100%;
260 }
260 }
261
261
262 // TODO: johbo: Avoids breaking "Repositories" chooser
262 // TODO: johbo: Avoids breaking "Repositories" chooser
263 .select2-container .select2-choice .select2-arrow {
263 .select2-container .select2-choice .select2-arrow {
264 display: none;
264 display: none;
265 }
265 }
266 }
266 }
267
267
268 #header-inner {
268 #header-inner {
269 &.title {
269 &.title {
270 margin: 0;
270 margin: 0;
271 }
271 }
272 &:before,
272 &:before,
273 &:after {
273 &:after {
274 content: "";
274 content: "";
275 clear: both;
275 clear: both;
276 }
276 }
277 }
277 }
278
278
279 // Gists
279 // Gists
280 #files_data {
280 #files_data {
281 clear: both; //for firefox
281 clear: both; //for firefox
282 padding-top: 10px;
282 padding-top: 10px;
283 }
283 }
284
284
285 #gistid {
285 #gistid {
286 margin-right: @padding;
286 margin-right: @padding;
287 }
287 }
288
288
289 // Global Settings Editor
289 // Global Settings Editor
290 .textarea.editor {
290 .textarea.editor {
291 float: left;
291 float: left;
292 position: relative;
292 position: relative;
293 max-width: @texteditor-width;
293 max-width: @texteditor-width;
294
294
295 select {
295 select {
296 position: absolute;
296 position: absolute;
297 top:10px;
297 top:10px;
298 right:0;
298 right:0;
299 }
299 }
300
300
301 .CodeMirror {
301 .CodeMirror {
302 margin: 0;
302 margin: 0;
303 }
303 }
304
304
305 .help-block {
305 .help-block {
306 margin: 0 0 @padding;
306 margin: 0 0 @padding;
307 padding:.5em;
307 padding:.5em;
308 background-color: @grey6;
308 background-color: @grey6;
309 &.pre-formatting {
309 &.pre-formatting {
310 white-space: pre;
310 white-space: pre;
311 }
311 }
312 }
312 }
313 }
313 }
314
314
315 ul.auth_plugins {
315 ul.auth_plugins {
316 margin: @padding 0 @padding @legend-width;
316 margin: @padding 0 @padding @legend-width;
317 padding: 0;
317 padding: 0;
318
318
319 li {
319 li {
320 margin-bottom: @padding;
320 margin-bottom: @padding;
321 line-height: 1em;
321 line-height: 1em;
322 list-style-type: none;
322 list-style-type: none;
323
323
324 .auth_buttons .btn {
324 .auth_buttons .btn {
325 margin-right: @padding;
325 margin-right: @padding;
326 }
326 }
327
327
328 }
328 }
329 }
329 }
330
330
331
331
332 // My Account PR list
332 // My Account PR list
333
333
334 #show_closed {
334 #show_closed {
335 margin: 0 1em 0 0;
335 margin: 0 1em 0 0;
336 }
336 }
337
337
338 #pull_request_list_table {
338 #pull_request_list_table {
339 .closed {
339 .closed {
340 background-color: @grey6;
340 background-color: @grey6;
341 }
341 }
342
342
343 .state-creating,
343 .state-creating,
344 .state-updating,
344 .state-updating,
345 .state-merging
345 .state-merging
346 {
346 {
347 background-color: @grey6;
347 background-color: @grey6;
348 }
348 }
349
349
350 .td-status {
350 .td-status {
351 padding-left: .5em;
351 padding-left: .5em;
352 }
352 }
353 .log-container .truncate {
353 .log-container .truncate {
354 height: 2.75em;
354 height: 2.75em;
355 white-space: pre-line;
355 white-space: pre-line;
356 }
356 }
357 table.rctable .user {
357 table.rctable .user {
358 padding-left: 0;
358 padding-left: 0;
359 }
359 }
360 table.rctable {
360 table.rctable {
361 td.td-description,
361 td.td-description,
362 .rc-user {
362 .rc-user {
363 min-width: auto;
363 min-width: auto;
364 }
364 }
365 }
365 }
366 }
366 }
367
367
368 // Pull Requests
368 // Pull Requests
369
369
370 .pullrequests_section_head {
370 .pullrequests_section_head {
371 display: block;
371 display: block;
372 clear: both;
372 clear: both;
373 margin: @padding 0;
373 margin: @padding 0;
374 font-weight: @text-bold-weight;
374 font-weight: @text-bold-weight;
375 font-family: @text-bold;
375 font-family: @text-bold;
376 }
376 }
377
377
378 .pr-origininfo, .pr-targetinfo {
378 .pr-origininfo, .pr-targetinfo {
379 position: relative;
379 position: relative;
380
380
381 .tag {
381 .tag {
382 display: inline-block;
382 display: inline-block;
383 margin: 0 1em .5em 0;
383 margin: 0 1em .5em 0;
384 }
384 }
385
385
386 .clone-url {
386 .clone-url {
387 display: inline-block;
387 display: inline-block;
388 margin: 0 0 .5em 0;
388 margin: 0 0 .5em 0;
389 padding: 0;
389 padding: 0;
390 line-height: 1.2em;
390 line-height: 1.2em;
391 }
391 }
392 }
392 }
393
393
394 .pr-mergeinfo {
394 .pr-mergeinfo {
395 min-width: 95% !important;
395 min-width: 95% !important;
396 padding: 0 !important;
396 padding: 0 !important;
397 border: 0;
397 border: 0;
398 }
398 }
399 .pr-mergeinfo-copy {
399 .pr-mergeinfo-copy {
400 padding: 0 0;
400 padding: 0 0;
401 }
401 }
402
402
403 .pr-pullinfo {
403 .pr-pullinfo {
404 min-width: 95% !important;
404 min-width: 95% !important;
405 padding: 0 !important;
405 padding: 0 !important;
406 border: 0;
406 border: 0;
407 }
407 }
408 .pr-pullinfo-copy {
408 .pr-pullinfo-copy {
409 padding: 0 0;
409 padding: 0 0;
410 }
410 }
411
411
412
412
413 #pr-title-input {
413 #pr-title-input {
414 width: 72%;
414 width: 72%;
415 font-size: 1em;
415 font-size: 1em;
416 margin: 0;
416 margin: 0;
417 padding: 0 0 0 @padding/4;
417 padding: 0 0 0 @padding/4;
418 line-height: 1.7em;
418 line-height: 1.7em;
419 color: @text-color;
419 color: @text-color;
420 letter-spacing: .02em;
420 letter-spacing: .02em;
421 font-weight: @text-bold-weight;
421 font-weight: @text-bold-weight;
422 font-family: @text-bold;
422 font-family: @text-bold;
423 }
423 }
424
424
425 #pullrequest_title {
425 #pullrequest_title {
426 width: 100%;
426 width: 100%;
427 box-sizing: border-box;
427 box-sizing: border-box;
428 }
428 }
429
429
430 #pr_open_message {
430 #pr_open_message {
431 border: @border-thickness solid #fff;
431 border: @border-thickness solid #fff;
432 border-radius: @border-radius;
432 border-radius: @border-radius;
433 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
433 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
434 text-align: left;
434 text-align: left;
435 overflow: hidden;
435 overflow: hidden;
436 }
436 }
437
437
438 .pr-submit-button {
438 .pr-submit-button {
439 float: right;
439 float: right;
440 margin: 0 0 0 5px;
440 margin: 0 0 0 5px;
441 }
441 }
442
442
443 .pr-spacing-container {
443 .pr-spacing-container {
444 padding: 20px;
444 padding: 20px;
445 clear: both
445 clear: both
446 }
446 }
447
447
448 #pr-description-input {
448 #pr-description-input {
449 margin-bottom: 0;
449 margin-bottom: 0;
450 }
450 }
451
451
452 .pr-description-label {
452 .pr-description-label {
453 vertical-align: top;
453 vertical-align: top;
454 }
454 }
455
455
456 .perms_section_head {
456 .perms_section_head {
457 min-width: 625px;
457 min-width: 625px;
458
458
459 h2 {
459 h2 {
460 margin-bottom: 0;
460 margin-bottom: 0;
461 }
461 }
462
462
463 .label-checkbox {
463 .label-checkbox {
464 float: left;
464 float: left;
465 }
465 }
466
466
467 &.field {
467 &.field {
468 margin: @space 0 @padding;
468 margin: @space 0 @padding;
469 }
469 }
470
470
471 &:first-child.field {
471 &:first-child.field {
472 margin-top: 0;
472 margin-top: 0;
473
473
474 .label {
474 .label {
475 margin-top: 0;
475 margin-top: 0;
476 padding-top: 0;
476 padding-top: 0;
477 }
477 }
478
478
479 .radios {
479 .radios {
480 padding-top: 0;
480 padding-top: 0;
481 }
481 }
482 }
482 }
483
483
484 .radios {
484 .radios {
485 position: relative;
485 position: relative;
486 width: 505px;
486 width: 505px;
487 }
487 }
488 }
488 }
489
489
490 //--- MODULES ------------------//
490 //--- MODULES ------------------//
491
491
492
492
493 // Server Announcement
493 // Server Announcement
494 #server-announcement {
494 #server-announcement {
495 width: 95%;
495 width: 95%;
496 margin: @padding auto;
496 margin: @padding auto;
497 padding: @padding;
497 padding: @padding;
498 border-width: 2px;
498 border-width: 2px;
499 border-style: solid;
499 border-style: solid;
500 .border-radius(2px);
500 .border-radius(2px);
501 font-weight: @text-bold-weight;
501 font-weight: @text-bold-weight;
502 font-family: @text-bold;
502 font-family: @text-bold;
503
503
504 &.info { border-color: @alert4; background-color: @alert4-inner; }
504 &.info { border-color: @alert4; background-color: @alert4-inner; }
505 &.warning { border-color: @alert3; background-color: @alert3-inner; }
505 &.warning { border-color: @alert3; background-color: @alert3-inner; }
506 &.error { border-color: @alert2; background-color: @alert2-inner; }
506 &.error { border-color: @alert2; background-color: @alert2-inner; }
507 &.success { border-color: @alert1; background-color: @alert1-inner; }
507 &.success { border-color: @alert1; background-color: @alert1-inner; }
508 &.neutral { border-color: @grey3; background-color: @grey6; }
508 &.neutral { border-color: @grey3; background-color: @grey6; }
509 }
509 }
510
510
511 // Fixed Sidebar Column
511 // Fixed Sidebar Column
512 .sidebar-col-wrapper {
512 .sidebar-col-wrapper {
513 padding-left: @sidebar-all-width;
513 padding-left: @sidebar-all-width;
514
514
515 .sidebar {
515 .sidebar {
516 width: @sidebar-width;
516 width: @sidebar-width;
517 margin-left: -@sidebar-all-width;
517 margin-left: -@sidebar-all-width;
518 }
518 }
519 }
519 }
520
520
521 .sidebar-col-wrapper.scw-small {
521 .sidebar-col-wrapper.scw-small {
522 padding-left: @sidebar-small-all-width;
522 padding-left: @sidebar-small-all-width;
523
523
524 .sidebar {
524 .sidebar {
525 width: @sidebar-small-width;
525 width: @sidebar-small-width;
526 margin-left: -@sidebar-small-all-width;
526 margin-left: -@sidebar-small-all-width;
527 }
527 }
528 }
528 }
529
529
530
530
531 // FOOTER
531 // FOOTER
532 #footer {
532 #footer {
533 padding: 0;
533 padding: 0;
534 text-align: center;
534 text-align: center;
535 vertical-align: middle;
535 vertical-align: middle;
536 color: @grey2;
536 color: @grey2;
537 font-size: 11px;
537 font-size: 11px;
538
538
539 p {
539 p {
540 margin: 0;
540 margin: 0;
541 padding: 1em;
541 padding: 1em;
542 line-height: 1em;
542 line-height: 1em;
543 }
543 }
544
544
545 .server-instance { //server instance
545 .server-instance { //server instance
546 display: none;
546 display: none;
547 }
547 }
548
548
549 .title {
549 .title {
550 float: none;
550 float: none;
551 margin: 0 auto;
551 margin: 0 auto;
552 }
552 }
553 }
553 }
554
554
555 button.close {
555 button.close {
556 padding: 0;
556 padding: 0;
557 cursor: pointer;
557 cursor: pointer;
558 background: transparent;
558 background: transparent;
559 border: 0;
559 border: 0;
560 .box-shadow(none);
560 .box-shadow(none);
561 -webkit-appearance: none;
561 -webkit-appearance: none;
562 }
562 }
563
563
564 .close {
564 .close {
565 float: right;
565 float: right;
566 font-size: 21px;
566 font-size: 21px;
567 font-family: @text-bootstrap;
567 font-family: @text-bootstrap;
568 line-height: 1em;
568 line-height: 1em;
569 font-weight: bold;
569 font-weight: bold;
570 color: @grey2;
570 color: @grey2;
571
571
572 &:hover,
572 &:hover,
573 &:focus {
573 &:focus {
574 color: @grey1;
574 color: @grey1;
575 text-decoration: none;
575 text-decoration: none;
576 cursor: pointer;
576 cursor: pointer;
577 }
577 }
578 }
578 }
579
579
580 // GRID
580 // GRID
581 .sorting,
581 .sorting,
582 .sorting_desc,
582 .sorting_desc,
583 .sorting_asc {
583 .sorting_asc {
584 cursor: pointer;
584 cursor: pointer;
585 }
585 }
586 .sorting_desc:after {
586 .sorting_desc:after {
587 content: "\00A0\25B2";
587 content: "\00A0\25B2";
588 font-size: .75em;
588 font-size: .75em;
589 }
589 }
590 .sorting_asc:after {
590 .sorting_asc:after {
591 content: "\00A0\25BC";
591 content: "\00A0\25BC";
592 font-size: .68em;
592 font-size: .68em;
593 }
593 }
594
594
595
595
596 .user_auth_tokens {
596 .user_auth_tokens {
597
597
598 &.truncate {
598 &.truncate {
599 white-space: nowrap;
599 white-space: nowrap;
600 overflow: hidden;
600 overflow: hidden;
601 text-overflow: ellipsis;
601 text-overflow: ellipsis;
602 }
602 }
603
603
604 .fields .field .input {
604 .fields .field .input {
605 margin: 0;
605 margin: 0;
606 }
606 }
607
607
608 input#description {
608 input#description {
609 width: 100px;
609 width: 100px;
610 margin: 0;
610 margin: 0;
611 }
611 }
612
612
613 .drop-menu {
613 .drop-menu {
614 // TODO: johbo: Remove this, should work out of the box when
614 // TODO: johbo: Remove this, should work out of the box when
615 // having multiple inputs inline
615 // having multiple inputs inline
616 margin: 0 0 0 5px;
616 margin: 0 0 0 5px;
617 }
617 }
618 }
618 }
619 #user_list_table {
619 #user_list_table {
620 .closed {
620 .closed {
621 background-color: @grey6;
621 background-color: @grey6;
622 }
622 }
623 }
623 }
624
624
625
625
626 input, textarea {
626 input, textarea {
627 &.disabled {
627 &.disabled {
628 opacity: .5;
628 opacity: .5;
629 }
629 }
630
630
631 &:hover {
631 &:hover {
632 border-color: @grey3;
632 border-color: @grey3;
633 box-shadow: @button-shadow;
633 box-shadow: @button-shadow;
634 }
634 }
635
635
636 &:focus {
636 &:focus {
637 border-color: @rcblue;
637 border-color: @rcblue;
638 box-shadow: @button-shadow;
638 box-shadow: @button-shadow;
639 }
639 }
640 }
640 }
641
641
642 // remove extra padding in firefox
642 // remove extra padding in firefox
643 input::-moz-focus-inner { border:0; padding:0 }
643 input::-moz-focus-inner { border:0; padding:0 }
644
644
645 .adjacent input {
645 .adjacent input {
646 margin-bottom: @padding;
646 margin-bottom: @padding;
647 }
647 }
648
648
649 .permissions_boxes {
649 .permissions_boxes {
650 display: block;
650 display: block;
651 }
651 }
652
652
653 //FORMS
653 //FORMS
654
654
655 .medium-inline,
655 .medium-inline,
656 input#description.medium-inline {
656 input#description.medium-inline {
657 display: inline;
657 display: inline;
658 width: @medium-inline-input-width;
658 width: @medium-inline-input-width;
659 min-width: 100px;
659 min-width: 100px;
660 }
660 }
661
661
662 select {
662 select {
663 //reset
663 //reset
664 -webkit-appearance: none;
664 -webkit-appearance: none;
665 -moz-appearance: none;
665 -moz-appearance: none;
666
666
667 display: inline-block;
667 display: inline-block;
668 height: 28px;
668 height: 28px;
669 width: auto;
669 width: auto;
670 margin: 0 @padding @padding 0;
670 margin: 0 @padding @padding 0;
671 padding: 0 18px 0 8px;
671 padding: 0 18px 0 8px;
672 line-height:1em;
672 line-height:1em;
673 font-size: @basefontsize;
673 font-size: @basefontsize;
674 border: @border-thickness solid @grey5;
674 border: @border-thickness solid @grey5;
675 border-radius: @border-radius;
675 border-radius: @border-radius;
676 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
676 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
677 color: @grey4;
677 color: @grey4;
678 box-shadow: @button-shadow;
678 box-shadow: @button-shadow;
679
679
680 &:after {
680 &:after {
681 content: "\00A0\25BE";
681 content: "\00A0\25BE";
682 }
682 }
683
683
684 &:focus, &:hover {
684 &:focus, &:hover {
685 outline: none;
685 outline: none;
686 border-color: @grey4;
686 border-color: @grey4;
687 color: @rcdarkblue;
687 color: @rcdarkblue;
688 }
688 }
689 }
689 }
690
690
691 option {
691 option {
692 &:focus {
692 &:focus {
693 outline: none;
693 outline: none;
694 }
694 }
695 }
695 }
696
696
697 input,
697 input,
698 textarea {
698 textarea {
699 padding: @input-padding;
699 padding: @input-padding;
700 border: @input-border-thickness solid @border-highlight-color;
700 border: @input-border-thickness solid @border-highlight-color;
701 .border-radius (@border-radius);
701 .border-radius (@border-radius);
702 font-family: @text-light;
702 font-family: @text-light;
703 font-size: @basefontsize;
703 font-size: @basefontsize;
704
704
705 &.input-sm {
705 &.input-sm {
706 padding: 5px;
706 padding: 5px;
707 }
707 }
708
708
709 &#description {
709 &#description {
710 min-width: @input-description-minwidth;
710 min-width: @input-description-minwidth;
711 min-height: 1em;
711 min-height: 1em;
712 padding: 10px;
712 padding: 10px;
713 }
713 }
714 }
714 }
715
715
716 .field-sm {
716 .field-sm {
717 input,
717 input,
718 textarea {
718 textarea {
719 padding: 5px;
719 padding: 5px;
720 }
720 }
721 }
721 }
722
722
723 textarea {
723 textarea {
724 display: block;
724 display: block;
725 clear: both;
725 clear: both;
726 width: 100%;
726 width: 100%;
727 min-height: 100px;
727 min-height: 100px;
728 margin-bottom: @padding;
728 margin-bottom: @padding;
729 .box-sizing(border-box);
729 .box-sizing(border-box);
730 overflow: auto;
730 overflow: auto;
731 }
731 }
732
732
733 label {
733 label {
734 font-family: @text-light;
734 font-family: @text-light;
735 }
735 }
736
736
737 // GRAVATARS
737 // GRAVATARS
738 // centers gravatar on username to the right
738 // centers gravatar on username to the right
739
739
740 .gravatar {
740 .gravatar {
741 display: inline;
741 display: inline;
742 min-width: 16px;
742 min-width: 16px;
743 min-height: 16px;
743 min-height: 16px;
744 margin: -5px 0;
744 margin: -5px 0;
745 padding: 0;
745 padding: 0;
746 line-height: 1em;
746 line-height: 1em;
747 box-sizing: content-box;
747 box-sizing: content-box;
748 border-radius: 50%;
748 border-radius: 50%;
749
749
750 &.gravatar-large {
750 &.gravatar-large {
751 margin: -0.5em .25em -0.5em 0;
751 margin: -0.5em .25em -0.5em 0;
752 }
752 }
753
753
754 & + .user {
754 & + .user {
755 display: inline;
755 display: inline;
756 margin: 0;
756 margin: 0;
757 padding: 0 0 0 .17em;
757 padding: 0 0 0 .17em;
758 line-height: 1em;
758 line-height: 1em;
759 }
759 }
760 }
760 }
761
761
762 .user-inline-data {
762 .user-inline-data {
763 display: inline-block;
763 display: inline-block;
764 float: left;
764 float: left;
765 padding-left: .5em;
765 padding-left: .5em;
766 line-height: 1.3em;
766 line-height: 1.3em;
767 }
767 }
768
768
769 .rc-user { // gravatar + user wrapper
769 .rc-user { // gravatar + user wrapper
770 float: left;
770 float: left;
771 position: relative;
771 position: relative;
772 min-width: 100px;
772 min-width: 100px;
773 max-width: 200px;
773 max-width: 200px;
774 min-height: (@gravatar-size + @border-thickness * 2); // account for border
774 min-height: (@gravatar-size + @border-thickness * 2); // account for border
775 display: block;
775 display: block;
776 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
776 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
777
777
778
778
779 .gravatar {
779 .gravatar {
780 display: block;
780 display: block;
781 position: absolute;
781 position: absolute;
782 top: 0;
782 top: 0;
783 left: 0;
783 left: 0;
784 min-width: @gravatar-size;
784 min-width: @gravatar-size;
785 min-height: @gravatar-size;
785 min-height: @gravatar-size;
786 margin: 0;
786 margin: 0;
787 }
787 }
788
788
789 .user {
789 .user {
790 display: block;
790 display: block;
791 max-width: 175px;
791 max-width: 175px;
792 padding-top: 2px;
792 padding-top: 2px;
793 overflow: hidden;
793 overflow: hidden;
794 text-overflow: ellipsis;
794 text-overflow: ellipsis;
795 }
795 }
796 }
796 }
797
797
798 .gist-gravatar,
798 .gist-gravatar,
799 .journal_container {
799 .journal_container {
800 .gravatar-large {
800 .gravatar-large {
801 margin: 0 .5em -10px 0;
801 margin: 0 .5em -10px 0;
802 }
802 }
803 }
803 }
804
804
805
805
806 // ADMIN SETTINGS
806 // ADMIN SETTINGS
807
807
808 // Tag Patterns
808 // Tag Patterns
809 .tag_patterns {
809 .tag_patterns {
810 .tag_input {
810 .tag_input {
811 margin-bottom: @padding;
811 margin-bottom: @padding;
812 }
812 }
813 }
813 }
814
814
815 .locked_input {
815 .locked_input {
816 position: relative;
816 position: relative;
817
817
818 input {
818 input {
819 display: inline;
819 display: inline;
820 margin: 3px 5px 0px 0px;
820 margin: 3px 5px 0px 0px;
821 }
821 }
822
822
823 br {
823 br {
824 display: none;
824 display: none;
825 }
825 }
826
826
827 .error-message {
827 .error-message {
828 float: left;
828 float: left;
829 width: 100%;
829 width: 100%;
830 }
830 }
831
831
832 .lock_input_button {
832 .lock_input_button {
833 display: inline;
833 display: inline;
834 }
834 }
835
835
836 .help-block {
836 .help-block {
837 clear: both;
837 clear: both;
838 }
838 }
839 }
839 }
840
840
841 // Notifications
841 // Notifications
842
842
843 .notifications_buttons {
843 .notifications_buttons {
844 margin: 0 0 @space 0;
844 margin: 0 0 @space 0;
845 padding: 0;
845 padding: 0;
846
846
847 .btn {
847 .btn {
848 display: inline-block;
848 display: inline-block;
849 }
849 }
850 }
850 }
851
851
852 .notification-list {
852 .notification-list {
853
853
854 div {
854 div {
855 display: inline-block;
855 display: inline-block;
856 vertical-align: middle;
856 vertical-align: middle;
857 }
857 }
858
858
859 .container {
859 .container {
860 display: block;
860 display: block;
861 margin: 0 0 @padding 0;
861 margin: 0 0 @padding 0;
862 }
862 }
863
863
864 .delete-notifications {
864 .delete-notifications {
865 margin-left: @padding;
865 margin-left: @padding;
866 text-align: right;
866 text-align: right;
867 cursor: pointer;
867 cursor: pointer;
868 }
868 }
869
869
870 .read-notifications {
870 .read-notifications {
871 margin-left: @padding/2;
871 margin-left: @padding/2;
872 text-align: right;
872 text-align: right;
873 width: 35px;
873 width: 35px;
874 cursor: pointer;
874 cursor: pointer;
875 }
875 }
876
876
877 .icon-minus-sign {
877 .icon-minus-sign {
878 color: @alert2;
878 color: @alert2;
879 }
879 }
880
880
881 .icon-ok-sign {
881 .icon-ok-sign {
882 color: @alert1;
882 color: @alert1;
883 }
883 }
884 }
884 }
885
885
886 .user_settings {
886 .user_settings {
887 float: left;
887 float: left;
888 clear: both;
888 clear: both;
889 display: block;
889 display: block;
890 width: 100%;
890 width: 100%;
891
891
892 .gravatar_box {
892 .gravatar_box {
893 margin-bottom: @padding;
893 margin-bottom: @padding;
894
894
895 &:after {
895 &:after {
896 content: " ";
896 content: " ";
897 clear: both;
897 clear: both;
898 width: 100%;
898 width: 100%;
899 }
899 }
900 }
900 }
901
901
902 .fields .field {
902 .fields .field {
903 clear: both;
903 clear: both;
904 }
904 }
905 }
905 }
906
906
907 .advanced_settings {
907 .advanced_settings {
908 margin-bottom: @space;
908 margin-bottom: @space;
909
909
910 .help-block {
910 .help-block {
911 margin-left: 0;
911 margin-left: 0;
912 }
912 }
913
913
914 button + .help-block {
914 button + .help-block {
915 margin-top: @padding;
915 margin-top: @padding;
916 }
916 }
917 }
917 }
918
918
919 // admin settings radio buttons and labels
919 // admin settings radio buttons and labels
920 .label-2 {
920 .label-2 {
921 float: left;
921 float: left;
922 width: @label2-width;
922 width: @label2-width;
923
923
924 label {
924 label {
925 color: @grey1;
925 color: @grey1;
926 }
926 }
927 }
927 }
928 .checkboxes {
928 .checkboxes {
929 float: left;
929 float: left;
930 width: @checkboxes-width;
930 width: @checkboxes-width;
931 margin-bottom: @padding;
931 margin-bottom: @padding;
932
932
933 .checkbox {
933 .checkbox {
934 width: 100%;
934 width: 100%;
935
935
936 label {
936 label {
937 margin: 0;
937 margin: 0;
938 padding: 0;
938 padding: 0;
939 }
939 }
940 }
940 }
941
941
942 .checkbox + .checkbox {
942 .checkbox + .checkbox {
943 display: inline-block;
943 display: inline-block;
944 }
944 }
945
945
946 label {
946 label {
947 margin-right: 1em;
947 margin-right: 1em;
948 }
948 }
949 }
949 }
950
950
951 // CHANGELOG
951 // CHANGELOG
952 .container_header {
952 .container_header {
953 float: left;
953 float: left;
954 display: block;
954 display: block;
955 width: 100%;
955 width: 100%;
956 margin: @padding 0 @padding;
956 margin: @padding 0 @padding;
957
957
958 #filter_changelog {
958 #filter_changelog {
959 float: left;
959 float: left;
960 margin-right: @padding;
960 margin-right: @padding;
961 }
961 }
962
962
963 .breadcrumbs_light {
963 .breadcrumbs_light {
964 display: inline-block;
964 display: inline-block;
965 }
965 }
966 }
966 }
967
967
968 .info_box {
968 .info_box {
969 float: right;
969 float: right;
970 }
970 }
971
971
972
972
973
973
974 #graph_content{
974 #graph_content{
975
975
976 // adjust for table headers so that graph renders properly
976 // adjust for table headers so that graph renders properly
977 // #graph_nodes padding - table cell padding
977 // #graph_nodes padding - table cell padding
978 padding-top: (@space - (@basefontsize * 2.4));
978 padding-top: (@space - (@basefontsize * 2.4));
979
979
980 &.graph_full_width {
980 &.graph_full_width {
981 width: 100%;
981 width: 100%;
982 max-width: 100%;
982 max-width: 100%;
983 }
983 }
984 }
984 }
985
985
986 #graph {
986 #graph {
987
987
988 .pagination-left {
988 .pagination-left {
989 float: left;
989 float: left;
990 clear: both;
990 clear: both;
991 }
991 }
992
992
993 .log-container {
993 .log-container {
994 max-width: 345px;
994 max-width: 345px;
995
995
996 .message{
996 .message{
997 max-width: 340px;
997 max-width: 340px;
998 }
998 }
999 }
999 }
1000
1000
1001 .graph-col-wrapper {
1001 .graph-col-wrapper {
1002
1002
1003 #graph_nodes {
1003 #graph_nodes {
1004 width: 100px;
1004 width: 100px;
1005 position: absolute;
1005 position: absolute;
1006 left: 70px;
1006 left: 70px;
1007 z-index: -1;
1007 z-index: -1;
1008 }
1008 }
1009 }
1009 }
1010
1010
1011 .load-more-commits {
1011 .load-more-commits {
1012 text-align: center;
1012 text-align: center;
1013 }
1013 }
1014 .load-more-commits:hover {
1014 .load-more-commits:hover {
1015 background-color: @grey7;
1015 background-color: @grey7;
1016 }
1016 }
1017 .load-more-commits {
1017 .load-more-commits {
1018 a {
1018 a {
1019 display: block;
1019 display: block;
1020 }
1020 }
1021 }
1021 }
1022 }
1022 }
1023
1023
1024 .obsolete-toggle {
1024 .obsolete-toggle {
1025 line-height: 30px;
1025 line-height: 30px;
1026 margin-left: -15px;
1026 margin-left: -15px;
1027 }
1027 }
1028
1028
1029 #rev_range_container, #rev_range_clear, #rev_range_more {
1029 #rev_range_container, #rev_range_clear, #rev_range_more {
1030 margin-top: -5px;
1030 margin-top: -5px;
1031 margin-bottom: -5px;
1031 margin-bottom: -5px;
1032 }
1032 }
1033
1033
1034 #filter_changelog {
1034 #filter_changelog {
1035 float: left;
1035 float: left;
1036 }
1036 }
1037
1037
1038
1038
1039 //--- THEME ------------------//
1039 //--- THEME ------------------//
1040
1040
1041 #logo {
1041 #logo {
1042 float: left;
1042 float: left;
1043 margin: 9px 0 0 0;
1043 margin: 9px 0 0 0;
1044
1044
1045 .header {
1045 .header {
1046 background-color: transparent;
1046 background-color: transparent;
1047 }
1047 }
1048
1048
1049 a {
1049 a {
1050 display: inline-block;
1050 display: inline-block;
1051 }
1051 }
1052
1052
1053 img {
1053 img {
1054 height:30px;
1054 height:30px;
1055 }
1055 }
1056 }
1056 }
1057
1057
1058 .logo-wrapper {
1058 .logo-wrapper {
1059 float:left;
1059 float:left;
1060 }
1060 }
1061
1061
1062 .branding {
1062 .branding {
1063 float: left;
1063 float: left;
1064 padding: 9px 2px;
1064 padding: 9px 2px;
1065 line-height: 1em;
1065 line-height: 1em;
1066 font-size: @navigation-fontsize;
1066 font-size: @navigation-fontsize;
1067
1067
1068 a {
1068 a {
1069 color: @grey5
1069 color: @grey5
1070 }
1070 }
1071 @media screen and (max-width: 1200px) {
1071 @media screen and (max-width: 1200px) {
1072 display: none;
1072 display: none;
1073 }
1073 }
1074 }
1074 }
1075
1075
1076 img {
1076 img {
1077 border: none;
1077 border: none;
1078 outline: none;
1078 outline: none;
1079 }
1079 }
1080 user-profile-header
1080 user-profile-header
1081 label {
1081 label {
1082
1082
1083 input[type="checkbox"] {
1083 input[type="checkbox"] {
1084 margin-right: 1em;
1084 margin-right: 1em;
1085 }
1085 }
1086 input[type="radio"] {
1086 input[type="radio"] {
1087 margin-right: 1em;
1087 margin-right: 1em;
1088 }
1088 }
1089 }
1089 }
1090
1090
1091 .review-status {
1091 .review-status {
1092 &.under_review {
1092 &.under_review {
1093 color: @alert3;
1093 color: @alert3;
1094 }
1094 }
1095 &.approved {
1095 &.approved {
1096 color: @alert1;
1096 color: @alert1;
1097 }
1097 }
1098 &.rejected,
1098 &.rejected,
1099 &.forced_closed{
1099 &.forced_closed{
1100 color: @alert2;
1100 color: @alert2;
1101 }
1101 }
1102 &.not_reviewed {
1102 &.not_reviewed {
1103 color: @grey5;
1103 color: @grey5;
1104 }
1104 }
1105 }
1105 }
1106
1106
1107 .review-status-under_review {
1107 .review-status-under_review {
1108 color: @alert3;
1108 color: @alert3;
1109 }
1109 }
1110 .status-tag-under_review {
1110 .status-tag-under_review {
1111 border-color: @alert3;
1111 border-color: @alert3;
1112 }
1112 }
1113
1113
1114 .review-status-approved {
1114 .review-status-approved {
1115 color: @alert1;
1115 color: @alert1;
1116 }
1116 }
1117 .status-tag-approved {
1117 .status-tag-approved {
1118 border-color: @alert1;
1118 border-color: @alert1;
1119 }
1119 }
1120
1120
1121 .review-status-rejected,
1121 .review-status-rejected,
1122 .review-status-forced_closed {
1122 .review-status-forced_closed {
1123 color: @alert2;
1123 color: @alert2;
1124 }
1124 }
1125 .status-tag-rejected,
1125 .status-tag-rejected,
1126 .status-tag-forced_closed {
1126 .status-tag-forced_closed {
1127 border-color: @alert2;
1127 border-color: @alert2;
1128 }
1128 }
1129
1129
1130 .review-status-not_reviewed {
1130 .review-status-not_reviewed {
1131 color: @grey5;
1131 color: @grey5;
1132 }
1132 }
1133 .status-tag-not_reviewed {
1133 .status-tag-not_reviewed {
1134 border-color: @grey5;
1134 border-color: @grey5;
1135 }
1135 }
1136
1136
1137 .test_pattern_preview {
1137 .test_pattern_preview {
1138 margin: @space 0;
1138 margin: @space 0;
1139
1139
1140 p {
1140 p {
1141 margin-bottom: 0;
1141 margin-bottom: 0;
1142 border-bottom: @border-thickness solid @border-default-color;
1142 border-bottom: @border-thickness solid @border-default-color;
1143 color: @grey3;
1143 color: @grey3;
1144 }
1144 }
1145
1145
1146 .btn {
1146 .btn {
1147 margin-bottom: @padding;
1147 margin-bottom: @padding;
1148 }
1148 }
1149 }
1149 }
1150 #test_pattern_result {
1150 #test_pattern_result {
1151 display: none;
1151 display: none;
1152 &:extend(pre);
1152 &:extend(pre);
1153 padding: .9em;
1153 padding: .9em;
1154 color: @grey3;
1154 color: @grey3;
1155 background-color: @grey7;
1155 background-color: @grey7;
1156 border-right: @border-thickness solid @border-default-color;
1156 border-right: @border-thickness solid @border-default-color;
1157 border-bottom: @border-thickness solid @border-default-color;
1157 border-bottom: @border-thickness solid @border-default-color;
1158 border-left: @border-thickness solid @border-default-color;
1158 border-left: @border-thickness solid @border-default-color;
1159 }
1159 }
1160
1160
1161 #repo_vcs_settings {
1161 #repo_vcs_settings {
1162 #inherit_overlay_vcs_default {
1162 #inherit_overlay_vcs_default {
1163 display: none;
1163 display: none;
1164 }
1164 }
1165 #inherit_overlay_vcs_custom {
1165 #inherit_overlay_vcs_custom {
1166 display: custom;
1166 display: custom;
1167 }
1167 }
1168 &.inherited {
1168 &.inherited {
1169 #inherit_overlay_vcs_default {
1169 #inherit_overlay_vcs_default {
1170 display: block;
1170 display: block;
1171 }
1171 }
1172 #inherit_overlay_vcs_custom {
1172 #inherit_overlay_vcs_custom {
1173 display: none;
1173 display: none;
1174 }
1174 }
1175 }
1175 }
1176 }
1176 }
1177
1177
1178 .issue-tracker-link {
1178 .issue-tracker-link {
1179 color: @rcblue;
1179 color: @rcblue;
1180 }
1180 }
1181
1181
1182 // Issue Tracker Table Show/Hide
1182 // Issue Tracker Table Show/Hide
1183 #repo_issue_tracker {
1183 #repo_issue_tracker {
1184 #inherit_overlay {
1184 #inherit_overlay {
1185 display: none;
1185 display: none;
1186 }
1186 }
1187 #custom_overlay {
1187 #custom_overlay {
1188 display: custom;
1188 display: custom;
1189 }
1189 }
1190 &.inherited {
1190 &.inherited {
1191 #inherit_overlay {
1191 #inherit_overlay {
1192 display: block;
1192 display: block;
1193 }
1193 }
1194 #custom_overlay {
1194 #custom_overlay {
1195 display: none;
1195 display: none;
1196 }
1196 }
1197 }
1197 }
1198 }
1198 }
1199 table.issuetracker {
1199 table.issuetracker {
1200 &.readonly {
1200 &.readonly {
1201 tr, td {
1201 tr, td {
1202 color: @grey3;
1202 color: @grey3;
1203 }
1203 }
1204 }
1204 }
1205 .edit {
1205 .edit {
1206 display: none;
1206 display: none;
1207 }
1207 }
1208 .editopen {
1208 .editopen {
1209 .edit {
1209 .edit {
1210 display: inline;
1210 display: inline;
1211 }
1211 }
1212 .entry {
1212 .entry {
1213 display: none;
1213 display: none;
1214 }
1214 }
1215 }
1215 }
1216 tr td.td-action {
1216 tr td.td-action {
1217 min-width: 117px;
1217 min-width: 117px;
1218 }
1218 }
1219 td input {
1219 td input {
1220 max-width: none;
1220 max-width: none;
1221 min-width: 30px;
1221 min-width: 30px;
1222 width: 80%;
1222 width: 80%;
1223 }
1223 }
1224 .issuetracker_pref input {
1224 .issuetracker_pref input {
1225 width: 40%;
1225 width: 40%;
1226 }
1226 }
1227 input.edit_issuetracker_update {
1227 input.edit_issuetracker_update {
1228 margin-right: 0;
1228 margin-right: 0;
1229 width: auto;
1229 width: auto;
1230 }
1230 }
1231 }
1231 }
1232
1232
1233 table.integrations {
1233 table.integrations {
1234 .td-icon {
1234 .td-icon {
1235 width: 20px;
1235 width: 20px;
1236 .integration-icon {
1236 .integration-icon {
1237 height: 20px;
1237 height: 20px;
1238 width: 20px;
1238 width: 20px;
1239 }
1239 }
1240 }
1240 }
1241 }
1241 }
1242
1242
1243 .integrations {
1243 .integrations {
1244 a.integration-box {
1244 a.integration-box {
1245 color: @text-color;
1245 color: @text-color;
1246 &:hover {
1246 &:hover {
1247 .panel {
1247 .panel {
1248 background: #fbfbfb;
1248 background: #fbfbfb;
1249 }
1249 }
1250 }
1250 }
1251 .integration-icon {
1251 .integration-icon {
1252 width: 30px;
1252 width: 30px;
1253 height: 30px;
1253 height: 30px;
1254 margin-right: 20px;
1254 margin-right: 20px;
1255 float: left;
1255 float: left;
1256 }
1256 }
1257
1257
1258 .panel-body {
1258 .panel-body {
1259 padding: 10px;
1259 padding: 10px;
1260 }
1260 }
1261 .panel {
1261 .panel {
1262 margin-bottom: 10px;
1262 margin-bottom: 10px;
1263 }
1263 }
1264 h2 {
1264 h2 {
1265 display: inline-block;
1265 display: inline-block;
1266 margin: 0;
1266 margin: 0;
1267 min-width: 140px;
1267 min-width: 140px;
1268 }
1268 }
1269 }
1269 }
1270 a.integration-box.dummy-integration {
1270 a.integration-box.dummy-integration {
1271 color: @grey4
1271 color: @grey4
1272 }
1272 }
1273 }
1273 }
1274
1274
1275 //Permissions Settings
1275 //Permissions Settings
1276 #add_perm {
1276 #add_perm {
1277 margin: 0 0 @padding;
1277 margin: 0 0 @padding;
1278 cursor: pointer;
1278 cursor: pointer;
1279 }
1279 }
1280
1280
1281 .perm_ac {
1281 .perm_ac {
1282 input {
1282 input {
1283 width: 95%;
1283 width: 95%;
1284 }
1284 }
1285 }
1285 }
1286
1286
1287 .autocomplete-suggestions {
1287 .autocomplete-suggestions {
1288 width: auto !important; // overrides autocomplete.js
1288 width: auto !important; // overrides autocomplete.js
1289 min-width: 278px;
1289 min-width: 278px;
1290 margin: 0;
1290 margin: 0;
1291 border: @border-thickness solid @grey5;
1291 border: @border-thickness solid @grey5;
1292 border-radius: @border-radius;
1292 border-radius: @border-radius;
1293 color: @grey2;
1293 color: @grey2;
1294 background-color: white;
1294 background-color: white;
1295 }
1295 }
1296
1296
1297 .autocomplete-qfilter-suggestions {
1297 .autocomplete-qfilter-suggestions {
1298 width: auto !important; // overrides autocomplete.js
1298 width: auto !important; // overrides autocomplete.js
1299 max-height: 100% !important;
1299 max-height: 100% !important;
1300 min-width: 376px;
1300 min-width: 376px;
1301 margin: 0;
1301 margin: 0;
1302 border: @border-thickness solid @grey5;
1302 border: @border-thickness solid @grey5;
1303 color: @grey2;
1303 color: @grey2;
1304 background-color: white;
1304 background-color: white;
1305 }
1305 }
1306
1306
1307 .autocomplete-selected {
1307 .autocomplete-selected {
1308 background: #F0F0F0;
1308 background: #F0F0F0;
1309 }
1309 }
1310
1310
1311 .ac-container-wrap {
1311 .ac-container-wrap {
1312 margin: 0;
1312 margin: 0;
1313 padding: 8px;
1313 padding: 8px;
1314 border-bottom: @border-thickness solid @grey5;
1314 border-bottom: @border-thickness solid @grey5;
1315 list-style-type: none;
1315 list-style-type: none;
1316 cursor: pointer;
1316 cursor: pointer;
1317
1317
1318 &:hover {
1318 &:hover {
1319 background-color: @grey7;
1319 background-color: @grey7;
1320 }
1320 }
1321
1321
1322 img {
1322 img {
1323 height: @gravatar-size;
1323 height: @gravatar-size;
1324 width: @gravatar-size;
1324 width: @gravatar-size;
1325 margin-right: 1em;
1325 margin-right: 1em;
1326 }
1326 }
1327
1327
1328 strong {
1328 strong {
1329 font-weight: normal;
1329 font-weight: normal;
1330 }
1330 }
1331 }
1331 }
1332
1332
1333 // Settings Dropdown
1333 // Settings Dropdown
1334 .user-menu .container {
1334 .user-menu .container {
1335 padding: 0 4px;
1335 padding: 0 4px;
1336 margin: 0;
1336 margin: 0;
1337 }
1337 }
1338
1338
1339 .user-menu .gravatar {
1339 .user-menu .gravatar {
1340 cursor: pointer;
1340 cursor: pointer;
1341 }
1341 }
1342
1342
1343 .codeblock {
1343 .codeblock {
1344 margin-bottom: @padding;
1344 margin-bottom: @padding;
1345 clear: both;
1345 clear: both;
1346
1346
1347 .stats {
1347 .stats {
1348 overflow: hidden;
1348 overflow: hidden;
1349 }
1349 }
1350
1350
1351 .message{
1351 .message{
1352 textarea{
1352 textarea{
1353 margin: 0;
1353 margin: 0;
1354 }
1354 }
1355 }
1355 }
1356
1356
1357 .code-header {
1357 .code-header {
1358 .stats {
1358 .stats {
1359 line-height: 2em;
1359 line-height: 2em;
1360
1360
1361 .revision_id {
1361 .revision_id {
1362 margin-left: 0;
1362 margin-left: 0;
1363 }
1363 }
1364 .buttons {
1364 .buttons {
1365 padding-right: 0;
1365 padding-right: 0;
1366 }
1366 }
1367 }
1367 }
1368
1368
1369 .item{
1369 .item{
1370 margin-right: 0.5em;
1370 margin-right: 0.5em;
1371 }
1371 }
1372 }
1372 }
1373
1373
1374 #editor_container {
1374 #editor_container {
1375 position: relative;
1375 position: relative;
1376 margin: @padding 10px;
1376 margin: @padding 10px;
1377 }
1377 }
1378 }
1378 }
1379
1379
1380 #file_history_container {
1380 #file_history_container {
1381 display: none;
1381 display: none;
1382 }
1382 }
1383
1383
1384 .file-history-inner {
1384 .file-history-inner {
1385 margin-bottom: 10px;
1385 margin-bottom: 10px;
1386 }
1386 }
1387
1387
1388 // Pull Requests
1388 // Pull Requests
1389 .summary-details {
1389 .summary-details {
1390 width: 72%;
1390 width: 72%;
1391 }
1391 }
1392 .pr-summary {
1392 .pr-summary {
1393 border-bottom: @border-thickness solid @grey5;
1393 border-bottom: @border-thickness solid @grey5;
1394 margin-bottom: @space;
1394 margin-bottom: @space;
1395 }
1395 }
1396 .reviewers-title {
1396 .reviewers-title {
1397 width: 25%;
1397 width: 25%;
1398 min-width: 200px;
1398 min-width: 200px;
1399 }
1399 }
1400 .reviewers {
1400 .reviewers {
1401 width: 25%;
1401 width: 25%;
1402 min-width: 200px;
1402 min-width: 200px;
1403 }
1403 }
1404 .reviewers ul li {
1404 .reviewers ul li {
1405 position: relative;
1405 position: relative;
1406 width: 100%;
1406 width: 100%;
1407 padding-bottom: 8px;
1407 padding-bottom: 8px;
1408 list-style-type: none;
1408 list-style-type: none;
1409 }
1409 }
1410
1410
1411 .reviewer_entry {
1411 .reviewer_entry {
1412 min-height: 55px;
1412 min-height: 55px;
1413 }
1413 }
1414
1414
1415 .reviewers_member {
1415 .reviewers_member {
1416 width: 100%;
1416 width: 100%;
1417 overflow: auto;
1417 overflow: auto;
1418 }
1418 }
1419 .reviewer_reason {
1419 .reviewer_reason {
1420 padding-left: 20px;
1420 padding-left: 20px;
1421 line-height: 1.5em;
1421 line-height: 1.5em;
1422 }
1422 }
1423 .reviewer_status {
1423 .reviewer_status {
1424 display: inline-block;
1424 display: inline-block;
1425 width: 25px;
1425 width: 25px;
1426 min-width: 25px;
1426 min-width: 25px;
1427 height: 1.2em;
1427 height: 1.2em;
1428 line-height: 1em;
1428 line-height: 1em;
1429 }
1429 }
1430
1430
1431 .reviewer_name {
1431 .reviewer_name {
1432 display: inline-block;
1432 display: inline-block;
1433 max-width: 83%;
1433 max-width: 83%;
1434 padding-right: 20px;
1434 padding-right: 20px;
1435 vertical-align: middle;
1435 vertical-align: middle;
1436 line-height: 1;
1436 line-height: 1;
1437
1437
1438 .rc-user {
1438 .rc-user {
1439 min-width: 0;
1439 min-width: 0;
1440 margin: -2px 1em 0 0;
1440 margin: -2px 1em 0 0;
1441 }
1441 }
1442
1442
1443 .reviewer {
1443 .reviewer {
1444 float: left;
1444 float: left;
1445 }
1445 }
1446 }
1446 }
1447
1447
1448 .reviewer_member_mandatory {
1448 .reviewer_member_mandatory {
1449 position: absolute;
1449 position: absolute;
1450 left: 15px;
1450 left: 15px;
1451 top: 8px;
1451 top: 8px;
1452 width: 16px;
1452 width: 16px;
1453 font-size: 11px;
1453 font-size: 11px;
1454 margin: 0;
1454 margin: 0;
1455 padding: 0;
1455 padding: 0;
1456 color: black;
1456 color: black;
1457 }
1457 }
1458
1458
1459 .reviewer_member_mandatory_remove,
1459 .reviewer_member_mandatory_remove,
1460 .reviewer_member_remove {
1460 .reviewer_member_remove {
1461 position: absolute;
1461 position: absolute;
1462 right: 0;
1462 right: 0;
1463 top: 0;
1463 top: 0;
1464 width: 16px;
1464 width: 16px;
1465 margin-bottom: 10px;
1465 margin-bottom: 10px;
1466 padding: 0;
1466 padding: 0;
1467 color: black;
1467 color: black;
1468 }
1468 }
1469
1469
1470 .reviewer_member_mandatory_remove {
1470 .reviewer_member_mandatory_remove {
1471 color: @grey4;
1471 color: @grey4;
1472 }
1472 }
1473
1473
1474 .reviewer_member_status {
1474 .reviewer_member_status {
1475 margin-top: 5px;
1475 margin-top: 5px;
1476 }
1476 }
1477 .pr-summary #summary{
1477 .pr-summary #summary{
1478 width: 100%;
1478 width: 100%;
1479 }
1479 }
1480 .pr-summary .action_button:hover {
1480 .pr-summary .action_button:hover {
1481 border: 0;
1481 border: 0;
1482 cursor: pointer;
1482 cursor: pointer;
1483 }
1483 }
1484 .pr-details-title {
1484 .pr-details-title {
1485 padding-bottom: 8px;
1485 padding-bottom: 8px;
1486 border-bottom: @border-thickness solid @grey5;
1486 border-bottom: @border-thickness solid @grey5;
1487
1487
1488 .action_button.disabled {
1488 .action_button.disabled {
1489 color: @grey4;
1489 color: @grey4;
1490 cursor: inherit;
1490 cursor: inherit;
1491 }
1491 }
1492 .action_button {
1492 .action_button {
1493 color: @rcblue;
1493 color: @rcblue;
1494 }
1494 }
1495 }
1495 }
1496 .pr-details-content {
1496 .pr-details-content {
1497 margin-top: @textmargin;
1497 margin-top: @textmargin;
1498 margin-bottom: @textmargin;
1498 margin-bottom: @textmargin;
1499 }
1499 }
1500
1500
1501 .pr-reviewer-rules {
1501 .pr-reviewer-rules {
1502 padding: 10px 0px 20px 0px;
1502 padding: 10px 0px 20px 0px;
1503 }
1503 }
1504
1504
1505 .group_members {
1505 .group_members {
1506 margin-top: 0;
1506 margin-top: 0;
1507 padding: 0;
1507 padding: 0;
1508 list-style: outside none none;
1508 list-style: outside none none;
1509
1509
1510 img {
1510 img {
1511 height: @gravatar-size;
1511 height: @gravatar-size;
1512 width: @gravatar-size;
1512 width: @gravatar-size;
1513 margin-right: .5em;
1513 margin-right: .5em;
1514 margin-left: 3px;
1514 margin-left: 3px;
1515 }
1515 }
1516
1516
1517 .to-delete {
1517 .to-delete {
1518 .user {
1518 .user {
1519 text-decoration: line-through;
1519 text-decoration: line-through;
1520 }
1520 }
1521 }
1521 }
1522 }
1522 }
1523
1523
1524 .compare_view_commits_title {
1524 .compare_view_commits_title {
1525 .disabled {
1525 .disabled {
1526 cursor: inherit;
1526 cursor: inherit;
1527 &:hover{
1527 &:hover{
1528 background-color: inherit;
1528 background-color: inherit;
1529 color: inherit;
1529 color: inherit;
1530 }
1530 }
1531 }
1531 }
1532 }
1532 }
1533
1533
1534 .subtitle-compare {
1534 .subtitle-compare {
1535 margin: -15px 0px 0px 0px;
1535 margin: -15px 0px 0px 0px;
1536 }
1536 }
1537
1537
1538 // new entry in group_members
1538 // new entry in group_members
1539 .td-author-new-entry {
1539 .td-author-new-entry {
1540 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1540 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1541 }
1541 }
1542
1542
1543 .usergroup_member_remove {
1543 .usergroup_member_remove {
1544 width: 16px;
1544 width: 16px;
1545 margin-bottom: 10px;
1545 margin-bottom: 10px;
1546 padding: 0;
1546 padding: 0;
1547 color: black !important;
1547 color: black !important;
1548 cursor: pointer;
1548 cursor: pointer;
1549 }
1549 }
1550
1550
1551 .reviewer_ac .ac-input {
1551 .reviewer_ac .ac-input {
1552 width: 92%;
1552 width: 92%;
1553 margin-bottom: 1em;
1553 margin-bottom: 1em;
1554 }
1554 }
1555
1555
1556 .compare_view_commits tr{
1556 .compare_view_commits tr{
1557 height: 20px;
1557 height: 20px;
1558 }
1558 }
1559 .compare_view_commits td {
1559 .compare_view_commits td {
1560 vertical-align: top;
1560 vertical-align: top;
1561 padding-top: 10px;
1561 padding-top: 10px;
1562 }
1562 }
1563 .compare_view_commits .author {
1563 .compare_view_commits .author {
1564 margin-left: 5px;
1564 margin-left: 5px;
1565 }
1565 }
1566
1566
1567 .compare_view_commits {
1567 .compare_view_commits {
1568 .color-a {
1568 .color-a {
1569 color: @alert1;
1569 color: @alert1;
1570 }
1570 }
1571
1571
1572 .color-c {
1572 .color-c {
1573 color: @color3;
1573 color: @color3;
1574 }
1574 }
1575
1575
1576 .color-r {
1576 .color-r {
1577 color: @color5;
1577 color: @color5;
1578 }
1578 }
1579
1579
1580 .color-a-bg {
1580 .color-a-bg {
1581 background-color: @alert1;
1581 background-color: @alert1;
1582 }
1582 }
1583
1583
1584 .color-c-bg {
1584 .color-c-bg {
1585 background-color: @alert3;
1585 background-color: @alert3;
1586 }
1586 }
1587
1587
1588 .color-r-bg {
1588 .color-r-bg {
1589 background-color: @alert2;
1589 background-color: @alert2;
1590 }
1590 }
1591
1591
1592 .color-a-border {
1592 .color-a-border {
1593 border: 1px solid @alert1;
1593 border: 1px solid @alert1;
1594 }
1594 }
1595
1595
1596 .color-c-border {
1596 .color-c-border {
1597 border: 1px solid @alert3;
1597 border: 1px solid @alert3;
1598 }
1598 }
1599
1599
1600 .color-r-border {
1600 .color-r-border {
1601 border: 1px solid @alert2;
1601 border: 1px solid @alert2;
1602 }
1602 }
1603
1603
1604 .commit-change-indicator {
1604 .commit-change-indicator {
1605 width: 15px;
1605 width: 15px;
1606 height: 15px;
1606 height: 15px;
1607 position: relative;
1607 position: relative;
1608 left: 15px;
1608 left: 15px;
1609 }
1609 }
1610
1610
1611 .commit-change-content {
1611 .commit-change-content {
1612 text-align: center;
1612 text-align: center;
1613 vertical-align: middle;
1613 vertical-align: middle;
1614 line-height: 15px;
1614 line-height: 15px;
1615 }
1615 }
1616 }
1616 }
1617
1617
1618 .compare_view_filepath {
1618 .compare_view_filepath {
1619 color: @grey1;
1619 color: @grey1;
1620 }
1620 }
1621
1621
1622 .show_more {
1622 .show_more {
1623 display: inline-block;
1623 display: inline-block;
1624 width: 0;
1624 width: 0;
1625 height: 0;
1625 height: 0;
1626 vertical-align: middle;
1626 vertical-align: middle;
1627 content: "";
1627 content: "";
1628 border: 4px solid;
1628 border: 4px solid;
1629 border-right-color: transparent;
1629 border-right-color: transparent;
1630 border-bottom-color: transparent;
1630 border-bottom-color: transparent;
1631 border-left-color: transparent;
1631 border-left-color: transparent;
1632 font-size: 0;
1632 font-size: 0;
1633 }
1633 }
1634
1634
1635 .journal_more .show_more {
1635 .journal_more .show_more {
1636 display: inline;
1636 display: inline;
1637
1637
1638 &:after {
1638 &:after {
1639 content: none;
1639 content: none;
1640 }
1640 }
1641 }
1641 }
1642
1642
1643 .compare_view_commits .collapse_commit:after {
1643 .compare_view_commits .collapse_commit:after {
1644 cursor: pointer;
1644 cursor: pointer;
1645 content: "\00A0\25B4";
1645 content: "\00A0\25B4";
1646 margin-left: -3px;
1646 margin-left: -3px;
1647 font-size: 17px;
1647 font-size: 17px;
1648 color: @grey4;
1648 color: @grey4;
1649 }
1649 }
1650
1650
1651 .diff_links {
1651 .diff_links {
1652 margin-left: 8px;
1652 margin-left: 8px;
1653 }
1653 }
1654
1654
1655 #pull_request_overview {
1655 #pull_request_overview {
1656 div.ancestor {
1656 div.ancestor {
1657 margin: -33px 0;
1657 margin: -33px 0;
1658 }
1658 }
1659 }
1659 }
1660
1660
1661 div.ancestor {
1661 div.ancestor {
1662 line-height: 33px;
1662 line-height: 33px;
1663 }
1663 }
1664
1664
1665 .cs_icon_td input[type="checkbox"] {
1665 .cs_icon_td input[type="checkbox"] {
1666 display: none;
1666 display: none;
1667 }
1667 }
1668
1668
1669 .cs_icon_td .expand_file_icon:after {
1669 .cs_icon_td .expand_file_icon:after {
1670 cursor: pointer;
1670 cursor: pointer;
1671 content: "\00A0\25B6";
1671 content: "\00A0\25B6";
1672 font-size: 12px;
1672 font-size: 12px;
1673 color: @grey4;
1673 color: @grey4;
1674 }
1674 }
1675
1675
1676 .cs_icon_td .collapse_file_icon:after {
1676 .cs_icon_td .collapse_file_icon:after {
1677 cursor: pointer;
1677 cursor: pointer;
1678 content: "\00A0\25BC";
1678 content: "\00A0\25BC";
1679 font-size: 12px;
1679 font-size: 12px;
1680 color: @grey4;
1680 color: @grey4;
1681 }
1681 }
1682
1682
1683 /*new binary
1683 /*new binary
1684 NEW_FILENODE = 1
1684 NEW_FILENODE = 1
1685 DEL_FILENODE = 2
1685 DEL_FILENODE = 2
1686 MOD_FILENODE = 3
1686 MOD_FILENODE = 3
1687 RENAMED_FILENODE = 4
1687 RENAMED_FILENODE = 4
1688 COPIED_FILENODE = 5
1688 COPIED_FILENODE = 5
1689 CHMOD_FILENODE = 6
1689 CHMOD_FILENODE = 6
1690 BIN_FILENODE = 7
1690 BIN_FILENODE = 7
1691 */
1691 */
1692 .cs_files_expand {
1692 .cs_files_expand {
1693 font-size: @basefontsize + 5px;
1693 font-size: @basefontsize + 5px;
1694 line-height: 1.8em;
1694 line-height: 1.8em;
1695 float: right;
1695 float: right;
1696 }
1696 }
1697
1697
1698 .cs_files_expand span{
1698 .cs_files_expand span{
1699 color: @rcblue;
1699 color: @rcblue;
1700 cursor: pointer;
1700 cursor: pointer;
1701 }
1701 }
1702 .cs_files {
1702 .cs_files {
1703 clear: both;
1703 clear: both;
1704 padding-bottom: @padding;
1704 padding-bottom: @padding;
1705
1705
1706 .cur_cs {
1706 .cur_cs {
1707 margin: 10px 2px;
1707 margin: 10px 2px;
1708 font-weight: bold;
1708 font-weight: bold;
1709 }
1709 }
1710
1710
1711 .node {
1711 .node {
1712 float: left;
1712 float: left;
1713 }
1713 }
1714
1714
1715 .changes {
1715 .changes {
1716 float: right;
1716 float: right;
1717 color: white;
1717 color: white;
1718 font-size: @basefontsize - 4px;
1718 font-size: @basefontsize - 4px;
1719 margin-top: 4px;
1719 margin-top: 4px;
1720 opacity: 0.6;
1720 opacity: 0.6;
1721 filter: Alpha(opacity=60); /* IE8 and earlier */
1721 filter: Alpha(opacity=60); /* IE8 and earlier */
1722
1722
1723 .added {
1723 .added {
1724 background-color: @alert1;
1724 background-color: @alert1;
1725 float: left;
1725 float: left;
1726 text-align: center;
1726 text-align: center;
1727 }
1727 }
1728
1728
1729 .deleted {
1729 .deleted {
1730 background-color: @alert2;
1730 background-color: @alert2;
1731 float: left;
1731 float: left;
1732 text-align: center;
1732 text-align: center;
1733 }
1733 }
1734
1734
1735 .bin {
1735 .bin {
1736 background-color: @alert1;
1736 background-color: @alert1;
1737 text-align: center;
1737 text-align: center;
1738 }
1738 }
1739
1739
1740 /*new binary*/
1740 /*new binary*/
1741 .bin.bin1 {
1741 .bin.bin1 {
1742 background-color: @alert1;
1742 background-color: @alert1;
1743 text-align: center;
1743 text-align: center;
1744 }
1744 }
1745
1745
1746 /*deleted binary*/
1746 /*deleted binary*/
1747 .bin.bin2 {
1747 .bin.bin2 {
1748 background-color: @alert2;
1748 background-color: @alert2;
1749 text-align: center;
1749 text-align: center;
1750 }
1750 }
1751
1751
1752 /*mod binary*/
1752 /*mod binary*/
1753 .bin.bin3 {
1753 .bin.bin3 {
1754 background-color: @grey2;
1754 background-color: @grey2;
1755 text-align: center;
1755 text-align: center;
1756 }
1756 }
1757
1757
1758 /*rename file*/
1758 /*rename file*/
1759 .bin.bin4 {
1759 .bin.bin4 {
1760 background-color: @alert4;
1760 background-color: @alert4;
1761 text-align: center;
1761 text-align: center;
1762 }
1762 }
1763
1763
1764 /*copied file*/
1764 /*copied file*/
1765 .bin.bin5 {
1765 .bin.bin5 {
1766 background-color: @alert4;
1766 background-color: @alert4;
1767 text-align: center;
1767 text-align: center;
1768 }
1768 }
1769
1769
1770 /*chmod file*/
1770 /*chmod file*/
1771 .bin.bin6 {
1771 .bin.bin6 {
1772 background-color: @grey2;
1772 background-color: @grey2;
1773 text-align: center;
1773 text-align: center;
1774 }
1774 }
1775 }
1775 }
1776 }
1776 }
1777
1777
1778 .cs_files .cs_added, .cs_files .cs_A,
1778 .cs_files .cs_added, .cs_files .cs_A,
1779 .cs_files .cs_added, .cs_files .cs_M,
1779 .cs_files .cs_added, .cs_files .cs_M,
1780 .cs_files .cs_added, .cs_files .cs_D {
1780 .cs_files .cs_added, .cs_files .cs_D {
1781 height: 16px;
1781 height: 16px;
1782 padding-right: 10px;
1782 padding-right: 10px;
1783 margin-top: 7px;
1783 margin-top: 7px;
1784 text-align: left;
1784 text-align: left;
1785 }
1785 }
1786
1786
1787 .cs_icon_td {
1787 .cs_icon_td {
1788 min-width: 16px;
1788 min-width: 16px;
1789 width: 16px;
1789 width: 16px;
1790 }
1790 }
1791
1791
1792 .pull-request-merge {
1792 .pull-request-merge {
1793 border: 1px solid @grey5;
1793 border: 1px solid @grey5;
1794 padding: 10px 0px 20px;
1794 padding: 10px 0px 20px;
1795 margin-top: 10px;
1795 margin-top: 10px;
1796 margin-bottom: 20px;
1796 margin-bottom: 20px;
1797 }
1797 }
1798
1798
1799 .pull-request-merge-refresh {
1799 .pull-request-merge-refresh {
1800 margin: 2px 7px;
1800 margin: 2px 7px;
1801 }
1801 }
1802
1802
1803 .pull-request-merge ul {
1803 .pull-request-merge ul {
1804 padding: 0px 0px;
1804 padding: 0px 0px;
1805 }
1805 }
1806
1806
1807 .pull-request-merge li {
1807 .pull-request-merge li {
1808 list-style-type: none;
1808 list-style-type: none;
1809 }
1809 }
1810
1810
1811 .pull-request-merge .pull-request-wrap {
1811 .pull-request-merge .pull-request-wrap {
1812 height: auto;
1812 height: auto;
1813 padding: 0px 0px;
1813 padding: 0px 0px;
1814 text-align: right;
1814 text-align: right;
1815 }
1815 }
1816
1816
1817 .pull-request-merge span {
1817 .pull-request-merge span {
1818 margin-right: 5px;
1818 margin-right: 5px;
1819 }
1819 }
1820
1820
1821 .pull-request-merge-actions {
1821 .pull-request-merge-actions {
1822 min-height: 30px;
1822 min-height: 30px;
1823 padding: 0px 0px;
1823 padding: 0px 0px;
1824 }
1824 }
1825
1825
1826 .pull-request-merge-info {
1826 .pull-request-merge-info {
1827 padding: 0px 5px 5px 0px;
1827 padding: 0px 5px 5px 0px;
1828 }
1828 }
1829
1829
1830 .merge-status {
1830 .merge-status {
1831 margin-right: 5px;
1831 margin-right: 5px;
1832 }
1832 }
1833
1833
1834 .merge-message {
1834 .merge-message {
1835 font-size: 1.2em
1835 font-size: 1.2em
1836 }
1836 }
1837
1837
1838 .merge-message.success i,
1838 .merge-message.success i,
1839 .merge-icon.success i {
1839 .merge-icon.success i {
1840 color:@alert1;
1840 color:@alert1;
1841 }
1841 }
1842
1842
1843 .merge-message.warning i,
1843 .merge-message.warning i,
1844 .merge-icon.warning i {
1844 .merge-icon.warning i {
1845 color: @alert3;
1845 color: @alert3;
1846 }
1846 }
1847
1847
1848 .merge-message.error i,
1848 .merge-message.error i,
1849 .merge-icon.error i {
1849 .merge-icon.error i {
1850 color:@alert2;
1850 color:@alert2;
1851 }
1851 }
1852
1852
1853 .pr-versions {
1853 .pr-versions {
1854 font-size: 1.1em;
1854 font-size: 1.1em;
1855
1855
1856 table {
1856 table {
1857 padding: 0px 5px;
1857 padding: 0px 5px;
1858 }
1858 }
1859
1859
1860 td {
1860 td {
1861 line-height: 15px;
1861 line-height: 15px;
1862 }
1862 }
1863
1863
1864 .compare-radio-button {
1864 .compare-radio-button {
1865 position: relative;
1865 position: relative;
1866 top: -3px;
1866 top: -3px;
1867 }
1867 }
1868 }
1868 }
1869
1869
1870
1870
1871 #close_pull_request {
1871 #close_pull_request {
1872 margin-right: 0px;
1872 margin-right: 0px;
1873 }
1873 }
1874
1874
1875 .empty_data {
1875 .empty_data {
1876 color: @grey4;
1876 color: @grey4;
1877 }
1877 }
1878
1878
1879 #changeset_compare_view_content {
1879 #changeset_compare_view_content {
1880 clear: both;
1880 clear: both;
1881 width: 100%;
1881 width: 100%;
1882 box-sizing: border-box;
1882 box-sizing: border-box;
1883 .border-radius(@border-radius);
1883 .border-radius(@border-radius);
1884
1884
1885 .help-block {
1885 .help-block {
1886 margin: @padding 0;
1886 margin: @padding 0;
1887 color: @text-color;
1887 color: @text-color;
1888 &.pre-formatting {
1888 &.pre-formatting {
1889 white-space: pre;
1889 white-space: pre;
1890 }
1890 }
1891 }
1891 }
1892
1892
1893 .empty_data {
1893 .empty_data {
1894 margin: @padding 0;
1894 margin: @padding 0;
1895 }
1895 }
1896
1896
1897 .alert {
1897 .alert {
1898 margin-bottom: @space;
1898 margin-bottom: @space;
1899 }
1899 }
1900 }
1900 }
1901
1901
1902 .table_disp {
1902 .table_disp {
1903 .status {
1903 .status {
1904 width: auto;
1904 width: auto;
1905 }
1905 }
1906 }
1906 }
1907
1907
1908
1908
1909 .creation_in_progress {
1909 .creation_in_progress {
1910 color: @grey4
1910 color: @grey4
1911 }
1911 }
1912
1912
1913 .status_box_menu {
1913 .status_box_menu {
1914 margin: 0;
1914 margin: 0;
1915 }
1915 }
1916
1916
1917 .notification-table{
1917 .notification-table{
1918 margin-bottom: @space;
1918 margin-bottom: @space;
1919 display: table;
1919 display: table;
1920 width: 100%;
1920 width: 100%;
1921
1921
1922 .container{
1922 .container{
1923 display: table-row;
1923 display: table-row;
1924
1924
1925 .notification-header{
1925 .notification-header{
1926 border-bottom: @border-thickness solid @border-default-color;
1926 border-bottom: @border-thickness solid @border-default-color;
1927 }
1927 }
1928
1928
1929 .notification-subject{
1929 .notification-subject{
1930 display: table-cell;
1930 display: table-cell;
1931 }
1931 }
1932 }
1932 }
1933 }
1933 }
1934
1934
1935 // Notifications
1935 // Notifications
1936 .notification-header{
1936 .notification-header{
1937 display: table;
1937 display: table;
1938 width: 100%;
1938 width: 100%;
1939 padding: floor(@basefontsize/2) 0;
1939 padding: floor(@basefontsize/2) 0;
1940 line-height: 1em;
1940 line-height: 1em;
1941
1941
1942 .desc, .delete-notifications, .read-notifications{
1942 .desc, .delete-notifications, .read-notifications{
1943 display: table-cell;
1943 display: table-cell;
1944 text-align: left;
1944 text-align: left;
1945 }
1945 }
1946
1946
1947 .desc{
1947 .desc{
1948 width: 1163px;
1948 width: 1163px;
1949 }
1949 }
1950
1950
1951 .delete-notifications, .read-notifications{
1951 .delete-notifications, .read-notifications{
1952 width: 35px;
1952 width: 35px;
1953 min-width: 35px; //fixes when only one button is displayed
1953 min-width: 35px; //fixes when only one button is displayed
1954 }
1954 }
1955 }
1955 }
1956
1956
1957 .notification-body {
1957 .notification-body {
1958 .markdown-block,
1958 .markdown-block,
1959 .rst-block {
1959 .rst-block {
1960 padding: @padding 0;
1960 padding: @padding 0;
1961 }
1961 }
1962
1962
1963 .notification-subject {
1963 .notification-subject {
1964 padding: @textmargin 0;
1964 padding: @textmargin 0;
1965 border-bottom: @border-thickness solid @border-default-color;
1965 border-bottom: @border-thickness solid @border-default-color;
1966 }
1966 }
1967 }
1967 }
1968
1968
1969
1969
1970 .notifications_buttons{
1970 .notifications_buttons{
1971 float: right;
1971 float: right;
1972 }
1972 }
1973
1973
1974 #notification-status{
1974 #notification-status{
1975 display: inline;
1975 display: inline;
1976 }
1976 }
1977
1977
1978 // Repositories
1978 // Repositories
1979
1979
1980 #summary.fields{
1980 #summary.fields{
1981 display: table;
1981 display: table;
1982
1982
1983 .field{
1983 .field{
1984 display: table-row;
1984 display: table-row;
1985
1985
1986 .label-summary{
1986 .label-summary{
1987 display: table-cell;
1987 display: table-cell;
1988 min-width: @label-summary-minwidth;
1988 min-width: @label-summary-minwidth;
1989 padding-top: @padding/2;
1989 padding-top: @padding/2;
1990 padding-bottom: @padding/2;
1990 padding-bottom: @padding/2;
1991 padding-right: @padding/2;
1991 padding-right: @padding/2;
1992 }
1992 }
1993
1993
1994 .input{
1994 .input{
1995 display: table-cell;
1995 display: table-cell;
1996 padding: @padding/2;
1996 padding: @padding/2;
1997
1997
1998 input{
1998 input{
1999 min-width: 29em;
1999 min-width: 29em;
2000 padding: @padding/4;
2000 padding: @padding/4;
2001 }
2001 }
2002 }
2002 }
2003 .statistics, .downloads{
2003 .statistics, .downloads{
2004 .disabled{
2004 .disabled{
2005 color: @grey4;
2005 color: @grey4;
2006 }
2006 }
2007 }
2007 }
2008 }
2008 }
2009 }
2009 }
2010
2010
2011 #summary{
2011 #summary{
2012 width: 70%;
2012 width: 70%;
2013 }
2013 }
2014
2014
2015
2015
2016 // Journal
2016 // Journal
2017 .journal.title {
2017 .journal.title {
2018 h5 {
2018 h5 {
2019 float: left;
2019 float: left;
2020 margin: 0;
2020 margin: 0;
2021 width: 70%;
2021 width: 70%;
2022 }
2022 }
2023
2023
2024 ul {
2024 ul {
2025 float: right;
2025 float: right;
2026 display: inline-block;
2026 display: inline-block;
2027 margin: 0;
2027 margin: 0;
2028 width: 30%;
2028 width: 30%;
2029 text-align: right;
2029 text-align: right;
2030
2030
2031 li {
2031 li {
2032 display: inline;
2032 display: inline;
2033 font-size: @journal-fontsize;
2033 font-size: @journal-fontsize;
2034 line-height: 1em;
2034 line-height: 1em;
2035
2035
2036 list-style-type: none;
2036 list-style-type: none;
2037 }
2037 }
2038 }
2038 }
2039 }
2039 }
2040
2040
2041 .filterexample {
2041 .filterexample {
2042 position: absolute;
2042 position: absolute;
2043 top: 95px;
2043 top: 95px;
2044 left: @contentpadding;
2044 left: @contentpadding;
2045 color: @rcblue;
2045 color: @rcblue;
2046 font-size: 11px;
2046 font-size: 11px;
2047 font-family: @text-regular;
2047 font-family: @text-regular;
2048 cursor: help;
2048 cursor: help;
2049
2049
2050 &:hover {
2050 &:hover {
2051 color: @rcdarkblue;
2051 color: @rcdarkblue;
2052 }
2052 }
2053
2053
2054 @media (max-width:768px) {
2054 @media (max-width:768px) {
2055 position: relative;
2055 position: relative;
2056 top: auto;
2056 top: auto;
2057 left: auto;
2057 left: auto;
2058 display: block;
2058 display: block;
2059 }
2059 }
2060 }
2060 }
2061
2061
2062
2062
2063 #journal{
2063 #journal{
2064 margin-bottom: @space;
2064 margin-bottom: @space;
2065
2065
2066 .journal_day{
2066 .journal_day{
2067 margin-bottom: @textmargin/2;
2067 margin-bottom: @textmargin/2;
2068 padding-bottom: @textmargin/2;
2068 padding-bottom: @textmargin/2;
2069 font-size: @journal-fontsize;
2069 font-size: @journal-fontsize;
2070 border-bottom: @border-thickness solid @border-default-color;
2070 border-bottom: @border-thickness solid @border-default-color;
2071 }
2071 }
2072
2072
2073 .journal_container{
2073 .journal_container{
2074 margin-bottom: @space;
2074 margin-bottom: @space;
2075
2075
2076 .journal_user{
2076 .journal_user{
2077 display: inline-block;
2077 display: inline-block;
2078 }
2078 }
2079 .journal_action_container{
2079 .journal_action_container{
2080 display: block;
2080 display: block;
2081 margin-top: @textmargin;
2081 margin-top: @textmargin;
2082
2082
2083 div{
2083 div{
2084 display: inline;
2084 display: inline;
2085 }
2085 }
2086
2086
2087 div.journal_action_params{
2087 div.journal_action_params{
2088 display: block;
2088 display: block;
2089 }
2089 }
2090
2090
2091 div.journal_repo:after{
2091 div.journal_repo:after{
2092 content: "\A";
2092 content: "\A";
2093 white-space: pre;
2093 white-space: pre;
2094 }
2094 }
2095
2095
2096 div.date{
2096 div.date{
2097 display: block;
2097 display: block;
2098 margin-bottom: @textmargin;
2098 margin-bottom: @textmargin;
2099 }
2099 }
2100 }
2100 }
2101 }
2101 }
2102 }
2102 }
2103
2103
2104 // Files
2104 // Files
2105 .edit-file-title {
2105 .edit-file-title {
2106 font-size: 16px;
2106 font-size: 16px;
2107
2107
2108 .title-heading {
2108 .title-heading {
2109 padding: 2px;
2109 padding: 2px;
2110 }
2110 }
2111 }
2111 }
2112
2112
2113 .edit-file-fieldset {
2113 .edit-file-fieldset {
2114 margin: @sidebarpadding 0;
2114 margin: @sidebarpadding 0;
2115
2115
2116 .fieldset {
2116 .fieldset {
2117 .left-label {
2117 .left-label {
2118 width: 13%;
2118 width: 13%;
2119 }
2119 }
2120 .right-content {
2120 .right-content {
2121 width: 87%;
2121 width: 87%;
2122 max-width: 100%;
2122 max-width: 100%;
2123 }
2123 }
2124 .filename-label {
2124 .filename-label {
2125 margin-top: 13px;
2125 margin-top: 13px;
2126 }
2126 }
2127 .commit-message-label {
2127 .commit-message-label {
2128 margin-top: 4px;
2128 margin-top: 4px;
2129 }
2129 }
2130 .file-upload-input {
2130 .file-upload-input {
2131 input {
2131 input {
2132 display: none;
2132 display: none;
2133 }
2133 }
2134 margin-top: 10px;
2134 margin-top: 10px;
2135 }
2135 }
2136 .file-upload-label {
2136 .file-upload-label {
2137 margin-top: 10px;
2137 margin-top: 10px;
2138 }
2138 }
2139 p {
2139 p {
2140 margin-top: 5px;
2140 margin-top: 5px;
2141 }
2141 }
2142
2142
2143 }
2143 }
2144 .custom-path-link {
2144 .custom-path-link {
2145 margin-left: 5px;
2145 margin-left: 5px;
2146 }
2146 }
2147 #commit {
2147 #commit {
2148 resize: vertical;
2148 resize: vertical;
2149 }
2149 }
2150 }
2150 }
2151
2151
2152 .delete-file-preview {
2152 .delete-file-preview {
2153 max-height: 250px;
2153 max-height: 250px;
2154 }
2154 }
2155
2155
2156 .new-file,
2156 .new-file,
2157 #filter_activate,
2157 #filter_activate,
2158 #filter_deactivate {
2158 #filter_deactivate {
2159 float: right;
2159 float: right;
2160 margin: 0 0 0 10px;
2160 margin: 0 0 0 10px;
2161 }
2161 }
2162
2162
2163 .file-upload-transaction-wrapper {
2163 .file-upload-transaction-wrapper {
2164 margin-top: 57px;
2164 margin-top: 57px;
2165 clear: both;
2165 clear: both;
2166 }
2166 }
2167
2167
2168 .file-upload-transaction-wrapper .error {
2168 .file-upload-transaction-wrapper .error {
2169 color: @color5;
2169 color: @color5;
2170 }
2170 }
2171
2171
2172 .file-upload-transaction {
2172 .file-upload-transaction {
2173 min-height: 200px;
2173 min-height: 200px;
2174 padding: 54px;
2174 padding: 54px;
2175 border: 1px solid @grey5;
2175 border: 1px solid @grey5;
2176 text-align: center;
2176 text-align: center;
2177 clear: both;
2177 clear: both;
2178 }
2178 }
2179
2179
2180 .file-upload-transaction i {
2180 .file-upload-transaction i {
2181 font-size: 48px
2181 font-size: 48px
2182 }
2182 }
2183
2183
2184 h3.files_location{
2184 h3.files_location{
2185 line-height: 2.4em;
2185 line-height: 2.4em;
2186 }
2186 }
2187
2187
2188 .browser-nav {
2188 .browser-nav {
2189 width: 100%;
2189 width: 100%;
2190 display: table;
2190 display: table;
2191 margin-bottom: 20px;
2191 margin-bottom: 20px;
2192
2192
2193 .info_box {
2193 .info_box {
2194 float: left;
2194 float: left;
2195 display: inline-table;
2195 display: inline-table;
2196 height: 2.5em;
2196 height: 2.5em;
2197
2197
2198 .browser-cur-rev, .info_box_elem {
2198 .browser-cur-rev, .info_box_elem {
2199 display: table-cell;
2199 display: table-cell;
2200 vertical-align: middle;
2200 vertical-align: middle;
2201 }
2201 }
2202
2202
2203 .drop-menu {
2203 .drop-menu {
2204 margin: 0 10px;
2204 margin: 0 10px;
2205 }
2205 }
2206
2206
2207 .info_box_elem {
2207 .info_box_elem {
2208 border-top: @border-thickness solid @grey5;
2208 border-top: @border-thickness solid @grey5;
2209 border-bottom: @border-thickness solid @grey5;
2209 border-bottom: @border-thickness solid @grey5;
2210 box-shadow: @button-shadow;
2210 box-shadow: @button-shadow;
2211
2211
2212 #at_rev, a {
2212 #at_rev, a {
2213 padding: 0.6em 0.4em;
2213 padding: 0.6em 0.4em;
2214 margin: 0;
2214 margin: 0;
2215 .box-shadow(none);
2215 .box-shadow(none);
2216 border: 0;
2216 border: 0;
2217 height: 12px;
2217 height: 12px;
2218 color: @grey2;
2218 color: @grey2;
2219 }
2219 }
2220
2220
2221 input#at_rev {
2221 input#at_rev {
2222 max-width: 50px;
2222 max-width: 50px;
2223 text-align: center;
2223 text-align: center;
2224 }
2224 }
2225
2225
2226 &.previous {
2226 &.previous {
2227 border: @border-thickness solid @grey5;
2227 border: @border-thickness solid @grey5;
2228 border-top-left-radius: @border-radius;
2228 border-top-left-radius: @border-radius;
2229 border-bottom-left-radius: @border-radius;
2229 border-bottom-left-radius: @border-radius;
2230
2230
2231 &:hover {
2231 &:hover {
2232 border-color: @grey4;
2232 border-color: @grey4;
2233 }
2233 }
2234
2234
2235 .disabled {
2235 .disabled {
2236 color: @grey5;
2236 color: @grey5;
2237 cursor: not-allowed;
2237 cursor: not-allowed;
2238 opacity: 0.5;
2238 opacity: 0.5;
2239 }
2239 }
2240 }
2240 }
2241
2241
2242 &.next {
2242 &.next {
2243 border: @border-thickness solid @grey5;
2243 border: @border-thickness solid @grey5;
2244 border-top-right-radius: @border-radius;
2244 border-top-right-radius: @border-radius;
2245 border-bottom-right-radius: @border-radius;
2245 border-bottom-right-radius: @border-radius;
2246
2246
2247 &:hover {
2247 &:hover {
2248 border-color: @grey4;
2248 border-color: @grey4;
2249 }
2249 }
2250
2250
2251 .disabled {
2251 .disabled {
2252 color: @grey5;
2252 color: @grey5;
2253 cursor: not-allowed;
2253 cursor: not-allowed;
2254 opacity: 0.5;
2254 opacity: 0.5;
2255 }
2255 }
2256 }
2256 }
2257 }
2257 }
2258
2258
2259 .browser-cur-rev {
2259 .browser-cur-rev {
2260
2260
2261 span{
2261 span{
2262 margin: 0;
2262 margin: 0;
2263 color: @rcblue;
2263 color: @rcblue;
2264 height: 12px;
2264 height: 12px;
2265 display: inline-block;
2265 display: inline-block;
2266 padding: 0.7em 1em ;
2266 padding: 0.7em 1em ;
2267 border: @border-thickness solid @rcblue;
2267 border: @border-thickness solid @rcblue;
2268 margin-right: @padding;
2268 margin-right: @padding;
2269 }
2269 }
2270 }
2270 }
2271
2271
2272 }
2272 }
2273
2273
2274 .select-index-number {
2274 .select-index-number {
2275 margin: 0 0 0 20px;
2275 margin: 0 0 0 20px;
2276 color: @grey3;
2276 color: @grey3;
2277 }
2277 }
2278
2278
2279 .search_activate {
2279 .search_activate {
2280 display: table-cell;
2280 display: table-cell;
2281 vertical-align: middle;
2281 vertical-align: middle;
2282
2282
2283 input, label{
2283 input, label{
2284 margin: 0;
2284 margin: 0;
2285 padding: 0;
2285 padding: 0;
2286 }
2286 }
2287
2287
2288 input{
2288 input{
2289 margin-left: @textmargin;
2289 margin-left: @textmargin;
2290 }
2290 }
2291
2291
2292 }
2292 }
2293 }
2293 }
2294
2294
2295 .browser-cur-rev{
2295 .browser-cur-rev{
2296 margin-bottom: @textmargin;
2296 margin-bottom: @textmargin;
2297 }
2297 }
2298
2298
2299 #node_filter_box_loading{
2299 #node_filter_box_loading{
2300 .info_text;
2300 .info_text;
2301 }
2301 }
2302
2302
2303 .browser-search {
2303 .browser-search {
2304 margin: -25px 0px 5px 0px;
2304 margin: -25px 0px 5px 0px;
2305 }
2305 }
2306
2306
2307 .files-quick-filter {
2307 .files-quick-filter {
2308 float: right;
2308 float: right;
2309 width: 180px;
2309 width: 180px;
2310 position: relative;
2310 position: relative;
2311 }
2311 }
2312
2312
2313 .files-filter-box {
2313 .files-filter-box {
2314 display: flex;
2314 display: flex;
2315 padding: 0px;
2315 padding: 0px;
2316 border-radius: 3px;
2316 border-radius: 3px;
2317 margin-bottom: 0;
2317 margin-bottom: 0;
2318
2318
2319 a {
2319 a {
2320 border: none !important;
2320 border: none !important;
2321 }
2321 }
2322
2322
2323 li {
2323 li {
2324 list-style-type: none
2324 list-style-type: none
2325 }
2325 }
2326 }
2326 }
2327
2327
2328 .files-filter-box-path {
2328 .files-filter-box-path {
2329 line-height: 33px;
2329 line-height: 33px;
2330 padding: 0;
2330 padding: 0;
2331 width: 20px;
2331 width: 20px;
2332 position: absolute;
2332 position: absolute;
2333 z-index: 11;
2333 z-index: 11;
2334 left: 5px;
2334 left: 5px;
2335 }
2335 }
2336
2336
2337 .files-filter-box-input {
2337 .files-filter-box-input {
2338 margin-right: 0;
2338 margin-right: 0;
2339
2339
2340 input {
2340 input {
2341 border: 1px solid @white;
2341 border: 1px solid @white;
2342 padding-left: 25px;
2342 padding-left: 25px;
2343 width: 145px;
2343 width: 145px;
2344
2344
2345 &:hover {
2345 &:hover {
2346 border-color: @grey6;
2346 border-color: @grey6;
2347 }
2347 }
2348
2348
2349 &:focus {
2349 &:focus {
2350 border-color: @grey5;
2350 border-color: @grey5;
2351 }
2351 }
2352 }
2352 }
2353 }
2353 }
2354
2354
2355 .browser-result{
2355 .browser-result{
2356 td a{
2356 td a{
2357 margin-left: 0.5em;
2357 margin-left: 0.5em;
2358 display: inline-block;
2358 display: inline-block;
2359
2359
2360 em {
2360 em {
2361 font-weight: @text-bold-weight;
2361 font-weight: @text-bold-weight;
2362 font-family: @text-bold;
2362 font-family: @text-bold;
2363 }
2363 }
2364 }
2364 }
2365 }
2365 }
2366
2366
2367 .browser-highlight{
2367 .browser-highlight{
2368 background-color: @grey5-alpha;
2368 background-color: @grey5-alpha;
2369 }
2369 }
2370
2370
2371
2371
2372 .edit-file-fieldset #location,
2372 .edit-file-fieldset #location,
2373 .edit-file-fieldset #filename {
2373 .edit-file-fieldset #filename {
2374 display: flex;
2374 display: flex;
2375 width: -moz-available; /* WebKit-based browsers will ignore this. */
2375 width: -moz-available; /* WebKit-based browsers will ignore this. */
2376 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2376 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2377 width: fill-available;
2377 width: fill-available;
2378 border: 0;
2378 border: 0;
2379 }
2379 }
2380
2380
2381 .path-items {
2381 .path-items {
2382 display: flex;
2382 display: flex;
2383 padding: 0;
2383 padding: 0;
2384 border: 1px solid #eeeeee;
2384 border: 1px solid #eeeeee;
2385 width: 100%;
2385 width: 100%;
2386 float: left;
2386 float: left;
2387
2387
2388 .breadcrumb-path {
2388 .breadcrumb-path {
2389 line-height: 30px;
2389 line-height: 30px;
2390 padding: 0 4px;
2390 padding: 0 4px;
2391 white-space: nowrap;
2391 white-space: nowrap;
2392 }
2392 }
2393
2393
2394 .location-path {
2394 .location-path {
2395 width: -moz-available; /* WebKit-based browsers will ignore this. */
2395 width: -moz-available; /* WebKit-based browsers will ignore this. */
2396 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2396 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2397 width: fill-available;
2397 width: fill-available;
2398
2398
2399 .file-name-input {
2399 .file-name-input {
2400 padding: 0.5em 0;
2400 padding: 0.5em 0;
2401 }
2401 }
2402
2402
2403 }
2403 }
2404
2404
2405 ul {
2405 ul {
2406 display: flex;
2406 display: flex;
2407 margin: 0;
2407 margin: 0;
2408 padding: 0;
2408 padding: 0;
2409 width: 100%;
2409 width: 100%;
2410 }
2410 }
2411
2411
2412 li {
2412 li {
2413 list-style-type: none;
2413 list-style-type: none;
2414 }
2414 }
2415
2415
2416 }
2416 }
2417
2417
2418 .editor-items {
2418 .editor-items {
2419 height: 40px;
2419 height: 40px;
2420 margin: 10px 0 -17px 10px;
2420 margin: 10px 0 -17px 10px;
2421
2421
2422 .editor-action {
2422 .editor-action {
2423 cursor: pointer;
2423 cursor: pointer;
2424 }
2424 }
2425
2425
2426 .editor-action.active {
2426 .editor-action.active {
2427 border-bottom: 2px solid #5C5C5C;
2427 border-bottom: 2px solid #5C5C5C;
2428 }
2428 }
2429
2429
2430 li {
2430 li {
2431 list-style-type: none;
2431 list-style-type: none;
2432 }
2432 }
2433 }
2433 }
2434
2434
2435 .edit-file-fieldset .message textarea {
2435 .edit-file-fieldset .message textarea {
2436 border: 1px solid #eeeeee;
2436 border: 1px solid #eeeeee;
2437 }
2437 }
2438
2438
2439 #files_data .codeblock {
2439 #files_data .codeblock {
2440 background-color: #F5F5F5;
2440 background-color: #F5F5F5;
2441 }
2441 }
2442
2442
2443 #editor_preview {
2443 #editor_preview {
2444 background: white;
2444 background: white;
2445 }
2445 }
2446
2446
2447 .show-editor {
2447 .show-editor {
2448 padding: 10px;
2448 padding: 10px;
2449 background-color: white;
2449 background-color: white;
2450
2450
2451 }
2451 }
2452
2452
2453 .show-preview {
2453 .show-preview {
2454 padding: 10px;
2454 padding: 10px;
2455 background-color: white;
2455 background-color: white;
2456 border-left: 1px solid #eeeeee;
2456 border-left: 1px solid #eeeeee;
2457 }
2457 }
2458 // quick filter
2458 // quick filter
2459 .grid-quick-filter {
2459 .grid-quick-filter {
2460 float: right;
2460 float: right;
2461 position: relative;
2461 position: relative;
2462 }
2462 }
2463
2463
2464 .grid-filter-box {
2464 .grid-filter-box {
2465 display: flex;
2465 display: flex;
2466 padding: 0px;
2466 padding: 0px;
2467 border-radius: 3px;
2467 border-radius: 3px;
2468 margin-bottom: 0;
2468 margin-bottom: 0;
2469
2469
2470 a {
2470 a {
2471 border: none !important;
2471 border: none !important;
2472 }
2472 }
2473
2473
2474 li {
2474 li {
2475 list-style-type: none
2475 list-style-type: none
2476 }
2476 }
2477 }
2477 }
2478
2478
2479 .grid-filter-box-icon {
2479 .grid-filter-box-icon {
2480 line-height: 33px;
2480 line-height: 33px;
2481 padding: 0;
2481 padding: 0;
2482 width: 20px;
2482 width: 20px;
2483 position: absolute;
2483 position: absolute;
2484 z-index: 11;
2484 z-index: 11;
2485 left: 5px;
2485 left: 5px;
2486 }
2486 }
2487
2487
2488 .grid-filter-box-input {
2488 .grid-filter-box-input {
2489 margin-right: 0;
2489 margin-right: 0;
2490
2490
2491 input {
2491 input {
2492 border: 1px solid @white;
2492 border: 1px solid @white;
2493 padding-left: 25px;
2493 padding-left: 25px;
2494 width: 145px;
2494 width: 145px;
2495
2495
2496 &:hover {
2496 &:hover {
2497 border-color: @grey6;
2497 border-color: @grey6;
2498 }
2498 }
2499
2499
2500 &:focus {
2500 &:focus {
2501 border-color: @grey5;
2501 border-color: @grey5;
2502 }
2502 }
2503 }
2503 }
2504 }
2504 }
2505
2505
2506
2506
2507
2507
2508 // Search
2508 // Search
2509
2509
2510 .search-form{
2510 .search-form{
2511 #q {
2511 #q {
2512 width: @search-form-width;
2512 width: @search-form-width;
2513 }
2513 }
2514 .fields{
2514 .fields{
2515 margin: 0 0 @space;
2515 margin: 0 0 @space;
2516 }
2516 }
2517
2517
2518 label{
2518 label{
2519 display: inline-block;
2519 display: inline-block;
2520 margin-right: @textmargin;
2520 margin-right: @textmargin;
2521 padding-top: 0.25em;
2521 padding-top: 0.25em;
2522 }
2522 }
2523
2523
2524
2524
2525 .results{
2525 .results{
2526 clear: both;
2526 clear: both;
2527 margin: 0 0 @padding;
2527 margin: 0 0 @padding;
2528 }
2528 }
2529
2529
2530 .search-tags {
2530 .search-tags {
2531 padding: 5px 0;
2531 padding: 5px 0;
2532 }
2532 }
2533 }
2533 }
2534
2534
2535 div.search-feedback-items {
2535 div.search-feedback-items {
2536 display: inline-block;
2536 display: inline-block;
2537 }
2537 }
2538
2538
2539 div.search-code-body {
2539 div.search-code-body {
2540 background-color: #ffffff; padding: 5px 0 5px 10px;
2540 background-color: #ffffff; padding: 5px 0 5px 10px;
2541 pre {
2541 pre {
2542 .match { background-color: #faffa6;}
2542 .match { background-color: #faffa6;}
2543 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2543 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2544 }
2544 }
2545 }
2545 }
2546
2546
2547 .expand_commit.search {
2547 .expand_commit.search {
2548 .show_more.open {
2548 .show_more.open {
2549 height: auto;
2549 height: auto;
2550 max-height: none;
2550 max-height: none;
2551 }
2551 }
2552 }
2552 }
2553
2553
2554 .search-results {
2554 .search-results {
2555
2555
2556 h2 {
2556 h2 {
2557 margin-bottom: 0;
2557 margin-bottom: 0;
2558 }
2558 }
2559 .codeblock {
2559 .codeblock {
2560 border: none;
2560 border: none;
2561 background: transparent;
2561 background: transparent;
2562 }
2562 }
2563
2563
2564 .codeblock-header {
2564 .codeblock-header {
2565 border: none;
2565 border: none;
2566 background: transparent;
2566 background: transparent;
2567 }
2567 }
2568
2568
2569 .code-body {
2569 .code-body {
2570 border: @border-thickness solid @grey6;
2570 border: @border-thickness solid @grey6;
2571 .border-radius(@border-radius);
2571 .border-radius(@border-radius);
2572 }
2572 }
2573
2573
2574 .td-commit {
2574 .td-commit {
2575 &:extend(pre);
2575 &:extend(pre);
2576 border-bottom: @border-thickness solid @border-default-color;
2576 border-bottom: @border-thickness solid @border-default-color;
2577 }
2577 }
2578
2578
2579 .message {
2579 .message {
2580 height: auto;
2580 height: auto;
2581 max-width: 350px;
2581 max-width: 350px;
2582 white-space: normal;
2582 white-space: normal;
2583 text-overflow: initial;
2583 text-overflow: initial;
2584 overflow: visible;
2584 overflow: visible;
2585
2585
2586 .match { background-color: #faffa6;}
2586 .match { background-color: #faffa6;}
2587 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2587 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2588 }
2588 }
2589
2589
2590 .path {
2590 .path {
2591 border-bottom: none !important;
2591 border-bottom: none !important;
2592 border-left: 1px solid @grey6 !important;
2592 border-left: 1px solid @grey6 !important;
2593 border-right: 1px solid @grey6 !important;
2593 border-right: 1px solid @grey6 !important;
2594 }
2594 }
2595 }
2595 }
2596
2596
2597 table.rctable td.td-search-results div {
2597 table.rctable td.td-search-results div {
2598 max-width: 100%;
2598 max-width: 100%;
2599 }
2599 }
2600
2600
2601 #tip-box, .tip-box{
2601 #tip-box, .tip-box{
2602 padding: @menupadding/2;
2602 padding: @menupadding/2;
2603 display: block;
2603 display: block;
2604 border: @border-thickness solid @border-highlight-color;
2604 border: @border-thickness solid @border-highlight-color;
2605 .border-radius(@border-radius);
2605 .border-radius(@border-radius);
2606 background-color: white;
2606 background-color: white;
2607 z-index: 99;
2607 z-index: 99;
2608 white-space: pre-wrap;
2608 white-space: pre-wrap;
2609 }
2609 }
2610
2610
2611 #linktt {
2611 #linktt {
2612 width: 79px;
2612 width: 79px;
2613 }
2613 }
2614
2614
2615 #help_kb .modal-content{
2615 #help_kb .modal-content{
2616 max-width: 750px;
2616 max-width: 750px;
2617 margin: 10% auto;
2617 margin: 10% auto;
2618
2618
2619 table{
2619 table{
2620 td,th{
2620 td,th{
2621 border-bottom: none;
2621 border-bottom: none;
2622 line-height: 2.5em;
2622 line-height: 2.5em;
2623 }
2623 }
2624 th{
2624 th{
2625 padding-bottom: @textmargin/2;
2625 padding-bottom: @textmargin/2;
2626 }
2626 }
2627 td.keys{
2627 td.keys{
2628 text-align: center;
2628 text-align: center;
2629 }
2629 }
2630 }
2630 }
2631
2631
2632 .block-left{
2632 .block-left{
2633 width: 45%;
2633 width: 45%;
2634 margin-right: 5%;
2634 margin-right: 5%;
2635 }
2635 }
2636 .modal-footer{
2636 .modal-footer{
2637 clear: both;
2637 clear: both;
2638 }
2638 }
2639 .key.tag{
2639 .key.tag{
2640 padding: 0.5em;
2640 padding: 0.5em;
2641 background-color: @rcblue;
2641 background-color: @rcblue;
2642 color: white;
2642 color: white;
2643 border-color: @rcblue;
2643 border-color: @rcblue;
2644 .box-shadow(none);
2644 .box-shadow(none);
2645 }
2645 }
2646 }
2646 }
2647
2647
2648
2648
2649
2649
2650 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2650 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2651
2651
2652 @import 'statistics-graph';
2652 @import 'statistics-graph';
2653 @import 'tables';
2653 @import 'tables';
2654 @import 'forms';
2654 @import 'forms';
2655 @import 'diff';
2655 @import 'diff';
2656 @import 'summary';
2656 @import 'summary';
2657 @import 'navigation';
2657 @import 'navigation';
2658
2658
2659 //--- SHOW/HIDE SECTIONS --//
2659 //--- SHOW/HIDE SECTIONS --//
2660
2660
2661 .btn-collapse {
2661 .btn-collapse {
2662 float: right;
2662 float: right;
2663 text-align: right;
2663 text-align: right;
2664 font-family: @text-light;
2664 font-family: @text-light;
2665 font-size: @basefontsize;
2665 font-size: @basefontsize;
2666 cursor: pointer;
2666 cursor: pointer;
2667 border: none;
2667 border: none;
2668 color: @rcblue;
2668 color: @rcblue;
2669 }
2669 }
2670
2670
2671 table.rctable,
2671 table.rctable,
2672 table.dataTable {
2672 table.dataTable {
2673 .btn-collapse {
2673 .btn-collapse {
2674 float: right;
2674 float: right;
2675 text-align: right;
2675 text-align: right;
2676 }
2676 }
2677 }
2677 }
2678
2678
2679 table.rctable {
2679 table.rctable {
2680 &.permissions {
2680 &.permissions {
2681
2681
2682 th.td-owner {
2682 th.td-owner {
2683 padding: 0;
2683 padding: 0;
2684 }
2684 }
2685
2685
2686 th {
2686 th {
2687 font-weight: normal;
2687 font-weight: normal;
2688 padding: 0 5px;
2688 padding: 0 5px;
2689 }
2689 }
2690
2690
2691 }
2691 }
2692 }
2692 }
2693
2693
2694
2694
2695 // TODO: johbo: Fix for IE10, this avoids that we see a border
2695 // TODO: johbo: Fix for IE10, this avoids that we see a border
2696 // and padding around checkboxes and radio boxes. Move to the right place,
2696 // and padding around checkboxes and radio boxes. Move to the right place,
2697 // or better: Remove this once we did the form refactoring.
2697 // or better: Remove this once we did the form refactoring.
2698 input[type=checkbox],
2698 input[type=checkbox],
2699 input[type=radio] {
2699 input[type=radio] {
2700 padding: 0;
2700 padding: 0;
2701 border: none;
2701 border: none;
2702 }
2702 }
2703
2703
2704 .toggle-ajax-spinner{
2704 .toggle-ajax-spinner{
2705 height: 16px;
2705 height: 16px;
2706 width: 16px;
2706 width: 16px;
2707 }
2707 }
2708
2708
2709
2709
2710 .markup-form .clearfix {
2710 .markup-form .clearfix {
2711 .border-radius(@border-radius);
2711 .border-radius(@border-radius);
2712 margin: 0px;
2712 margin: 0px;
2713 }
2713 }
2714
2714
2715 .markup-form-area {
2715 .markup-form-area {
2716 padding: 8px 12px;
2716 padding: 8px 12px;
2717 border: 1px solid @grey4;
2717 border: 1px solid @grey4;
2718 .border-radius(@border-radius);
2718 .border-radius(@border-radius);
2719 }
2719 }
2720
2720
2721 .markup-form-area-header .nav-links {
2721 .markup-form-area-header .nav-links {
2722 display: flex;
2722 display: flex;
2723 flex-flow: row wrap;
2723 flex-flow: row wrap;
2724 -webkit-flex-flow: row wrap;
2724 -webkit-flex-flow: row wrap;
2725 width: 100%;
2725 width: 100%;
2726 }
2726 }
2727
2727
2728 .markup-form-area-footer {
2728 .markup-form-area-footer {
2729 display: flex;
2729 display: flex;
2730 }
2730 }
2731
2731
2732 .markup-form-area-footer .toolbar {
2732 .markup-form-area-footer .toolbar {
2733
2733
2734 }
2734 }
2735
2735
2736 // markup Form
2736 // markup Form
2737 div.markup-form {
2737 div.markup-form {
2738 margin-top: 20px;
2738 margin-top: 20px;
2739 }
2739 }
2740
2740
2741 .markup-form strong {
2741 .markup-form strong {
2742 display: block;
2742 display: block;
2743 margin-bottom: 15px;
2743 margin-bottom: 15px;
2744 }
2744 }
2745
2745
2746 .markup-form textarea {
2746 .markup-form textarea {
2747 width: 100%;
2747 width: 100%;
2748 height: 100px;
2748 height: 100px;
2749 font-family: @text-monospace;
2749 font-family: @text-monospace;
2750 }
2750 }
2751
2751
2752 form.markup-form {
2752 form.markup-form {
2753 margin-top: 10px;
2753 margin-top: 10px;
2754 margin-left: 10px;
2754 margin-left: 10px;
2755 }
2755 }
2756
2756
2757 .markup-form .comment-block-ta,
2757 .markup-form .comment-block-ta,
2758 .markup-form .preview-box {
2758 .markup-form .preview-box {
2759 .border-radius(@border-radius);
2759 .border-radius(@border-radius);
2760 .box-sizing(border-box);
2760 .box-sizing(border-box);
2761 background-color: white;
2761 background-color: white;
2762 }
2762 }
2763
2763
2764 .markup-form .preview-box.unloaded {
2764 .markup-form .preview-box.unloaded {
2765 height: 50px;
2765 height: 50px;
2766 text-align: center;
2766 text-align: center;
2767 padding: 20px;
2767 padding: 20px;
2768 background-color: white;
2768 background-color: white;
2769 }
2769 }
2770
2770
2771
2771
2772 .dropzone-wrapper {
2772 .dropzone-wrapper {
2773 border: 1px solid @grey5;
2773 border: 1px solid @grey5;
2774 padding: 20px;
2774 padding: 20px;
2775 }
2775 }
2776
2776
2777 .dropzone,
2777 .dropzone,
2778 .dropzone-pure {
2778 .dropzone-pure {
2779 border: 2px dashed @grey5;
2779 border: 2px dashed @grey5;
2780 border-radius: 5px;
2780 border-radius: 5px;
2781 background: white;
2781 background: white;
2782 min-height: 200px;
2782 min-height: 200px;
2783 padding: 54px;
2783 padding: 54px;
2784
2784
2785 .dz-message {
2785 .dz-message {
2786 font-weight: 700;
2786 font-weight: 700;
2787 text-align: center;
2787 text-align: center;
2788 margin: 2em 0;
2788 margin: 2em 0;
2789 }
2789 }
2790
2790
2791 }
2791 }
2792
2792
2793 .dz-preview {
2793 .dz-preview {
2794 margin: 10px 0 !important;
2794 margin: 10px 0 !important;
2795 position: relative;
2795 position: relative;
2796 vertical-align: top;
2796 vertical-align: top;
2797 padding: 10px;
2797 padding: 10px;
2798 border-bottom: 1px solid @grey5;
2798 border-bottom: 1px solid @grey5;
2799 }
2799 }
2800
2800
2801 .dz-filename {
2801 .dz-filename {
2802 font-weight: 700;
2802 font-weight: 700;
2803 float: left;
2803 float: left;
2804 }
2804 }
2805
2805
2806 .dz-sending {
2806 .dz-sending {
2807 float: right;
2807 float: right;
2808 }
2808 }
2809
2809
2810 .dz-response {
2810 .dz-response {
2811 clear: both
2811 clear: both
2812 }
2812 }
2813
2813
2814 .dz-filename-size {
2814 .dz-filename-size {
2815 float: right
2815 float: right
2816 }
2816 }
2817
2817
2818 .dz-error-message {
2818 .dz-error-message {
2819 color: @alert2;
2819 color: @alert2;
2820 padding-top: 10px;
2820 padding-top: 10px;
2821 clear: both;
2821 clear: both;
2822 }
2822 }
2823
2823
2824
2824
2825 .user-hovercard {
2825 .user-hovercard {
2826 padding: 5px;
2826 padding: 5px;
2827 }
2827 }
2828
2828
2829 .user-hovercard-icon {
2829 .user-hovercard-icon {
2830 display: inline;
2830 display: inline;
2831 padding: 0;
2831 padding: 0;
2832 box-sizing: content-box;
2832 box-sizing: content-box;
2833 border-radius: 50%;
2833 border-radius: 50%;
2834 float: left;
2834 float: left;
2835 }
2835 }
2836
2836
2837 .user-hovercard-name {
2837 .user-hovercard-name {
2838 float: right;
2838 float: right;
2839 vertical-align: top;
2839 vertical-align: top;
2840 padding-left: 10px;
2840 padding-left: 10px;
2841 min-width: 150px;
2841 min-width: 150px;
2842 }
2842 }
2843
2843
2844 .user-hovercard-bio {
2844 .user-hovercard-bio {
2845 clear: both;
2845 clear: both;
2846 padding-top: 10px;
2846 padding-top: 10px;
2847 }
2847 }
2848
2848
2849 .user-hovercard-header {
2849 .user-hovercard-header {
2850 clear: both;
2850 clear: both;
2851 min-height: 10px;
2851 min-height: 10px;
2852 }
2852 }
2853
2853
2854 .user-hovercard-footer {
2854 .user-hovercard-footer {
2855 clear: both;
2855 clear: both;
2856 min-height: 10px;
2856 min-height: 10px;
2857 }
2857 }
2858
2858
2859 .user-group-hovercard {
2859 .user-group-hovercard {
2860 padding: 5px;
2860 padding: 5px;
2861 }
2861 }
2862
2862
2863 .user-group-hovercard-icon {
2863 .user-group-hovercard-icon {
2864 display: inline;
2864 display: inline;
2865 padding: 0;
2865 padding: 0;
2866 box-sizing: content-box;
2866 box-sizing: content-box;
2867 border-radius: 50%;
2867 border-radius: 50%;
2868 float: left;
2868 float: left;
2869 }
2869 }
2870
2870
2871 .user-group-hovercard-name {
2871 .user-group-hovercard-name {
2872 float: left;
2872 float: left;
2873 vertical-align: top;
2873 vertical-align: top;
2874 padding-left: 10px;
2874 padding-left: 10px;
2875 min-width: 150px;
2875 min-width: 150px;
2876 }
2876 }
2877
2877
2878 .user-group-hovercard-icon i {
2878 .user-group-hovercard-icon i {
2879 border: 1px solid @grey4;
2879 border: 1px solid @grey4;
2880 border-radius: 4px;
2880 border-radius: 4px;
2881 }
2881 }
2882
2882
2883 .user-group-hovercard-bio {
2883 .user-group-hovercard-bio {
2884 clear: both;
2884 clear: both;
2885 padding-top: 10px;
2885 padding-top: 10px;
2886 line-height: 1.0em;
2886 line-height: 1.0em;
2887 }
2887 }
2888
2888
2889 .user-group-hovercard-header {
2889 .user-group-hovercard-header {
2890 clear: both;
2890 clear: both;
2891 min-height: 10px;
2891 min-height: 10px;
2892 }
2892 }
2893
2893
2894 .user-group-hovercard-footer {
2894 .user-group-hovercard-footer {
2895 clear: both;
2895 clear: both;
2896 min-height: 10px;
2896 min-height: 10px;
2897 }
2897 }
2898
2899 .pr-hovercard-header {
2900 clear: both;
2901 display: block;
2902 line-height: 20px;
2903 }
2904
2905 .pr-hovercard-user {
2906 display: flex;
2907 align-items: center;
2908 padding-left: 5px;
2909 }
2910
2911 .pr-hovercard-title {
2912 padding-top: 5px;
2913 } No newline at end of file
@@ -1,387 +1,388 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('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 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_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 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_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
34 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
35 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
35 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
36 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
36 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
37 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
37 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
38 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
38 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
39 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
39 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
40 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
40 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
41 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
41 pyroutes.register('admin_home', '/_admin', []);
42 pyroutes.register('admin_home', '/_admin', []);
42 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
43 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
43 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
44 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
44 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
45 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
45 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
48 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
48 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
49 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
49 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
50 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
50 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
51 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
51 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
52 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
52 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
53 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
53 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
54 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
54 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
55 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
55 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
56 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
56 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
57 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
57 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
58 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
58 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
59 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
59 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
60 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
60 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
61 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
61 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
62 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
62 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
63 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
63 pyroutes.register('admin_settings', '/_admin/settings', []);
64 pyroutes.register('admin_settings', '/_admin/settings', []);
64 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
65 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
65 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
66 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
66 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
67 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
67 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
68 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
68 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
69 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
69 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
70 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
70 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
71 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
71 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
72 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
72 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
73 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
73 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
74 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
74 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
75 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
75 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
76 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
76 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
77 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
77 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
78 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
78 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
79 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
79 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
80 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
80 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
81 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
81 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
82 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
82 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
83 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
83 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
84 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
84 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
85 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
85 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
86 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
86 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
87 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
87 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
88 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
88 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
89 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
89 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
90 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
90 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
91 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
91 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
92 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
92 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
93 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
93 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
94 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
94 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
95 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
95 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
96 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
96 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
97 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
97 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
98 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
98 pyroutes.register('users', '/_admin/users', []);
99 pyroutes.register('users', '/_admin/users', []);
99 pyroutes.register('users_data', '/_admin/users_data', []);
100 pyroutes.register('users_data', '/_admin/users_data', []);
100 pyroutes.register('users_create', '/_admin/users/create', []);
101 pyroutes.register('users_create', '/_admin/users/create', []);
101 pyroutes.register('users_new', '/_admin/users/new', []);
102 pyroutes.register('users_new', '/_admin/users/new', []);
102 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
103 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
103 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
104 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
104 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
105 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
105 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
106 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
106 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
107 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
107 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
108 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
108 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
109 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
109 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
110 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
110 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
111 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
111 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
112 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
116 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
117 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
117 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
118 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
118 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
119 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
119 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
120 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
120 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
121 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
121 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
122 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
122 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
123 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
123 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
124 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
124 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
125 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
125 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
126 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
126 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
127 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
127 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
128 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
128 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
129 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
129 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
130 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
130 pyroutes.register('user_groups', '/_admin/user_groups', []);
131 pyroutes.register('user_groups', '/_admin/user_groups', []);
131 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
132 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
132 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
133 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
133 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
134 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
134 pyroutes.register('repos', '/_admin/repos', []);
135 pyroutes.register('repos', '/_admin/repos', []);
135 pyroutes.register('repo_new', '/_admin/repos/new', []);
136 pyroutes.register('repo_new', '/_admin/repos/new', []);
136 pyroutes.register('repo_create', '/_admin/repos/create', []);
137 pyroutes.register('repo_create', '/_admin/repos/create', []);
137 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
138 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
138 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
139 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
139 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
140 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
140 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
141 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
141 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
142 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
142 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
143 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
143 pyroutes.register('channelstream_proxy', '/_channelstream', []);
144 pyroutes.register('channelstream_proxy', '/_channelstream', []);
144 pyroutes.register('upload_file', '/_file_store/upload', []);
145 pyroutes.register('upload_file', '/_file_store/upload', []);
145 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
146 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
146 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
147 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
147 pyroutes.register('logout', '/_admin/logout', []);
148 pyroutes.register('logout', '/_admin/logout', []);
148 pyroutes.register('reset_password', '/_admin/password_reset', []);
149 pyroutes.register('reset_password', '/_admin/password_reset', []);
149 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
150 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
150 pyroutes.register('home', '/', []);
151 pyroutes.register('home', '/', []);
151 pyroutes.register('user_autocomplete_data', '/_users', []);
152 pyroutes.register('user_autocomplete_data', '/_users', []);
152 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
153 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
153 pyroutes.register('repo_list_data', '/_repos', []);
154 pyroutes.register('repo_list_data', '/_repos', []);
154 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
155 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
155 pyroutes.register('goto_switcher_data', '/_goto_data', []);
156 pyroutes.register('goto_switcher_data', '/_goto_data', []);
156 pyroutes.register('markup_preview', '/_markup_preview', []);
157 pyroutes.register('markup_preview', '/_markup_preview', []);
157 pyroutes.register('file_preview', '/_file_preview', []);
158 pyroutes.register('file_preview', '/_file_preview', []);
158 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
159 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
159 pyroutes.register('journal', '/_admin/journal', []);
160 pyroutes.register('journal', '/_admin/journal', []);
160 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
161 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
161 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
162 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
162 pyroutes.register('journal_public', '/_admin/public_journal', []);
163 pyroutes.register('journal_public', '/_admin/public_journal', []);
163 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
164 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
164 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
165 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
165 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
166 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
166 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
167 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
167 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
168 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
168 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
169 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
169 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
170 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
170 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
171 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
171 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
172 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
172 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
183 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
183 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
184 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
184 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
185 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
185 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
186 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
186 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
187 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
187 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
190 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
190 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
195 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
195 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
209 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
209 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
210 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
210 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
211 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
211 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
212 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
212 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
214 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
214 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
215 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
215 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
216 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
216 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
217 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
217 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
218 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
218 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']);
219 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']);
219 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
220 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
220 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
221 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
221 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
222 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
222 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
223 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
223 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
224 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
224 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
225 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
225 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
226 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
226 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
228 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
228 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
229 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
229 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
230 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
230 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
231 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
231 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
232 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
232 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
233 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
233 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
234 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
234 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
235 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
235 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
236 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
236 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
237 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
237 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']);
238 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']);
238 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
239 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
239 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
240 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
240 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
241 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
241 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
242 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
242 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
243 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
243 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
244 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
244 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
245 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
245 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
246 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
246 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
247 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
247 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
248 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
248 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
249 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
249 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
250 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
250 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
251 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
251 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
252 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
252 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
253 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
253 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
254 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
254 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
255 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
255 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
256 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
256 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
257 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
257 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
258 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
258 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
259 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
259 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
260 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
260 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
261 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
261 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
262 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
262 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
263 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
263 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
264 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
264 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
265 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
265 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
266 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
266 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
267 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
267 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
268 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
268 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
269 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
269 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
270 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
270 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
271 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
271 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
272 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
272 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
273 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
273 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
274 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
274 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
275 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
275 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
276 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
276 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
277 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
277 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
278 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
278 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
279 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
279 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
280 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
280 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
281 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
281 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
282 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
282 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
283 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
283 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
284 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
284 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
285 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
285 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
286 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
286 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
287 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
287 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
288 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
288 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
289 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
289 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
290 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
290 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
291 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
291 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
292 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
292 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
293 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
293 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
294 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
294 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
295 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
295 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
296 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
296 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
297 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
297 pyroutes.register('search', '/_admin/search', []);
298 pyroutes.register('search', '/_admin/search', []);
298 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
299 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
299 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
300 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
300 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
301 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
301 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
302 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
302 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
303 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
303 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
304 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
304 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
305 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
305 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
306 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
306 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
307 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
307 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
308 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
308 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
309 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
309 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
310 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
310 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
311 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
311 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
312 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
312 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
313 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
313 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
314 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
314 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
315 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
315 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
316 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
316 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
317 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
317 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
318 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
318 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
319 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
319 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
320 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
320 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
321 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
321 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
322 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
322 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
323 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
323 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
324 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
324 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
325 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
325 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
326 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
326 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
327 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
327 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
328 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
328 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
329 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
329 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
330 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
330 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
331 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
331 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
332 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
332 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
333 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
333 pyroutes.register('gists_show', '/_admin/gists', []);
334 pyroutes.register('gists_show', '/_admin/gists', []);
334 pyroutes.register('gists_new', '/_admin/gists/new', []);
335 pyroutes.register('gists_new', '/_admin/gists/new', []);
335 pyroutes.register('gists_create', '/_admin/gists/create', []);
336 pyroutes.register('gists_create', '/_admin/gists/create', []);
336 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
337 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
337 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
338 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
338 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
339 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
339 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
340 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
340 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
341 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
341 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
342 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
342 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
343 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
343 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
344 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
344 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
345 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
345 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
346 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
346 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
347 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
347 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
348 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
348 pyroutes.register('apiv2', '/_admin/api', []);
349 pyroutes.register('apiv2', '/_admin/api', []);
349 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
350 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
350 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
351 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
351 pyroutes.register('login', '/_admin/login', []);
352 pyroutes.register('login', '/_admin/login', []);
352 pyroutes.register('register', '/_admin/register', []);
353 pyroutes.register('register', '/_admin/register', []);
353 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
354 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
354 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
355 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
355 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
356 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
356 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
357 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
357 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
358 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
358 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
359 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
359 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
360 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
360 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
361 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
361 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
362 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
362 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
363 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
363 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
364 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
364 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
365 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
365 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
366 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
366 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
367 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
367 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
368 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
368 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
369 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
369 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
370 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
370 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
371 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
371 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
372 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
372 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
373 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
373 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
374 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
374 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
375 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
375 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
376 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
376 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
377 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
377 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
378 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
378 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
379 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
379 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
380 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
380 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
381 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
381 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
382 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
382 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
383 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
383 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
384 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
384 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
385 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
385 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
386 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
386 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
387 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
387 }
388 }
General Comments 0
You need to be logged in to leave comments. Login now