##// END OF EJS Templates
observers: hide it for CE edition....
marcink -
r4516:6a883584 stable
parent child Browse files
Show More
@@ -1,111 +1,111 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 from rhodecode.lib import helpers as h, rc_cache
21 from rhodecode.lib import helpers as h, rc_cache
22 from rhodecode.lib.utils2 import safe_int
22 from rhodecode.lib.utils2 import safe_int
23 from rhodecode.model.pull_request import get_diff_info
23 from rhodecode.model.pull_request import get_diff_info
24 from rhodecode.model.db import PullRequestReviewers
24 from rhodecode.model.db import PullRequestReviewers
25 # V3 - Reviewers, with default rules data
25 # V3 - Reviewers, with default rules data
26 # v4 - Added observers metadata
26 # v4 - Added observers metadata
27 REVIEWER_API_VERSION = 'V4'
27 REVIEWER_API_VERSION = 'V4'
28
28
29
29
30 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
30 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
31 """
31 """
32 Returns json struct of a reviewer for frontend
32 Returns json struct of a reviewer for frontend
33
33
34 :param user: the reviewer
34 :param user: the reviewer
35 :param reasons: list of strings of why they are reviewers
35 :param reasons: list of strings of why they are reviewers
36 :param mandatory: bool, to set user as mandatory
36 :param mandatory: bool, to set user as mandatory
37 """
37 """
38 role = role or PullRequestReviewers.ROLE_REVIEWER
38 role = role or PullRequestReviewers.ROLE_REVIEWER
39 if role not in PullRequestReviewers.ROLES:
39 if role not in PullRequestReviewers.ROLES:
40 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
40 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
41
41
42 return {
42 return {
43 'user_id': user.user_id,
43 'user_id': user.user_id,
44 'reasons': reasons or [],
44 'reasons': reasons or [],
45 'rules': rules or [],
45 'rules': rules or [],
46 'role': role,
46 'role': role,
47 'mandatory': mandatory,
47 'mandatory': mandatory,
48 'user_group': user_group,
48 'user_group': user_group,
49 'username': user.username,
49 'username': user.username,
50 'first_name': user.first_name,
50 'first_name': user.first_name,
51 'last_name': user.last_name,
51 'last_name': user.last_name,
52 'user_link': h.link_to_user(user),
52 'user_link': h.link_to_user(user),
53 'gravatar_link': h.gravatar_url(user.email, 14),
53 'gravatar_link': h.gravatar_url(user.email, 14),
54 }
54 }
55
55
56
56
57 def to_reviewers(e):
57 def to_reviewers(e):
58 if isinstance(e, (tuple, list)):
58 if isinstance(e, (tuple, list)):
59 return map(reviewer_as_json, e)
59 return map(reviewer_as_json, e)
60 else:
60 else:
61 return reviewer_as_json(e)
61 return reviewer_as_json(e)
62
62
63
63
64 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
64 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
65 include_diff_info=True):
65 include_diff_info=True):
66 """
66 """
67 Return json for default reviewers of a repository
67 Return json for default reviewers of a repository
68 """
68 """
69
69
70 diff_info = {}
70 diff_info = {}
71 if include_diff_info:
71 if include_diff_info:
72 diff_info = get_diff_info(
72 diff_info = get_diff_info(
73 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
73 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
74
74
75 reasons = ['Default reviewer', 'Repository owner']
75 reasons = ['Default reviewer', 'Repository owner']
76 json_reviewers = [reviewer_as_json(
76 json_reviewers = [reviewer_as_json(
77 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
77 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
78
78
79 compute_key = rc_cache.utils.compute_key_from_params(
79 compute_key = rc_cache.utils.compute_key_from_params(
80 current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
80 current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
81 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
81 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
82 target_ref.commit_id)
82 target_ref.commit_id)
83
83
84 return {
84 return {
85 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
85 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
86 'compute_key': compute_key,
86 'compute_key': compute_key,
87 'diff_info': diff_info,
87 'diff_info': diff_info,
88 'reviewers': json_reviewers,
88 'reviewers': json_reviewers,
89 'rules': {},
89 'rules': {},
90 'rules_data': {},
90 'rules_data': {},
91 }
91 }
92
92
93
93
94 def validate_default_reviewers(review_members, reviewer_rules):
94 def validate_default_reviewers(review_members, reviewer_rules):
95 """
95 """
96 Function to validate submitted reviewers against the saved rules
96 Function to validate submitted reviewers against the saved rules
97 """
97 """
98 reviewers = []
98 reviewers = []
99 reviewer_by_id = {}
99 reviewer_by_id = {}
100 for r in review_members:
100 for r in review_members:
101 reviewer_user_id = safe_int(r['user_id'])
101 reviewer_user_id = safe_int(r['user_id'])
102 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
102 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
103
103
104 reviewer_by_id[reviewer_user_id] = entry
104 reviewer_by_id[reviewer_user_id] = entry
105 reviewers.append(entry)
105 reviewers.append(entry)
106
106
107 return reviewers
107 return reviewers
108
108
109
109
110 def validate_observers(observer_members):
110 def validate_observers(observer_members, reviewer_rules):
111 return {}
111 return {}
@@ -1,767 +1,768 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26 import time
26 import time
27
27
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 import pyramid.events
29 import pyramid.events
30 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response
37
37
38 from rhodecode.model import meta
38 from rhodecode.model import meta
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config import utils as config_utils
40 from rhodecode.config import utils as config_utils
41 from rhodecode.config.environment import load_pyramid_environment
41 from rhodecode.config.environment import load_pyramid_environment
42
42
43 import rhodecode.events
43 import rhodecode.events
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
46 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.celerylib.loader import configure_celery
50 from rhodecode.lib.celerylib.loader import configure_celery
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.lib.exc_tracking import store_exception
54 from rhodecode.subscribers import (
54 from rhodecode.subscribers import (
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 write_metadata_if_needed, write_usage_data, inject_app_settings)
56 write_metadata_if_needed, write_usage_data, inject_app_settings)
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 def is_http_error(response):
62 def is_http_error(response):
63 # error which should have traceback
63 # error which should have traceback
64 return response.status_code > 499
64 return response.status_code > 499
65
65
66
66
67 def should_load_all():
67 def should_load_all():
68 """
68 """
69 Returns if all application components should be loaded. In some cases it's
69 Returns if all application components should be loaded. In some cases it's
70 desired to skip apps loading for faster shell script execution
70 desired to skip apps loading for faster shell script execution
71 """
71 """
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 if ssh_cmd:
73 if ssh_cmd:
74 return False
74 return False
75
75
76 return True
76 return True
77
77
78
78
79 def make_pyramid_app(global_config, **settings):
79 def make_pyramid_app(global_config, **settings):
80 """
80 """
81 Constructs the WSGI application based on Pyramid.
81 Constructs the WSGI application based on Pyramid.
82
82
83 Specials:
83 Specials:
84
84
85 * The application can also be integrated like a plugin via the call to
85 * The application can also be integrated like a plugin via the call to
86 `includeme`. This is accompanied with the other utility functions which
86 `includeme`. This is accompanied with the other utility functions which
87 are called. Changing this should be done with great care to not break
87 are called. Changing this should be done with great care to not break
88 cases when these fragments are assembled from another place.
88 cases when these fragments are assembled from another place.
89
89
90 """
90 """
91
91
92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
93 # will be replaced by the value of the environment variable "NAME" in this case.
93 # will be replaced by the value of the environment variable "NAME" in this case.
94 start_time = time.time()
94 start_time = time.time()
95
95
96 debug = asbool(global_config.get('debug'))
96 debug = asbool(global_config.get('debug'))
97 if debug:
97 if debug:
98 enable_debug()
98 enable_debug()
99
99
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101
101
102 global_config = _substitute_values(global_config, environ)
102 global_config = _substitute_values(global_config, environ)
103 settings = _substitute_values(settings, environ)
103 settings = _substitute_values(settings, environ)
104
104
105 sanitize_settings_and_apply_defaults(global_config, settings)
105 sanitize_settings_and_apply_defaults(global_config, settings)
106
106
107 config = Configurator(settings=settings)
107 config = Configurator(settings=settings)
108
108
109 # Apply compatibility patches
109 # Apply compatibility patches
110 patches.inspect_getargspec()
110 patches.inspect_getargspec()
111
111
112 load_pyramid_environment(global_config, settings)
112 load_pyramid_environment(global_config, settings)
113
113
114 # Static file view comes first
114 # Static file view comes first
115 includeme_first(config)
115 includeme_first(config)
116
116
117 includeme(config)
117 includeme(config)
118
118
119 pyramid_app = config.make_wsgi_app()
119 pyramid_app = config.make_wsgi_app()
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 pyramid_app.config = config
121 pyramid_app.config = config
122
122
123 config.configure_celery(global_config['__file__'])
123 config.configure_celery(global_config['__file__'])
124 # creating the app uses a connection - return it after we are done
124 # creating the app uses a connection - return it after we are done
125 meta.Session.remove()
125 meta.Session.remove()
126 total_time = time.time() - start_time
126 total_time = time.time() - start_time
127 log.info('Pyramid app `%s` created and configured in %.2fs',
127 log.info('Pyramid app `%s` created and configured in %.2fs',
128 pyramid_app.func_name, total_time)
128 pyramid_app.func_name, total_time)
129
129
130 return pyramid_app
130 return pyramid_app
131
131
132
132
133 def not_found_view(request):
133 def not_found_view(request):
134 """
134 """
135 This creates the view which should be registered as not-found-view to
135 This creates the view which should be registered as not-found-view to
136 pyramid.
136 pyramid.
137 """
137 """
138
138
139 if not getattr(request, 'vcs_call', None):
139 if not getattr(request, 'vcs_call', None):
140 # handle like regular case with our error_handler
140 # handle like regular case with our error_handler
141 return error_handler(HTTPNotFound(), request)
141 return error_handler(HTTPNotFound(), request)
142
142
143 # handle not found view as a vcs call
143 # handle not found view as a vcs call
144 settings = request.registry.settings
144 settings = request.registry.settings
145 ae_client = getattr(request, 'ae_client', None)
145 ae_client = getattr(request, 'ae_client', None)
146 vcs_app = VCSMiddleware(
146 vcs_app = VCSMiddleware(
147 HTTPNotFound(), request.registry, settings,
147 HTTPNotFound(), request.registry, settings,
148 appenlight_client=ae_client)
148 appenlight_client=ae_client)
149
149
150 return wsgiapp(vcs_app)(None, request)
150 return wsgiapp(vcs_app)(None, request)
151
151
152
152
153 def error_handler(exception, request):
153 def error_handler(exception, request):
154 import rhodecode
154 import rhodecode
155 from rhodecode.lib import helpers
155 from rhodecode.lib import helpers
156
156
157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
158
158
159 base_response = HTTPInternalServerError()
159 base_response = HTTPInternalServerError()
160 # prefer original exception for the response since it may have headers set
160 # prefer original exception for the response since it may have headers set
161 if isinstance(exception, HTTPException):
161 if isinstance(exception, HTTPException):
162 base_response = exception
162 base_response = exception
163 elif isinstance(exception, VCSCommunicationError):
163 elif isinstance(exception, VCSCommunicationError):
164 base_response = VCSServerUnavailable()
164 base_response = VCSServerUnavailable()
165
165
166 if is_http_error(base_response):
166 if is_http_error(base_response):
167 log.exception(
167 log.exception(
168 'error occurred handling this request for path: %s', request.path)
168 'error occurred handling this request for path: %s', request.path)
169
169
170 error_explanation = base_response.explanation or str(base_response)
170 error_explanation = base_response.explanation or str(base_response)
171 if base_response.status_code == 404:
171 if base_response.status_code == 404:
172 error_explanation += " Optionally you don't have permission to access this page."
172 error_explanation += " Optionally you don't have permission to access this page."
173 c = AttributeDict()
173 c = AttributeDict()
174 c.error_message = base_response.status
174 c.error_message = base_response.status
175 c.error_explanation = error_explanation
175 c.error_explanation = error_explanation
176 c.visual = AttributeDict()
176 c.visual = AttributeDict()
177
177
178 c.visual.rhodecode_support_url = (
178 c.visual.rhodecode_support_url = (
179 request.registry.settings.get('rhodecode_support_url') or
179 request.registry.settings.get('rhodecode_support_url') or
180 request.route_url('rhodecode_support')
180 request.route_url('rhodecode_support')
181 )
181 )
182 c.redirect_time = 0
182 c.redirect_time = 0
183 c.rhodecode_name = rhodecode_title
183 c.rhodecode_name = rhodecode_title
184 if not c.rhodecode_name:
184 if not c.rhodecode_name:
185 c.rhodecode_name = 'Rhodecode'
185 c.rhodecode_name = 'Rhodecode'
186
186
187 c.causes = []
187 c.causes = []
188 if is_http_error(base_response):
188 if is_http_error(base_response):
189 c.causes.append('Server is overloaded.')
189 c.causes.append('Server is overloaded.')
190 c.causes.append('Server database connection is lost.')
190 c.causes.append('Server database connection is lost.')
191 c.causes.append('Server expected unhandled error.')
191 c.causes.append('Server expected unhandled error.')
192
192
193 if hasattr(base_response, 'causes'):
193 if hasattr(base_response, 'causes'):
194 c.causes = base_response.causes
194 c.causes = base_response.causes
195
195
196 c.messages = helpers.flash.pop_messages(request=request)
196 c.messages = helpers.flash.pop_messages(request=request)
197
197
198 exc_info = sys.exc_info()
198 exc_info = sys.exc_info()
199 c.exception_id = id(exc_info)
199 c.exception_id = id(exc_info)
200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
201 or base_response.status_code > 499
201 or base_response.status_code > 499
202 c.exception_id_url = request.route_url(
202 c.exception_id_url = request.route_url(
203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
204
204
205 if c.show_exception_id:
205 if c.show_exception_id:
206 store_exception(c.exception_id, exc_info)
206 store_exception(c.exception_id, exc_info)
207
207
208 response = render_to_response(
208 response = render_to_response(
209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
210 response=base_response)
210 response=base_response)
211
211
212 return response
212 return response
213
213
214
214
215 def includeme_first(config):
215 def includeme_first(config):
216 # redirect automatic browser favicon.ico requests to correct place
216 # redirect automatic browser favicon.ico requests to correct place
217 def favicon_redirect(context, request):
217 def favicon_redirect(context, request):
218 return HTTPFound(
218 return HTTPFound(
219 request.static_path('rhodecode:public/images/favicon.ico'))
219 request.static_path('rhodecode:public/images/favicon.ico'))
220
220
221 config.add_view(favicon_redirect, route_name='favicon')
221 config.add_view(favicon_redirect, route_name='favicon')
222 config.add_route('favicon', '/favicon.ico')
222 config.add_route('favicon', '/favicon.ico')
223
223
224 def robots_redirect(context, request):
224 def robots_redirect(context, request):
225 return HTTPFound(
225 return HTTPFound(
226 request.static_path('rhodecode:public/robots.txt'))
226 request.static_path('rhodecode:public/robots.txt'))
227
227
228 config.add_view(robots_redirect, route_name='robots')
228 config.add_view(robots_redirect, route_name='robots')
229 config.add_route('robots', '/robots.txt')
229 config.add_route('robots', '/robots.txt')
230
230
231 config.add_static_view(
231 config.add_static_view(
232 '_static/deform', 'deform:static')
232 '_static/deform', 'deform:static')
233 config.add_static_view(
233 config.add_static_view(
234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
235
235
236
236
237 def includeme(config):
237 def includeme(config):
238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
239 settings = config.registry.settings
239 settings = config.registry.settings
240 config.set_request_factory(Request)
240 config.set_request_factory(Request)
241
241
242 # plugin information
242 # plugin information
243 config.registry.rhodecode_plugins = collections.OrderedDict()
243 config.registry.rhodecode_plugins = collections.OrderedDict()
244
244
245 config.add_directive(
245 config.add_directive(
246 'register_rhodecode_plugin', register_rhodecode_plugin)
246 'register_rhodecode_plugin', register_rhodecode_plugin)
247
247
248 config.add_directive('configure_celery', configure_celery)
248 config.add_directive('configure_celery', configure_celery)
249
249
250 if asbool(settings.get('appenlight', 'false')):
250 if asbool(settings.get('appenlight', 'false')):
251 config.include('appenlight_client.ext.pyramid_tween')
251 config.include('appenlight_client.ext.pyramid_tween')
252
252
253 load_all = should_load_all()
253 load_all = should_load_all()
254
254
255 # Includes which are required. The application would fail without them.
255 # Includes which are required. The application would fail without them.
256 config.include('pyramid_mako')
256 config.include('pyramid_mako')
257 config.include('rhodecode.lib.rc_beaker')
257 config.include('rhodecode.lib.rc_beaker')
258 config.include('rhodecode.lib.rc_cache')
258 config.include('rhodecode.lib.rc_cache')
259
259
260 config.include('rhodecode.apps._base.navigation')
260 config.include('rhodecode.apps._base.navigation')
261 config.include('rhodecode.apps._base.subscribers')
261 config.include('rhodecode.apps._base.subscribers')
262 config.include('rhodecode.tweens')
262 config.include('rhodecode.tweens')
263 config.include('rhodecode.authentication')
263 config.include('rhodecode.authentication')
264
264
265 if load_all:
265 if load_all:
266 config.include('rhodecode.integrations')
266 config.include('rhodecode.integrations')
267
267
268 if load_all:
268 if load_all:
269 # load CE authentication plugins
269 # load CE authentication plugins
270 config.include('rhodecode.authentication.plugins.auth_crowd')
270 config.include('rhodecode.authentication.plugins.auth_crowd')
271 config.include('rhodecode.authentication.plugins.auth_headers')
271 config.include('rhodecode.authentication.plugins.auth_headers')
272 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
272 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
273 config.include('rhodecode.authentication.plugins.auth_ldap')
273 config.include('rhodecode.authentication.plugins.auth_ldap')
274 config.include('rhodecode.authentication.plugins.auth_pam')
274 config.include('rhodecode.authentication.plugins.auth_pam')
275 config.include('rhodecode.authentication.plugins.auth_rhodecode')
275 config.include('rhodecode.authentication.plugins.auth_rhodecode')
276 config.include('rhodecode.authentication.plugins.auth_token')
276 config.include('rhodecode.authentication.plugins.auth_token')
277
277
278 # Auto discover authentication plugins and include their configuration.
278 # Auto discover authentication plugins and include their configuration.
279 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
279 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
280 from rhodecode.authentication import discover_legacy_plugins
280 from rhodecode.authentication import discover_legacy_plugins
281 discover_legacy_plugins(config)
281 discover_legacy_plugins(config)
282
282
283 # apps
283 # apps
284 if load_all:
284 if load_all:
285 config.include('rhodecode.apps._base')
285 config.include('rhodecode.apps._base')
286 config.include('rhodecode.apps.hovercards')
286 config.include('rhodecode.apps.hovercards')
287 config.include('rhodecode.apps.ops')
287 config.include('rhodecode.apps.ops')
288 config.include('rhodecode.apps.admin')
288 config.include('rhodecode.apps.admin')
289 config.include('rhodecode.apps.channelstream')
289 config.include('rhodecode.apps.channelstream')
290 config.include('rhodecode.apps.file_store')
290 config.include('rhodecode.apps.file_store')
291 config.include('rhodecode.apps.login')
291 config.include('rhodecode.apps.login')
292 config.include('rhodecode.apps.home')
292 config.include('rhodecode.apps.home')
293 config.include('rhodecode.apps.journal')
293 config.include('rhodecode.apps.journal')
294 config.include('rhodecode.apps.repository')
294 config.include('rhodecode.apps.repository')
295 config.include('rhodecode.apps.repo_group')
295 config.include('rhodecode.apps.repo_group')
296 config.include('rhodecode.apps.user_group')
296 config.include('rhodecode.apps.user_group')
297 config.include('rhodecode.apps.search')
297 config.include('rhodecode.apps.search')
298 config.include('rhodecode.apps.user_profile')
298 config.include('rhodecode.apps.user_profile')
299 config.include('rhodecode.apps.user_group_profile')
299 config.include('rhodecode.apps.user_group_profile')
300 config.include('rhodecode.apps.my_account')
300 config.include('rhodecode.apps.my_account')
301 config.include('rhodecode.apps.svn_support')
301 config.include('rhodecode.apps.svn_support')
302 config.include('rhodecode.apps.ssh_support')
302 config.include('rhodecode.apps.ssh_support')
303 config.include('rhodecode.apps.gist')
303 config.include('rhodecode.apps.gist')
304 config.include('rhodecode.apps.debug_style')
304 config.include('rhodecode.apps.debug_style')
305 config.include('rhodecode.api')
305 config.include('rhodecode.api')
306
306
307 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
307 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
308 config.add_translation_dirs('rhodecode:i18n/')
308 config.add_translation_dirs('rhodecode:i18n/')
309 settings['default_locale_name'] = settings.get('lang', 'en')
309 settings['default_locale_name'] = settings.get('lang', 'en')
310
310
311 # Add subscribers.
311 # Add subscribers.
312 if load_all:
312 if load_all:
313 config.add_subscriber(inject_app_settings,
313 config.add_subscriber(inject_app_settings,
314 pyramid.events.ApplicationCreated)
314 pyramid.events.ApplicationCreated)
315 config.add_subscriber(scan_repositories_if_enabled,
315 config.add_subscriber(scan_repositories_if_enabled,
316 pyramid.events.ApplicationCreated)
316 pyramid.events.ApplicationCreated)
317 config.add_subscriber(write_metadata_if_needed,
317 config.add_subscriber(write_metadata_if_needed,
318 pyramid.events.ApplicationCreated)
318 pyramid.events.ApplicationCreated)
319 config.add_subscriber(write_usage_data,
319 config.add_subscriber(write_usage_data,
320 pyramid.events.ApplicationCreated)
320 pyramid.events.ApplicationCreated)
321 config.add_subscriber(write_js_routes_if_enabled,
321 config.add_subscriber(write_js_routes_if_enabled,
322 pyramid.events.ApplicationCreated)
322 pyramid.events.ApplicationCreated)
323
323
324 # request custom methods
324 # request custom methods
325 config.add_request_method(
325 config.add_request_method(
326 'rhodecode.lib.partial_renderer.get_partial_renderer',
326 'rhodecode.lib.partial_renderer.get_partial_renderer',
327 'get_partial_renderer')
327 'get_partial_renderer')
328
328
329 config.add_request_method(
329 config.add_request_method(
330 'rhodecode.lib.request_counter.get_request_counter',
330 'rhodecode.lib.request_counter.get_request_counter',
331 'request_count')
331 'request_count')
332
332
333 # Set the authorization policy.
333 # Set the authorization policy.
334 authz_policy = ACLAuthorizationPolicy()
334 authz_policy = ACLAuthorizationPolicy()
335 config.set_authorization_policy(authz_policy)
335 config.set_authorization_policy(authz_policy)
336
336
337 # Set the default renderer for HTML templates to mako.
337 # Set the default renderer for HTML templates to mako.
338 config.add_mako_renderer('.html')
338 config.add_mako_renderer('.html')
339
339
340 config.add_renderer(
340 config.add_renderer(
341 name='json_ext',
341 name='json_ext',
342 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
342 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
343
343
344 config.add_renderer(
344 config.add_renderer(
345 name='string_html',
345 name='string_html',
346 factory='rhodecode.lib.string_renderer.html')
346 factory='rhodecode.lib.string_renderer.html')
347
347
348 # include RhodeCode plugins
348 # include RhodeCode plugins
349 includes = aslist(settings.get('rhodecode.includes', []))
349 includes = aslist(settings.get('rhodecode.includes', []))
350 for inc in includes:
350 for inc in includes:
351 config.include(inc)
351 config.include(inc)
352
352
353 # custom not found view, if our pyramid app doesn't know how to handle
353 # custom not found view, if our pyramid app doesn't know how to handle
354 # the request pass it to potential VCS handling ap
354 # the request pass it to potential VCS handling ap
355 config.add_notfound_view(not_found_view)
355 config.add_notfound_view(not_found_view)
356 if not settings.get('debugtoolbar.enabled', False):
356 if not settings.get('debugtoolbar.enabled', False):
357 # disabled debugtoolbar handle all exceptions via the error_handlers
357 # disabled debugtoolbar handle all exceptions via the error_handlers
358 config.add_view(error_handler, context=Exception)
358 config.add_view(error_handler, context=Exception)
359
359
360 # all errors including 403/404/50X
360 # all errors including 403/404/50X
361 config.add_view(error_handler, context=HTTPError)
361 config.add_view(error_handler, context=HTTPError)
362
362
363
363
364 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
364 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
365 """
365 """
366 Apply outer WSGI middlewares around the application.
366 Apply outer WSGI middlewares around the application.
367 """
367 """
368 registry = config.registry
368 registry = config.registry
369 settings = registry.settings
369 settings = registry.settings
370
370
371 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
371 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
372 pyramid_app = HttpsFixup(pyramid_app, settings)
372 pyramid_app = HttpsFixup(pyramid_app, settings)
373
373
374 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
374 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
375 pyramid_app, settings)
375 pyramid_app, settings)
376 registry.ae_client = _ae_client
376 registry.ae_client = _ae_client
377
377
378 if settings['gzip_responses']:
378 if settings['gzip_responses']:
379 pyramid_app = make_gzip_middleware(
379 pyramid_app = make_gzip_middleware(
380 pyramid_app, settings, compress_level=1)
380 pyramid_app, settings, compress_level=1)
381
381
382 # this should be the outer most middleware in the wsgi stack since
382 # this should be the outer most middleware in the wsgi stack since
383 # middleware like Routes make database calls
383 # middleware like Routes make database calls
384 def pyramid_app_with_cleanup(environ, start_response):
384 def pyramid_app_with_cleanup(environ, start_response):
385 try:
385 try:
386 return pyramid_app(environ, start_response)
386 return pyramid_app(environ, start_response)
387 finally:
387 finally:
388 # Dispose current database session and rollback uncommitted
388 # Dispose current database session and rollback uncommitted
389 # transactions.
389 # transactions.
390 meta.Session.remove()
390 meta.Session.remove()
391
391
392 # In a single threaded mode server, on non sqlite db we should have
392 # In a single threaded mode server, on non sqlite db we should have
393 # '0 Current Checked out connections' at the end of a request,
393 # '0 Current Checked out connections' at the end of a request,
394 # if not, then something, somewhere is leaving a connection open
394 # if not, then something, somewhere is leaving a connection open
395 pool = meta.Base.metadata.bind.engine.pool
395 pool = meta.Base.metadata.bind.engine.pool
396 log.debug('sa pool status: %s', pool.status())
396 log.debug('sa pool status: %s', pool.status())
397 log.debug('Request processing finalized')
397 log.debug('Request processing finalized')
398
398
399 return pyramid_app_with_cleanup
399 return pyramid_app_with_cleanup
400
400
401
401
402 def sanitize_settings_and_apply_defaults(global_config, settings):
402 def sanitize_settings_and_apply_defaults(global_config, settings):
403 """
403 """
404 Applies settings defaults and does all type conversion.
404 Applies settings defaults and does all type conversion.
405
405
406 We would move all settings parsing and preparation into this place, so that
406 We would move all settings parsing and preparation into this place, so that
407 we have only one place left which deals with this part. The remaining parts
407 we have only one place left which deals with this part. The remaining parts
408 of the application would start to rely fully on well prepared settings.
408 of the application would start to rely fully on well prepared settings.
409
409
410 This piece would later be split up per topic to avoid a big fat monster
410 This piece would later be split up per topic to avoid a big fat monster
411 function.
411 function.
412 """
412 """
413
413
414 settings.setdefault('rhodecode.edition', 'Community Edition')
414 settings.setdefault('rhodecode.edition', 'Community Edition')
415 settings.setdefault('rhodecode.edition_id', 'CE')
415
416
416 if 'mako.default_filters' not in settings:
417 if 'mako.default_filters' not in settings:
417 # set custom default filters if we don't have it defined
418 # set custom default filters if we don't have it defined
418 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
419 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
419 settings['mako.default_filters'] = 'h_filter'
420 settings['mako.default_filters'] = 'h_filter'
420
421
421 if 'mako.directories' not in settings:
422 if 'mako.directories' not in settings:
422 mako_directories = settings.setdefault('mako.directories', [
423 mako_directories = settings.setdefault('mako.directories', [
423 # Base templates of the original application
424 # Base templates of the original application
424 'rhodecode:templates',
425 'rhodecode:templates',
425 ])
426 ])
426 log.debug(
427 log.debug(
427 "Using the following Mako template directories: %s",
428 "Using the following Mako template directories: %s",
428 mako_directories)
429 mako_directories)
429
430
430 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
431 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
431 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
432 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
432 raw_url = settings['beaker.session.url']
433 raw_url = settings['beaker.session.url']
433 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
434 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
434 settings['beaker.session.url'] = 'redis://' + raw_url
435 settings['beaker.session.url'] = 'redis://' + raw_url
435
436
436 # Default includes, possible to change as a user
437 # Default includes, possible to change as a user
437 pyramid_includes = settings.setdefault('pyramid.includes', [])
438 pyramid_includes = settings.setdefault('pyramid.includes', [])
438 log.debug(
439 log.debug(
439 "Using the following pyramid.includes: %s",
440 "Using the following pyramid.includes: %s",
440 pyramid_includes)
441 pyramid_includes)
441
442
442 # TODO: johbo: Re-think this, usually the call to config.include
443 # TODO: johbo: Re-think this, usually the call to config.include
443 # should allow to pass in a prefix.
444 # should allow to pass in a prefix.
444 settings.setdefault('rhodecode.api.url', '/_admin/api')
445 settings.setdefault('rhodecode.api.url', '/_admin/api')
445 settings.setdefault('__file__', global_config.get('__file__'))
446 settings.setdefault('__file__', global_config.get('__file__'))
446
447
447 # Sanitize generic settings.
448 # Sanitize generic settings.
448 _list_setting(settings, 'default_encoding', 'UTF-8')
449 _list_setting(settings, 'default_encoding', 'UTF-8')
449 _bool_setting(settings, 'is_test', 'false')
450 _bool_setting(settings, 'is_test', 'false')
450 _bool_setting(settings, 'gzip_responses', 'false')
451 _bool_setting(settings, 'gzip_responses', 'false')
451
452
452 # Call split out functions that sanitize settings for each topic.
453 # Call split out functions that sanitize settings for each topic.
453 _sanitize_appenlight_settings(settings)
454 _sanitize_appenlight_settings(settings)
454 _sanitize_vcs_settings(settings)
455 _sanitize_vcs_settings(settings)
455 _sanitize_cache_settings(settings)
456 _sanitize_cache_settings(settings)
456
457
457 # configure instance id
458 # configure instance id
458 config_utils.set_instance_id(settings)
459 config_utils.set_instance_id(settings)
459
460
460 return settings
461 return settings
461
462
462
463
463 def enable_debug():
464 def enable_debug():
464 """
465 """
465 Helper to enable debug on running instance
466 Helper to enable debug on running instance
466 :return:
467 :return:
467 """
468 """
468 import tempfile
469 import tempfile
469 import textwrap
470 import textwrap
470 import logging.config
471 import logging.config
471
472
472 ini_template = textwrap.dedent("""
473 ini_template = textwrap.dedent("""
473 #####################################
474 #####################################
474 ### DEBUG LOGGING CONFIGURATION ####
475 ### DEBUG LOGGING CONFIGURATION ####
475 #####################################
476 #####################################
476 [loggers]
477 [loggers]
477 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
478 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
478
479
479 [handlers]
480 [handlers]
480 keys = console, console_sql
481 keys = console, console_sql
481
482
482 [formatters]
483 [formatters]
483 keys = generic, color_formatter, color_formatter_sql
484 keys = generic, color_formatter, color_formatter_sql
484
485
485 #############
486 #############
486 ## LOGGERS ##
487 ## LOGGERS ##
487 #############
488 #############
488 [logger_root]
489 [logger_root]
489 level = NOTSET
490 level = NOTSET
490 handlers = console
491 handlers = console
491
492
492 [logger_sqlalchemy]
493 [logger_sqlalchemy]
493 level = INFO
494 level = INFO
494 handlers = console_sql
495 handlers = console_sql
495 qualname = sqlalchemy.engine
496 qualname = sqlalchemy.engine
496 propagate = 0
497 propagate = 0
497
498
498 [logger_beaker]
499 [logger_beaker]
499 level = DEBUG
500 level = DEBUG
500 handlers =
501 handlers =
501 qualname = beaker.container
502 qualname = beaker.container
502 propagate = 1
503 propagate = 1
503
504
504 [logger_rhodecode]
505 [logger_rhodecode]
505 level = DEBUG
506 level = DEBUG
506 handlers =
507 handlers =
507 qualname = rhodecode
508 qualname = rhodecode
508 propagate = 1
509 propagate = 1
509
510
510 [logger_ssh_wrapper]
511 [logger_ssh_wrapper]
511 level = DEBUG
512 level = DEBUG
512 handlers =
513 handlers =
513 qualname = ssh_wrapper
514 qualname = ssh_wrapper
514 propagate = 1
515 propagate = 1
515
516
516 [logger_celery]
517 [logger_celery]
517 level = DEBUG
518 level = DEBUG
518 handlers =
519 handlers =
519 qualname = celery
520 qualname = celery
520
521
521
522
522 ##############
523 ##############
523 ## HANDLERS ##
524 ## HANDLERS ##
524 ##############
525 ##############
525
526
526 [handler_console]
527 [handler_console]
527 class = StreamHandler
528 class = StreamHandler
528 args = (sys.stderr, )
529 args = (sys.stderr, )
529 level = DEBUG
530 level = DEBUG
530 formatter = color_formatter
531 formatter = color_formatter
531
532
532 [handler_console_sql]
533 [handler_console_sql]
533 # "level = DEBUG" logs SQL queries and results.
534 # "level = DEBUG" logs SQL queries and results.
534 # "level = INFO" logs SQL queries.
535 # "level = INFO" logs SQL queries.
535 # "level = WARN" logs neither. (Recommended for production systems.)
536 # "level = WARN" logs neither. (Recommended for production systems.)
536 class = StreamHandler
537 class = StreamHandler
537 args = (sys.stderr, )
538 args = (sys.stderr, )
538 level = WARN
539 level = WARN
539 formatter = color_formatter_sql
540 formatter = color_formatter_sql
540
541
541 ################
542 ################
542 ## FORMATTERS ##
543 ## FORMATTERS ##
543 ################
544 ################
544
545
545 [formatter_generic]
546 [formatter_generic]
546 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
547 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
547 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
548 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
548 datefmt = %Y-%m-%d %H:%M:%S
549 datefmt = %Y-%m-%d %H:%M:%S
549
550
550 [formatter_color_formatter]
551 [formatter_color_formatter]
551 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
552 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
552 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
553 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
553 datefmt = %Y-%m-%d %H:%M:%S
554 datefmt = %Y-%m-%d %H:%M:%S
554
555
555 [formatter_color_formatter_sql]
556 [formatter_color_formatter_sql]
556 class = rhodecode.lib.logging_formatter.ColorFormatterSql
557 class = rhodecode.lib.logging_formatter.ColorFormatterSql
557 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
558 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
558 datefmt = %Y-%m-%d %H:%M:%S
559 datefmt = %Y-%m-%d %H:%M:%S
559 """)
560 """)
560
561
561 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
562 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
562 delete=False) as f:
563 delete=False) as f:
563 log.info('Saved Temporary DEBUG config at %s', f.name)
564 log.info('Saved Temporary DEBUG config at %s', f.name)
564 f.write(ini_template)
565 f.write(ini_template)
565
566
566 logging.config.fileConfig(f.name)
567 logging.config.fileConfig(f.name)
567 log.debug('DEBUG MODE ON')
568 log.debug('DEBUG MODE ON')
568 os.remove(f.name)
569 os.remove(f.name)
569
570
570
571
571 def _sanitize_appenlight_settings(settings):
572 def _sanitize_appenlight_settings(settings):
572 _bool_setting(settings, 'appenlight', 'false')
573 _bool_setting(settings, 'appenlight', 'false')
573
574
574
575
575 def _sanitize_vcs_settings(settings):
576 def _sanitize_vcs_settings(settings):
576 """
577 """
577 Applies settings defaults and does type conversion for all VCS related
578 Applies settings defaults and does type conversion for all VCS related
578 settings.
579 settings.
579 """
580 """
580 _string_setting(settings, 'vcs.svn.compatible_version', '')
581 _string_setting(settings, 'vcs.svn.compatible_version', '')
581 _string_setting(settings, 'vcs.hooks.protocol', 'http')
582 _string_setting(settings, 'vcs.hooks.protocol', 'http')
582 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
583 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
583 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
584 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
584 _string_setting(settings, 'vcs.server', '')
585 _string_setting(settings, 'vcs.server', '')
585 _string_setting(settings, 'vcs.server.protocol', 'http')
586 _string_setting(settings, 'vcs.server.protocol', 'http')
586 _bool_setting(settings, 'startup.import_repos', 'false')
587 _bool_setting(settings, 'startup.import_repos', 'false')
587 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
588 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
588 _bool_setting(settings, 'vcs.server.enable', 'true')
589 _bool_setting(settings, 'vcs.server.enable', 'true')
589 _bool_setting(settings, 'vcs.start_server', 'false')
590 _bool_setting(settings, 'vcs.start_server', 'false')
590 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
591 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
591 _int_setting(settings, 'vcs.connection_timeout', 3600)
592 _int_setting(settings, 'vcs.connection_timeout', 3600)
592
593
593 # Support legacy values of vcs.scm_app_implementation. Legacy
594 # Support legacy values of vcs.scm_app_implementation. Legacy
594 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
595 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
595 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
596 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
596 scm_app_impl = settings['vcs.scm_app_implementation']
597 scm_app_impl = settings['vcs.scm_app_implementation']
597 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
598 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
598 settings['vcs.scm_app_implementation'] = 'http'
599 settings['vcs.scm_app_implementation'] = 'http'
599
600
600
601
601 def _sanitize_cache_settings(settings):
602 def _sanitize_cache_settings(settings):
602 temp_store = tempfile.gettempdir()
603 temp_store = tempfile.gettempdir()
603 default_cache_dir = os.path.join(temp_store, 'rc_cache')
604 default_cache_dir = os.path.join(temp_store, 'rc_cache')
604
605
605 # save default, cache dir, and use it for all backends later.
606 # save default, cache dir, and use it for all backends later.
606 default_cache_dir = _string_setting(
607 default_cache_dir = _string_setting(
607 settings,
608 settings,
608 'cache_dir',
609 'cache_dir',
609 default_cache_dir, lower=False, default_when_empty=True)
610 default_cache_dir, lower=False, default_when_empty=True)
610
611
611 # ensure we have our dir created
612 # ensure we have our dir created
612 if not os.path.isdir(default_cache_dir):
613 if not os.path.isdir(default_cache_dir):
613 os.makedirs(default_cache_dir, mode=0o755)
614 os.makedirs(default_cache_dir, mode=0o755)
614
615
615 # exception store cache
616 # exception store cache
616 _string_setting(
617 _string_setting(
617 settings,
618 settings,
618 'exception_tracker.store_path',
619 'exception_tracker.store_path',
619 temp_store, lower=False, default_when_empty=True)
620 temp_store, lower=False, default_when_empty=True)
620 _bool_setting(
621 _bool_setting(
621 settings,
622 settings,
622 'exception_tracker.send_email',
623 'exception_tracker.send_email',
623 'false')
624 'false')
624 _string_setting(
625 _string_setting(
625 settings,
626 settings,
626 'exception_tracker.email_prefix',
627 'exception_tracker.email_prefix',
627 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
628 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
628
629
629 # cache_perms
630 # cache_perms
630 _string_setting(
631 _string_setting(
631 settings,
632 settings,
632 'rc_cache.cache_perms.backend',
633 'rc_cache.cache_perms.backend',
633 'dogpile.cache.rc.file_namespace', lower=False)
634 'dogpile.cache.rc.file_namespace', lower=False)
634 _int_setting(
635 _int_setting(
635 settings,
636 settings,
636 'rc_cache.cache_perms.expiration_time',
637 'rc_cache.cache_perms.expiration_time',
637 60)
638 60)
638 _string_setting(
639 _string_setting(
639 settings,
640 settings,
640 'rc_cache.cache_perms.arguments.filename',
641 'rc_cache.cache_perms.arguments.filename',
641 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
642 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
642
643
643 # cache_repo
644 # cache_repo
644 _string_setting(
645 _string_setting(
645 settings,
646 settings,
646 'rc_cache.cache_repo.backend',
647 'rc_cache.cache_repo.backend',
647 'dogpile.cache.rc.file_namespace', lower=False)
648 'dogpile.cache.rc.file_namespace', lower=False)
648 _int_setting(
649 _int_setting(
649 settings,
650 settings,
650 'rc_cache.cache_repo.expiration_time',
651 'rc_cache.cache_repo.expiration_time',
651 60)
652 60)
652 _string_setting(
653 _string_setting(
653 settings,
654 settings,
654 'rc_cache.cache_repo.arguments.filename',
655 'rc_cache.cache_repo.arguments.filename',
655 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
656 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
656
657
657 # cache_license
658 # cache_license
658 _string_setting(
659 _string_setting(
659 settings,
660 settings,
660 'rc_cache.cache_license.backend',
661 'rc_cache.cache_license.backend',
661 'dogpile.cache.rc.file_namespace', lower=False)
662 'dogpile.cache.rc.file_namespace', lower=False)
662 _int_setting(
663 _int_setting(
663 settings,
664 settings,
664 'rc_cache.cache_license.expiration_time',
665 'rc_cache.cache_license.expiration_time',
665 5*60)
666 5*60)
666 _string_setting(
667 _string_setting(
667 settings,
668 settings,
668 'rc_cache.cache_license.arguments.filename',
669 'rc_cache.cache_license.arguments.filename',
669 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
670 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
670
671
671 # cache_repo_longterm memory, 96H
672 # cache_repo_longterm memory, 96H
672 _string_setting(
673 _string_setting(
673 settings,
674 settings,
674 'rc_cache.cache_repo_longterm.backend',
675 'rc_cache.cache_repo_longterm.backend',
675 'dogpile.cache.rc.memory_lru', lower=False)
676 'dogpile.cache.rc.memory_lru', lower=False)
676 _int_setting(
677 _int_setting(
677 settings,
678 settings,
678 'rc_cache.cache_repo_longterm.expiration_time',
679 'rc_cache.cache_repo_longterm.expiration_time',
679 345600)
680 345600)
680 _int_setting(
681 _int_setting(
681 settings,
682 settings,
682 'rc_cache.cache_repo_longterm.max_size',
683 'rc_cache.cache_repo_longterm.max_size',
683 10000)
684 10000)
684
685
685 # sql_cache_short
686 # sql_cache_short
686 _string_setting(
687 _string_setting(
687 settings,
688 settings,
688 'rc_cache.sql_cache_short.backend',
689 'rc_cache.sql_cache_short.backend',
689 'dogpile.cache.rc.memory_lru', lower=False)
690 'dogpile.cache.rc.memory_lru', lower=False)
690 _int_setting(
691 _int_setting(
691 settings,
692 settings,
692 'rc_cache.sql_cache_short.expiration_time',
693 'rc_cache.sql_cache_short.expiration_time',
693 30)
694 30)
694 _int_setting(
695 _int_setting(
695 settings,
696 settings,
696 'rc_cache.sql_cache_short.max_size',
697 'rc_cache.sql_cache_short.max_size',
697 10000)
698 10000)
698
699
699
700
700 def _int_setting(settings, name, default):
701 def _int_setting(settings, name, default):
701 settings[name] = int(settings.get(name, default))
702 settings[name] = int(settings.get(name, default))
702 return settings[name]
703 return settings[name]
703
704
704
705
705 def _bool_setting(settings, name, default):
706 def _bool_setting(settings, name, default):
706 input_val = settings.get(name, default)
707 input_val = settings.get(name, default)
707 if isinstance(input_val, unicode):
708 if isinstance(input_val, unicode):
708 input_val = input_val.encode('utf8')
709 input_val = input_val.encode('utf8')
709 settings[name] = asbool(input_val)
710 settings[name] = asbool(input_val)
710 return settings[name]
711 return settings[name]
711
712
712
713
713 def _list_setting(settings, name, default):
714 def _list_setting(settings, name, default):
714 raw_value = settings.get(name, default)
715 raw_value = settings.get(name, default)
715
716
716 old_separator = ','
717 old_separator = ','
717 if old_separator in raw_value:
718 if old_separator in raw_value:
718 # If we get a comma separated list, pass it to our own function.
719 # If we get a comma separated list, pass it to our own function.
719 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
720 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
720 else:
721 else:
721 # Otherwise we assume it uses pyramids space/newline separation.
722 # Otherwise we assume it uses pyramids space/newline separation.
722 settings[name] = aslist(raw_value)
723 settings[name] = aslist(raw_value)
723 return settings[name]
724 return settings[name]
724
725
725
726
726 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
727 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
727 value = settings.get(name, default)
728 value = settings.get(name, default)
728
729
729 if default_when_empty and not value:
730 if default_when_empty and not value:
730 # use default value when value is empty
731 # use default value when value is empty
731 value = default
732 value = default
732
733
733 if lower:
734 if lower:
734 value = value.lower()
735 value = value.lower()
735 settings[name] = value
736 settings[name] = value
736 return settings[name]
737 return settings[name]
737
738
738
739
739 def _substitute_values(mapping, substitutions):
740 def _substitute_values(mapping, substitutions):
740 result = {}
741 result = {}
741
742
742 try:
743 try:
743 for key, value in mapping.items():
744 for key, value in mapping.items():
744 # initialize without substitution first
745 # initialize without substitution first
745 result[key] = value
746 result[key] = value
746
747
747 # Note: Cannot use regular replacements, since they would clash
748 # Note: Cannot use regular replacements, since they would clash
748 # with the implementation of ConfigParser. Using "format" instead.
749 # with the implementation of ConfigParser. Using "format" instead.
749 try:
750 try:
750 result[key] = value.format(**substitutions)
751 result[key] = value.format(**substitutions)
751 except KeyError as e:
752 except KeyError as e:
752 env_var = '{}'.format(e.args[0])
753 env_var = '{}'.format(e.args[0])
753
754
754 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
755 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
755 'Make sure your environment has {var} set, or remove this ' \
756 'Make sure your environment has {var} set, or remove this ' \
756 'variable from config file'.format(key=key, var=env_var)
757 'variable from config file'.format(key=key, var=env_var)
757
758
758 if env_var.startswith('ENV_'):
759 if env_var.startswith('ENV_'):
759 raise ValueError(msg)
760 raise ValueError(msg)
760 else:
761 else:
761 log.warning(msg)
762 log.warning(msg)
762
763
763 except ValueError as e:
764 except ValueError as e:
764 log.warning('Failed to substitute ENV variable: %s', e)
765 log.warning('Failed to substitute ENV variable: %s', e)
765 result = mapping
766 result = mapping
766
767
767 return result
768 return result
@@ -1,618 +1,619 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.notification import NotificationModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def _filter_proxy(ip):
54 def _filter_proxy(ip):
55 """
55 """
56 Passed in IP addresses in HEADERS can be in a special format of multiple
56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 ips. Those comma separated IPs are passed from various proxies in the
57 ips. Those comma separated IPs are passed from various proxies in the
58 chain of request processing. The left-most being the original client.
58 chain of request processing. The left-most being the original client.
59 We only care about the first IP which came from the org. client.
59 We only care about the first IP which came from the org. client.
60
60
61 :param ip: ip string from headers
61 :param ip: ip string from headers
62 """
62 """
63 if ',' in ip:
63 if ',' in ip:
64 _ips = ip.split(',')
64 _ips = ip.split(',')
65 _first_ip = _ips[0].strip()
65 _first_ip = _ips[0].strip()
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 return _first_ip
67 return _first_ip
68 return ip
68 return ip
69
69
70
70
71 def _filter_port(ip):
71 def _filter_port(ip):
72 """
72 """
73 Removes a port from ip, there are 4 main cases to handle here.
73 Removes a port from ip, there are 4 main cases to handle here.
74 - ipv4 eg. 127.0.0.1
74 - ipv4 eg. 127.0.0.1
75 - ipv6 eg. ::1
75 - ipv6 eg. ::1
76 - ipv4+port eg. 127.0.0.1:8080
76 - ipv4+port eg. 127.0.0.1:8080
77 - ipv6+port eg. [::1]:8080
77 - ipv6+port eg. [::1]:8080
78
78
79 :param ip:
79 :param ip:
80 """
80 """
81 def is_ipv6(ip_addr):
81 def is_ipv6(ip_addr):
82 if hasattr(socket, 'inet_pton'):
82 if hasattr(socket, 'inet_pton'):
83 try:
83 try:
84 socket.inet_pton(socket.AF_INET6, ip_addr)
84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 except socket.error:
85 except socket.error:
86 return False
86 return False
87 else:
87 else:
88 # fallback to ipaddress
88 # fallback to ipaddress
89 try:
89 try:
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 except Exception:
91 except Exception:
92 return False
92 return False
93 return True
93 return True
94
94
95 if ':' not in ip: # must be ipv4 pure ip
95 if ':' not in ip: # must be ipv4 pure ip
96 return ip
96 return ip
97
97
98 if '[' in ip and ']' in ip: # ipv6 with port
98 if '[' in ip and ']' in ip: # ipv6 with port
99 return ip.split(']')[0][1:].lower()
99 return ip.split(']')[0][1:].lower()
100
100
101 # must be ipv6 or ipv4 with port
101 # must be ipv6 or ipv4 with port
102 if is_ipv6(ip):
102 if is_ipv6(ip):
103 return ip
103 return ip
104 else:
104 else:
105 ip, _port = ip.split(':')[:2] # means ipv4+port
105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 return ip
106 return ip
107
107
108
108
109 def get_ip_addr(environ):
109 def get_ip_addr(environ):
110 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key = 'HTTP_X_REAL_IP'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 def_key = 'REMOTE_ADDR'
112 def_key = 'REMOTE_ADDR'
113 _filters = lambda x: _filter_port(_filter_proxy(x))
113 _filters = lambda x: _filter_port(_filter_proxy(x))
114
114
115 ip = environ.get(proxy_key)
115 ip = environ.get(proxy_key)
116 if ip:
116 if ip:
117 return _filters(ip)
117 return _filters(ip)
118
118
119 ip = environ.get(proxy_key2)
119 ip = environ.get(proxy_key2)
120 if ip:
120 if ip:
121 return _filters(ip)
121 return _filters(ip)
122
122
123 ip = environ.get(def_key, '0.0.0.0')
123 ip = environ.get(def_key, '0.0.0.0')
124 return _filters(ip)
124 return _filters(ip)
125
125
126
126
127 def get_server_ip_addr(environ, log_errors=True):
127 def get_server_ip_addr(environ, log_errors=True):
128 hostname = environ.get('SERVER_NAME')
128 hostname = environ.get('SERVER_NAME')
129 try:
129 try:
130 return socket.gethostbyname(hostname)
130 return socket.gethostbyname(hostname)
131 except Exception as e:
131 except Exception as e:
132 if log_errors:
132 if log_errors:
133 # in some cases this lookup is not possible, and we don't want to
133 # in some cases this lookup is not possible, and we don't want to
134 # make it an exception in logs
134 # make it an exception in logs
135 log.exception('Could not retrieve server ip address: %s', e)
135 log.exception('Could not retrieve server ip address: %s', e)
136 return hostname
136 return hostname
137
137
138
138
139 def get_server_port(environ):
139 def get_server_port(environ):
140 return environ.get('SERVER_PORT')
140 return environ.get('SERVER_PORT')
141
141
142
142
143 def get_access_path(environ):
143 def get_access_path(environ):
144 path = environ.get('PATH_INFO')
144 path = environ.get('PATH_INFO')
145 org_req = environ.get('pylons.original_request')
145 org_req = environ.get('pylons.original_request')
146 if org_req:
146 if org_req:
147 path = org_req.environ.get('PATH_INFO')
147 path = org_req.environ.get('PATH_INFO')
148 return path
148 return path
149
149
150
150
151 def get_user_agent(environ):
151 def get_user_agent(environ):
152 return environ.get('HTTP_USER_AGENT')
152 return environ.get('HTTP_USER_AGENT')
153
153
154
154
155 def vcs_operation_context(
155 def vcs_operation_context(
156 environ, repo_name, username, action, scm, check_locking=True,
156 environ, repo_name, username, action, scm, check_locking=True,
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 """
158 """
159 Generate the context for a vcs operation, e.g. push or pull.
159 Generate the context for a vcs operation, e.g. push or pull.
160
160
161 This context is passed over the layers so that hooks triggered by the
161 This context is passed over the layers so that hooks triggered by the
162 vcs operation know details like the user, the user's IP address etc.
162 vcs operation know details like the user, the user's IP address etc.
163
163
164 :param check_locking: Allows to switch of the computation of the locking
164 :param check_locking: Allows to switch of the computation of the locking
165 data. This serves mainly the need of the simplevcs middleware to be
165 data. This serves mainly the need of the simplevcs middleware to be
166 able to disable this for certain operations.
166 able to disable this for certain operations.
167
167
168 """
168 """
169 # Tri-state value: False: unlock, None: nothing, True: lock
169 # Tri-state value: False: unlock, None: nothing, True: lock
170 make_lock = None
170 make_lock = None
171 locked_by = [None, None, None]
171 locked_by = [None, None, None]
172 is_anonymous = username == User.DEFAULT_USER
172 is_anonymous = username == User.DEFAULT_USER
173 user = User.get_by_username(username)
173 user = User.get_by_username(username)
174 if not is_anonymous and check_locking:
174 if not is_anonymous and check_locking:
175 log.debug('Checking locking on repository "%s"', repo_name)
175 log.debug('Checking locking on repository "%s"', repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
177 make_lock, __, locked_by = repo.get_locking_state(
177 make_lock, __, locked_by = repo.get_locking_state(
178 action, user.user_id)
178 action, user.user_id)
179 user_id = user.user_id
179 user_id = user.user_id
180 settings_model = VcsSettingsModel(repo=repo_name)
180 settings_model = VcsSettingsModel(repo=repo_name)
181 ui_settings = settings_model.get_ui_settings()
181 ui_settings = settings_model.get_ui_settings()
182
182
183 # NOTE(marcink): This should be also in sync with
183 # NOTE(marcink): This should be also in sync with
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 store = [x for x in ui_settings if x.key == '/']
185 store = [x for x in ui_settings if x.key == '/']
186 repo_store = ''
186 repo_store = ''
187 if store:
187 if store:
188 repo_store = store[0].value
188 repo_store = store[0].value
189
189
190 scm_data = {
190 scm_data = {
191 'ip': get_ip_addr(environ),
191 'ip': get_ip_addr(environ),
192 'username': username,
192 'username': username,
193 'user_id': user_id,
193 'user_id': user_id,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'repo_store': repo_store,
198 'repo_store': repo_store,
199 'make_lock': make_lock,
199 'make_lock': make_lock,
200 'locked_by': locked_by,
200 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
201 'server_url': utils2.get_server_url(environ),
202 'user_agent': get_user_agent(environ),
202 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
204 'is_shadow_repo': is_shadow_repo,
205 'detect_force_push': detect_force_push,
205 'detect_force_push': detect_force_push,
206 'check_branch_perms': check_branch_perms,
206 'check_branch_perms': check_branch_perms,
207 }
207 }
208 return scm_data
208 return scm_data
209
209
210
210
211 class BasicAuth(AuthBasicAuthenticator):
211 class BasicAuth(AuthBasicAuthenticator):
212
212
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
215 self.realm = realm
215 self.realm = realm
216 self.rc_realm = rc_realm
216 self.rc_realm = rc_realm
217 self.initial_call = initial_call_detection
217 self.initial_call = initial_call_detection
218 self.authfunc = authfunc
218 self.authfunc = authfunc
219 self.registry = registry
219 self.registry = registry
220 self.acl_repo_name = acl_repo_name
220 self.acl_repo_name = acl_repo_name
221 self._rc_auth_http_code = auth_http_code
221 self._rc_auth_http_code = auth_http_code
222
222
223 def _get_response_from_code(self, http_code):
223 def _get_response_from_code(self, http_code):
224 try:
224 try:
225 return get_exception(safe_int(http_code))
225 return get_exception(safe_int(http_code))
226 except Exception:
226 except Exception:
227 log.exception('Failed to fetch response for code %s', http_code)
227 log.exception('Failed to fetch response for code %s', http_code)
228 return HTTPForbidden
228 return HTTPForbidden
229
229
230 def get_rc_realm(self):
230 def get_rc_realm(self):
231 return safe_str(self.rc_realm)
231 return safe_str(self.rc_realm)
232
232
233 def build_authentication(self):
233 def build_authentication(self):
234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
235 if self._rc_auth_http_code and not self.initial_call:
235 if self._rc_auth_http_code and not self.initial_call:
236 # return alternative HTTP code if alternative http return code
236 # return alternative HTTP code if alternative http return code
237 # is specified in RhodeCode config, but ONLY if it's not the
237 # is specified in RhodeCode config, but ONLY if it's not the
238 # FIRST call
238 # FIRST call
239 custom_response_klass = self._get_response_from_code(
239 custom_response_klass = self._get_response_from_code(
240 self._rc_auth_http_code)
240 self._rc_auth_http_code)
241 return custom_response_klass(headers=head)
241 return custom_response_klass(headers=head)
242 return HTTPUnauthorized(headers=head)
242 return HTTPUnauthorized(headers=head)
243
243
244 def authenticate(self, environ):
244 def authenticate(self, environ):
245 authorization = AUTHORIZATION(environ)
245 authorization = AUTHORIZATION(environ)
246 if not authorization:
246 if not authorization:
247 return self.build_authentication()
247 return self.build_authentication()
248 (authmeth, auth) = authorization.split(' ', 1)
248 (authmeth, auth) = authorization.split(' ', 1)
249 if 'basic' != authmeth.lower():
249 if 'basic' != authmeth.lower():
250 return self.build_authentication()
250 return self.build_authentication()
251 auth = auth.strip().decode('base64')
251 auth = auth.strip().decode('base64')
252 _parts = auth.split(':', 1)
252 _parts = auth.split(':', 1)
253 if len(_parts) == 2:
253 if len(_parts) == 2:
254 username, password = _parts
254 username, password = _parts
255 auth_data = self.authfunc(
255 auth_data = self.authfunc(
256 username, password, environ, VCS_TYPE,
256 username, password, environ, VCS_TYPE,
257 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 registry=self.registry, acl_repo_name=self.acl_repo_name)
258 if auth_data:
258 if auth_data:
259 return {'username': username, 'auth_data': auth_data}
259 return {'username': username, 'auth_data': auth_data}
260 if username and password:
260 if username and password:
261 # we mark that we actually executed authentication once, at
261 # we mark that we actually executed authentication once, at
262 # that point we can use the alternative auth code
262 # that point we can use the alternative auth code
263 self.initial_call = False
263 self.initial_call = False
264
264
265 return self.build_authentication()
265 return self.build_authentication()
266
266
267 __call__ = authenticate
267 __call__ = authenticate
268
268
269
269
270 def calculate_version_hash(config):
270 def calculate_version_hash(config):
271 return sha1(
271 return sha1(
272 config.get('beaker.session.secret', '') +
272 config.get('beaker.session.secret', '') +
273 rhodecode.__version__)[:8]
273 rhodecode.__version__)[:8]
274
274
275
275
276 def get_current_lang(request):
276 def get_current_lang(request):
277 # NOTE(marcink): remove after pyramid move
277 # NOTE(marcink): remove after pyramid move
278 try:
278 try:
279 return translation.get_lang()[0]
279 return translation.get_lang()[0]
280 except:
280 except:
281 pass
281 pass
282
282
283 return getattr(request, '_LOCALE_', request.locale_name)
283 return getattr(request, '_LOCALE_', request.locale_name)
284
284
285
285
286 def attach_context_attributes(context, request, user_id=None, is_api=None):
286 def attach_context_attributes(context, request, user_id=None, is_api=None):
287 """
287 """
288 Attach variables into template context called `c`.
288 Attach variables into template context called `c`.
289 """
289 """
290 config = request.registry.settings
290 config = request.registry.settings
291
291
292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
293 context.rc_config = rc_config
293 context.rc_config = rc_config
294 context.rhodecode_version = rhodecode.__version__
294 context.rhodecode_version = rhodecode.__version__
295 context.rhodecode_edition = config.get('rhodecode.edition')
295 context.rhodecode_edition = config.get('rhodecode.edition')
296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
296 # unique secret + version does not leak the version but keep consistency
297 # unique secret + version does not leak the version but keep consistency
297 context.rhodecode_version_hash = calculate_version_hash(config)
298 context.rhodecode_version_hash = calculate_version_hash(config)
298
299
299 # Default language set for the incoming request
300 # Default language set for the incoming request
300 context.language = get_current_lang(request)
301 context.language = get_current_lang(request)
301
302
302 # Visual options
303 # Visual options
303 context.visual = AttributeDict({})
304 context.visual = AttributeDict({})
304
305
305 # DB stored Visual Items
306 # DB stored Visual Items
306 context.visual.show_public_icon = str2bool(
307 context.visual.show_public_icon = str2bool(
307 rc_config.get('rhodecode_show_public_icon'))
308 rc_config.get('rhodecode_show_public_icon'))
308 context.visual.show_private_icon = str2bool(
309 context.visual.show_private_icon = str2bool(
309 rc_config.get('rhodecode_show_private_icon'))
310 rc_config.get('rhodecode_show_private_icon'))
310 context.visual.stylify_metatags = str2bool(
311 context.visual.stylify_metatags = str2bool(
311 rc_config.get('rhodecode_stylify_metatags'))
312 rc_config.get('rhodecode_stylify_metatags'))
312 context.visual.dashboard_items = safe_int(
313 context.visual.dashboard_items = safe_int(
313 rc_config.get('rhodecode_dashboard_items', 100))
314 rc_config.get('rhodecode_dashboard_items', 100))
314 context.visual.admin_grid_items = safe_int(
315 context.visual.admin_grid_items = safe_int(
315 rc_config.get('rhodecode_admin_grid_items', 100))
316 rc_config.get('rhodecode_admin_grid_items', 100))
316 context.visual.show_revision_number = str2bool(
317 context.visual.show_revision_number = str2bool(
317 rc_config.get('rhodecode_show_revision_number', True))
318 rc_config.get('rhodecode_show_revision_number', True))
318 context.visual.show_sha_length = safe_int(
319 context.visual.show_sha_length = safe_int(
319 rc_config.get('rhodecode_show_sha_length', 100))
320 rc_config.get('rhodecode_show_sha_length', 100))
320 context.visual.repository_fields = str2bool(
321 context.visual.repository_fields = str2bool(
321 rc_config.get('rhodecode_repository_fields'))
322 rc_config.get('rhodecode_repository_fields'))
322 context.visual.show_version = str2bool(
323 context.visual.show_version = str2bool(
323 rc_config.get('rhodecode_show_version'))
324 rc_config.get('rhodecode_show_version'))
324 context.visual.use_gravatar = str2bool(
325 context.visual.use_gravatar = str2bool(
325 rc_config.get('rhodecode_use_gravatar'))
326 rc_config.get('rhodecode_use_gravatar'))
326 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
327 context.visual.default_renderer = rc_config.get(
328 context.visual.default_renderer = rc_config.get(
328 'rhodecode_markup_renderer', 'rst')
329 'rhodecode_markup_renderer', 'rst')
329 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
330 context.visual.rhodecode_support_url = \
331 context.visual.rhodecode_support_url = \
331 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
332
333
333 context.visual.affected_files_cut_off = 60
334 context.visual.affected_files_cut_off = 60
334
335
335 context.pre_code = rc_config.get('rhodecode_pre_code')
336 context.pre_code = rc_config.get('rhodecode_pre_code')
336 context.post_code = rc_config.get('rhodecode_post_code')
337 context.post_code = rc_config.get('rhodecode_post_code')
337 context.rhodecode_name = rc_config.get('rhodecode_title')
338 context.rhodecode_name = rc_config.get('rhodecode_title')
338 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
339 # if we have specified default_encoding in the request, it has more
340 # if we have specified default_encoding in the request, it has more
340 # priority
341 # priority
341 if request.GET.get('default_encoding'):
342 if request.GET.get('default_encoding'):
342 context.default_encodings.insert(0, request.GET.get('default_encoding'))
343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
343 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
344 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
345 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
345
346
346 # INI stored
347 # INI stored
347 context.labs_active = str2bool(
348 context.labs_active = str2bool(
348 config.get('labs_settings_active', 'false'))
349 config.get('labs_settings_active', 'false'))
349 context.ssh_enabled = str2bool(
350 context.ssh_enabled = str2bool(
350 config.get('ssh.generate_authorized_keyfile', 'false'))
351 config.get('ssh.generate_authorized_keyfile', 'false'))
351 context.ssh_key_generator_enabled = str2bool(
352 context.ssh_key_generator_enabled = str2bool(
352 config.get('ssh.enable_ui_key_generator', 'true'))
353 config.get('ssh.enable_ui_key_generator', 'true'))
353
354
354 context.visual.allow_repo_location_change = str2bool(
355 context.visual.allow_repo_location_change = str2bool(
355 config.get('allow_repo_location_change', True))
356 config.get('allow_repo_location_change', True))
356 context.visual.allow_custom_hooks_settings = str2bool(
357 context.visual.allow_custom_hooks_settings = str2bool(
357 config.get('allow_custom_hooks_settings', True))
358 config.get('allow_custom_hooks_settings', True))
358 context.debug_style = str2bool(config.get('debug_style', False))
359 context.debug_style = str2bool(config.get('debug_style', False))
359
360
360 context.rhodecode_instanceid = config.get('instance_id')
361 context.rhodecode_instanceid = config.get('instance_id')
361
362
362 context.visual.cut_off_limit_diff = safe_int(
363 context.visual.cut_off_limit_diff = safe_int(
363 config.get('cut_off_limit_diff'))
364 config.get('cut_off_limit_diff'))
364 context.visual.cut_off_limit_file = safe_int(
365 context.visual.cut_off_limit_file = safe_int(
365 config.get('cut_off_limit_file'))
366 config.get('cut_off_limit_file'))
366
367
367 context.license = AttributeDict({})
368 context.license = AttributeDict({})
368 context.license.hide_license_info = str2bool(
369 context.license.hide_license_info = str2bool(
369 config.get('license.hide_license_info', False))
370 config.get('license.hide_license_info', False))
370
371
371 # AppEnlight
372 # AppEnlight
372 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
373 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
373 context.appenlight_api_public_key = config.get(
374 context.appenlight_api_public_key = config.get(
374 'appenlight.api_public_key', '')
375 'appenlight.api_public_key', '')
375 context.appenlight_server_url = config.get('appenlight.server_url', '')
376 context.appenlight_server_url = config.get('appenlight.server_url', '')
376
377
377 diffmode = {
378 diffmode = {
378 "unified": "unified",
379 "unified": "unified",
379 "sideside": "sideside"
380 "sideside": "sideside"
380 }.get(request.GET.get('diffmode'))
381 }.get(request.GET.get('diffmode'))
381
382
382 if is_api is not None:
383 if is_api is not None:
383 is_api = hasattr(request, 'rpc_user')
384 is_api = hasattr(request, 'rpc_user')
384 session_attrs = {
385 session_attrs = {
385 # defaults
386 # defaults
386 "clone_url_format": "http",
387 "clone_url_format": "http",
387 "diffmode": "sideside",
388 "diffmode": "sideside",
388 "license_fingerprint": request.session.get('license_fingerprint')
389 "license_fingerprint": request.session.get('license_fingerprint')
389 }
390 }
390
391
391 if not is_api:
392 if not is_api:
392 # don't access pyramid session for API calls
393 # don't access pyramid session for API calls
393 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
394 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
394 request.session['rc_user_session_attr.diffmode'] = diffmode
395 request.session['rc_user_session_attr.diffmode'] = diffmode
395
396
396 # session settings per user
397 # session settings per user
397
398
398 for k, v in request.session.items():
399 for k, v in request.session.items():
399 pref = 'rc_user_session_attr.'
400 pref = 'rc_user_session_attr.'
400 if k and k.startswith(pref):
401 if k and k.startswith(pref):
401 k = k[len(pref):]
402 k = k[len(pref):]
402 session_attrs[k] = v
403 session_attrs[k] = v
403
404
404 context.user_session_attrs = session_attrs
405 context.user_session_attrs = session_attrs
405
406
406 # JS template context
407 # JS template context
407 context.template_context = {
408 context.template_context = {
408 'repo_name': None,
409 'repo_name': None,
409 'repo_type': None,
410 'repo_type': None,
410 'repo_landing_commit': None,
411 'repo_landing_commit': None,
411 'rhodecode_user': {
412 'rhodecode_user': {
412 'username': None,
413 'username': None,
413 'email': None,
414 'email': None,
414 'notification_status': False
415 'notification_status': False
415 },
416 },
416 'session_attrs': session_attrs,
417 'session_attrs': session_attrs,
417 'visual': {
418 'visual': {
418 'default_renderer': None
419 'default_renderer': None
419 },
420 },
420 'commit_data': {
421 'commit_data': {
421 'commit_id': None
422 'commit_id': None
422 },
423 },
423 'pull_request_data': {'pull_request_id': None},
424 'pull_request_data': {'pull_request_id': None},
424 'timeago': {
425 'timeago': {
425 'refresh_time': 120 * 1000,
426 'refresh_time': 120 * 1000,
426 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
427 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
427 },
428 },
428 'pyramid_dispatch': {
429 'pyramid_dispatch': {
429
430
430 },
431 },
431 'extra': {'plugins': {}}
432 'extra': {'plugins': {}}
432 }
433 }
433 # END CONFIG VARS
434 # END CONFIG VARS
434 if is_api:
435 if is_api:
435 csrf_token = None
436 csrf_token = None
436 else:
437 else:
437 csrf_token = auth.get_csrf_token(session=request.session)
438 csrf_token = auth.get_csrf_token(session=request.session)
438
439
439 context.csrf_token = csrf_token
440 context.csrf_token = csrf_token
440 context.backends = rhodecode.BACKENDS.keys()
441 context.backends = rhodecode.BACKENDS.keys()
441
442
442 unread_count = 0
443 unread_count = 0
443 user_bookmark_list = []
444 user_bookmark_list = []
444 if user_id:
445 if user_id:
445 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
446 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
446 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
447 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
447 context.unread_notifications = unread_count
448 context.unread_notifications = unread_count
448 context.bookmark_items = user_bookmark_list
449 context.bookmark_items = user_bookmark_list
449
450
450 # web case
451 # web case
451 if hasattr(request, 'user'):
452 if hasattr(request, 'user'):
452 context.auth_user = request.user
453 context.auth_user = request.user
453 context.rhodecode_user = request.user
454 context.rhodecode_user = request.user
454
455
455 # api case
456 # api case
456 if hasattr(request, 'rpc_user'):
457 if hasattr(request, 'rpc_user'):
457 context.auth_user = request.rpc_user
458 context.auth_user = request.rpc_user
458 context.rhodecode_user = request.rpc_user
459 context.rhodecode_user = request.rpc_user
459
460
460 # attach the whole call context to the request
461 # attach the whole call context to the request
461 request.call_context = context
462 request.call_context = context
462
463
463
464
464 def get_auth_user(request):
465 def get_auth_user(request):
465 environ = request.environ
466 environ = request.environ
466 session = request.session
467 session = request.session
467
468
468 ip_addr = get_ip_addr(environ)
469 ip_addr = get_ip_addr(environ)
469
470
470 # make sure that we update permissions each time we call controller
471 # make sure that we update permissions each time we call controller
471 _auth_token = (request.GET.get('auth_token', '') or request.GET.get('api_key', ''))
472 _auth_token = (request.GET.get('auth_token', '') or request.GET.get('api_key', ''))
472 if not _auth_token and request.matchdict:
473 if not _auth_token and request.matchdict:
473 url_auth_token = request.matchdict.get('_auth_token')
474 url_auth_token = request.matchdict.get('_auth_token')
474 _auth_token = url_auth_token
475 _auth_token = url_auth_token
475 if _auth_token:
476 if _auth_token:
476 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
477 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
477
478
478 if _auth_token:
479 if _auth_token:
479 # when using API_KEY we assume user exists, and
480 # when using API_KEY we assume user exists, and
480 # doesn't need auth based on cookies.
481 # doesn't need auth based on cookies.
481 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
482 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
482 authenticated = False
483 authenticated = False
483 else:
484 else:
484 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
485 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
485 try:
486 try:
486 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
487 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
487 ip_addr=ip_addr)
488 ip_addr=ip_addr)
488 except UserCreationError as e:
489 except UserCreationError as e:
489 h.flash(e, 'error')
490 h.flash(e, 'error')
490 # container auth or other auth functions that create users
491 # container auth or other auth functions that create users
491 # on the fly can throw this exception signaling that there's
492 # on the fly can throw this exception signaling that there's
492 # issue with user creation, explanation should be provided
493 # issue with user creation, explanation should be provided
493 # in Exception itself. We then create a simple blank
494 # in Exception itself. We then create a simple blank
494 # AuthUser
495 # AuthUser
495 auth_user = AuthUser(ip_addr=ip_addr)
496 auth_user = AuthUser(ip_addr=ip_addr)
496
497
497 # in case someone changes a password for user it triggers session
498 # in case someone changes a password for user it triggers session
498 # flush and forces a re-login
499 # flush and forces a re-login
499 if password_changed(auth_user, session):
500 if password_changed(auth_user, session):
500 session.invalidate()
501 session.invalidate()
501 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
502 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
502 auth_user = AuthUser(ip_addr=ip_addr)
503 auth_user = AuthUser(ip_addr=ip_addr)
503
504
504 authenticated = cookie_store.get('is_authenticated')
505 authenticated = cookie_store.get('is_authenticated')
505
506
506 if not auth_user.is_authenticated and auth_user.is_user_object:
507 if not auth_user.is_authenticated and auth_user.is_user_object:
507 # user is not authenticated and not empty
508 # user is not authenticated and not empty
508 auth_user.set_authenticated(authenticated)
509 auth_user.set_authenticated(authenticated)
509
510
510 return auth_user, _auth_token
511 return auth_user, _auth_token
511
512
512
513
513 def h_filter(s):
514 def h_filter(s):
514 """
515 """
515 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
516 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
516 we wrap this with additional functionality that converts None to empty
517 we wrap this with additional functionality that converts None to empty
517 strings
518 strings
518 """
519 """
519 if s is None:
520 if s is None:
520 return markupsafe.Markup()
521 return markupsafe.Markup()
521 return markupsafe.escape(s)
522 return markupsafe.escape(s)
522
523
523
524
524 def add_events_routes(config):
525 def add_events_routes(config):
525 """
526 """
526 Adds routing that can be used in events. Because some events are triggered
527 Adds routing that can be used in events. Because some events are triggered
527 outside of pyramid context, we need to bootstrap request with some
528 outside of pyramid context, we need to bootstrap request with some
528 routing registered
529 routing registered
529 """
530 """
530
531
531 from rhodecode.apps._base import ADMIN_PREFIX
532 from rhodecode.apps._base import ADMIN_PREFIX
532
533
533 config.add_route(name='home', pattern='/')
534 config.add_route(name='home', pattern='/')
534 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
535 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
535 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
536 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
536
537
537 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
538 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
538 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
539 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
539 config.add_route(name='repo_summary', pattern='/{repo_name}')
540 config.add_route(name='repo_summary', pattern='/{repo_name}')
540 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
541 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
541 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
542 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
542
543
543 config.add_route(name='pullrequest_show',
544 config.add_route(name='pullrequest_show',
544 pattern='/{repo_name}/pull-request/{pull_request_id}')
545 pattern='/{repo_name}/pull-request/{pull_request_id}')
545 config.add_route(name='pull_requests_global',
546 config.add_route(name='pull_requests_global',
546 pattern='/pull-request/{pull_request_id}')
547 pattern='/pull-request/{pull_request_id}')
547
548
548 config.add_route(name='repo_commit',
549 config.add_route(name='repo_commit',
549 pattern='/{repo_name}/changeset/{commit_id}')
550 pattern='/{repo_name}/changeset/{commit_id}')
550 config.add_route(name='repo_files',
551 config.add_route(name='repo_files',
551 pattern='/{repo_name}/files/{commit_id}/{f_path}')
552 pattern='/{repo_name}/files/{commit_id}/{f_path}')
552
553
553 config.add_route(name='hovercard_user',
554 config.add_route(name='hovercard_user',
554 pattern='/_hovercard/user/{user_id}')
555 pattern='/_hovercard/user/{user_id}')
555
556
556 config.add_route(name='hovercard_user_group',
557 config.add_route(name='hovercard_user_group',
557 pattern='/_hovercard/user_group/{user_group_id}')
558 pattern='/_hovercard/user_group/{user_group_id}')
558
559
559 config.add_route(name='hovercard_pull_request',
560 config.add_route(name='hovercard_pull_request',
560 pattern='/_hovercard/pull_request/{pull_request_id}')
561 pattern='/_hovercard/pull_request/{pull_request_id}')
561
562
562 config.add_route(name='hovercard_repo_commit',
563 config.add_route(name='hovercard_repo_commit',
563 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
564 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
564
565
565
566
566 def bootstrap_config(request):
567 def bootstrap_config(request):
567 import pyramid.testing
568 import pyramid.testing
568 registry = pyramid.testing.Registry('RcTestRegistry')
569 registry = pyramid.testing.Registry('RcTestRegistry')
569
570
570 config = pyramid.testing.setUp(registry=registry, request=request)
571 config = pyramid.testing.setUp(registry=registry, request=request)
571
572
572 # allow pyramid lookup in testing
573 # allow pyramid lookup in testing
573 config.include('pyramid_mako')
574 config.include('pyramid_mako')
574 config.include('rhodecode.lib.rc_beaker')
575 config.include('rhodecode.lib.rc_beaker')
575 config.include('rhodecode.lib.rc_cache')
576 config.include('rhodecode.lib.rc_cache')
576
577
577 add_events_routes(config)
578 add_events_routes(config)
578
579
579 return config
580 return config
580
581
581
582
582 def bootstrap_request(**kwargs):
583 def bootstrap_request(**kwargs):
583 import pyramid.testing
584 import pyramid.testing
584
585
585 class TestRequest(pyramid.testing.DummyRequest):
586 class TestRequest(pyramid.testing.DummyRequest):
586 application_url = kwargs.pop('application_url', 'http://example.com')
587 application_url = kwargs.pop('application_url', 'http://example.com')
587 host = kwargs.pop('host', 'example.com:80')
588 host = kwargs.pop('host', 'example.com:80')
588 domain = kwargs.pop('domain', 'example.com')
589 domain = kwargs.pop('domain', 'example.com')
589
590
590 def translate(self, msg):
591 def translate(self, msg):
591 return msg
592 return msg
592
593
593 def plularize(self, singular, plural, n):
594 def plularize(self, singular, plural, n):
594 return singular
595 return singular
595
596
596 def get_partial_renderer(self, tmpl_name):
597 def get_partial_renderer(self, tmpl_name):
597
598
598 from rhodecode.lib.partial_renderer import get_partial_renderer
599 from rhodecode.lib.partial_renderer import get_partial_renderer
599 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
600 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
600
601
601 _call_context = TemplateArgs()
602 _call_context = TemplateArgs()
602 _call_context.visual = TemplateArgs()
603 _call_context.visual = TemplateArgs()
603 _call_context.visual.show_sha_length = 12
604 _call_context.visual.show_sha_length = 12
604 _call_context.visual.show_revision_number = True
605 _call_context.visual.show_revision_number = True
605
606
606 @property
607 @property
607 def call_context(self):
608 def call_context(self):
608 return self._call_context
609 return self._call_context
609
610
610 class TestDummySession(pyramid.testing.DummySession):
611 class TestDummySession(pyramid.testing.DummySession):
611 def save(*arg, **kw):
612 def save(*arg, **kw):
612 pass
613 pass
613
614
614 request = TestRequest(**kwargs)
615 request = TestRequest(**kwargs)
615 request.session = TestDummySession()
616 request.session = TestDummySession()
616
617
617 return request
618 return request
618
619
@@ -1,3238 +1,3239 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 @import 'sweetalert2';
30 @import 'sweetalert2';
31
31
32
32
33 //--- BASE ------------------//
33 //--- BASE ------------------//
34 .noscript-error {
34 .noscript-error {
35 top: 0;
35 top: 0;
36 left: 0;
36 left: 0;
37 width: 100%;
37 width: 100%;
38 z-index: 101;
38 z-index: 101;
39 text-align: center;
39 text-align: center;
40 font-size: 120%;
40 font-size: 120%;
41 color: white;
41 color: white;
42 background-color: @alert2;
42 background-color: @alert2;
43 padding: 5px 0 5px 0;
43 padding: 5px 0 5px 0;
44 font-weight: @text-semibold-weight;
44 font-weight: @text-semibold-weight;
45 font-family: @text-semibold;
45 font-family: @text-semibold;
46 }
46 }
47
47
48 html {
48 html {
49 display: table;
49 display: table;
50 height: 100%;
50 height: 100%;
51 width: 100%;
51 width: 100%;
52 }
52 }
53
53
54 body {
54 body {
55 display: table-cell;
55 display: table-cell;
56 width: 100%;
56 width: 100%;
57 }
57 }
58
58
59 //--- LAYOUT ------------------//
59 //--- LAYOUT ------------------//
60
60
61 .hidden{
61 .hidden{
62 display: none !important;
62 display: none !important;
63 }
63 }
64
64
65 .box{
65 .box{
66 float: left;
66 float: left;
67 width: 100%;
67 width: 100%;
68 }
68 }
69
69
70 .browser-header {
70 .browser-header {
71 clear: both;
71 clear: both;
72 }
72 }
73 .main {
73 .main {
74 clear: both;
74 clear: both;
75 padding:0 0 @pagepadding;
75 padding:0 0 @pagepadding;
76 height: auto;
76 height: auto;
77
77
78 &:after { //clearfix
78 &:after { //clearfix
79 content:"";
79 content:"";
80 clear:both;
80 clear:both;
81 width:100%;
81 width:100%;
82 display:block;
82 display:block;
83 }
83 }
84 }
84 }
85
85
86 .flex-container {
86 .flex-container {
87 display: flex;
87 display: flex;
88 justify-content: space-between;
88 justify-content: space-between;
89 }
89 }
90
90
91 .action-link{
91 .action-link{
92 margin-left: @padding;
92 margin-left: @padding;
93 padding-left: @padding;
93 padding-left: @padding;
94 border-left: @border-thickness solid @border-default-color;
94 border-left: @border-thickness solid @border-default-color;
95 }
95 }
96
96
97 .cursor-pointer {
97 .cursor-pointer {
98 cursor: pointer;
98 cursor: pointer;
99 }
99 }
100
100
101 input + .action-link, .action-link.first{
101 input + .action-link, .action-link.first{
102 border-left: none;
102 border-left: none;
103 }
103 }
104
104
105 .link-disabled {
105 .link-disabled {
106 color: @grey4;
106 color: @grey4;
107 cursor: default;
107 cursor: default;
108 }
108 }
109
109
110 .action-link.last{
110 .action-link.last{
111 margin-right: @padding;
111 margin-right: @padding;
112 padding-right: @padding;
112 padding-right: @padding;
113 }
113 }
114
114
115 .action-link.active,
115 .action-link.active,
116 .action-link.active a{
116 .action-link.active a{
117 color: @grey4;
117 color: @grey4;
118 }
118 }
119
119
120 .action-link.disabled {
120 .action-link.disabled {
121 color: @grey4;
121 color: @grey4;
122 cursor: inherit;
122 cursor: inherit;
123 }
123 }
124
124
125 .grey-link-action {
125 .grey-link-action {
126 cursor: pointer;
126 cursor: pointer;
127 &:hover {
127 &:hover {
128 color: @grey2;
128 color: @grey2;
129 }
129 }
130 color: @grey4;
130 color: @grey4;
131 }
131 }
132
132
133 .clipboard-action {
133 .clipboard-action {
134 cursor: pointer;
134 cursor: pointer;
135 margin-left: 5px;
135 margin-left: 5px;
136
136
137 &:not(.no-grey) {
137 &:not(.no-grey) {
138
138
139 &:hover {
139 &:hover {
140 color: @grey2;
140 color: @grey2;
141 }
141 }
142 color: @grey4;
142 color: @grey4;
143 }
143 }
144 }
144 }
145
145
146 ul.simple-list{
146 ul.simple-list{
147 list-style: none;
147 list-style: none;
148 margin: 0;
148 margin: 0;
149 padding: 0;
149 padding: 0;
150 }
150 }
151
151
152 .main-content {
152 .main-content {
153 padding-bottom: @pagepadding;
153 padding-bottom: @pagepadding;
154 }
154 }
155
155
156 .wide-mode-wrapper {
156 .wide-mode-wrapper {
157 max-width:4000px !important;
157 max-width:4000px !important;
158 }
158 }
159
159
160 .wrapper {
160 .wrapper {
161 position: relative;
161 position: relative;
162 max-width: @wrapper-maxwidth;
162 max-width: @wrapper-maxwidth;
163 margin: 0 auto;
163 margin: 0 auto;
164 }
164 }
165
165
166 #content {
166 #content {
167 clear: both;
167 clear: both;
168 padding: 0 @contentpadding;
168 padding: 0 @contentpadding;
169 }
169 }
170
170
171 .advanced-settings-fields{
171 .advanced-settings-fields{
172 input{
172 input{
173 margin-left: @textmargin;
173 margin-left: @textmargin;
174 margin-right: @padding/2;
174 margin-right: @padding/2;
175 }
175 }
176 }
176 }
177
177
178 .cs_files_title {
178 .cs_files_title {
179 margin: @pagepadding 0 0;
179 margin: @pagepadding 0 0;
180 }
180 }
181
181
182 input.inline[type="file"] {
182 input.inline[type="file"] {
183 display: inline;
183 display: inline;
184 }
184 }
185
185
186 .error_page {
186 .error_page {
187 margin: 10% auto;
187 margin: 10% auto;
188
188
189 h1 {
189 h1 {
190 color: @grey2;
190 color: @grey2;
191 }
191 }
192
192
193 .alert {
193 .alert {
194 margin: @padding 0;
194 margin: @padding 0;
195 }
195 }
196
196
197 .error-branding {
197 .error-branding {
198 color: @grey4;
198 color: @grey4;
199 font-weight: @text-semibold-weight;
199 font-weight: @text-semibold-weight;
200 font-family: @text-semibold;
200 font-family: @text-semibold;
201 }
201 }
202
202
203 .error_message {
203 .error_message {
204 font-family: @text-regular;
204 font-family: @text-regular;
205 }
205 }
206
206
207 .sidebar {
207 .sidebar {
208 min-height: 275px;
208 min-height: 275px;
209 margin: 0;
209 margin: 0;
210 padding: 0 0 @sidebarpadding @sidebarpadding;
210 padding: 0 0 @sidebarpadding @sidebarpadding;
211 border: none;
211 border: none;
212 }
212 }
213
213
214 .main-content {
214 .main-content {
215 position: relative;
215 position: relative;
216 margin: 0 @sidebarpadding @sidebarpadding;
216 margin: 0 @sidebarpadding @sidebarpadding;
217 padding: 0 0 0 @sidebarpadding;
217 padding: 0 0 0 @sidebarpadding;
218 border-left: @border-thickness solid @grey5;
218 border-left: @border-thickness solid @grey5;
219
219
220 @media (max-width:767px) {
220 @media (max-width:767px) {
221 clear: both;
221 clear: both;
222 width: 100%;
222 width: 100%;
223 margin: 0;
223 margin: 0;
224 border: none;
224 border: none;
225 }
225 }
226 }
226 }
227
227
228 .inner-column {
228 .inner-column {
229 float: left;
229 float: left;
230 width: 29.75%;
230 width: 29.75%;
231 min-height: 150px;
231 min-height: 150px;
232 margin: @sidebarpadding 2% 0 0;
232 margin: @sidebarpadding 2% 0 0;
233 padding: 0 2% 0 0;
233 padding: 0 2% 0 0;
234 border-right: @border-thickness solid @grey5;
234 border-right: @border-thickness solid @grey5;
235
235
236 @media (max-width:767px) {
236 @media (max-width:767px) {
237 clear: both;
237 clear: both;
238 width: 100%;
238 width: 100%;
239 border: none;
239 border: none;
240 }
240 }
241
241
242 ul {
242 ul {
243 padding-left: 1.25em;
243 padding-left: 1.25em;
244 }
244 }
245
245
246 &:last-child {
246 &:last-child {
247 margin: @sidebarpadding 0 0;
247 margin: @sidebarpadding 0 0;
248 border: none;
248 border: none;
249 }
249 }
250
250
251 h4 {
251 h4 {
252 margin: 0 0 @padding;
252 margin: 0 0 @padding;
253 font-weight: @text-semibold-weight;
253 font-weight: @text-semibold-weight;
254 font-family: @text-semibold;
254 font-family: @text-semibold;
255 }
255 }
256 }
256 }
257 }
257 }
258 .error-page-logo {
258 .error-page-logo {
259 width: 130px;
259 width: 130px;
260 height: 160px;
260 height: 160px;
261 }
261 }
262
262
263 // HEADER
263 // HEADER
264 .header {
264 .header {
265
265
266 // TODO: johbo: Fix login pages, so that they work without a min-height
266 // TODO: johbo: Fix login pages, so that they work without a min-height
267 // for the header and then remove the min-height. I chose a smaller value
267 // for the header and then remove the min-height. I chose a smaller value
268 // intentionally here to avoid rendering issues in the main navigation.
268 // intentionally here to avoid rendering issues in the main navigation.
269 min-height: 49px;
269 min-height: 49px;
270 min-width: 1024px;
270 min-width: 1024px;
271
271
272 position: relative;
272 position: relative;
273 vertical-align: bottom;
273 vertical-align: bottom;
274 padding: 0 @header-padding;
274 padding: 0 @header-padding;
275 background-color: @grey1;
275 background-color: @grey1;
276 color: @grey5;
276 color: @grey5;
277
277
278 .title {
278 .title {
279 overflow: visible;
279 overflow: visible;
280 }
280 }
281
281
282 &:before,
282 &:before,
283 &:after {
283 &:after {
284 content: "";
284 content: "";
285 clear: both;
285 clear: both;
286 width: 100%;
286 width: 100%;
287 }
287 }
288
288
289 // TODO: johbo: Avoids breaking "Repositories" chooser
289 // TODO: johbo: Avoids breaking "Repositories" chooser
290 .select2-container .select2-choice .select2-arrow {
290 .select2-container .select2-choice .select2-arrow {
291 display: none;
291 display: none;
292 }
292 }
293 }
293 }
294
294
295 #header-inner {
295 #header-inner {
296 &.title {
296 &.title {
297 margin: 0;
297 margin: 0;
298 }
298 }
299 &:before,
299 &:before,
300 &:after {
300 &:after {
301 content: "";
301 content: "";
302 clear: both;
302 clear: both;
303 }
303 }
304 }
304 }
305
305
306 // Gists
306 // Gists
307 #files_data {
307 #files_data {
308 clear: both; //for firefox
308 clear: both; //for firefox
309 padding-top: 10px;
309 padding-top: 10px;
310 }
310 }
311
311
312 #gistid {
312 #gistid {
313 margin-right: @padding;
313 margin-right: @padding;
314 }
314 }
315
315
316 // Global Settings Editor
316 // Global Settings Editor
317 .textarea.editor {
317 .textarea.editor {
318 float: left;
318 float: left;
319 position: relative;
319 position: relative;
320 max-width: @texteditor-width;
320 max-width: @texteditor-width;
321
321
322 select {
322 select {
323 position: absolute;
323 position: absolute;
324 top:10px;
324 top:10px;
325 right:0;
325 right:0;
326 }
326 }
327
327
328 .CodeMirror {
328 .CodeMirror {
329 margin: 0;
329 margin: 0;
330 }
330 }
331
331
332 .help-block {
332 .help-block {
333 margin: 0 0 @padding;
333 margin: 0 0 @padding;
334 padding:.5em;
334 padding:.5em;
335 background-color: @grey6;
335 background-color: @grey6;
336 &.pre-formatting {
336 &.pre-formatting {
337 white-space: pre;
337 white-space: pre;
338 }
338 }
339 }
339 }
340 }
340 }
341
341
342 ul.auth_plugins {
342 ul.auth_plugins {
343 margin: @padding 0 @padding @legend-width;
343 margin: @padding 0 @padding @legend-width;
344 padding: 0;
344 padding: 0;
345
345
346 li {
346 li {
347 margin-bottom: @padding;
347 margin-bottom: @padding;
348 line-height: 1em;
348 line-height: 1em;
349 list-style-type: none;
349 list-style-type: none;
350
350
351 .auth_buttons .btn {
351 .auth_buttons .btn {
352 margin-right: @padding;
352 margin-right: @padding;
353 }
353 }
354
354
355 }
355 }
356 }
356 }
357
357
358
358
359 // My Account PR list
359 // My Account PR list
360
360
361 #show_closed {
361 #show_closed {
362 margin: 0 1em 0 0;
362 margin: 0 1em 0 0;
363 }
363 }
364
364
365 #pull_request_list_table {
365 #pull_request_list_table {
366 .closed {
366 .closed {
367 background-color: @grey6;
367 background-color: @grey6;
368 }
368 }
369
369
370 .state-creating,
370 .state-creating,
371 .state-updating,
371 .state-updating,
372 .state-merging
372 .state-merging
373 {
373 {
374 background-color: @grey6;
374 background-color: @grey6;
375 }
375 }
376
376
377 .log-container .truncate {
377 .log-container .truncate {
378 height: 2.75em;
378 height: 2.75em;
379 white-space: pre-line;
379 white-space: pre-line;
380 }
380 }
381 table.rctable .user {
381 table.rctable .user {
382 padding-left: 0;
382 padding-left: 0;
383 }
383 }
384 .td-status {
384 .td-status {
385 padding: 0 0px 0px 10px;
385 padding: 0 0px 0px 10px;
386 width: 15px;
386 width: 15px;
387 }
387 }
388 table.rctable {
388 table.rctable {
389 td.td-description,
389 td.td-description,
390 .rc-user {
390 .rc-user {
391 min-width: auto;
391 min-width: auto;
392 }
392 }
393 }
393 }
394 }
394 }
395
395
396 // Pull Requests
396 // Pull Requests
397
397
398 .pullrequests_section_head {
398 .pullrequests_section_head {
399 display: block;
399 display: block;
400 clear: both;
400 clear: both;
401 margin: @padding 0;
401 margin: @padding 0;
402 font-weight: @text-bold-weight;
402 font-weight: @text-bold-weight;
403 font-family: @text-bold;
403 font-family: @text-bold;
404 }
404 }
405
405
406 .pr-commit-flow {
406 .pr-commit-flow {
407 position: relative;
407 position: relative;
408 font-weight: 600;
408 font-weight: 600;
409
409
410 .tag {
410 .tag {
411 display: inline-block;
411 display: inline-block;
412 margin: 0 1em .5em 0;
412 margin: 0 1em .5em 0;
413 }
413 }
414
414
415 .clone-url {
415 .clone-url {
416 display: inline-block;
416 display: inline-block;
417 margin: 0 0 .5em 0;
417 margin: 0 0 .5em 0;
418 padding: 0;
418 padding: 0;
419 line-height: 1.2em;
419 line-height: 1.2em;
420 }
420 }
421 }
421 }
422
422
423 .pr-mergeinfo {
423 .pr-mergeinfo {
424 min-width: 95% !important;
424 min-width: 95% !important;
425 padding: 0 !important;
425 padding: 0 !important;
426 border: 0;
426 border: 0;
427 }
427 }
428 .pr-mergeinfo-copy {
428 .pr-mergeinfo-copy {
429 padding: 0 0;
429 padding: 0 0;
430 }
430 }
431
431
432 .pr-pullinfo {
432 .pr-pullinfo {
433 min-width: 95% !important;
433 min-width: 95% !important;
434 padding: 0 !important;
434 padding: 0 !important;
435 border: 0;
435 border: 0;
436 }
436 }
437 .pr-pullinfo-copy {
437 .pr-pullinfo-copy {
438 padding: 0 0;
438 padding: 0 0;
439 }
439 }
440
440
441 .pr-title-input {
441 .pr-title-input {
442 width: 100%;
442 width: 100%;
443 font-size: 18px;
443 font-size: 18px;
444 margin: 0 0 4px 0;
444 margin: 0 0 4px 0;
445 padding: 0;
445 padding: 0;
446 line-height: 1.7em;
446 line-height: 1.7em;
447 color: @text-color;
447 color: @text-color;
448 letter-spacing: .02em;
448 letter-spacing: .02em;
449 font-weight: @text-bold-weight;
449 font-weight: @text-bold-weight;
450 font-family: @text-bold;
450 font-family: @text-bold;
451
451
452 &:hover {
452 &:hover {
453 box-shadow: none;
453 box-shadow: none;
454 }
454 }
455 }
455 }
456
456
457 #pr-title {
457 #pr-title {
458 input {
458 input {
459 border: 1px transparent;
459 border: 1px transparent;
460 color: black;
460 color: black;
461 opacity: 1;
461 opacity: 1;
462 background: #fff;
462 background: #fff;
463 font-size: 18px;
463 font-size: 18px;
464 }
464 }
465 }
465 }
466
466
467 .pr-title-closed-tag {
467 .pr-title-closed-tag {
468 font-size: 16px;
468 font-size: 16px;
469 }
469 }
470
470
471 #pr-desc {
471 #pr-desc {
472 padding: 10px 0;
472 padding: 10px 0;
473
473
474 .markdown-block {
474 .markdown-block {
475 padding: 0;
475 padding: 0;
476 margin-bottom: -30px;
476 margin-bottom: -30px;
477 }
477 }
478 }
478 }
479
479
480 #pullrequest_title {
480 #pullrequest_title {
481 width: 100%;
481 width: 100%;
482 box-sizing: border-box;
482 box-sizing: border-box;
483 }
483 }
484
484
485 #pr_open_message {
485 #pr_open_message {
486 border: @border-thickness solid #fff;
486 border: @border-thickness solid #fff;
487 border-radius: @border-radius;
487 border-radius: @border-radius;
488 text-align: left;
488 text-align: left;
489 overflow: hidden;
489 overflow: hidden;
490 white-space: pre-line;
490 white-space: pre-line;
491 padding-top: 5px
491 padding-top: 5px
492 }
492 }
493
493
494 #add_reviewer {
494 #add_reviewer {
495 padding-top: 10px;
495 padding-top: 10px;
496 }
496 }
497
497
498 #add_reviewer_input {
498 #add_reviewer_input,
499 #add_observer_input {
499 padding-top: 10px
500 padding-top: 10px
500 }
501 }
501
502
502 .pr-details-title-author-pref {
503 .pr-details-title-author-pref {
503 padding-right: 10px
504 padding-right: 10px
504 }
505 }
505
506
506 .label-pr-detail {
507 .label-pr-detail {
507 display: table-cell;
508 display: table-cell;
508 width: 120px;
509 width: 120px;
509 padding-top: 7.5px;
510 padding-top: 7.5px;
510 padding-bottom: 7.5px;
511 padding-bottom: 7.5px;
511 padding-right: 7.5px;
512 padding-right: 7.5px;
512 }
513 }
513
514
514 .source-details ul {
515 .source-details ul {
515 padding: 10px 16px;
516 padding: 10px 16px;
516 }
517 }
517
518
518 .source-details-action {
519 .source-details-action {
519 color: @grey4;
520 color: @grey4;
520 font-size: 11px
521 font-size: 11px
521 }
522 }
522
523
523 .pr-submit-button {
524 .pr-submit-button {
524 float: right;
525 float: right;
525 margin: 0 0 0 5px;
526 margin: 0 0 0 5px;
526 }
527 }
527
528
528 .pr-spacing-container {
529 .pr-spacing-container {
529 padding: 20px;
530 padding: 20px;
530 clear: both
531 clear: both
531 }
532 }
532
533
533 #pr-description-input {
534 #pr-description-input {
534 margin-bottom: 0;
535 margin-bottom: 0;
535 }
536 }
536
537
537 .pr-description-label {
538 .pr-description-label {
538 vertical-align: top;
539 vertical-align: top;
539 }
540 }
540
541
541 #open_edit_pullrequest {
542 #open_edit_pullrequest {
542 padding: 0;
543 padding: 0;
543 }
544 }
544
545
545 #close_edit_pullrequest {
546 #close_edit_pullrequest {
546
547
547 }
548 }
548
549
549 #delete_pullrequest {
550 #delete_pullrequest {
550 clear: inherit;
551 clear: inherit;
551
552
552 form {
553 form {
553 display: inline;
554 display: inline;
554 }
555 }
555
556
556 }
557 }
557
558
558 .perms_section_head {
559 .perms_section_head {
559 min-width: 625px;
560 min-width: 625px;
560
561
561 h2 {
562 h2 {
562 margin-bottom: 0;
563 margin-bottom: 0;
563 }
564 }
564
565
565 .label-checkbox {
566 .label-checkbox {
566 float: left;
567 float: left;
567 }
568 }
568
569
569 &.field {
570 &.field {
570 margin: @space 0 @padding;
571 margin: @space 0 @padding;
571 }
572 }
572
573
573 &:first-child.field {
574 &:first-child.field {
574 margin-top: 0;
575 margin-top: 0;
575
576
576 .label {
577 .label {
577 margin-top: 0;
578 margin-top: 0;
578 padding-top: 0;
579 padding-top: 0;
579 }
580 }
580
581
581 .radios {
582 .radios {
582 padding-top: 0;
583 padding-top: 0;
583 }
584 }
584 }
585 }
585
586
586 .radios {
587 .radios {
587 position: relative;
588 position: relative;
588 width: 505px;
589 width: 505px;
589 }
590 }
590 }
591 }
591
592
592 //--- MODULES ------------------//
593 //--- MODULES ------------------//
593
594
594
595
595 // Server Announcement
596 // Server Announcement
596 #server-announcement {
597 #server-announcement {
597 width: 95%;
598 width: 95%;
598 margin: @padding auto;
599 margin: @padding auto;
599 padding: @padding;
600 padding: @padding;
600 border-width: 2px;
601 border-width: 2px;
601 border-style: solid;
602 border-style: solid;
602 .border-radius(2px);
603 .border-radius(2px);
603 font-weight: @text-bold-weight;
604 font-weight: @text-bold-weight;
604 font-family: @text-bold;
605 font-family: @text-bold;
605
606
606 &.info { border-color: @alert4; background-color: @alert4-inner; }
607 &.info { border-color: @alert4; background-color: @alert4-inner; }
607 &.warning { border-color: @alert3; background-color: @alert3-inner; }
608 &.warning { border-color: @alert3; background-color: @alert3-inner; }
608 &.error { border-color: @alert2; background-color: @alert2-inner; }
609 &.error { border-color: @alert2; background-color: @alert2-inner; }
609 &.success { border-color: @alert1; background-color: @alert1-inner; }
610 &.success { border-color: @alert1; background-color: @alert1-inner; }
610 &.neutral { border-color: @grey3; background-color: @grey6; }
611 &.neutral { border-color: @grey3; background-color: @grey6; }
611 }
612 }
612
613
613 // Fixed Sidebar Column
614 // Fixed Sidebar Column
614 .sidebar-col-wrapper {
615 .sidebar-col-wrapper {
615 padding-left: @sidebar-all-width;
616 padding-left: @sidebar-all-width;
616
617
617 .sidebar {
618 .sidebar {
618 width: @sidebar-width;
619 width: @sidebar-width;
619 margin-left: -@sidebar-all-width;
620 margin-left: -@sidebar-all-width;
620 }
621 }
621 }
622 }
622
623
623 .sidebar-col-wrapper.scw-small {
624 .sidebar-col-wrapper.scw-small {
624 padding-left: @sidebar-small-all-width;
625 padding-left: @sidebar-small-all-width;
625
626
626 .sidebar {
627 .sidebar {
627 width: @sidebar-small-width;
628 width: @sidebar-small-width;
628 margin-left: -@sidebar-small-all-width;
629 margin-left: -@sidebar-small-all-width;
629 }
630 }
630 }
631 }
631
632
632
633
633 // FOOTER
634 // FOOTER
634 #footer {
635 #footer {
635 padding: 0;
636 padding: 0;
636 text-align: center;
637 text-align: center;
637 vertical-align: middle;
638 vertical-align: middle;
638 color: @grey2;
639 color: @grey2;
639 font-size: 11px;
640 font-size: 11px;
640
641
641 p {
642 p {
642 margin: 0;
643 margin: 0;
643 padding: 1em;
644 padding: 1em;
644 line-height: 1em;
645 line-height: 1em;
645 }
646 }
646
647
647 .server-instance { //server instance
648 .server-instance { //server instance
648 display: none;
649 display: none;
649 }
650 }
650
651
651 .title {
652 .title {
652 float: none;
653 float: none;
653 margin: 0 auto;
654 margin: 0 auto;
654 }
655 }
655 }
656 }
656
657
657 button.close {
658 button.close {
658 padding: 0;
659 padding: 0;
659 cursor: pointer;
660 cursor: pointer;
660 background: transparent;
661 background: transparent;
661 border: 0;
662 border: 0;
662 .box-shadow(none);
663 .box-shadow(none);
663 -webkit-appearance: none;
664 -webkit-appearance: none;
664 }
665 }
665
666
666 .close {
667 .close {
667 float: right;
668 float: right;
668 font-size: 21px;
669 font-size: 21px;
669 font-family: @text-bootstrap;
670 font-family: @text-bootstrap;
670 line-height: 1em;
671 line-height: 1em;
671 font-weight: bold;
672 font-weight: bold;
672 color: @grey2;
673 color: @grey2;
673
674
674 &:hover,
675 &:hover,
675 &:focus {
676 &:focus {
676 color: @grey1;
677 color: @grey1;
677 text-decoration: none;
678 text-decoration: none;
678 cursor: pointer;
679 cursor: pointer;
679 }
680 }
680 }
681 }
681
682
682 // GRID
683 // GRID
683 .sorting,
684 .sorting,
684 .sorting_desc,
685 .sorting_desc,
685 .sorting_asc {
686 .sorting_asc {
686 cursor: pointer;
687 cursor: pointer;
687 }
688 }
688 .sorting_desc:after {
689 .sorting_desc:after {
689 content: "\00A0\25B2";
690 content: "\00A0\25B2";
690 font-size: .75em;
691 font-size: .75em;
691 }
692 }
692 .sorting_asc:after {
693 .sorting_asc:after {
693 content: "\00A0\25BC";
694 content: "\00A0\25BC";
694 font-size: .68em;
695 font-size: .68em;
695 }
696 }
696
697
697
698
698 .user_auth_tokens {
699 .user_auth_tokens {
699
700
700 &.truncate {
701 &.truncate {
701 white-space: nowrap;
702 white-space: nowrap;
702 overflow: hidden;
703 overflow: hidden;
703 text-overflow: ellipsis;
704 text-overflow: ellipsis;
704 }
705 }
705
706
706 .fields .field .input {
707 .fields .field .input {
707 margin: 0;
708 margin: 0;
708 }
709 }
709
710
710 input#description {
711 input#description {
711 width: 100px;
712 width: 100px;
712 margin: 0;
713 margin: 0;
713 }
714 }
714
715
715 .drop-menu {
716 .drop-menu {
716 // TODO: johbo: Remove this, should work out of the box when
717 // TODO: johbo: Remove this, should work out of the box when
717 // having multiple inputs inline
718 // having multiple inputs inline
718 margin: 0 0 0 5px;
719 margin: 0 0 0 5px;
719 }
720 }
720 }
721 }
721 #user_list_table {
722 #user_list_table {
722 .closed {
723 .closed {
723 background-color: @grey6;
724 background-color: @grey6;
724 }
725 }
725 }
726 }
726
727
727
728
728 input, textarea {
729 input, textarea {
729 &.disabled {
730 &.disabled {
730 opacity: .5;
731 opacity: .5;
731 }
732 }
732
733
733 &:hover {
734 &:hover {
734 border-color: @grey3;
735 border-color: @grey3;
735 box-shadow: @button-shadow;
736 box-shadow: @button-shadow;
736 }
737 }
737
738
738 &:focus {
739 &:focus {
739 border-color: @rcblue;
740 border-color: @rcblue;
740 box-shadow: @button-shadow;
741 box-shadow: @button-shadow;
741 }
742 }
742 }
743 }
743
744
744 // remove extra padding in firefox
745 // remove extra padding in firefox
745 input::-moz-focus-inner { border:0; padding:0 }
746 input::-moz-focus-inner { border:0; padding:0 }
746
747
747 .adjacent input {
748 .adjacent input {
748 margin-bottom: @padding;
749 margin-bottom: @padding;
749 }
750 }
750
751
751 .permissions_boxes {
752 .permissions_boxes {
752 display: block;
753 display: block;
753 }
754 }
754
755
755 //FORMS
756 //FORMS
756
757
757 .medium-inline,
758 .medium-inline,
758 input#description.medium-inline {
759 input#description.medium-inline {
759 display: inline;
760 display: inline;
760 width: @medium-inline-input-width;
761 width: @medium-inline-input-width;
761 min-width: 100px;
762 min-width: 100px;
762 }
763 }
763
764
764 select {
765 select {
765 //reset
766 //reset
766 -webkit-appearance: none;
767 -webkit-appearance: none;
767 -moz-appearance: none;
768 -moz-appearance: none;
768
769
769 display: inline-block;
770 display: inline-block;
770 height: 28px;
771 height: 28px;
771 width: auto;
772 width: auto;
772 margin: 0 @padding @padding 0;
773 margin: 0 @padding @padding 0;
773 padding: 0 18px 0 8px;
774 padding: 0 18px 0 8px;
774 line-height:1em;
775 line-height:1em;
775 font-size: @basefontsize;
776 font-size: @basefontsize;
776 border: @border-thickness solid @grey5;
777 border: @border-thickness solid @grey5;
777 border-radius: @border-radius;
778 border-radius: @border-radius;
778 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
779 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
779 color: @grey4;
780 color: @grey4;
780 box-shadow: @button-shadow;
781 box-shadow: @button-shadow;
781
782
782 &:after {
783 &:after {
783 content: "\00A0\25BE";
784 content: "\00A0\25BE";
784 }
785 }
785
786
786 &:focus, &:hover {
787 &:focus, &:hover {
787 outline: none;
788 outline: none;
788 border-color: @grey4;
789 border-color: @grey4;
789 color: @rcdarkblue;
790 color: @rcdarkblue;
790 }
791 }
791 }
792 }
792
793
793 option {
794 option {
794 &:focus {
795 &:focus {
795 outline: none;
796 outline: none;
796 }
797 }
797 }
798 }
798
799
799 input,
800 input,
800 textarea {
801 textarea {
801 padding: @input-padding;
802 padding: @input-padding;
802 border: @input-border-thickness solid @border-highlight-color;
803 border: @input-border-thickness solid @border-highlight-color;
803 .border-radius (@border-radius);
804 .border-radius (@border-radius);
804 font-family: @text-light;
805 font-family: @text-light;
805 font-size: @basefontsize;
806 font-size: @basefontsize;
806
807
807 &.input-sm {
808 &.input-sm {
808 padding: 5px;
809 padding: 5px;
809 }
810 }
810
811
811 &#description {
812 &#description {
812 min-width: @input-description-minwidth;
813 min-width: @input-description-minwidth;
813 min-height: 1em;
814 min-height: 1em;
814 padding: 10px;
815 padding: 10px;
815 }
816 }
816 }
817 }
817
818
818 .field-sm {
819 .field-sm {
819 input,
820 input,
820 textarea {
821 textarea {
821 padding: 5px;
822 padding: 5px;
822 }
823 }
823 }
824 }
824
825
825 textarea {
826 textarea {
826 display: block;
827 display: block;
827 clear: both;
828 clear: both;
828 width: 100%;
829 width: 100%;
829 min-height: 100px;
830 min-height: 100px;
830 margin-bottom: @padding;
831 margin-bottom: @padding;
831 .box-sizing(border-box);
832 .box-sizing(border-box);
832 overflow: auto;
833 overflow: auto;
833 }
834 }
834
835
835 label {
836 label {
836 font-family: @text-light;
837 font-family: @text-light;
837 }
838 }
838
839
839 // GRAVATARS
840 // GRAVATARS
840 // centers gravatar on username to the right
841 // centers gravatar on username to the right
841
842
842 .gravatar {
843 .gravatar {
843 display: inline;
844 display: inline;
844 min-width: 16px;
845 min-width: 16px;
845 min-height: 16px;
846 min-height: 16px;
846 margin: -5px 0;
847 margin: -5px 0;
847 padding: 0;
848 padding: 0;
848 line-height: 1em;
849 line-height: 1em;
849 box-sizing: content-box;
850 box-sizing: content-box;
850 border-radius: 50%;
851 border-radius: 50%;
851
852
852 &.gravatar-large {
853 &.gravatar-large {
853 margin: -0.5em .25em -0.5em 0;
854 margin: -0.5em .25em -0.5em 0;
854 }
855 }
855
856
856 & + .user {
857 & + .user {
857 display: inline;
858 display: inline;
858 margin: 0;
859 margin: 0;
859 padding: 0 0 0 .17em;
860 padding: 0 0 0 .17em;
860 line-height: 1em;
861 line-height: 1em;
861 }
862 }
862
863
863 & + .no-margin {
864 & + .no-margin {
864 margin: 0
865 margin: 0
865 }
866 }
866
867
867 }
868 }
868
869
869 .user-inline-data {
870 .user-inline-data {
870 display: inline-block;
871 display: inline-block;
871 float: left;
872 float: left;
872 padding-left: .5em;
873 padding-left: .5em;
873 line-height: 1.3em;
874 line-height: 1.3em;
874 }
875 }
875
876
876 .rc-user { // gravatar + user wrapper
877 .rc-user { // gravatar + user wrapper
877 float: left;
878 float: left;
878 position: relative;
879 position: relative;
879 min-width: 100px;
880 min-width: 100px;
880 max-width: 200px;
881 max-width: 200px;
881 min-height: (@gravatar-size + @border-thickness * 2); // account for border
882 min-height: (@gravatar-size + @border-thickness * 2); // account for border
882 display: block;
883 display: block;
883 padding: 0 0 0 (@gravatar-size + @basefontsize/4);
884 padding: 0 0 0 (@gravatar-size + @basefontsize/4);
884
885
885
886
886 .gravatar {
887 .gravatar {
887 display: block;
888 display: block;
888 position: absolute;
889 position: absolute;
889 top: 0;
890 top: 0;
890 left: 0;
891 left: 0;
891 min-width: @gravatar-size;
892 min-width: @gravatar-size;
892 min-height: @gravatar-size;
893 min-height: @gravatar-size;
893 margin: 0;
894 margin: 0;
894 }
895 }
895
896
896 .user {
897 .user {
897 display: block;
898 display: block;
898 max-width: 175px;
899 max-width: 175px;
899 padding-top: 2px;
900 padding-top: 2px;
900 overflow: hidden;
901 overflow: hidden;
901 text-overflow: ellipsis;
902 text-overflow: ellipsis;
902 }
903 }
903 }
904 }
904
905
905 .gist-gravatar,
906 .gist-gravatar,
906 .journal_container {
907 .journal_container {
907 .gravatar-large {
908 .gravatar-large {
908 margin: 0 .5em -10px 0;
909 margin: 0 .5em -10px 0;
909 }
910 }
910 }
911 }
911
912
912 .gist-type-fields {
913 .gist-type-fields {
913 line-height: 30px;
914 line-height: 30px;
914 height: 30px;
915 height: 30px;
915
916
916 .gist-type-fields-wrapper {
917 .gist-type-fields-wrapper {
917 vertical-align: middle;
918 vertical-align: middle;
918 display: inline-block;
919 display: inline-block;
919 line-height: 25px;
920 line-height: 25px;
920 }
921 }
921 }
922 }
922
923
923 // ADMIN SETTINGS
924 // ADMIN SETTINGS
924
925
925 // Tag Patterns
926 // Tag Patterns
926 .tag_patterns {
927 .tag_patterns {
927 .tag_input {
928 .tag_input {
928 margin-bottom: @padding;
929 margin-bottom: @padding;
929 }
930 }
930 }
931 }
931
932
932 .locked_input {
933 .locked_input {
933 position: relative;
934 position: relative;
934
935
935 input {
936 input {
936 display: inline;
937 display: inline;
937 margin: 3px 5px 0px 0px;
938 margin: 3px 5px 0px 0px;
938 }
939 }
939
940
940 br {
941 br {
941 display: none;
942 display: none;
942 }
943 }
943
944
944 .error-message {
945 .error-message {
945 float: left;
946 float: left;
946 width: 100%;
947 width: 100%;
947 }
948 }
948
949
949 .lock_input_button {
950 .lock_input_button {
950 display: inline;
951 display: inline;
951 }
952 }
952
953
953 .help-block {
954 .help-block {
954 clear: both;
955 clear: both;
955 }
956 }
956 }
957 }
957
958
958 // Notifications
959 // Notifications
959
960
960 .notifications_buttons {
961 .notifications_buttons {
961 margin: 0 0 @space 0;
962 margin: 0 0 @space 0;
962 padding: 0;
963 padding: 0;
963
964
964 .btn {
965 .btn {
965 display: inline-block;
966 display: inline-block;
966 }
967 }
967 }
968 }
968
969
969 .notification-list {
970 .notification-list {
970
971
971 div {
972 div {
972 vertical-align: middle;
973 vertical-align: middle;
973 }
974 }
974
975
975 .container {
976 .container {
976 display: block;
977 display: block;
977 margin: 0 0 @padding 0;
978 margin: 0 0 @padding 0;
978 }
979 }
979
980
980 .delete-notifications {
981 .delete-notifications {
981 margin-left: @padding;
982 margin-left: @padding;
982 text-align: right;
983 text-align: right;
983 cursor: pointer;
984 cursor: pointer;
984 }
985 }
985
986
986 .read-notifications {
987 .read-notifications {
987 margin-left: @padding/2;
988 margin-left: @padding/2;
988 text-align: right;
989 text-align: right;
989 width: 35px;
990 width: 35px;
990 cursor: pointer;
991 cursor: pointer;
991 }
992 }
992
993
993 .icon-minus-sign {
994 .icon-minus-sign {
994 color: @alert2;
995 color: @alert2;
995 }
996 }
996
997
997 .icon-ok-sign {
998 .icon-ok-sign {
998 color: @alert1;
999 color: @alert1;
999 }
1000 }
1000 }
1001 }
1001
1002
1002 .user_settings {
1003 .user_settings {
1003 float: left;
1004 float: left;
1004 clear: both;
1005 clear: both;
1005 display: block;
1006 display: block;
1006 width: 100%;
1007 width: 100%;
1007
1008
1008 .gravatar_box {
1009 .gravatar_box {
1009 margin-bottom: @padding;
1010 margin-bottom: @padding;
1010
1011
1011 &:after {
1012 &:after {
1012 content: " ";
1013 content: " ";
1013 clear: both;
1014 clear: both;
1014 width: 100%;
1015 width: 100%;
1015 }
1016 }
1016 }
1017 }
1017
1018
1018 .fields .field {
1019 .fields .field {
1019 clear: both;
1020 clear: both;
1020 }
1021 }
1021 }
1022 }
1022
1023
1023 .advanced_settings {
1024 .advanced_settings {
1024 margin-bottom: @space;
1025 margin-bottom: @space;
1025
1026
1026 .help-block {
1027 .help-block {
1027 margin-left: 0;
1028 margin-left: 0;
1028 }
1029 }
1029
1030
1030 button + .help-block {
1031 button + .help-block {
1031 margin-top: @padding;
1032 margin-top: @padding;
1032 }
1033 }
1033 }
1034 }
1034
1035
1035 // admin settings radio buttons and labels
1036 // admin settings radio buttons and labels
1036 .label-2 {
1037 .label-2 {
1037 float: left;
1038 float: left;
1038 width: @label2-width;
1039 width: @label2-width;
1039
1040
1040 label {
1041 label {
1041 color: @grey1;
1042 color: @grey1;
1042 }
1043 }
1043 }
1044 }
1044 .checkboxes {
1045 .checkboxes {
1045 float: left;
1046 float: left;
1046 width: @checkboxes-width;
1047 width: @checkboxes-width;
1047 margin-bottom: @padding;
1048 margin-bottom: @padding;
1048
1049
1049 .checkbox {
1050 .checkbox {
1050 width: 100%;
1051 width: 100%;
1051
1052
1052 label {
1053 label {
1053 margin: 0;
1054 margin: 0;
1054 padding: 0;
1055 padding: 0;
1055 }
1056 }
1056 }
1057 }
1057
1058
1058 .checkbox + .checkbox {
1059 .checkbox + .checkbox {
1059 display: inline-block;
1060 display: inline-block;
1060 }
1061 }
1061
1062
1062 label {
1063 label {
1063 margin-right: 1em;
1064 margin-right: 1em;
1064 }
1065 }
1065 }
1066 }
1066
1067
1067 // CHANGELOG
1068 // CHANGELOG
1068 .container_header {
1069 .container_header {
1069 float: left;
1070 float: left;
1070 display: block;
1071 display: block;
1071 width: 100%;
1072 width: 100%;
1072 margin: @padding 0 @padding;
1073 margin: @padding 0 @padding;
1073
1074
1074 #filter_changelog {
1075 #filter_changelog {
1075 float: left;
1076 float: left;
1076 margin-right: @padding;
1077 margin-right: @padding;
1077 }
1078 }
1078
1079
1079 .breadcrumbs_light {
1080 .breadcrumbs_light {
1080 display: inline-block;
1081 display: inline-block;
1081 }
1082 }
1082 }
1083 }
1083
1084
1084 .info_box {
1085 .info_box {
1085 float: right;
1086 float: right;
1086 }
1087 }
1087
1088
1088
1089
1089
1090
1090 #graph_content{
1091 #graph_content{
1091
1092
1092 // adjust for table headers so that graph renders properly
1093 // adjust for table headers so that graph renders properly
1093 // #graph_nodes padding - table cell padding
1094 // #graph_nodes padding - table cell padding
1094 padding-top: (@space - (@basefontsize * 2.4));
1095 padding-top: (@space - (@basefontsize * 2.4));
1095
1096
1096 &.graph_full_width {
1097 &.graph_full_width {
1097 width: 100%;
1098 width: 100%;
1098 max-width: 100%;
1099 max-width: 100%;
1099 }
1100 }
1100 }
1101 }
1101
1102
1102 #graph {
1103 #graph {
1103
1104
1104 .pagination-left {
1105 .pagination-left {
1105 float: left;
1106 float: left;
1106 clear: both;
1107 clear: both;
1107 }
1108 }
1108
1109
1109 .log-container {
1110 .log-container {
1110 max-width: 345px;
1111 max-width: 345px;
1111
1112
1112 .message{
1113 .message{
1113 max-width: 340px;
1114 max-width: 340px;
1114 }
1115 }
1115 }
1116 }
1116
1117
1117 .graph-col-wrapper {
1118 .graph-col-wrapper {
1118
1119
1119 #graph_nodes {
1120 #graph_nodes {
1120 width: 100px;
1121 width: 100px;
1121 position: absolute;
1122 position: absolute;
1122 left: 70px;
1123 left: 70px;
1123 z-index: -1;
1124 z-index: -1;
1124 }
1125 }
1125 }
1126 }
1126
1127
1127 .load-more-commits {
1128 .load-more-commits {
1128 text-align: center;
1129 text-align: center;
1129 }
1130 }
1130 .load-more-commits:hover {
1131 .load-more-commits:hover {
1131 background-color: @grey7;
1132 background-color: @grey7;
1132 }
1133 }
1133 .load-more-commits {
1134 .load-more-commits {
1134 a {
1135 a {
1135 display: block;
1136 display: block;
1136 }
1137 }
1137 }
1138 }
1138 }
1139 }
1139
1140
1140 .obsolete-toggle {
1141 .obsolete-toggle {
1141 line-height: 30px;
1142 line-height: 30px;
1142 margin-left: -15px;
1143 margin-left: -15px;
1143 }
1144 }
1144
1145
1145 #rev_range_container, #rev_range_clear, #rev_range_more {
1146 #rev_range_container, #rev_range_clear, #rev_range_more {
1146 margin-top: -5px;
1147 margin-top: -5px;
1147 margin-bottom: -5px;
1148 margin-bottom: -5px;
1148 }
1149 }
1149
1150
1150 #filter_changelog {
1151 #filter_changelog {
1151 float: left;
1152 float: left;
1152 }
1153 }
1153
1154
1154
1155
1155 //--- THEME ------------------//
1156 //--- THEME ------------------//
1156
1157
1157 #logo {
1158 #logo {
1158 float: left;
1159 float: left;
1159 margin: 9px 0 0 0;
1160 margin: 9px 0 0 0;
1160
1161
1161 .header {
1162 .header {
1162 background-color: transparent;
1163 background-color: transparent;
1163 }
1164 }
1164
1165
1165 a {
1166 a {
1166 display: inline-block;
1167 display: inline-block;
1167 }
1168 }
1168
1169
1169 img {
1170 img {
1170 height:30px;
1171 height:30px;
1171 }
1172 }
1172 }
1173 }
1173
1174
1174 .logo-wrapper {
1175 .logo-wrapper {
1175 float:left;
1176 float:left;
1176 }
1177 }
1177
1178
1178 .branding {
1179 .branding {
1179 float: left;
1180 float: left;
1180 padding: 9px 2px;
1181 padding: 9px 2px;
1181 line-height: 1em;
1182 line-height: 1em;
1182 font-size: @navigation-fontsize;
1183 font-size: @navigation-fontsize;
1183
1184
1184 a {
1185 a {
1185 color: @grey5
1186 color: @grey5
1186 }
1187 }
1187
1188
1188 // 1024px or smaller
1189 // 1024px or smaller
1189 @media screen and (max-width: 1180px) {
1190 @media screen and (max-width: 1180px) {
1190 display: none;
1191 display: none;
1191 }
1192 }
1192
1193
1193 }
1194 }
1194
1195
1195 img {
1196 img {
1196 border: none;
1197 border: none;
1197 outline: none;
1198 outline: none;
1198 }
1199 }
1199 user-profile-header
1200 user-profile-header
1200 label {
1201 label {
1201
1202
1202 input[type="checkbox"] {
1203 input[type="checkbox"] {
1203 margin-right: 1em;
1204 margin-right: 1em;
1204 }
1205 }
1205 input[type="radio"] {
1206 input[type="radio"] {
1206 margin-right: 1em;
1207 margin-right: 1em;
1207 }
1208 }
1208 }
1209 }
1209
1210
1210 .review-status {
1211 .review-status {
1211 &.under_review {
1212 &.under_review {
1212 color: @alert3;
1213 color: @alert3;
1213 }
1214 }
1214 &.approved {
1215 &.approved {
1215 color: @alert1;
1216 color: @alert1;
1216 }
1217 }
1217 &.rejected,
1218 &.rejected,
1218 &.forced_closed{
1219 &.forced_closed{
1219 color: @alert2;
1220 color: @alert2;
1220 }
1221 }
1221 &.not_reviewed {
1222 &.not_reviewed {
1222 color: @grey5;
1223 color: @grey5;
1223 }
1224 }
1224 }
1225 }
1225
1226
1226 .review-status-under_review {
1227 .review-status-under_review {
1227 color: @alert3;
1228 color: @alert3;
1228 }
1229 }
1229 .status-tag-under_review {
1230 .status-tag-under_review {
1230 border-color: @alert3;
1231 border-color: @alert3;
1231 }
1232 }
1232
1233
1233 .review-status-approved {
1234 .review-status-approved {
1234 color: @alert1;
1235 color: @alert1;
1235 }
1236 }
1236 .status-tag-approved {
1237 .status-tag-approved {
1237 border-color: @alert1;
1238 border-color: @alert1;
1238 }
1239 }
1239
1240
1240 .review-status-rejected,
1241 .review-status-rejected,
1241 .review-status-forced_closed {
1242 .review-status-forced_closed {
1242 color: @alert2;
1243 color: @alert2;
1243 }
1244 }
1244 .status-tag-rejected,
1245 .status-tag-rejected,
1245 .status-tag-forced_closed {
1246 .status-tag-forced_closed {
1246 border-color: @alert2;
1247 border-color: @alert2;
1247 }
1248 }
1248
1249
1249 .review-status-not_reviewed {
1250 .review-status-not_reviewed {
1250 color: @grey5;
1251 color: @grey5;
1251 }
1252 }
1252 .status-tag-not_reviewed {
1253 .status-tag-not_reviewed {
1253 border-color: @grey5;
1254 border-color: @grey5;
1254 }
1255 }
1255
1256
1256 .test_pattern_preview {
1257 .test_pattern_preview {
1257 margin: @space 0;
1258 margin: @space 0;
1258
1259
1259 p {
1260 p {
1260 margin-bottom: 0;
1261 margin-bottom: 0;
1261 border-bottom: @border-thickness solid @border-default-color;
1262 border-bottom: @border-thickness solid @border-default-color;
1262 color: @grey3;
1263 color: @grey3;
1263 }
1264 }
1264
1265
1265 .btn {
1266 .btn {
1266 margin-bottom: @padding;
1267 margin-bottom: @padding;
1267 }
1268 }
1268 }
1269 }
1269 #test_pattern_result {
1270 #test_pattern_result {
1270 display: none;
1271 display: none;
1271 &:extend(pre);
1272 &:extend(pre);
1272 padding: .9em;
1273 padding: .9em;
1273 color: @grey3;
1274 color: @grey3;
1274 background-color: @grey7;
1275 background-color: @grey7;
1275 border-right: @border-thickness solid @border-default-color;
1276 border-right: @border-thickness solid @border-default-color;
1276 border-bottom: @border-thickness solid @border-default-color;
1277 border-bottom: @border-thickness solid @border-default-color;
1277 border-left: @border-thickness solid @border-default-color;
1278 border-left: @border-thickness solid @border-default-color;
1278 }
1279 }
1279
1280
1280 #repo_vcs_settings {
1281 #repo_vcs_settings {
1281 #inherit_overlay_vcs_default {
1282 #inherit_overlay_vcs_default {
1282 display: none;
1283 display: none;
1283 }
1284 }
1284 #inherit_overlay_vcs_custom {
1285 #inherit_overlay_vcs_custom {
1285 display: custom;
1286 display: custom;
1286 }
1287 }
1287 &.inherited {
1288 &.inherited {
1288 #inherit_overlay_vcs_default {
1289 #inherit_overlay_vcs_default {
1289 display: block;
1290 display: block;
1290 }
1291 }
1291 #inherit_overlay_vcs_custom {
1292 #inherit_overlay_vcs_custom {
1292 display: none;
1293 display: none;
1293 }
1294 }
1294 }
1295 }
1295 }
1296 }
1296
1297
1297 .issue-tracker-link {
1298 .issue-tracker-link {
1298 color: @rcblue;
1299 color: @rcblue;
1299 }
1300 }
1300
1301
1301 // Issue Tracker Table Show/Hide
1302 // Issue Tracker Table Show/Hide
1302 #repo_issue_tracker {
1303 #repo_issue_tracker {
1303 #inherit_overlay {
1304 #inherit_overlay {
1304 display: none;
1305 display: none;
1305 }
1306 }
1306 #custom_overlay {
1307 #custom_overlay {
1307 display: custom;
1308 display: custom;
1308 }
1309 }
1309 &.inherited {
1310 &.inherited {
1310 #inherit_overlay {
1311 #inherit_overlay {
1311 display: block;
1312 display: block;
1312 }
1313 }
1313 #custom_overlay {
1314 #custom_overlay {
1314 display: none;
1315 display: none;
1315 }
1316 }
1316 }
1317 }
1317 }
1318 }
1318 table.issuetracker {
1319 table.issuetracker {
1319 &.readonly {
1320 &.readonly {
1320 tr, td {
1321 tr, td {
1321 color: @grey3;
1322 color: @grey3;
1322 }
1323 }
1323 }
1324 }
1324 .edit {
1325 .edit {
1325 display: none;
1326 display: none;
1326 }
1327 }
1327 .editopen {
1328 .editopen {
1328 .edit {
1329 .edit {
1329 display: inline;
1330 display: inline;
1330 }
1331 }
1331 .entry {
1332 .entry {
1332 display: none;
1333 display: none;
1333 }
1334 }
1334 }
1335 }
1335 tr td.td-action {
1336 tr td.td-action {
1336 min-width: 117px;
1337 min-width: 117px;
1337 }
1338 }
1338 td input {
1339 td input {
1339 max-width: none;
1340 max-width: none;
1340 min-width: 30px;
1341 min-width: 30px;
1341 width: 80%;
1342 width: 80%;
1342 }
1343 }
1343 .issuetracker_pref input {
1344 .issuetracker_pref input {
1344 width: 40%;
1345 width: 40%;
1345 }
1346 }
1346 input.edit_issuetracker_update {
1347 input.edit_issuetracker_update {
1347 margin-right: 0;
1348 margin-right: 0;
1348 width: auto;
1349 width: auto;
1349 }
1350 }
1350 }
1351 }
1351
1352
1352 table.integrations {
1353 table.integrations {
1353 .td-icon {
1354 .td-icon {
1354 width: 20px;
1355 width: 20px;
1355 .integration-icon {
1356 .integration-icon {
1356 height: 20px;
1357 height: 20px;
1357 width: 20px;
1358 width: 20px;
1358 }
1359 }
1359 }
1360 }
1360 }
1361 }
1361
1362
1362 .integrations {
1363 .integrations {
1363 a.integration-box {
1364 a.integration-box {
1364 color: @text-color;
1365 color: @text-color;
1365 &:hover {
1366 &:hover {
1366 .panel {
1367 .panel {
1367 background: #fbfbfb;
1368 background: #fbfbfb;
1368 }
1369 }
1369 }
1370 }
1370 .integration-icon {
1371 .integration-icon {
1371 width: 30px;
1372 width: 30px;
1372 height: 30px;
1373 height: 30px;
1373 margin-right: 20px;
1374 margin-right: 20px;
1374 float: left;
1375 float: left;
1375 }
1376 }
1376
1377
1377 .panel-body {
1378 .panel-body {
1378 padding: 10px;
1379 padding: 10px;
1379 }
1380 }
1380 .panel {
1381 .panel {
1381 margin-bottom: 10px;
1382 margin-bottom: 10px;
1382 }
1383 }
1383 h2 {
1384 h2 {
1384 display: inline-block;
1385 display: inline-block;
1385 margin: 0;
1386 margin: 0;
1386 min-width: 140px;
1387 min-width: 140px;
1387 }
1388 }
1388 }
1389 }
1389 a.integration-box.dummy-integration {
1390 a.integration-box.dummy-integration {
1390 color: @grey4
1391 color: @grey4
1391 }
1392 }
1392 }
1393 }
1393
1394
1394 //Permissions Settings
1395 //Permissions Settings
1395 #add_perm {
1396 #add_perm {
1396 margin: 0 0 @padding;
1397 margin: 0 0 @padding;
1397 cursor: pointer;
1398 cursor: pointer;
1398 }
1399 }
1399
1400
1400 .perm_ac {
1401 .perm_ac {
1401 input {
1402 input {
1402 width: 95%;
1403 width: 95%;
1403 }
1404 }
1404 }
1405 }
1405
1406
1406 .autocomplete-suggestions {
1407 .autocomplete-suggestions {
1407 width: auto !important; // overrides autocomplete.js
1408 width: auto !important; // overrides autocomplete.js
1408 min-width: 278px;
1409 min-width: 278px;
1409 margin: 0;
1410 margin: 0;
1410 border: @border-thickness solid @grey5;
1411 border: @border-thickness solid @grey5;
1411 border-radius: @border-radius;
1412 border-radius: @border-radius;
1412 color: @grey2;
1413 color: @grey2;
1413 background-color: white;
1414 background-color: white;
1414 }
1415 }
1415
1416
1416 .autocomplete-qfilter-suggestions {
1417 .autocomplete-qfilter-suggestions {
1417 width: auto !important; // overrides autocomplete.js
1418 width: auto !important; // overrides autocomplete.js
1418 max-height: 100% !important;
1419 max-height: 100% !important;
1419 min-width: 376px;
1420 min-width: 376px;
1420 margin: 0;
1421 margin: 0;
1421 border: @border-thickness solid @grey5;
1422 border: @border-thickness solid @grey5;
1422 color: @grey2;
1423 color: @grey2;
1423 background-color: white;
1424 background-color: white;
1424 }
1425 }
1425
1426
1426 .autocomplete-selected {
1427 .autocomplete-selected {
1427 background: #F0F0F0;
1428 background: #F0F0F0;
1428 }
1429 }
1429
1430
1430 .ac-container-wrap {
1431 .ac-container-wrap {
1431 margin: 0;
1432 margin: 0;
1432 padding: 8px;
1433 padding: 8px;
1433 border-bottom: @border-thickness solid @grey5;
1434 border-bottom: @border-thickness solid @grey5;
1434 list-style-type: none;
1435 list-style-type: none;
1435 cursor: pointer;
1436 cursor: pointer;
1436
1437
1437 &:hover {
1438 &:hover {
1438 background-color: @grey7;
1439 background-color: @grey7;
1439 }
1440 }
1440
1441
1441 img {
1442 img {
1442 height: @gravatar-size;
1443 height: @gravatar-size;
1443 width: @gravatar-size;
1444 width: @gravatar-size;
1444 margin-right: 1em;
1445 margin-right: 1em;
1445 }
1446 }
1446
1447
1447 strong {
1448 strong {
1448 font-weight: normal;
1449 font-weight: normal;
1449 }
1450 }
1450 }
1451 }
1451
1452
1452 // Settings Dropdown
1453 // Settings Dropdown
1453 .user-menu .container {
1454 .user-menu .container {
1454 padding: 0 4px;
1455 padding: 0 4px;
1455 margin: 0;
1456 margin: 0;
1456 }
1457 }
1457
1458
1458 .user-menu .gravatar {
1459 .user-menu .gravatar {
1459 cursor: pointer;
1460 cursor: pointer;
1460 }
1461 }
1461
1462
1462 .codeblock {
1463 .codeblock {
1463 margin-bottom: @padding;
1464 margin-bottom: @padding;
1464 clear: both;
1465 clear: both;
1465
1466
1466 .stats {
1467 .stats {
1467 overflow: hidden;
1468 overflow: hidden;
1468 }
1469 }
1469
1470
1470 .message{
1471 .message{
1471 textarea{
1472 textarea{
1472 margin: 0;
1473 margin: 0;
1473 }
1474 }
1474 }
1475 }
1475
1476
1476 .code-header {
1477 .code-header {
1477 .stats {
1478 .stats {
1478 line-height: 2em;
1479 line-height: 2em;
1479
1480
1480 .revision_id {
1481 .revision_id {
1481 margin-left: 0;
1482 margin-left: 0;
1482 }
1483 }
1483 .buttons {
1484 .buttons {
1484 padding-right: 0;
1485 padding-right: 0;
1485 }
1486 }
1486 }
1487 }
1487
1488
1488 .item{
1489 .item{
1489 margin-right: 0.5em;
1490 margin-right: 0.5em;
1490 }
1491 }
1491 }
1492 }
1492
1493
1493 #editor_container {
1494 #editor_container {
1494 position: relative;
1495 position: relative;
1495 margin: @padding 10px;
1496 margin: @padding 10px;
1496 }
1497 }
1497 }
1498 }
1498
1499
1499 #file_history_container {
1500 #file_history_container {
1500 display: none;
1501 display: none;
1501 }
1502 }
1502
1503
1503 .file-history-inner {
1504 .file-history-inner {
1504 margin-bottom: 10px;
1505 margin-bottom: 10px;
1505 }
1506 }
1506
1507
1507 // Pull Requests
1508 // Pull Requests
1508 .summary-details {
1509 .summary-details {
1509 width: 100%;
1510 width: 100%;
1510 }
1511 }
1511 .pr-summary {
1512 .pr-summary {
1512 border-bottom: @border-thickness solid @grey5;
1513 border-bottom: @border-thickness solid @grey5;
1513 margin-bottom: @space;
1514 margin-bottom: @space;
1514 }
1515 }
1515
1516
1516 .reviewers {
1517 .reviewers {
1517 width: 98%;
1518 width: 98%;
1518 }
1519 }
1519
1520
1520 .reviewers ul li {
1521 .reviewers ul li {
1521 position: relative;
1522 position: relative;
1522 width: 100%;
1523 width: 100%;
1523 padding-bottom: 8px;
1524 padding-bottom: 8px;
1524 list-style-type: none;
1525 list-style-type: none;
1525 }
1526 }
1526
1527
1527 .reviewer_entry {
1528 .reviewer_entry {
1528 min-height: 55px;
1529 min-height: 55px;
1529 }
1530 }
1530
1531
1531 .reviewer_reason {
1532 .reviewer_reason {
1532 padding-left: 20px;
1533 padding-left: 20px;
1533 line-height: 1.5em;
1534 line-height: 1.5em;
1534 }
1535 }
1535 .reviewer_status {
1536 .reviewer_status {
1536 display: inline-block;
1537 display: inline-block;
1537 width: 20px;
1538 width: 20px;
1538 min-width: 20px;
1539 min-width: 20px;
1539 height: 1.2em;
1540 height: 1.2em;
1540 line-height: 1em;
1541 line-height: 1em;
1541 }
1542 }
1542
1543
1543 .reviewer_name {
1544 .reviewer_name {
1544 display: inline-block;
1545 display: inline-block;
1545 max-width: 83%;
1546 max-width: 83%;
1546 padding-right: 20px;
1547 padding-right: 20px;
1547 vertical-align: middle;
1548 vertical-align: middle;
1548 line-height: 1;
1549 line-height: 1;
1549
1550
1550 .rc-user {
1551 .rc-user {
1551 min-width: 0;
1552 min-width: 0;
1552 margin: -2px 1em 0 0;
1553 margin: -2px 1em 0 0;
1553 }
1554 }
1554
1555
1555 .reviewer {
1556 .reviewer {
1556 float: left;
1557 float: left;
1557 }
1558 }
1558 }
1559 }
1559
1560
1560 .reviewer_member_mandatory {
1561 .reviewer_member_mandatory {
1561 width: 16px;
1562 width: 16px;
1562 font-size: 11px;
1563 font-size: 11px;
1563 margin: 0;
1564 margin: 0;
1564 padding: 0;
1565 padding: 0;
1565 color: black;
1566 color: black;
1566 opacity: 0.4;
1567 opacity: 0.4;
1567 }
1568 }
1568
1569
1569 .reviewer_member_mandatory_remove,
1570 .reviewer_member_mandatory_remove,
1570 .reviewer_member_remove {
1571 .reviewer_member_remove {
1571 width: 16px;
1572 width: 16px;
1572 padding: 0;
1573 padding: 0;
1573 color: black;
1574 color: black;
1574 cursor: pointer;
1575 cursor: pointer;
1575 }
1576 }
1576
1577
1577 .reviewer_member_mandatory_remove {
1578 .reviewer_member_mandatory_remove {
1578 color: @grey4;
1579 color: @grey4;
1579 }
1580 }
1580
1581
1581 .reviewer_member_status {
1582 .reviewer_member_status {
1582 margin-top: 5px;
1583 margin-top: 5px;
1583 }
1584 }
1584 .pr-summary #summary{
1585 .pr-summary #summary{
1585 width: 100%;
1586 width: 100%;
1586 }
1587 }
1587 .pr-summary .action_button:hover {
1588 .pr-summary .action_button:hover {
1588 border: 0;
1589 border: 0;
1589 cursor: pointer;
1590 cursor: pointer;
1590 }
1591 }
1591 .pr-details-title {
1592 .pr-details-title {
1592 height: 20px;
1593 height: 20px;
1593 line-height: 20px;
1594 line-height: 20px;
1594
1595
1595 padding-bottom: 8px;
1596 padding-bottom: 8px;
1596 border-bottom: @border-thickness solid @grey5;
1597 border-bottom: @border-thickness solid @grey5;
1597
1598
1598 .action_button.disabled {
1599 .action_button.disabled {
1599 color: @grey4;
1600 color: @grey4;
1600 cursor: inherit;
1601 cursor: inherit;
1601 }
1602 }
1602 .action_button {
1603 .action_button {
1603 color: @rcblue;
1604 color: @rcblue;
1604 }
1605 }
1605 }
1606 }
1606 .pr-details-content {
1607 .pr-details-content {
1607 margin-top: @textmargin - 5;
1608 margin-top: @textmargin - 5;
1608 margin-bottom: @textmargin - 5;
1609 margin-bottom: @textmargin - 5;
1609 }
1610 }
1610
1611
1611 .pr-reviewer-rules {
1612 .pr-reviewer-rules {
1612 padding: 10px 0px 20px 0px;
1613 padding: 10px 0px 20px 0px;
1613 }
1614 }
1614
1615
1615 .todo-resolved {
1616 .todo-resolved {
1616 text-decoration: line-through;
1617 text-decoration: line-through;
1617 }
1618 }
1618
1619
1619 .todo-table, .comments-table {
1620 .todo-table, .comments-table {
1620 width: 100%;
1621 width: 100%;
1621
1622
1622 td {
1623 td {
1623 padding: 5px 0px;
1624 padding: 5px 0px;
1624 }
1625 }
1625
1626
1626 .td-todo-number {
1627 .td-todo-number {
1627 text-align: left;
1628 text-align: left;
1628 white-space: nowrap;
1629 white-space: nowrap;
1629 width: 1%;
1630 width: 1%;
1630 padding-right: 2px;
1631 padding-right: 2px;
1631 }
1632 }
1632
1633
1633 .td-todo-gravatar {
1634 .td-todo-gravatar {
1634 width: 5%;
1635 width: 5%;
1635
1636
1636 img {
1637 img {
1637 margin: -3px 0;
1638 margin: -3px 0;
1638 }
1639 }
1639 }
1640 }
1640
1641
1641 }
1642 }
1642
1643
1643 .todo-comment-text-wrapper {
1644 .todo-comment-text-wrapper {
1644 display: inline-grid;
1645 display: inline-grid;
1645 }
1646 }
1646
1647
1647 .todo-comment-text {
1648 .todo-comment-text {
1648 margin-left: 5px;
1649 margin-left: 5px;
1649 white-space: nowrap;
1650 white-space: nowrap;
1650 overflow: hidden;
1651 overflow: hidden;
1651 text-overflow: ellipsis;
1652 text-overflow: ellipsis;
1652 }
1653 }
1653
1654
1654 table.group_members {
1655 table.group_members {
1655 width: 100%
1656 width: 100%
1656 }
1657 }
1657
1658
1658 .group_members {
1659 .group_members {
1659 margin-top: 0;
1660 margin-top: 0;
1660 padding: 0;
1661 padding: 0;
1661
1662
1662 img {
1663 img {
1663 height: @gravatar-size;
1664 height: @gravatar-size;
1664 width: @gravatar-size;
1665 width: @gravatar-size;
1665 margin-right: .5em;
1666 margin-right: .5em;
1666 margin-left: 3px;
1667 margin-left: 3px;
1667 }
1668 }
1668
1669
1669 .to-delete {
1670 .to-delete {
1670 .user {
1671 .user {
1671 text-decoration: line-through;
1672 text-decoration: line-through;
1672 }
1673 }
1673 }
1674 }
1674 }
1675 }
1675
1676
1676 .compare_view_commits_title {
1677 .compare_view_commits_title {
1677 .disabled {
1678 .disabled {
1678 cursor: inherit;
1679 cursor: inherit;
1679 &:hover{
1680 &:hover{
1680 background-color: inherit;
1681 background-color: inherit;
1681 color: inherit;
1682 color: inherit;
1682 }
1683 }
1683 }
1684 }
1684 }
1685 }
1685
1686
1686 .subtitle-compare {
1687 .subtitle-compare {
1687 margin: -15px 0px 0px 0px;
1688 margin: -15px 0px 0px 0px;
1688 }
1689 }
1689
1690
1690 // new entry in group_members
1691 // new entry in group_members
1691 .td-author-new-entry {
1692 .td-author-new-entry {
1692 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1693 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1693 }
1694 }
1694
1695
1695 .usergroup_member_remove {
1696 .usergroup_member_remove {
1696 width: 16px;
1697 width: 16px;
1697 margin-bottom: 10px;
1698 margin-bottom: 10px;
1698 padding: 0;
1699 padding: 0;
1699 color: black !important;
1700 color: black !important;
1700 cursor: pointer;
1701 cursor: pointer;
1701 }
1702 }
1702
1703
1703 .reviewer_ac .ac-input {
1704 .reviewer_ac .ac-input {
1704 width: 98%;
1705 width: 98%;
1705 margin-bottom: 1em;
1706 margin-bottom: 1em;
1706 }
1707 }
1707
1708
1708 .observer_ac .ac-input {
1709 .observer_ac .ac-input {
1709 width: 98%;
1710 width: 98%;
1710 margin-bottom: 1em;
1711 margin-bottom: 1em;
1711 }
1712 }
1712
1713
1713 .rule-table {
1714 .rule-table {
1714 width: 100%;
1715 width: 100%;
1715 }
1716 }
1716
1717
1717 .rule-table td {
1718 .rule-table td {
1718
1719
1719 }
1720 }
1720
1721
1721 .rule-table .td-role {
1722 .rule-table .td-role {
1722 width: 100px
1723 width: 100px
1723 }
1724 }
1724
1725
1725 .rule-table .td-mandatory {
1726 .rule-table .td-mandatory {
1726 width: 100px
1727 width: 100px
1727 }
1728 }
1728
1729
1729 .rule-table .td-group-votes {
1730 .rule-table .td-group-votes {
1730 width: 150px
1731 width: 150px
1731 }
1732 }
1732
1733
1733 .compare_view_commits tr{
1734 .compare_view_commits tr{
1734 height: 20px;
1735 height: 20px;
1735 }
1736 }
1736 .compare_view_commits td {
1737 .compare_view_commits td {
1737 vertical-align: top;
1738 vertical-align: top;
1738 padding-top: 10px;
1739 padding-top: 10px;
1739 }
1740 }
1740 .compare_view_commits .author {
1741 .compare_view_commits .author {
1741 margin-left: 5px;
1742 margin-left: 5px;
1742 }
1743 }
1743
1744
1744 .compare_view_commits {
1745 .compare_view_commits {
1745 .color-a {
1746 .color-a {
1746 color: @alert1;
1747 color: @alert1;
1747 }
1748 }
1748
1749
1749 .color-c {
1750 .color-c {
1750 color: @color3;
1751 color: @color3;
1751 }
1752 }
1752
1753
1753 .color-r {
1754 .color-r {
1754 color: @color5;
1755 color: @color5;
1755 }
1756 }
1756
1757
1757 .color-a-bg {
1758 .color-a-bg {
1758 background-color: @alert1;
1759 background-color: @alert1;
1759 }
1760 }
1760
1761
1761 .color-c-bg {
1762 .color-c-bg {
1762 background-color: @alert3;
1763 background-color: @alert3;
1763 }
1764 }
1764
1765
1765 .color-r-bg {
1766 .color-r-bg {
1766 background-color: @alert2;
1767 background-color: @alert2;
1767 }
1768 }
1768
1769
1769 .color-a-border {
1770 .color-a-border {
1770 border: 1px solid @alert1;
1771 border: 1px solid @alert1;
1771 }
1772 }
1772
1773
1773 .color-c-border {
1774 .color-c-border {
1774 border: 1px solid @alert3;
1775 border: 1px solid @alert3;
1775 }
1776 }
1776
1777
1777 .color-r-border {
1778 .color-r-border {
1778 border: 1px solid @alert2;
1779 border: 1px solid @alert2;
1779 }
1780 }
1780
1781
1781 .commit-change-indicator {
1782 .commit-change-indicator {
1782 width: 15px;
1783 width: 15px;
1783 height: 15px;
1784 height: 15px;
1784 position: relative;
1785 position: relative;
1785 left: 15px;
1786 left: 15px;
1786 }
1787 }
1787
1788
1788 .commit-change-content {
1789 .commit-change-content {
1789 text-align: center;
1790 text-align: center;
1790 vertical-align: middle;
1791 vertical-align: middle;
1791 line-height: 15px;
1792 line-height: 15px;
1792 }
1793 }
1793 }
1794 }
1794
1795
1795 .compare_view_filepath {
1796 .compare_view_filepath {
1796 color: @grey1;
1797 color: @grey1;
1797 }
1798 }
1798
1799
1799 .show_more {
1800 .show_more {
1800 display: inline-block;
1801 display: inline-block;
1801 width: 0;
1802 width: 0;
1802 height: 0;
1803 height: 0;
1803 vertical-align: middle;
1804 vertical-align: middle;
1804 content: "";
1805 content: "";
1805 border: 4px solid;
1806 border: 4px solid;
1806 border-right-color: transparent;
1807 border-right-color: transparent;
1807 border-bottom-color: transparent;
1808 border-bottom-color: transparent;
1808 border-left-color: transparent;
1809 border-left-color: transparent;
1809 font-size: 0;
1810 font-size: 0;
1810 }
1811 }
1811
1812
1812 .journal_more .show_more {
1813 .journal_more .show_more {
1813 display: inline;
1814 display: inline;
1814
1815
1815 &:after {
1816 &:after {
1816 content: none;
1817 content: none;
1817 }
1818 }
1818 }
1819 }
1819
1820
1820 .compare_view_commits .collapse_commit:after {
1821 .compare_view_commits .collapse_commit:after {
1821 cursor: pointer;
1822 cursor: pointer;
1822 content: "\00A0\25B4";
1823 content: "\00A0\25B4";
1823 margin-left: -3px;
1824 margin-left: -3px;
1824 font-size: 17px;
1825 font-size: 17px;
1825 color: @grey4;
1826 color: @grey4;
1826 }
1827 }
1827
1828
1828 .diff_links {
1829 .diff_links {
1829 margin-left: 8px;
1830 margin-left: 8px;
1830 }
1831 }
1831
1832
1832 #pull_request_overview {
1833 #pull_request_overview {
1833 div.ancestor {
1834 div.ancestor {
1834 margin: -33px 0;
1835 margin: -33px 0;
1835 }
1836 }
1836 }
1837 }
1837
1838
1838 div.ancestor {
1839 div.ancestor {
1839
1840
1840 }
1841 }
1841
1842
1842 .cs_icon_td input[type="checkbox"] {
1843 .cs_icon_td input[type="checkbox"] {
1843 display: none;
1844 display: none;
1844 }
1845 }
1845
1846
1846 .cs_icon_td .expand_file_icon:after {
1847 .cs_icon_td .expand_file_icon:after {
1847 cursor: pointer;
1848 cursor: pointer;
1848 content: "\00A0\25B6";
1849 content: "\00A0\25B6";
1849 font-size: 12px;
1850 font-size: 12px;
1850 color: @grey4;
1851 color: @grey4;
1851 }
1852 }
1852
1853
1853 .cs_icon_td .collapse_file_icon:after {
1854 .cs_icon_td .collapse_file_icon:after {
1854 cursor: pointer;
1855 cursor: pointer;
1855 content: "\00A0\25BC";
1856 content: "\00A0\25BC";
1856 font-size: 12px;
1857 font-size: 12px;
1857 color: @grey4;
1858 color: @grey4;
1858 }
1859 }
1859
1860
1860 /*new binary
1861 /*new binary
1861 NEW_FILENODE = 1
1862 NEW_FILENODE = 1
1862 DEL_FILENODE = 2
1863 DEL_FILENODE = 2
1863 MOD_FILENODE = 3
1864 MOD_FILENODE = 3
1864 RENAMED_FILENODE = 4
1865 RENAMED_FILENODE = 4
1865 COPIED_FILENODE = 5
1866 COPIED_FILENODE = 5
1866 CHMOD_FILENODE = 6
1867 CHMOD_FILENODE = 6
1867 BIN_FILENODE = 7
1868 BIN_FILENODE = 7
1868 */
1869 */
1869 .cs_files_expand {
1870 .cs_files_expand {
1870 font-size: @basefontsize + 5px;
1871 font-size: @basefontsize + 5px;
1871 line-height: 1.8em;
1872 line-height: 1.8em;
1872 float: right;
1873 float: right;
1873 }
1874 }
1874
1875
1875 .cs_files_expand span{
1876 .cs_files_expand span{
1876 color: @rcblue;
1877 color: @rcblue;
1877 cursor: pointer;
1878 cursor: pointer;
1878 }
1879 }
1879 .cs_files {
1880 .cs_files {
1880 clear: both;
1881 clear: both;
1881 padding-bottom: @padding;
1882 padding-bottom: @padding;
1882
1883
1883 .cur_cs {
1884 .cur_cs {
1884 margin: 10px 2px;
1885 margin: 10px 2px;
1885 font-weight: bold;
1886 font-weight: bold;
1886 }
1887 }
1887
1888
1888 .node {
1889 .node {
1889 float: left;
1890 float: left;
1890 }
1891 }
1891
1892
1892 .changes {
1893 .changes {
1893 float: right;
1894 float: right;
1894 color: white;
1895 color: white;
1895 font-size: @basefontsize - 4px;
1896 font-size: @basefontsize - 4px;
1896 margin-top: 4px;
1897 margin-top: 4px;
1897 opacity: 0.6;
1898 opacity: 0.6;
1898 filter: Alpha(opacity=60); /* IE8 and earlier */
1899 filter: Alpha(opacity=60); /* IE8 and earlier */
1899
1900
1900 .added {
1901 .added {
1901 background-color: @alert1;
1902 background-color: @alert1;
1902 float: left;
1903 float: left;
1903 text-align: center;
1904 text-align: center;
1904 }
1905 }
1905
1906
1906 .deleted {
1907 .deleted {
1907 background-color: @alert2;
1908 background-color: @alert2;
1908 float: left;
1909 float: left;
1909 text-align: center;
1910 text-align: center;
1910 }
1911 }
1911
1912
1912 .bin {
1913 .bin {
1913 background-color: @alert1;
1914 background-color: @alert1;
1914 text-align: center;
1915 text-align: center;
1915 }
1916 }
1916
1917
1917 /*new binary*/
1918 /*new binary*/
1918 .bin.bin1 {
1919 .bin.bin1 {
1919 background-color: @alert1;
1920 background-color: @alert1;
1920 text-align: center;
1921 text-align: center;
1921 }
1922 }
1922
1923
1923 /*deleted binary*/
1924 /*deleted binary*/
1924 .bin.bin2 {
1925 .bin.bin2 {
1925 background-color: @alert2;
1926 background-color: @alert2;
1926 text-align: center;
1927 text-align: center;
1927 }
1928 }
1928
1929
1929 /*mod binary*/
1930 /*mod binary*/
1930 .bin.bin3 {
1931 .bin.bin3 {
1931 background-color: @grey2;
1932 background-color: @grey2;
1932 text-align: center;
1933 text-align: center;
1933 }
1934 }
1934
1935
1935 /*rename file*/
1936 /*rename file*/
1936 .bin.bin4 {
1937 .bin.bin4 {
1937 background-color: @alert4;
1938 background-color: @alert4;
1938 text-align: center;
1939 text-align: center;
1939 }
1940 }
1940
1941
1941 /*copied file*/
1942 /*copied file*/
1942 .bin.bin5 {
1943 .bin.bin5 {
1943 background-color: @alert4;
1944 background-color: @alert4;
1944 text-align: center;
1945 text-align: center;
1945 }
1946 }
1946
1947
1947 /*chmod file*/
1948 /*chmod file*/
1948 .bin.bin6 {
1949 .bin.bin6 {
1949 background-color: @grey2;
1950 background-color: @grey2;
1950 text-align: center;
1951 text-align: center;
1951 }
1952 }
1952 }
1953 }
1953 }
1954 }
1954
1955
1955 .cs_files .cs_added, .cs_files .cs_A,
1956 .cs_files .cs_added, .cs_files .cs_A,
1956 .cs_files .cs_added, .cs_files .cs_M,
1957 .cs_files .cs_added, .cs_files .cs_M,
1957 .cs_files .cs_added, .cs_files .cs_D {
1958 .cs_files .cs_added, .cs_files .cs_D {
1958 height: 16px;
1959 height: 16px;
1959 padding-right: 10px;
1960 padding-right: 10px;
1960 margin-top: 7px;
1961 margin-top: 7px;
1961 text-align: left;
1962 text-align: left;
1962 }
1963 }
1963
1964
1964 .cs_icon_td {
1965 .cs_icon_td {
1965 min-width: 16px;
1966 min-width: 16px;
1966 width: 16px;
1967 width: 16px;
1967 }
1968 }
1968
1969
1969 .pull-request-merge {
1970 .pull-request-merge {
1970 border: 1px solid @grey5;
1971 border: 1px solid @grey5;
1971 padding: 10px 0px 20px;
1972 padding: 10px 0px 20px;
1972 margin-top: 10px;
1973 margin-top: 10px;
1973 margin-bottom: 20px;
1974 margin-bottom: 20px;
1974 }
1975 }
1975
1976
1976 .pull-request-merge-refresh {
1977 .pull-request-merge-refresh {
1977 margin: 2px 7px;
1978 margin: 2px 7px;
1978 a {
1979 a {
1979 color: @grey3;
1980 color: @grey3;
1980 }
1981 }
1981 }
1982 }
1982
1983
1983 .pull-request-merge ul {
1984 .pull-request-merge ul {
1984 padding: 0px 0px;
1985 padding: 0px 0px;
1985 }
1986 }
1986
1987
1987 .pull-request-merge li {
1988 .pull-request-merge li {
1988 list-style-type: none;
1989 list-style-type: none;
1989 }
1990 }
1990
1991
1991 .pull-request-merge .pull-request-wrap {
1992 .pull-request-merge .pull-request-wrap {
1992 height: auto;
1993 height: auto;
1993 padding: 0px 0px;
1994 padding: 0px 0px;
1994 text-align: right;
1995 text-align: right;
1995 }
1996 }
1996
1997
1997 .pull-request-merge span {
1998 .pull-request-merge span {
1998 margin-right: 5px;
1999 margin-right: 5px;
1999 }
2000 }
2000
2001
2001 .pull-request-merge-actions {
2002 .pull-request-merge-actions {
2002 min-height: 30px;
2003 min-height: 30px;
2003 padding: 0px 0px;
2004 padding: 0px 0px;
2004 }
2005 }
2005
2006
2006 .pull-request-merge-info {
2007 .pull-request-merge-info {
2007 padding: 0px 5px 5px 0px;
2008 padding: 0px 5px 5px 0px;
2008 }
2009 }
2009
2010
2010 .merge-status {
2011 .merge-status {
2011 margin-right: 5px;
2012 margin-right: 5px;
2012 }
2013 }
2013
2014
2014 .merge-message {
2015 .merge-message {
2015 font-size: 1.2em
2016 font-size: 1.2em
2016 }
2017 }
2017
2018
2018 .merge-message.success i,
2019 .merge-message.success i,
2019 .merge-icon.success i {
2020 .merge-icon.success i {
2020 color:@alert1;
2021 color:@alert1;
2021 }
2022 }
2022
2023
2023 .merge-message.warning i,
2024 .merge-message.warning i,
2024 .merge-icon.warning i {
2025 .merge-icon.warning i {
2025 color: @alert3;
2026 color: @alert3;
2026 }
2027 }
2027
2028
2028 .merge-message.error i,
2029 .merge-message.error i,
2029 .merge-icon.error i {
2030 .merge-icon.error i {
2030 color:@alert2;
2031 color:@alert2;
2031 }
2032 }
2032
2033
2033 .pr-versions {
2034 .pr-versions {
2034 font-size: 1.1em;
2035 font-size: 1.1em;
2035 padding: 7.5px;
2036 padding: 7.5px;
2036
2037
2037 table {
2038 table {
2038
2039
2039 }
2040 }
2040
2041
2041 td {
2042 td {
2042 line-height: 15px;
2043 line-height: 15px;
2043 }
2044 }
2044
2045
2045 .compare-radio-button {
2046 .compare-radio-button {
2046 position: relative;
2047 position: relative;
2047 top: -3px;
2048 top: -3px;
2048 }
2049 }
2049 }
2050 }
2050
2051
2051
2052
2052 #close_pull_request {
2053 #close_pull_request {
2053 margin-right: 0px;
2054 margin-right: 0px;
2054 }
2055 }
2055
2056
2056 .empty_data {
2057 .empty_data {
2057 color: @grey4;
2058 color: @grey4;
2058 }
2059 }
2059
2060
2060 #changeset_compare_view_content {
2061 #changeset_compare_view_content {
2061 clear: both;
2062 clear: both;
2062 width: 100%;
2063 width: 100%;
2063 box-sizing: border-box;
2064 box-sizing: border-box;
2064 .border-radius(@border-radius);
2065 .border-radius(@border-radius);
2065
2066
2066 .help-block {
2067 .help-block {
2067 margin: @padding 0;
2068 margin: @padding 0;
2068 color: @text-color;
2069 color: @text-color;
2069 &.pre-formatting {
2070 &.pre-formatting {
2070 white-space: pre;
2071 white-space: pre;
2071 }
2072 }
2072 }
2073 }
2073
2074
2074 .empty_data {
2075 .empty_data {
2075 margin: @padding 0;
2076 margin: @padding 0;
2076 }
2077 }
2077
2078
2078 .alert {
2079 .alert {
2079 margin-bottom: @space;
2080 margin-bottom: @space;
2080 }
2081 }
2081 }
2082 }
2082
2083
2083 .table_disp {
2084 .table_disp {
2084 .status {
2085 .status {
2085 width: auto;
2086 width: auto;
2086 }
2087 }
2087 }
2088 }
2088
2089
2089
2090
2090 .creation_in_progress {
2091 .creation_in_progress {
2091 color: @grey4
2092 color: @grey4
2092 }
2093 }
2093
2094
2094 .status_box_menu {
2095 .status_box_menu {
2095 margin: 0;
2096 margin: 0;
2096 }
2097 }
2097
2098
2098 .notification-table{
2099 .notification-table{
2099 margin-bottom: @space;
2100 margin-bottom: @space;
2100 display: table;
2101 display: table;
2101 width: 100%;
2102 width: 100%;
2102
2103
2103 .container{
2104 .container{
2104 display: table-row;
2105 display: table-row;
2105
2106
2106 .notification-header{
2107 .notification-header{
2107 border-bottom: @border-thickness solid @border-default-color;
2108 border-bottom: @border-thickness solid @border-default-color;
2108 }
2109 }
2109
2110
2110 .notification-subject{
2111 .notification-subject{
2111 display: table-cell;
2112 display: table-cell;
2112 }
2113 }
2113 }
2114 }
2114 }
2115 }
2115
2116
2116 // Notifications
2117 // Notifications
2117 .notification-header{
2118 .notification-header{
2118 display: table;
2119 display: table;
2119 width: 100%;
2120 width: 100%;
2120 padding: floor(@basefontsize/2) 0;
2121 padding: floor(@basefontsize/2) 0;
2121 line-height: 1em;
2122 line-height: 1em;
2122
2123
2123 .desc, .delete-notifications, .read-notifications{
2124 .desc, .delete-notifications, .read-notifications{
2124 display: table-cell;
2125 display: table-cell;
2125 text-align: left;
2126 text-align: left;
2126 }
2127 }
2127
2128
2128 .delete-notifications, .read-notifications{
2129 .delete-notifications, .read-notifications{
2129 width: 35px;
2130 width: 35px;
2130 min-width: 35px; //fixes when only one button is displayed
2131 min-width: 35px; //fixes when only one button is displayed
2131 }
2132 }
2132 }
2133 }
2133
2134
2134 .notification-body {
2135 .notification-body {
2135 .markdown-block,
2136 .markdown-block,
2136 .rst-block {
2137 .rst-block {
2137 padding: @padding 0;
2138 padding: @padding 0;
2138 }
2139 }
2139
2140
2140 .notification-subject {
2141 .notification-subject {
2141 padding: @textmargin 0;
2142 padding: @textmargin 0;
2142 border-bottom: @border-thickness solid @border-default-color;
2143 border-bottom: @border-thickness solid @border-default-color;
2143 }
2144 }
2144 }
2145 }
2145
2146
2146 .notice-messages {
2147 .notice-messages {
2147 .markdown-block,
2148 .markdown-block,
2148 .rst-block {
2149 .rst-block {
2149 padding: 0;
2150 padding: 0;
2150 }
2151 }
2151 }
2152 }
2152
2153
2153 .notifications_buttons{
2154 .notifications_buttons{
2154 float: right;
2155 float: right;
2155 }
2156 }
2156
2157
2157 #notification-status{
2158 #notification-status{
2158 display: inline;
2159 display: inline;
2159 }
2160 }
2160
2161
2161 // Repositories
2162 // Repositories
2162
2163
2163 #summary.fields{
2164 #summary.fields{
2164 display: table;
2165 display: table;
2165
2166
2166 .field{
2167 .field{
2167 display: table-row;
2168 display: table-row;
2168
2169
2169 .label-summary{
2170 .label-summary{
2170 display: table-cell;
2171 display: table-cell;
2171 min-width: @label-summary-minwidth;
2172 min-width: @label-summary-minwidth;
2172 padding-top: @padding/2;
2173 padding-top: @padding/2;
2173 padding-bottom: @padding/2;
2174 padding-bottom: @padding/2;
2174 padding-right: @padding/2;
2175 padding-right: @padding/2;
2175 }
2176 }
2176
2177
2177 .input{
2178 .input{
2178 display: table-cell;
2179 display: table-cell;
2179 padding: @padding/2;
2180 padding: @padding/2;
2180
2181
2181 input{
2182 input{
2182 min-width: 29em;
2183 min-width: 29em;
2183 padding: @padding/4;
2184 padding: @padding/4;
2184 }
2185 }
2185 }
2186 }
2186 .statistics, .downloads{
2187 .statistics, .downloads{
2187 .disabled{
2188 .disabled{
2188 color: @grey4;
2189 color: @grey4;
2189 }
2190 }
2190 }
2191 }
2191 }
2192 }
2192 }
2193 }
2193
2194
2194 #summary{
2195 #summary{
2195 width: 70%;
2196 width: 70%;
2196 }
2197 }
2197
2198
2198
2199
2199 // Journal
2200 // Journal
2200 .journal.title {
2201 .journal.title {
2201 h5 {
2202 h5 {
2202 float: left;
2203 float: left;
2203 margin: 0;
2204 margin: 0;
2204 width: 70%;
2205 width: 70%;
2205 }
2206 }
2206
2207
2207 ul {
2208 ul {
2208 float: right;
2209 float: right;
2209 display: inline-block;
2210 display: inline-block;
2210 margin: 0;
2211 margin: 0;
2211 width: 30%;
2212 width: 30%;
2212 text-align: right;
2213 text-align: right;
2213
2214
2214 li {
2215 li {
2215 display: inline;
2216 display: inline;
2216 font-size: @journal-fontsize;
2217 font-size: @journal-fontsize;
2217 line-height: 1em;
2218 line-height: 1em;
2218
2219
2219 list-style-type: none;
2220 list-style-type: none;
2220 }
2221 }
2221 }
2222 }
2222 }
2223 }
2223
2224
2224 .filterexample {
2225 .filterexample {
2225 position: absolute;
2226 position: absolute;
2226 top: 95px;
2227 top: 95px;
2227 left: @contentpadding;
2228 left: @contentpadding;
2228 color: @rcblue;
2229 color: @rcblue;
2229 font-size: 11px;
2230 font-size: 11px;
2230 font-family: @text-regular;
2231 font-family: @text-regular;
2231 cursor: help;
2232 cursor: help;
2232
2233
2233 &:hover {
2234 &:hover {
2234 color: @rcdarkblue;
2235 color: @rcdarkblue;
2235 }
2236 }
2236
2237
2237 @media (max-width:768px) {
2238 @media (max-width:768px) {
2238 position: relative;
2239 position: relative;
2239 top: auto;
2240 top: auto;
2240 left: auto;
2241 left: auto;
2241 display: block;
2242 display: block;
2242 }
2243 }
2243 }
2244 }
2244
2245
2245
2246
2246 #journal{
2247 #journal{
2247 margin-bottom: @space;
2248 margin-bottom: @space;
2248
2249
2249 .journal_day{
2250 .journal_day{
2250 margin-bottom: @textmargin/2;
2251 margin-bottom: @textmargin/2;
2251 padding-bottom: @textmargin/2;
2252 padding-bottom: @textmargin/2;
2252 font-size: @journal-fontsize;
2253 font-size: @journal-fontsize;
2253 border-bottom: @border-thickness solid @border-default-color;
2254 border-bottom: @border-thickness solid @border-default-color;
2254 }
2255 }
2255
2256
2256 .journal_container{
2257 .journal_container{
2257 margin-bottom: @space;
2258 margin-bottom: @space;
2258
2259
2259 .journal_user{
2260 .journal_user{
2260 display: inline-block;
2261 display: inline-block;
2261 }
2262 }
2262 .journal_action_container{
2263 .journal_action_container{
2263 display: block;
2264 display: block;
2264 margin-top: @textmargin;
2265 margin-top: @textmargin;
2265
2266
2266 div{
2267 div{
2267 display: inline;
2268 display: inline;
2268 }
2269 }
2269
2270
2270 div.journal_action_params{
2271 div.journal_action_params{
2271 display: block;
2272 display: block;
2272 }
2273 }
2273
2274
2274 div.journal_repo:after{
2275 div.journal_repo:after{
2275 content: "\A";
2276 content: "\A";
2276 white-space: pre;
2277 white-space: pre;
2277 }
2278 }
2278
2279
2279 div.date{
2280 div.date{
2280 display: block;
2281 display: block;
2281 margin-bottom: @textmargin;
2282 margin-bottom: @textmargin;
2282 }
2283 }
2283 }
2284 }
2284 }
2285 }
2285 }
2286 }
2286
2287
2287 // Files
2288 // Files
2288 .edit-file-title {
2289 .edit-file-title {
2289 font-size: 16px;
2290 font-size: 16px;
2290
2291
2291 .title-heading {
2292 .title-heading {
2292 padding: 2px;
2293 padding: 2px;
2293 }
2294 }
2294 }
2295 }
2295
2296
2296 .edit-file-fieldset {
2297 .edit-file-fieldset {
2297 margin: @sidebarpadding 0;
2298 margin: @sidebarpadding 0;
2298
2299
2299 .fieldset {
2300 .fieldset {
2300 .left-label {
2301 .left-label {
2301 width: 13%;
2302 width: 13%;
2302 }
2303 }
2303 .right-content {
2304 .right-content {
2304 width: 87%;
2305 width: 87%;
2305 max-width: 100%;
2306 max-width: 100%;
2306 }
2307 }
2307 .filename-label {
2308 .filename-label {
2308 margin-top: 13px;
2309 margin-top: 13px;
2309 }
2310 }
2310 .commit-message-label {
2311 .commit-message-label {
2311 margin-top: 4px;
2312 margin-top: 4px;
2312 }
2313 }
2313 .file-upload-input {
2314 .file-upload-input {
2314 input {
2315 input {
2315 display: none;
2316 display: none;
2316 }
2317 }
2317 margin-top: 10px;
2318 margin-top: 10px;
2318 }
2319 }
2319 .file-upload-label {
2320 .file-upload-label {
2320 margin-top: 10px;
2321 margin-top: 10px;
2321 }
2322 }
2322 p {
2323 p {
2323 margin-top: 5px;
2324 margin-top: 5px;
2324 }
2325 }
2325
2326
2326 }
2327 }
2327 .custom-path-link {
2328 .custom-path-link {
2328 margin-left: 5px;
2329 margin-left: 5px;
2329 }
2330 }
2330 #commit {
2331 #commit {
2331 resize: vertical;
2332 resize: vertical;
2332 }
2333 }
2333 }
2334 }
2334
2335
2335 .delete-file-preview {
2336 .delete-file-preview {
2336 max-height: 250px;
2337 max-height: 250px;
2337 }
2338 }
2338
2339
2339 .new-file,
2340 .new-file,
2340 #filter_activate,
2341 #filter_activate,
2341 #filter_deactivate {
2342 #filter_deactivate {
2342 float: right;
2343 float: right;
2343 margin: 0 0 0 10px;
2344 margin: 0 0 0 10px;
2344 }
2345 }
2345
2346
2346 .file-upload-transaction-wrapper {
2347 .file-upload-transaction-wrapper {
2347 margin-top: 57px;
2348 margin-top: 57px;
2348 clear: both;
2349 clear: both;
2349 }
2350 }
2350
2351
2351 .file-upload-transaction-wrapper .error {
2352 .file-upload-transaction-wrapper .error {
2352 color: @color5;
2353 color: @color5;
2353 }
2354 }
2354
2355
2355 .file-upload-transaction {
2356 .file-upload-transaction {
2356 min-height: 200px;
2357 min-height: 200px;
2357 padding: 54px;
2358 padding: 54px;
2358 border: 1px solid @grey5;
2359 border: 1px solid @grey5;
2359 text-align: center;
2360 text-align: center;
2360 clear: both;
2361 clear: both;
2361 }
2362 }
2362
2363
2363 .file-upload-transaction i {
2364 .file-upload-transaction i {
2364 font-size: 48px
2365 font-size: 48px
2365 }
2366 }
2366
2367
2367 h3.files_location{
2368 h3.files_location{
2368 line-height: 2.4em;
2369 line-height: 2.4em;
2369 }
2370 }
2370
2371
2371 .browser-nav {
2372 .browser-nav {
2372 width: 100%;
2373 width: 100%;
2373 display: table;
2374 display: table;
2374 margin-bottom: 20px;
2375 margin-bottom: 20px;
2375
2376
2376 .info_box {
2377 .info_box {
2377 float: left;
2378 float: left;
2378 display: inline-table;
2379 display: inline-table;
2379 height: 2.5em;
2380 height: 2.5em;
2380
2381
2381 .browser-cur-rev, .info_box_elem {
2382 .browser-cur-rev, .info_box_elem {
2382 display: table-cell;
2383 display: table-cell;
2383 vertical-align: middle;
2384 vertical-align: middle;
2384 }
2385 }
2385
2386
2386 .drop-menu {
2387 .drop-menu {
2387 margin: 0 10px;
2388 margin: 0 10px;
2388 }
2389 }
2389
2390
2390 .info_box_elem {
2391 .info_box_elem {
2391 border-top: @border-thickness solid @grey5;
2392 border-top: @border-thickness solid @grey5;
2392 border-bottom: @border-thickness solid @grey5;
2393 border-bottom: @border-thickness solid @grey5;
2393 box-shadow: @button-shadow;
2394 box-shadow: @button-shadow;
2394
2395
2395 #at_rev, a {
2396 #at_rev, a {
2396 padding: 0.6em 0.4em;
2397 padding: 0.6em 0.4em;
2397 margin: 0;
2398 margin: 0;
2398 .box-shadow(none);
2399 .box-shadow(none);
2399 border: 0;
2400 border: 0;
2400 height: 12px;
2401 height: 12px;
2401 color: @grey2;
2402 color: @grey2;
2402 }
2403 }
2403
2404
2404 input#at_rev {
2405 input#at_rev {
2405 max-width: 50px;
2406 max-width: 50px;
2406 text-align: center;
2407 text-align: center;
2407 }
2408 }
2408
2409
2409 &.previous {
2410 &.previous {
2410 border: @border-thickness solid @grey5;
2411 border: @border-thickness solid @grey5;
2411 border-top-left-radius: @border-radius;
2412 border-top-left-radius: @border-radius;
2412 border-bottom-left-radius: @border-radius;
2413 border-bottom-left-radius: @border-radius;
2413
2414
2414 &:hover {
2415 &:hover {
2415 border-color: @grey4;
2416 border-color: @grey4;
2416 }
2417 }
2417
2418
2418 .disabled {
2419 .disabled {
2419 color: @grey5;
2420 color: @grey5;
2420 cursor: not-allowed;
2421 cursor: not-allowed;
2421 opacity: 0.5;
2422 opacity: 0.5;
2422 }
2423 }
2423 }
2424 }
2424
2425
2425 &.next {
2426 &.next {
2426 border: @border-thickness solid @grey5;
2427 border: @border-thickness solid @grey5;
2427 border-top-right-radius: @border-radius;
2428 border-top-right-radius: @border-radius;
2428 border-bottom-right-radius: @border-radius;
2429 border-bottom-right-radius: @border-radius;
2429
2430
2430 &:hover {
2431 &:hover {
2431 border-color: @grey4;
2432 border-color: @grey4;
2432 }
2433 }
2433
2434
2434 .disabled {
2435 .disabled {
2435 color: @grey5;
2436 color: @grey5;
2436 cursor: not-allowed;
2437 cursor: not-allowed;
2437 opacity: 0.5;
2438 opacity: 0.5;
2438 }
2439 }
2439 }
2440 }
2440 }
2441 }
2441
2442
2442 .browser-cur-rev {
2443 .browser-cur-rev {
2443
2444
2444 span{
2445 span{
2445 margin: 0;
2446 margin: 0;
2446 color: @rcblue;
2447 color: @rcblue;
2447 height: 12px;
2448 height: 12px;
2448 display: inline-block;
2449 display: inline-block;
2449 padding: 0.7em 1em ;
2450 padding: 0.7em 1em ;
2450 border: @border-thickness solid @rcblue;
2451 border: @border-thickness solid @rcblue;
2451 margin-right: @padding;
2452 margin-right: @padding;
2452 }
2453 }
2453 }
2454 }
2454
2455
2455 }
2456 }
2456
2457
2457 .select-index-number {
2458 .select-index-number {
2458 margin: 0 0 0 20px;
2459 margin: 0 0 0 20px;
2459 color: @grey3;
2460 color: @grey3;
2460 }
2461 }
2461
2462
2462 .search_activate {
2463 .search_activate {
2463 display: table-cell;
2464 display: table-cell;
2464 vertical-align: middle;
2465 vertical-align: middle;
2465
2466
2466 input, label{
2467 input, label{
2467 margin: 0;
2468 margin: 0;
2468 padding: 0;
2469 padding: 0;
2469 }
2470 }
2470
2471
2471 input{
2472 input{
2472 margin-left: @textmargin;
2473 margin-left: @textmargin;
2473 }
2474 }
2474
2475
2475 }
2476 }
2476 }
2477 }
2477
2478
2478 .browser-cur-rev{
2479 .browser-cur-rev{
2479 margin-bottom: @textmargin;
2480 margin-bottom: @textmargin;
2480 }
2481 }
2481
2482
2482 #node_filter_box_loading{
2483 #node_filter_box_loading{
2483 .info_text;
2484 .info_text;
2484 }
2485 }
2485
2486
2486 .browser-search {
2487 .browser-search {
2487 margin: -25px 0px 5px 0px;
2488 margin: -25px 0px 5px 0px;
2488 }
2489 }
2489
2490
2490 .files-quick-filter {
2491 .files-quick-filter {
2491 float: right;
2492 float: right;
2492 width: 180px;
2493 width: 180px;
2493 position: relative;
2494 position: relative;
2494 }
2495 }
2495
2496
2496 .files-filter-box {
2497 .files-filter-box {
2497 display: flex;
2498 display: flex;
2498 padding: 0px;
2499 padding: 0px;
2499 border-radius: 3px;
2500 border-radius: 3px;
2500 margin-bottom: 0;
2501 margin-bottom: 0;
2501
2502
2502 a {
2503 a {
2503 border: none !important;
2504 border: none !important;
2504 }
2505 }
2505
2506
2506 li {
2507 li {
2507 list-style-type: none
2508 list-style-type: none
2508 }
2509 }
2509 }
2510 }
2510
2511
2511 .files-filter-box-path {
2512 .files-filter-box-path {
2512 line-height: 33px;
2513 line-height: 33px;
2513 padding: 0;
2514 padding: 0;
2514 width: 20px;
2515 width: 20px;
2515 position: absolute;
2516 position: absolute;
2516 z-index: 11;
2517 z-index: 11;
2517 left: 5px;
2518 left: 5px;
2518 }
2519 }
2519
2520
2520 .files-filter-box-input {
2521 .files-filter-box-input {
2521 margin-right: 0;
2522 margin-right: 0;
2522
2523
2523 input {
2524 input {
2524 border: 1px solid @white;
2525 border: 1px solid @white;
2525 padding-left: 25px;
2526 padding-left: 25px;
2526 width: 145px;
2527 width: 145px;
2527
2528
2528 &:hover {
2529 &:hover {
2529 border-color: @grey6;
2530 border-color: @grey6;
2530 }
2531 }
2531
2532
2532 &:focus {
2533 &:focus {
2533 border-color: @grey5;
2534 border-color: @grey5;
2534 }
2535 }
2535 }
2536 }
2536 }
2537 }
2537
2538
2538 .browser-result{
2539 .browser-result{
2539 td a{
2540 td a{
2540 margin-left: 0.5em;
2541 margin-left: 0.5em;
2541 display: inline-block;
2542 display: inline-block;
2542
2543
2543 em {
2544 em {
2544 font-weight: @text-bold-weight;
2545 font-weight: @text-bold-weight;
2545 font-family: @text-bold;
2546 font-family: @text-bold;
2546 }
2547 }
2547 }
2548 }
2548 }
2549 }
2549
2550
2550 .browser-highlight{
2551 .browser-highlight{
2551 background-color: @grey5-alpha;
2552 background-color: @grey5-alpha;
2552 }
2553 }
2553
2554
2554
2555
2555 .edit-file-fieldset #location,
2556 .edit-file-fieldset #location,
2556 .edit-file-fieldset #filename {
2557 .edit-file-fieldset #filename {
2557 display: flex;
2558 display: flex;
2558 width: -moz-available; /* WebKit-based browsers will ignore this. */
2559 width: -moz-available; /* WebKit-based browsers will ignore this. */
2559 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2560 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2560 width: fill-available;
2561 width: fill-available;
2561 border: 0;
2562 border: 0;
2562 }
2563 }
2563
2564
2564 .path-items {
2565 .path-items {
2565 display: flex;
2566 display: flex;
2566 padding: 0;
2567 padding: 0;
2567 border: 1px solid #eeeeee;
2568 border: 1px solid #eeeeee;
2568 width: 100%;
2569 width: 100%;
2569 float: left;
2570 float: left;
2570
2571
2571 .breadcrumb-path {
2572 .breadcrumb-path {
2572 line-height: 30px;
2573 line-height: 30px;
2573 padding: 0 4px;
2574 padding: 0 4px;
2574 white-space: nowrap;
2575 white-space: nowrap;
2575 }
2576 }
2576
2577
2577 .upload-form {
2578 .upload-form {
2578 margin-top: 46px;
2579 margin-top: 46px;
2579 }
2580 }
2580
2581
2581 .location-path {
2582 .location-path {
2582 width: -moz-available; /* WebKit-based browsers will ignore this. */
2583 width: -moz-available; /* WebKit-based browsers will ignore this. */
2583 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2584 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2584 width: fill-available;
2585 width: fill-available;
2585
2586
2586 .file-name-input {
2587 .file-name-input {
2587 padding: 0.5em 0;
2588 padding: 0.5em 0;
2588 }
2589 }
2589
2590
2590 }
2591 }
2591
2592
2592 ul {
2593 ul {
2593 display: flex;
2594 display: flex;
2594 margin: 0;
2595 margin: 0;
2595 padding: 0;
2596 padding: 0;
2596 width: 100%;
2597 width: 100%;
2597 }
2598 }
2598
2599
2599 li {
2600 li {
2600 list-style-type: none;
2601 list-style-type: none;
2601 }
2602 }
2602
2603
2603 }
2604 }
2604
2605
2605 .editor-items {
2606 .editor-items {
2606 height: 40px;
2607 height: 40px;
2607 margin: 10px 0 -17px 10px;
2608 margin: 10px 0 -17px 10px;
2608
2609
2609 .editor-action {
2610 .editor-action {
2610 cursor: pointer;
2611 cursor: pointer;
2611 }
2612 }
2612
2613
2613 .editor-action.active {
2614 .editor-action.active {
2614 border-bottom: 2px solid #5C5C5C;
2615 border-bottom: 2px solid #5C5C5C;
2615 }
2616 }
2616
2617
2617 li {
2618 li {
2618 list-style-type: none;
2619 list-style-type: none;
2619 }
2620 }
2620 }
2621 }
2621
2622
2622 .edit-file-fieldset .message textarea {
2623 .edit-file-fieldset .message textarea {
2623 border: 1px solid #eeeeee;
2624 border: 1px solid #eeeeee;
2624 }
2625 }
2625
2626
2626 #files_data .codeblock {
2627 #files_data .codeblock {
2627 background-color: #F5F5F5;
2628 background-color: #F5F5F5;
2628 }
2629 }
2629
2630
2630 #editor_preview {
2631 #editor_preview {
2631 background: white;
2632 background: white;
2632 }
2633 }
2633
2634
2634 .show-editor {
2635 .show-editor {
2635 padding: 10px;
2636 padding: 10px;
2636 background-color: white;
2637 background-color: white;
2637
2638
2638 }
2639 }
2639
2640
2640 .show-preview {
2641 .show-preview {
2641 padding: 10px;
2642 padding: 10px;
2642 background-color: white;
2643 background-color: white;
2643 border-left: 1px solid #eeeeee;
2644 border-left: 1px solid #eeeeee;
2644 }
2645 }
2645 // quick filter
2646 // quick filter
2646 .grid-quick-filter {
2647 .grid-quick-filter {
2647 float: right;
2648 float: right;
2648 position: relative;
2649 position: relative;
2649 }
2650 }
2650
2651
2651 .grid-filter-box {
2652 .grid-filter-box {
2652 display: flex;
2653 display: flex;
2653 padding: 0px;
2654 padding: 0px;
2654 border-radius: 3px;
2655 border-radius: 3px;
2655 margin-bottom: 0;
2656 margin-bottom: 0;
2656
2657
2657 a {
2658 a {
2658 border: none !important;
2659 border: none !important;
2659 }
2660 }
2660
2661
2661 li {
2662 li {
2662 list-style-type: none
2663 list-style-type: none
2663 }
2664 }
2664
2665
2665 }
2666 }
2666
2667
2667 .grid-filter-box-icon {
2668 .grid-filter-box-icon {
2668 line-height: 33px;
2669 line-height: 33px;
2669 padding: 0;
2670 padding: 0;
2670 width: 20px;
2671 width: 20px;
2671 position: absolute;
2672 position: absolute;
2672 z-index: 11;
2673 z-index: 11;
2673 left: 5px;
2674 left: 5px;
2674 }
2675 }
2675
2676
2676 .grid-filter-box-input {
2677 .grid-filter-box-input {
2677 margin-right: 0;
2678 margin-right: 0;
2678
2679
2679 input {
2680 input {
2680 border: 1px solid @white;
2681 border: 1px solid @white;
2681 padding-left: 25px;
2682 padding-left: 25px;
2682 width: 145px;
2683 width: 145px;
2683
2684
2684 &:hover {
2685 &:hover {
2685 border-color: @grey6;
2686 border-color: @grey6;
2686 }
2687 }
2687
2688
2688 &:focus {
2689 &:focus {
2689 border-color: @grey5;
2690 border-color: @grey5;
2690 }
2691 }
2691 }
2692 }
2692 }
2693 }
2693
2694
2694
2695
2695
2696
2696 // Search
2697 // Search
2697
2698
2698 .search-form{
2699 .search-form{
2699 #q {
2700 #q {
2700 width: @search-form-width;
2701 width: @search-form-width;
2701 }
2702 }
2702 .fields{
2703 .fields{
2703 margin: 0 0 @space;
2704 margin: 0 0 @space;
2704 }
2705 }
2705
2706
2706 label{
2707 label{
2707 display: inline-block;
2708 display: inline-block;
2708 margin-right: @textmargin;
2709 margin-right: @textmargin;
2709 padding-top: 0.25em;
2710 padding-top: 0.25em;
2710 }
2711 }
2711
2712
2712
2713
2713 .results{
2714 .results{
2714 clear: both;
2715 clear: both;
2715 margin: 0 0 @padding;
2716 margin: 0 0 @padding;
2716 }
2717 }
2717
2718
2718 .search-tags {
2719 .search-tags {
2719 padding: 5px 0;
2720 padding: 5px 0;
2720 }
2721 }
2721 }
2722 }
2722
2723
2723 div.search-feedback-items {
2724 div.search-feedback-items {
2724 display: inline-block;
2725 display: inline-block;
2725 }
2726 }
2726
2727
2727 div.search-code-body {
2728 div.search-code-body {
2728 background-color: #ffffff; padding: 5px 0 5px 10px;
2729 background-color: #ffffff; padding: 5px 0 5px 10px;
2729 pre {
2730 pre {
2730 .match { background-color: #faffa6;}
2731 .match { background-color: #faffa6;}
2731 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2732 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2732 }
2733 }
2733 }
2734 }
2734
2735
2735 .expand_commit.search {
2736 .expand_commit.search {
2736 .show_more.open {
2737 .show_more.open {
2737 height: auto;
2738 height: auto;
2738 max-height: none;
2739 max-height: none;
2739 }
2740 }
2740 }
2741 }
2741
2742
2742 .search-results {
2743 .search-results {
2743
2744
2744 h2 {
2745 h2 {
2745 margin-bottom: 0;
2746 margin-bottom: 0;
2746 }
2747 }
2747 .codeblock {
2748 .codeblock {
2748 border: none;
2749 border: none;
2749 background: transparent;
2750 background: transparent;
2750 }
2751 }
2751
2752
2752 .codeblock-header {
2753 .codeblock-header {
2753 border: none;
2754 border: none;
2754 background: transparent;
2755 background: transparent;
2755 }
2756 }
2756
2757
2757 .code-body {
2758 .code-body {
2758 border: @border-thickness solid @grey6;
2759 border: @border-thickness solid @grey6;
2759 .border-radius(@border-radius);
2760 .border-radius(@border-radius);
2760 }
2761 }
2761
2762
2762 .td-commit {
2763 .td-commit {
2763 &:extend(pre);
2764 &:extend(pre);
2764 border-bottom: @border-thickness solid @border-default-color;
2765 border-bottom: @border-thickness solid @border-default-color;
2765 }
2766 }
2766
2767
2767 .message {
2768 .message {
2768 height: auto;
2769 height: auto;
2769 max-width: 350px;
2770 max-width: 350px;
2770 white-space: normal;
2771 white-space: normal;
2771 text-overflow: initial;
2772 text-overflow: initial;
2772 overflow: visible;
2773 overflow: visible;
2773
2774
2774 .match { background-color: #faffa6;}
2775 .match { background-color: #faffa6;}
2775 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2776 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2776 }
2777 }
2777
2778
2778 .path {
2779 .path {
2779 border-bottom: none !important;
2780 border-bottom: none !important;
2780 border-left: 1px solid @grey6 !important;
2781 border-left: 1px solid @grey6 !important;
2781 border-right: 1px solid @grey6 !important;
2782 border-right: 1px solid @grey6 !important;
2782 }
2783 }
2783 }
2784 }
2784
2785
2785 table.rctable td.td-search-results div {
2786 table.rctable td.td-search-results div {
2786 max-width: 100%;
2787 max-width: 100%;
2787 }
2788 }
2788
2789
2789 #tip-box, .tip-box{
2790 #tip-box, .tip-box{
2790 padding: @menupadding/2;
2791 padding: @menupadding/2;
2791 display: block;
2792 display: block;
2792 border: @border-thickness solid @border-highlight-color;
2793 border: @border-thickness solid @border-highlight-color;
2793 .border-radius(@border-radius);
2794 .border-radius(@border-radius);
2794 background-color: white;
2795 background-color: white;
2795 z-index: 99;
2796 z-index: 99;
2796 white-space: pre-wrap;
2797 white-space: pre-wrap;
2797 }
2798 }
2798
2799
2799 #linktt {
2800 #linktt {
2800 width: 79px;
2801 width: 79px;
2801 }
2802 }
2802
2803
2803 #help_kb .modal-content{
2804 #help_kb .modal-content{
2804 max-width: 800px;
2805 max-width: 800px;
2805 margin: 10% auto;
2806 margin: 10% auto;
2806
2807
2807 table{
2808 table{
2808 td,th{
2809 td,th{
2809 border-bottom: none;
2810 border-bottom: none;
2810 line-height: 2.5em;
2811 line-height: 2.5em;
2811 }
2812 }
2812 th{
2813 th{
2813 padding-bottom: @textmargin/2;
2814 padding-bottom: @textmargin/2;
2814 }
2815 }
2815 td.keys{
2816 td.keys{
2816 text-align: center;
2817 text-align: center;
2817 }
2818 }
2818 }
2819 }
2819
2820
2820 .block-left{
2821 .block-left{
2821 width: 45%;
2822 width: 45%;
2822 margin-right: 5%;
2823 margin-right: 5%;
2823 }
2824 }
2824 .modal-footer{
2825 .modal-footer{
2825 clear: both;
2826 clear: both;
2826 }
2827 }
2827 .key.tag{
2828 .key.tag{
2828 padding: 0.5em;
2829 padding: 0.5em;
2829 background-color: @rcblue;
2830 background-color: @rcblue;
2830 color: white;
2831 color: white;
2831 border-color: @rcblue;
2832 border-color: @rcblue;
2832 .box-shadow(none);
2833 .box-shadow(none);
2833 }
2834 }
2834 }
2835 }
2835
2836
2836
2837
2837
2838
2838 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2839 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2839
2840
2840 @import 'statistics-graph';
2841 @import 'statistics-graph';
2841 @import 'tables';
2842 @import 'tables';
2842 @import 'forms';
2843 @import 'forms';
2843 @import 'diff';
2844 @import 'diff';
2844 @import 'summary';
2845 @import 'summary';
2845 @import 'navigation';
2846 @import 'navigation';
2846
2847
2847 //--- SHOW/HIDE SECTIONS --//
2848 //--- SHOW/HIDE SECTIONS --//
2848
2849
2849 .btn-collapse {
2850 .btn-collapse {
2850 float: right;
2851 float: right;
2851 text-align: right;
2852 text-align: right;
2852 font-family: @text-light;
2853 font-family: @text-light;
2853 font-size: @basefontsize;
2854 font-size: @basefontsize;
2854 cursor: pointer;
2855 cursor: pointer;
2855 border: none;
2856 border: none;
2856 color: @rcblue;
2857 color: @rcblue;
2857 }
2858 }
2858
2859
2859 table.rctable,
2860 table.rctable,
2860 table.dataTable {
2861 table.dataTable {
2861 .btn-collapse {
2862 .btn-collapse {
2862 float: right;
2863 float: right;
2863 text-align: right;
2864 text-align: right;
2864 }
2865 }
2865 }
2866 }
2866
2867
2867 table.rctable {
2868 table.rctable {
2868 &.permissions {
2869 &.permissions {
2869
2870
2870 th.td-owner {
2871 th.td-owner {
2871 padding: 0;
2872 padding: 0;
2872 }
2873 }
2873
2874
2874 th {
2875 th {
2875 font-weight: normal;
2876 font-weight: normal;
2876 padding: 0 5px;
2877 padding: 0 5px;
2877 }
2878 }
2878
2879
2879 }
2880 }
2880 }
2881 }
2881
2882
2882
2883
2883 // TODO: johbo: Fix for IE10, this avoids that we see a border
2884 // TODO: johbo: Fix for IE10, this avoids that we see a border
2884 // and padding around checkboxes and radio boxes. Move to the right place,
2885 // and padding around checkboxes and radio boxes. Move to the right place,
2885 // or better: Remove this once we did the form refactoring.
2886 // or better: Remove this once we did the form refactoring.
2886 input[type=checkbox],
2887 input[type=checkbox],
2887 input[type=radio] {
2888 input[type=radio] {
2888 padding: 0;
2889 padding: 0;
2889 border: none;
2890 border: none;
2890 }
2891 }
2891
2892
2892 .toggle-ajax-spinner{
2893 .toggle-ajax-spinner{
2893 height: 16px;
2894 height: 16px;
2894 width: 16px;
2895 width: 16px;
2895 }
2896 }
2896
2897
2897
2898
2898 .markup-form .clearfix {
2899 .markup-form .clearfix {
2899 .border-radius(@border-radius);
2900 .border-radius(@border-radius);
2900 margin: 0px;
2901 margin: 0px;
2901 }
2902 }
2902
2903
2903 .markup-form-area {
2904 .markup-form-area {
2904 padding: 8px 12px;
2905 padding: 8px 12px;
2905 border: 1px solid @grey4;
2906 border: 1px solid @grey4;
2906 .border-radius(@border-radius);
2907 .border-radius(@border-radius);
2907 }
2908 }
2908
2909
2909 .markup-form-area-header .nav-links {
2910 .markup-form-area-header .nav-links {
2910 display: flex;
2911 display: flex;
2911 flex-flow: row wrap;
2912 flex-flow: row wrap;
2912 -webkit-flex-flow: row wrap;
2913 -webkit-flex-flow: row wrap;
2913 width: 100%;
2914 width: 100%;
2914 }
2915 }
2915
2916
2916 .markup-form-area-footer {
2917 .markup-form-area-footer {
2917 display: flex;
2918 display: flex;
2918 }
2919 }
2919
2920
2920 .markup-form-area-footer .toolbar {
2921 .markup-form-area-footer .toolbar {
2921
2922
2922 }
2923 }
2923
2924
2924 // markup Form
2925 // markup Form
2925 div.markup-form {
2926 div.markup-form {
2926 margin-top: 20px;
2927 margin-top: 20px;
2927 }
2928 }
2928
2929
2929 .markup-form strong {
2930 .markup-form strong {
2930 display: block;
2931 display: block;
2931 margin-bottom: 15px;
2932 margin-bottom: 15px;
2932 }
2933 }
2933
2934
2934 .markup-form textarea {
2935 .markup-form textarea {
2935 width: 100%;
2936 width: 100%;
2936 height: 100px;
2937 height: 100px;
2937 font-family: @text-monospace;
2938 font-family: @text-monospace;
2938 }
2939 }
2939
2940
2940 form.markup-form {
2941 form.markup-form {
2941 margin-top: 10px;
2942 margin-top: 10px;
2942 margin-left: 10px;
2943 margin-left: 10px;
2943 }
2944 }
2944
2945
2945 .markup-form .comment-block-ta,
2946 .markup-form .comment-block-ta,
2946 .markup-form .preview-box {
2947 .markup-form .preview-box {
2947 .border-radius(@border-radius);
2948 .border-radius(@border-radius);
2948 .box-sizing(border-box);
2949 .box-sizing(border-box);
2949 background-color: white;
2950 background-color: white;
2950 }
2951 }
2951
2952
2952 .markup-form .preview-box.unloaded {
2953 .markup-form .preview-box.unloaded {
2953 height: 50px;
2954 height: 50px;
2954 text-align: center;
2955 text-align: center;
2955 padding: 20px;
2956 padding: 20px;
2956 background-color: white;
2957 background-color: white;
2957 }
2958 }
2958
2959
2959
2960
2960 .dropzone-wrapper {
2961 .dropzone-wrapper {
2961 border: 1px solid @grey5;
2962 border: 1px solid @grey5;
2962 padding: 20px;
2963 padding: 20px;
2963 }
2964 }
2964
2965
2965 .dropzone,
2966 .dropzone,
2966 .dropzone-pure {
2967 .dropzone-pure {
2967 border: 2px dashed @grey5;
2968 border: 2px dashed @grey5;
2968 border-radius: 5px;
2969 border-radius: 5px;
2969 background: white;
2970 background: white;
2970 min-height: 200px;
2971 min-height: 200px;
2971 padding: 54px;
2972 padding: 54px;
2972
2973
2973 .dz-message {
2974 .dz-message {
2974 font-weight: 700;
2975 font-weight: 700;
2975 text-align: center;
2976 text-align: center;
2976 margin: 2em 0;
2977 margin: 2em 0;
2977 }
2978 }
2978
2979
2979 }
2980 }
2980
2981
2981 .dz-preview {
2982 .dz-preview {
2982 margin: 10px 0 !important;
2983 margin: 10px 0 !important;
2983 position: relative;
2984 position: relative;
2984 vertical-align: top;
2985 vertical-align: top;
2985 padding: 10px;
2986 padding: 10px;
2986 border-bottom: 1px solid @grey5;
2987 border-bottom: 1px solid @grey5;
2987 }
2988 }
2988
2989
2989 .dz-filename {
2990 .dz-filename {
2990 font-weight: 700;
2991 font-weight: 700;
2991 float: left;
2992 float: left;
2992 }
2993 }
2993
2994
2994 .dz-sending {
2995 .dz-sending {
2995 float: right;
2996 float: right;
2996 }
2997 }
2997
2998
2998 .dz-response {
2999 .dz-response {
2999 clear: both
3000 clear: both
3000 }
3001 }
3001
3002
3002 .dz-filename-size {
3003 .dz-filename-size {
3003 float: right
3004 float: right
3004 }
3005 }
3005
3006
3006 .dz-error-message {
3007 .dz-error-message {
3007 color: @alert2;
3008 color: @alert2;
3008 padding-top: 10px;
3009 padding-top: 10px;
3009 clear: both;
3010 clear: both;
3010 }
3011 }
3011
3012
3012
3013
3013 .user-hovercard {
3014 .user-hovercard {
3014 padding: 5px;
3015 padding: 5px;
3015 }
3016 }
3016
3017
3017 .user-hovercard-icon {
3018 .user-hovercard-icon {
3018 display: inline;
3019 display: inline;
3019 padding: 0;
3020 padding: 0;
3020 box-sizing: content-box;
3021 box-sizing: content-box;
3021 border-radius: 50%;
3022 border-radius: 50%;
3022 float: left;
3023 float: left;
3023 }
3024 }
3024
3025
3025 .user-hovercard-name {
3026 .user-hovercard-name {
3026 float: right;
3027 float: right;
3027 vertical-align: top;
3028 vertical-align: top;
3028 padding-left: 10px;
3029 padding-left: 10px;
3029 min-width: 150px;
3030 min-width: 150px;
3030 }
3031 }
3031
3032
3032 .user-hovercard-bio {
3033 .user-hovercard-bio {
3033 clear: both;
3034 clear: both;
3034 padding-top: 10px;
3035 padding-top: 10px;
3035 }
3036 }
3036
3037
3037 .user-hovercard-header {
3038 .user-hovercard-header {
3038 clear: both;
3039 clear: both;
3039 min-height: 10px;
3040 min-height: 10px;
3040 }
3041 }
3041
3042
3042 .user-hovercard-footer {
3043 .user-hovercard-footer {
3043 clear: both;
3044 clear: both;
3044 min-height: 10px;
3045 min-height: 10px;
3045 }
3046 }
3046
3047
3047 .user-group-hovercard {
3048 .user-group-hovercard {
3048 padding: 5px;
3049 padding: 5px;
3049 }
3050 }
3050
3051
3051 .user-group-hovercard-icon {
3052 .user-group-hovercard-icon {
3052 display: inline;
3053 display: inline;
3053 padding: 0;
3054 padding: 0;
3054 box-sizing: content-box;
3055 box-sizing: content-box;
3055 border-radius: 50%;
3056 border-radius: 50%;
3056 float: left;
3057 float: left;
3057 }
3058 }
3058
3059
3059 .user-group-hovercard-name {
3060 .user-group-hovercard-name {
3060 float: left;
3061 float: left;
3061 vertical-align: top;
3062 vertical-align: top;
3062 padding-left: 10px;
3063 padding-left: 10px;
3063 min-width: 150px;
3064 min-width: 150px;
3064 }
3065 }
3065
3066
3066 .user-group-hovercard-icon i {
3067 .user-group-hovercard-icon i {
3067 border: 1px solid @grey4;
3068 border: 1px solid @grey4;
3068 border-radius: 4px;
3069 border-radius: 4px;
3069 }
3070 }
3070
3071
3071 .user-group-hovercard-bio {
3072 .user-group-hovercard-bio {
3072 clear: both;
3073 clear: both;
3073 padding-top: 10px;
3074 padding-top: 10px;
3074 line-height: 1.0em;
3075 line-height: 1.0em;
3075 }
3076 }
3076
3077
3077 .user-group-hovercard-header {
3078 .user-group-hovercard-header {
3078 clear: both;
3079 clear: both;
3079 min-height: 10px;
3080 min-height: 10px;
3080 }
3081 }
3081
3082
3082 .user-group-hovercard-footer {
3083 .user-group-hovercard-footer {
3083 clear: both;
3084 clear: both;
3084 min-height: 10px;
3085 min-height: 10px;
3085 }
3086 }
3086
3087
3087 .pr-hovercard-header {
3088 .pr-hovercard-header {
3088 clear: both;
3089 clear: both;
3089 display: block;
3090 display: block;
3090 line-height: 20px;
3091 line-height: 20px;
3091 }
3092 }
3092
3093
3093 .pr-hovercard-user {
3094 .pr-hovercard-user {
3094 display: flex;
3095 display: flex;
3095 align-items: center;
3096 align-items: center;
3096 padding-left: 5px;
3097 padding-left: 5px;
3097 }
3098 }
3098
3099
3099 .pr-hovercard-title {
3100 .pr-hovercard-title {
3100 padding-top: 5px;
3101 padding-top: 5px;
3101 }
3102 }
3102
3103
3103 .action-divider {
3104 .action-divider {
3104 opacity: 0.5;
3105 opacity: 0.5;
3105 }
3106 }
3106
3107
3107 .details-inline-block {
3108 .details-inline-block {
3108 display: inline-block;
3109 display: inline-block;
3109 position: relative;
3110 position: relative;
3110 }
3111 }
3111
3112
3112 .details-inline-block summary {
3113 .details-inline-block summary {
3113 list-style: none;
3114 list-style: none;
3114 }
3115 }
3115
3116
3116 details:not([open]) > :not(summary) {
3117 details:not([open]) > :not(summary) {
3117 display: none !important;
3118 display: none !important;
3118 }
3119 }
3119
3120
3120 .details-reset > summary {
3121 .details-reset > summary {
3121 list-style: none;
3122 list-style: none;
3122 }
3123 }
3123
3124
3124 .details-reset > summary::-webkit-details-marker {
3125 .details-reset > summary::-webkit-details-marker {
3125 display: none;
3126 display: none;
3126 }
3127 }
3127
3128
3128 .details-dropdown {
3129 .details-dropdown {
3129 position: absolute;
3130 position: absolute;
3130 top: 100%;
3131 top: 100%;
3131 width: 185px;
3132 width: 185px;
3132 list-style: none;
3133 list-style: none;
3133 background-color: #fff;
3134 background-color: #fff;
3134 background-clip: padding-box;
3135 background-clip: padding-box;
3135 border: 1px solid @grey5;
3136 border: 1px solid @grey5;
3136 box-shadow: 0 8px 24px rgba(149, 157, 165, .2);
3137 box-shadow: 0 8px 24px rgba(149, 157, 165, .2);
3137 left: -150px;
3138 left: -150px;
3138 text-align: left;
3139 text-align: left;
3139 z-index: 90;
3140 z-index: 90;
3140 }
3141 }
3141
3142
3142 .dropdown-divider {
3143 .dropdown-divider {
3143 display: block;
3144 display: block;
3144 height: 0;
3145 height: 0;
3145 margin: 8px 0;
3146 margin: 8px 0;
3146 border-top: 1px solid @grey5;
3147 border-top: 1px solid @grey5;
3147 }
3148 }
3148
3149
3149 .dropdown-item {
3150 .dropdown-item {
3150 display: block;
3151 display: block;
3151 padding: 4px 8px 4px 16px;
3152 padding: 4px 8px 4px 16px;
3152 overflow: hidden;
3153 overflow: hidden;
3153 text-overflow: ellipsis;
3154 text-overflow: ellipsis;
3154 white-space: nowrap;
3155 white-space: nowrap;
3155 font-weight: normal;
3156 font-weight: normal;
3156 }
3157 }
3157
3158
3158 .right-sidebar {
3159 .right-sidebar {
3159 position: fixed;
3160 position: fixed;
3160 top: 0px;
3161 top: 0px;
3161 bottom: 0;
3162 bottom: 0;
3162 right: 0;
3163 right: 0;
3163
3164
3164 background: #fafafa;
3165 background: #fafafa;
3165 z-index: 50;
3166 z-index: 50;
3166 }
3167 }
3167
3168
3168 .right-sidebar {
3169 .right-sidebar {
3169 border-left: 1px solid @grey5;
3170 border-left: 1px solid @grey5;
3170 }
3171 }
3171
3172
3172 .right-sidebar.right-sidebar-expanded {
3173 .right-sidebar.right-sidebar-expanded {
3173 width: 300px;
3174 width: 300px;
3174 overflow: scroll;
3175 overflow: scroll;
3175 }
3176 }
3176
3177
3177 .right-sidebar.right-sidebar-collapsed {
3178 .right-sidebar.right-sidebar-collapsed {
3178 width: 40px;
3179 width: 40px;
3179 padding: 0;
3180 padding: 0;
3180 display: block;
3181 display: block;
3181 overflow: hidden;
3182 overflow: hidden;
3182 }
3183 }
3183
3184
3184 .sidenav {
3185 .sidenav {
3185 float: right;
3186 float: right;
3186 will-change: min-height;
3187 will-change: min-height;
3187 background: #fafafa;
3188 background: #fafafa;
3188 width: 100%;
3189 width: 100%;
3189 }
3190 }
3190
3191
3191 .sidebar-toggle {
3192 .sidebar-toggle {
3192 height: 30px;
3193 height: 30px;
3193 text-align: center;
3194 text-align: center;
3194 margin: 15px 0px 0 0;
3195 margin: 15px 0px 0 0;
3195 }
3196 }
3196
3197
3197 .sidebar-toggle a {
3198 .sidebar-toggle a {
3198
3199
3199 }
3200 }
3200
3201
3201 .sidebar-content {
3202 .sidebar-content {
3202 margin-left: 15px;
3203 margin-left: 15px;
3203 margin-right: 15px;
3204 margin-right: 15px;
3204 }
3205 }
3205
3206
3206 .sidebar-heading {
3207 .sidebar-heading {
3207 font-size: 1.2em;
3208 font-size: 1.2em;
3208 font-weight: 700;
3209 font-weight: 700;
3209 margin-top: 10px;
3210 margin-top: 10px;
3210 }
3211 }
3211
3212
3212 .sidebar-element {
3213 .sidebar-element {
3213 margin-top: 20px;
3214 margin-top: 20px;
3214 }
3215 }
3215
3216
3216 .right-sidebar-collapsed-state {
3217 .right-sidebar-collapsed-state {
3217 display: flex;
3218 display: flex;
3218 flex-direction: column;
3219 flex-direction: column;
3219 justify-content: center;
3220 justify-content: center;
3220 align-items: center;
3221 align-items: center;
3221 padding: 0 10px;
3222 padding: 0 10px;
3222 cursor: pointer;
3223 cursor: pointer;
3223 font-size: 1.3em;
3224 font-size: 1.3em;
3224 margin: 0 -15px;
3225 margin: 0 -15px;
3225 }
3226 }
3226
3227
3227 .right-sidebar-collapsed-state:hover {
3228 .right-sidebar-collapsed-state:hover {
3228 background-color: @grey5;
3229 background-color: @grey5;
3229 }
3230 }
3230
3231
3231 .old-comments-marker {
3232 .old-comments-marker {
3232 text-align: left;
3233 text-align: left;
3233 }
3234 }
3234
3235
3235 .old-comments-marker td {
3236 .old-comments-marker td {
3236 padding-top: 15px;
3237 padding-top: 15px;
3237 border-bottom: 1px solid @grey5;
3238 border-bottom: 1px solid @grey5;
3238 }
3239 }
@@ -1,635 +1,640 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${c.repo_name} ${_('New pull request')}
5 ${c.repo_name} ${_('New pull request')}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()"></%def>
8 <%def name="breadcrumbs_links()"></%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="menu_bar_subnav()">
14 <%def name="menu_bar_subnav()">
15 ${self.repo_menu(active='showpullrequest')}
15 ${self.repo_menu(active='showpullrequest')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="summary-details block-left">
24 <div class="summary-details block-left">
25
25
26 <div class="form" style="padding-top: 10px">
26 <div class="form" style="padding-top: 10px">
27
27
28 <div class="fields" >
28 <div class="fields" >
29
29
30 ## COMMIT FLOW
30 ## COMMIT FLOW
31 <div class="field">
31 <div class="field">
32 <div class="label label-textarea">
32 <div class="label label-textarea">
33 <label for="commit_flow">${_('Commit flow')}:</label>
33 <label for="commit_flow">${_('Commit flow')}:</label>
34 </div>
34 </div>
35
35
36 <div class="content">
36 <div class="content">
37 <div class="flex-container">
37 <div class="flex-container">
38 <div style="width: 45%;">
38 <div style="width: 45%;">
39 <div class="panel panel-default source-panel">
39 <div class="panel panel-default source-panel">
40 <div class="panel-heading">
40 <div class="panel-heading">
41 <h3 class="panel-title">${_('Source repository')}</h3>
41 <h3 class="panel-title">${_('Source repository')}</h3>
42 </div>
42 </div>
43 <div class="panel-body">
43 <div class="panel-body">
44 <div style="display:none">${c.rhodecode_db_repo.description}</div>
44 <div style="display:none">${c.rhodecode_db_repo.description}</div>
45 ${h.hidden('source_repo')}
45 ${h.hidden('source_repo')}
46 ${h.hidden('source_ref')}
46 ${h.hidden('source_ref')}
47
47
48 <div id="pr_open_message"></div>
48 <div id="pr_open_message"></div>
49 </div>
49 </div>
50 </div>
50 </div>
51 </div>
51 </div>
52
52
53 <div style="width: 90px; text-align: center; padding-top: 30px">
53 <div style="width: 90px; text-align: center; padding-top: 30px">
54 <div>
54 <div>
55 <i class="icon-right" style="font-size: 2.2em"></i>
55 <i class="icon-right" style="font-size: 2.2em"></i>
56 </div>
56 </div>
57 <div style="position: relative; top: 10px">
57 <div style="position: relative; top: 10px">
58 <span class="tag tag">
58 <span class="tag tag">
59 <span id="switch_base"></span>
59 <span id="switch_base"></span>
60 </span>
60 </span>
61 </div>
61 </div>
62
62
63 </div>
63 </div>
64
64
65 <div style="width: 45%;">
65 <div style="width: 45%;">
66
66
67 <div class="panel panel-default target-panel">
67 <div class="panel panel-default target-panel">
68 <div class="panel-heading">
68 <div class="panel-heading">
69 <h3 class="panel-title">${_('Target repository')}</h3>
69 <h3 class="panel-title">${_('Target repository')}</h3>
70 </div>
70 </div>
71 <div class="panel-body">
71 <div class="panel-body">
72 <div style="display:none" id="target_repo_desc"></div>
72 <div style="display:none" id="target_repo_desc"></div>
73 ${h.hidden('target_repo')}
73 ${h.hidden('target_repo')}
74 ${h.hidden('target_ref')}
74 ${h.hidden('target_ref')}
75 <span id="target_ref_loading" style="display: none">
75 <span id="target_ref_loading" style="display: none">
76 ${_('Loading refs...')}
76 ${_('Loading refs...')}
77 </span>
77 </span>
78 </div>
78 </div>
79 </div>
79 </div>
80
80
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 </div>
84 </div>
85
85
86 </div>
86 </div>
87
87
88 ## TITLE
88 ## TITLE
89 <div class="field">
89 <div class="field">
90 <div class="label">
90 <div class="label">
91 <label for="pullrequest_title">${_('Title')}:</label>
91 <label for="pullrequest_title">${_('Title')}:</label>
92 </div>
92 </div>
93 <div class="input">
93 <div class="input">
94 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
94 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
95 </div>
95 </div>
96 <p class="help-block">
96 <p class="help-block">
97 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
97 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
98 </p>
98 </p>
99 </div>
99 </div>
100
100
101 ## DESC
101 ## DESC
102 <div class="field">
102 <div class="field">
103 <div class="label label-textarea">
103 <div class="label label-textarea">
104 <label for="pullrequest_desc">${_('Description')}:</label>
104 <label for="pullrequest_desc">${_('Description')}:</label>
105 </div>
105 </div>
106 <div class="textarea text-area">
106 <div class="textarea text-area">
107 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
107 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
108 ${dt.markup_form('pullrequest_desc')}
108 ${dt.markup_form('pullrequest_desc')}
109 </div>
109 </div>
110 </div>
110 </div>
111
111
112 ## REVIEWERS
112 ## REVIEWERS
113 <div class="field">
113 <div class="field">
114 <div class="label label-textarea">
114 <div class="label label-textarea">
115 <label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label>
115 <label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label>
116 </div>
116 </div>
117 <div class="content">
117 <div class="content">
118 ## REVIEW RULES
118 ## REVIEW RULES
119 <div id="review_rules" style="display: none" class="reviewers-title">
119 <div id="review_rules" style="display: none" class="reviewers-title">
120 <div class="pr-details-title">
120 <div class="pr-details-title">
121 ${_('Reviewer rules')}
121 ${_('Reviewer rules')}
122 </div>
122 </div>
123 <div class="pr-reviewer-rules">
123 <div class="pr-reviewer-rules">
124 ## review rules will be appended here, by default reviewers logic
124 ## review rules will be appended here, by default reviewers logic
125 </div>
125 </div>
126 </div>
126 </div>
127
127
128 ## REVIEWERS / OBSERVERS
128 ## REVIEWERS / OBSERVERS
129 <div class="reviewers-title">
129 <div class="reviewers-title">
130
130
131 <ul class="nav-links clearfix">
131 <ul class="nav-links clearfix">
132
132
133 ## TAB1 MANDATORY REVIEWERS
133 ## TAB1 MANDATORY REVIEWERS
134 <li class="active">
134 <li class="active">
135 <a id="reviewers-btn" href="#showReviewers" tabindex="-1">
135 <a id="reviewers-btn" href="#showReviewers" tabindex="-1">
136 Reviewers
136 Reviewers
137 <span id="reviewers-cnt" data-count="0" class="menulink-counter">0</span>
137 <span id="reviewers-cnt" data-count="0" class="menulink-counter">0</span>
138 </a>
138 </a>
139 </li>
139 </li>
140
140
141 ## TAB2 OBSERVERS
141 ## TAB2 OBSERVERS
142 <li class="">
142 <li class="">
143 <a id="observers-btn" href="#showObservers" tabindex="-1">
143 <a id="observers-btn" href="#showObservers" tabindex="-1">
144 Observers
144 Observers
145 <span id="observers-cnt" data-count="0" class="menulink-counter">0</span>
145 <span id="observers-cnt" data-count="0" class="menulink-counter">0</span>
146 </a>
146 </a>
147 </li>
147 </li>
148
148
149 </ul>
149 </ul>
150
150
151 ## TAB1 MANDATORY REVIEWERS
151 ## TAB1 MANDATORY REVIEWERS
152 <div id="reviewers-container">
152 <div id="reviewers-container">
153 <span class="calculate-reviewers">
153 <span class="calculate-reviewers">
154 <h4>${_('loading...')}</h4>
154 <h4>${_('loading...')}</h4>
155 </span>
155 </span>
156
156
157 <div id="reviewers" class="pr-details-content reviewers">
157 <div id="reviewers" class="pr-details-content reviewers">
158 ## members goes here, filled via JS based on initial selection !
158 ## members goes here, filled via JS based on initial selection !
159 <input type="hidden" name="__start__" value="review_members:sequence">
159 <input type="hidden" name="__start__" value="review_members:sequence">
160 <table id="review_members" class="group_members">
160 <table id="review_members" class="group_members">
161 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
161 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
162 </table>
162 </table>
163 <input type="hidden" name="__end__" value="review_members:sequence">
163 <input type="hidden" name="__end__" value="review_members:sequence">
164
164
165 <div id="add_reviewer_input" class='ac'>
165 <div id="add_reviewer_input" class='ac'>
166 <div class="reviewer_ac">
166 <div class="reviewer_ac">
167 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
167 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
168 <div id="reviewers_container"></div>
168 <div id="reviewers_container"></div>
169 </div>
169 </div>
170 </div>
170 </div>
171
171
172 </div>
172 </div>
173 </div>
173 </div>
174
174
175 ## TAB2 OBSERVERS
175 ## TAB2 OBSERVERS
176 <div id="observers-container" style="display: none">
176 <div id="observers-container" style="display: none">
177 <span class="calculate-reviewers">
177 <span class="calculate-reviewers">
178 <h4>${_('loading...')}</h4>
178 <h4>${_('loading...')}</h4>
179 </span>
179 </span>
180
180 % if c.rhodecode_edition_id == 'EE':
181 <div id="observers" class="pr-details-content observers">
181 <div id="observers" class="pr-details-content observers">
182 ## members goes here, filled via JS based on initial selection !
182 ## members goes here, filled via JS based on initial selection !
183 <input type="hidden" name="__start__" value="observer_members:sequence">
183 <input type="hidden" name="__start__" value="observer_members:sequence">
184 <table id="observer_members" class="group_members">
184 <table id="observer_members" class="group_members">
185 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
185 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
186 </table>
186 </table>
187 <input type="hidden" name="__end__" value="observer_members:sequence">
187 <input type="hidden" name="__end__" value="observer_members:sequence">
188
188
189 <div id="add_observer_input" class='ac'>
189 <div id="add_observer_input" class='ac'>
190 <div class="observer_ac">
190 <div class="observer_ac">
191 ${h.text('observer', class_='ac-input', placeholder=_('Add observer or observer group'))}
191 ${h.text('observer', class_='ac-input', placeholder=_('Add observer or observer group'))}
192 <div id="observers_container"></div>
192 <div id="observers_container"></div>
193 </div>
193 </div>
194 </div>
194 </div>
195 </div>
195 </div>
196
196 % else:
197 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
198 <p>
199 Pull request observers allows adding users who don't need to leave mandatory votes, but need to be aware about certain changes.
200 </p>
201 % endif
197 </div>
202 </div>
198
203
199 </div>
204 </div>
200
205
201 </div>
206 </div>
202 </div>
207 </div>
203
208
204 ## SUBMIT
209 ## SUBMIT
205 <div class="field">
210 <div class="field">
206 <div class="label label-textarea">
211 <div class="label label-textarea">
207 <label for="pullrequest_submit"></label>
212 <label for="pullrequest_submit"></label>
208 </div>
213 </div>
209 <div class="input">
214 <div class="input">
210 <div class="pr-submit-button">
215 <div class="pr-submit-button">
211 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
216 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
212 </div>
217 </div>
213 </div>
218 </div>
214 </div>
219 </div>
215 </div>
220 </div>
216 </div>
221 </div>
217 </div>
222 </div>
218
223
219 </div>
224 </div>
220
225
221 ${h.end_form()}
226 ${h.end_form()}
222 </div>
227 </div>
223
228
224 <script type="text/javascript">
229 <script type="text/javascript">
225 $(function(){
230 $(function(){
226 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
231 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
227 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
232 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
228 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
233 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
229 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
234 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
230
235
231 var $pullRequestForm = $('#pull_request_form');
236 var $pullRequestForm = $('#pull_request_form');
232 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
237 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
233 var $sourceRepo = $('#source_repo', $pullRequestForm);
238 var $sourceRepo = $('#source_repo', $pullRequestForm);
234 var $targetRepo = $('#target_repo', $pullRequestForm);
239 var $targetRepo = $('#target_repo', $pullRequestForm);
235 var $sourceRef = $('#source_ref', $pullRequestForm);
240 var $sourceRef = $('#source_ref', $pullRequestForm);
236 var $targetRef = $('#target_ref', $pullRequestForm);
241 var $targetRef = $('#target_ref', $pullRequestForm);
237
242
238 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
243 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
239 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
244 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
240
245
241 var targetRepo = function() { return $targetRepo.eq(0).val() };
246 var targetRepo = function() { return $targetRepo.eq(0).val() };
242 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
247 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
243
248
244 var calculateContainerWidth = function() {
249 var calculateContainerWidth = function() {
245 var maxWidth = 0;
250 var maxWidth = 0;
246 var repoSelect2Containers = ['#source_repo', '#target_repo'];
251 var repoSelect2Containers = ['#source_repo', '#target_repo'];
247 $.each(repoSelect2Containers, function(idx, value) {
252 $.each(repoSelect2Containers, function(idx, value) {
248 $(value).select2('container').width('auto');
253 $(value).select2('container').width('auto');
249 var curWidth = $(value).select2('container').width();
254 var curWidth = $(value).select2('container').width();
250 if (maxWidth <= curWidth) {
255 if (maxWidth <= curWidth) {
251 maxWidth = curWidth;
256 maxWidth = curWidth;
252 }
257 }
253 $.each(repoSelect2Containers, function(idx, value) {
258 $.each(repoSelect2Containers, function(idx, value) {
254 $(value).select2('container').width(maxWidth + 10);
259 $(value).select2('container').width(maxWidth + 10);
255 });
260 });
256 });
261 });
257 };
262 };
258
263
259 var initRefSelection = function(selectedRef) {
264 var initRefSelection = function(selectedRef) {
260 return function(element, callback) {
265 return function(element, callback) {
261 // translate our select2 id into a text, it's a mapping to show
266 // translate our select2 id into a text, it's a mapping to show
262 // simple label when selecting by internal ID.
267 // simple label when selecting by internal ID.
263 var id, refData;
268 var id, refData;
264 if (selectedRef === undefined || selectedRef === null) {
269 if (selectedRef === undefined || selectedRef === null) {
265 id = element.val();
270 id = element.val();
266 refData = element.val().split(':');
271 refData = element.val().split(':');
267
272
268 if (refData.length !== 3){
273 if (refData.length !== 3){
269 refData = ["", "", ""]
274 refData = ["", "", ""]
270 }
275 }
271 } else {
276 } else {
272 id = selectedRef;
277 id = selectedRef;
273 refData = selectedRef.split(':');
278 refData = selectedRef.split(':');
274 }
279 }
275
280
276 var text = refData[1];
281 var text = refData[1];
277 if (refData[0] === 'rev') {
282 if (refData[0] === 'rev') {
278 text = text.substring(0, 12);
283 text = text.substring(0, 12);
279 }
284 }
280
285
281 var data = {id: id, text: text};
286 var data = {id: id, text: text};
282 callback(data);
287 callback(data);
283 };
288 };
284 };
289 };
285
290
286 var formatRefSelection = function(data, container, escapeMarkup) {
291 var formatRefSelection = function(data, container, escapeMarkup) {
287 var prefix = '';
292 var prefix = '';
288 var refData = data.id.split(':');
293 var refData = data.id.split(':');
289 if (refData[0] === 'branch') {
294 if (refData[0] === 'branch') {
290 prefix = '<i class="icon-branch"></i>';
295 prefix = '<i class="icon-branch"></i>';
291 }
296 }
292 else if (refData[0] === 'book') {
297 else if (refData[0] === 'book') {
293 prefix = '<i class="icon-bookmark"></i>';
298 prefix = '<i class="icon-bookmark"></i>';
294 }
299 }
295 else if (refData[0] === 'tag') {
300 else if (refData[0] === 'tag') {
296 prefix = '<i class="icon-tag"></i>';
301 prefix = '<i class="icon-tag"></i>';
297 }
302 }
298
303
299 var originalOption = data.element;
304 var originalOption = data.element;
300 return prefix + escapeMarkup(data.text);
305 return prefix + escapeMarkup(data.text);
301 };formatSelection:
306 };formatSelection:
302
307
303 // custom code mirror
308 // custom code mirror
304 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
309 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
305
310
306 var diffDataHandler = function(data) {
311 var diffDataHandler = function(data) {
307
312
308 var commitElements = data['commits'];
313 var commitElements = data['commits'];
309 var files = data['files'];
314 var files = data['files'];
310 var added = data['stats'][0]
315 var added = data['stats'][0]
311 var deleted = data['stats'][1]
316 var deleted = data['stats'][1]
312 var commonAncestorId = data['ancestor'];
317 var commonAncestorId = data['ancestor'];
313 var _sourceRefType = sourceRef()[0];
318 var _sourceRefType = sourceRef()[0];
314 var _sourceRefName = sourceRef()[1];
319 var _sourceRefName = sourceRef()[1];
315 var prTitleAndDesc = getTitleAndDescription(_sourceRefType, _sourceRefName, commitElements, 5);
320 var prTitleAndDesc = getTitleAndDescription(_sourceRefType, _sourceRefName, commitElements, 5);
316
321
317 var title = prTitleAndDesc[0];
322 var title = prTitleAndDesc[0];
318 var proposedDescription = prTitleAndDesc[1];
323 var proposedDescription = prTitleAndDesc[1];
319
324
320 var useGeneratedTitle = (
325 var useGeneratedTitle = (
321 $('#pullrequest_title').hasClass('autogenerated-title') ||
326 $('#pullrequest_title').hasClass('autogenerated-title') ||
322 $('#pullrequest_title').val() === "");
327 $('#pullrequest_title').val() === "");
323
328
324 if (title && useGeneratedTitle) {
329 if (title && useGeneratedTitle) {
325 // use generated title if we haven't specified our own
330 // use generated title if we haven't specified our own
326 $('#pullrequest_title').val(title);
331 $('#pullrequest_title').val(title);
327 $('#pullrequest_title').addClass('autogenerated-title');
332 $('#pullrequest_title').addClass('autogenerated-title');
328
333
329 }
334 }
330
335
331 var useGeneratedDescription = (
336 var useGeneratedDescription = (
332 !codeMirrorInstance._userDefinedValue ||
337 !codeMirrorInstance._userDefinedValue ||
333 codeMirrorInstance.getValue() === "");
338 codeMirrorInstance.getValue() === "");
334
339
335 if (proposedDescription && useGeneratedDescription) {
340 if (proposedDescription && useGeneratedDescription) {
336 // set proposed content, if we haven't defined our own,
341 // set proposed content, if we haven't defined our own,
337 // or we don't have description written
342 // or we don't have description written
338 codeMirrorInstance._userDefinedValue = false; // reset state
343 codeMirrorInstance._userDefinedValue = false; // reset state
339 codeMirrorInstance.setValue(proposedDescription);
344 codeMirrorInstance.setValue(proposedDescription);
340 }
345 }
341
346
342 // refresh our codeMirror so events kicks in and it's change aware
347 // refresh our codeMirror so events kicks in and it's change aware
343 codeMirrorInstance.refresh();
348 codeMirrorInstance.refresh();
344
349
345 var url_data = {
350 var url_data = {
346 'repo_name': targetRepo(),
351 'repo_name': targetRepo(),
347 'target_repo': sourceRepo(),
352 'target_repo': sourceRepo(),
348 'source_ref': targetRef()[2],
353 'source_ref': targetRef()[2],
349 'source_ref_type': 'rev',
354 'source_ref_type': 'rev',
350 'target_ref': sourceRef()[2],
355 'target_ref': sourceRef()[2],
351 'target_ref_type': 'rev',
356 'target_ref_type': 'rev',
352 'merge': true,
357 'merge': true,
353 '_': Date.now() // bypass browser caching
358 '_': Date.now() // bypass browser caching
354 }; // gather the source/target ref and repo here
359 }; // gather the source/target ref and repo here
355 var url = pyroutes.url('repo_compare', url_data);
360 var url = pyroutes.url('repo_compare', url_data);
356
361
357 var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId);
362 var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId);
358 msg += '<input type="hidden" name="__start__" value="revisions:sequence">'
363 msg += '<input type="hidden" name="__start__" value="revisions:sequence">'
359
364
360 $.each(commitElements, function(idx, value) {
365 $.each(commitElements, function(idx, value) {
361 msg += '<input type="hidden" name="revisions" value="{0}">'.format(value["raw_id"]);
366 msg += '<input type="hidden" name="revisions" value="{0}">'.format(value["raw_id"]);
362 });
367 });
363
368
364 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
369 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
365 msg += _ngettext(
370 msg += _ngettext(
366 'Compare summary: <strong>{0} commit</strong>',
371 'Compare summary: <strong>{0} commit</strong>',
367 'Compare summary: <strong>{0} commits</strong>',
372 'Compare summary: <strong>{0} commits</strong>',
368 commitElements.length).format(commitElements.length)
373 commitElements.length).format(commitElements.length)
369
374
370 msg += '';
375 msg += '';
371 msg += _ngettext(
376 msg += _ngettext(
372 '<strong>, and {0} file</strong> changed.',
377 '<strong>, and {0} file</strong> changed.',
373 '<strong>, and {0} files</strong> changed.',
378 '<strong>, and {0} files</strong> changed.',
374 files.length).format(files.length)
379 files.length).format(files.length)
375
380
376 msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted)
381 msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted)
377
382
378 msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
383 msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
379
384
380 if (commitElements.length) {
385 if (commitElements.length) {
381 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
386 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
382 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
387 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
383 }
388 }
384 else {
389 else {
385 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
390 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
386 _gettext('There are no commits to merge.'));
391 _gettext('There are no commits to merge.'));
387 prButtonLock(true, noCommitsMsg, 'compare');
392 prButtonLock(true, noCommitsMsg, 'compare');
388 }
393 }
389
394
390 //make both panels equal
395 //make both panels equal
391 $('.target-panel').height($('.source-panel').height())
396 $('.target-panel').height($('.source-panel').height())
392 };
397 };
393
398
394 reviewersController = new ReviewersController();
399 reviewersController = new ReviewersController();
395 reviewersController.diffDataHandler = diffDataHandler;
400 reviewersController.diffDataHandler = diffDataHandler;
396
401
397 var queryTargetRepo = function(self, query) {
402 var queryTargetRepo = function(self, query) {
398 // cache ALL results if query is empty
403 // cache ALL results if query is empty
399 var cacheKey = query.term || '__';
404 var cacheKey = query.term || '__';
400 var cachedData = self.cachedDataSource[cacheKey];
405 var cachedData = self.cachedDataSource[cacheKey];
401
406
402 if (cachedData) {
407 if (cachedData) {
403 query.callback({results: cachedData.results});
408 query.callback({results: cachedData.results});
404 } else {
409 } else {
405 $.ajax({
410 $.ajax({
406 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
411 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
407 data: {query: query.term},
412 data: {query: query.term},
408 dataType: 'json',
413 dataType: 'json',
409 type: 'GET',
414 type: 'GET',
410 success: function(data) {
415 success: function(data) {
411 self.cachedDataSource[cacheKey] = data;
416 self.cachedDataSource[cacheKey] = data;
412 query.callback({results: data.results});
417 query.callback({results: data.results});
413 },
418 },
414 error: function(jqXHR, textStatus, errorThrown) {
419 error: function(jqXHR, textStatus, errorThrown) {
415 var prefix = "Error while fetching entries.\n"
420 var prefix = "Error while fetching entries.\n"
416 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
421 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
417 ajaxErrorSwal(message);
422 ajaxErrorSwal(message);
418 }
423 }
419 });
424 });
420 }
425 }
421 };
426 };
422
427
423 var queryTargetRefs = function(initialData, query) {
428 var queryTargetRefs = function(initialData, query) {
424 var data = {results: []};
429 var data = {results: []};
425 // filter initialData
430 // filter initialData
426 $.each(initialData, function() {
431 $.each(initialData, function() {
427 var section = this.text;
432 var section = this.text;
428 var children = [];
433 var children = [];
429 $.each(this.children, function() {
434 $.each(this.children, function() {
430 if (query.term.length === 0 ||
435 if (query.term.length === 0 ||
431 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
436 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
432 children.push({'id': this.id, 'text': this.text})
437 children.push({'id': this.id, 'text': this.text})
433 }
438 }
434 });
439 });
435 data.results.push({'text': section, 'children': children})
440 data.results.push({'text': section, 'children': children})
436 });
441 });
437 query.callback({results: data.results});
442 query.callback({results: data.results});
438 };
443 };
439
444
440 var Select2Box = function(element, overrides) {
445 var Select2Box = function(element, overrides) {
441 var globalDefaults = {
446 var globalDefaults = {
442 dropdownAutoWidth: true,
447 dropdownAutoWidth: true,
443 containerCssClass: "drop-menu",
448 containerCssClass: "drop-menu",
444 dropdownCssClass: "drop-menu-dropdown"
449 dropdownCssClass: "drop-menu-dropdown"
445 };
450 };
446
451
447 var initSelect2 = function(defaultOptions) {
452 var initSelect2 = function(defaultOptions) {
448 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
453 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
449 element.select2(options);
454 element.select2(options);
450 };
455 };
451
456
452 return {
457 return {
453 initRef: function() {
458 initRef: function() {
454 var defaultOptions = {
459 var defaultOptions = {
455 minimumResultsForSearch: 5,
460 minimumResultsForSearch: 5,
456 formatSelection: formatRefSelection
461 formatSelection: formatRefSelection
457 };
462 };
458
463
459 initSelect2(defaultOptions);
464 initSelect2(defaultOptions);
460 },
465 },
461
466
462 initRepo: function(defaultValue, readOnly) {
467 initRepo: function(defaultValue, readOnly) {
463 var defaultOptions = {
468 var defaultOptions = {
464 initSelection : function (element, callback) {
469 initSelection : function (element, callback) {
465 var data = {id: defaultValue, text: defaultValue};
470 var data = {id: defaultValue, text: defaultValue};
466 callback(data);
471 callback(data);
467 }
472 }
468 };
473 };
469
474
470 initSelect2(defaultOptions);
475 initSelect2(defaultOptions);
471
476
472 element.select2('val', defaultSourceRepo);
477 element.select2('val', defaultSourceRepo);
473 if (readOnly === true) {
478 if (readOnly === true) {
474 element.select2('readonly', true);
479 element.select2('readonly', true);
475 }
480 }
476 }
481 }
477 };
482 };
478 };
483 };
479
484
480 var initTargetRefs = function(refsData, selectedRef) {
485 var initTargetRefs = function(refsData, selectedRef) {
481
486
482 Select2Box($targetRef, {
487 Select2Box($targetRef, {
483 placeholder: "${_('Select commit reference')}",
488 placeholder: "${_('Select commit reference')}",
484 query: function(query) {
489 query: function(query) {
485 queryTargetRefs(refsData, query);
490 queryTargetRefs(refsData, query);
486 },
491 },
487 initSelection : initRefSelection(selectedRef)
492 initSelection : initRefSelection(selectedRef)
488 }).initRef();
493 }).initRef();
489
494
490 if (!(selectedRef === undefined)) {
495 if (!(selectedRef === undefined)) {
491 $targetRef.select2('val', selectedRef);
496 $targetRef.select2('val', selectedRef);
492 }
497 }
493 };
498 };
494
499
495 var targetRepoChanged = function(repoData) {
500 var targetRepoChanged = function(repoData) {
496 // generate new DESC of target repo displayed next to select
501 // generate new DESC of target repo displayed next to select
497
502
498 $('#target_repo_desc').html(repoData['description']);
503 $('#target_repo_desc').html(repoData['description']);
499
504
500 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
505 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
501 var title = _gettext('Switch target repository with the source.')
506 var title = _gettext('Switch target repository with the source.')
502 $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
507 $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
503
508
504 // generate dynamic select2 for refs.
509 // generate dynamic select2 for refs.
505 initTargetRefs(repoData['refs']['select2_refs'],
510 initTargetRefs(repoData['refs']['select2_refs'],
506 repoData['refs']['selected_ref']);
511 repoData['refs']['selected_ref']);
507
512
508 };
513 };
509
514
510 var sourceRefSelect2 = Select2Box($sourceRef, {
515 var sourceRefSelect2 = Select2Box($sourceRef, {
511 placeholder: "${_('Select commit reference')}",
516 placeholder: "${_('Select commit reference')}",
512 query: function(query) {
517 query: function(query) {
513 var initialData = defaultSourceRepoData['refs']['select2_refs'];
518 var initialData = defaultSourceRepoData['refs']['select2_refs'];
514 queryTargetRefs(initialData, query)
519 queryTargetRefs(initialData, query)
515 },
520 },
516 initSelection: initRefSelection()
521 initSelection: initRefSelection()
517 });
522 });
518
523
519 var sourceRepoSelect2 = Select2Box($sourceRepo, {
524 var sourceRepoSelect2 = Select2Box($sourceRepo, {
520 query: function(query) {}
525 query: function(query) {}
521 });
526 });
522
527
523 var targetRepoSelect2 = Select2Box($targetRepo, {
528 var targetRepoSelect2 = Select2Box($targetRepo, {
524 cachedDataSource: {},
529 cachedDataSource: {},
525 query: $.debounce(250, function(query) {
530 query: $.debounce(250, function(query) {
526 queryTargetRepo(this, query);
531 queryTargetRepo(this, query);
527 }),
532 }),
528 formatResult: formatRepoResult
533 formatResult: formatRepoResult
529 });
534 });
530
535
531 sourceRefSelect2.initRef();
536 sourceRefSelect2.initRef();
532
537
533 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
538 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
534
539
535 targetRepoSelect2.initRepo(defaultTargetRepo, false);
540 targetRepoSelect2.initRepo(defaultTargetRepo, false);
536
541
537 $sourceRef.on('change', function(e){
542 $sourceRef.on('change', function(e){
538 reviewersController.loadDefaultReviewers(
543 reviewersController.loadDefaultReviewers(
539 sourceRepo(), sourceRef(), targetRepo(), targetRef());
544 sourceRepo(), sourceRef(), targetRepo(), targetRef());
540 });
545 });
541
546
542 $targetRef.on('change', function(e){
547 $targetRef.on('change', function(e){
543 reviewersController.loadDefaultReviewers(
548 reviewersController.loadDefaultReviewers(
544 sourceRepo(), sourceRef(), targetRepo(), targetRef());
549 sourceRepo(), sourceRef(), targetRepo(), targetRef());
545 });
550 });
546
551
547 $targetRepo.on('change', function(e){
552 $targetRepo.on('change', function(e){
548 var repoName = $(this).val();
553 var repoName = $(this).val();
549 calculateContainerWidth();
554 calculateContainerWidth();
550 $targetRef.select2('destroy');
555 $targetRef.select2('destroy');
551 $('#target_ref_loading').show();
556 $('#target_ref_loading').show();
552
557
553 $.ajax({
558 $.ajax({
554 url: pyroutes.url('pullrequest_repo_refs',
559 url: pyroutes.url('pullrequest_repo_refs',
555 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
560 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
556 data: {},
561 data: {},
557 dataType: 'json',
562 dataType: 'json',
558 type: 'GET',
563 type: 'GET',
559 success: function(data) {
564 success: function(data) {
560 $('#target_ref_loading').hide();
565 $('#target_ref_loading').hide();
561 targetRepoChanged(data);
566 targetRepoChanged(data);
562 },
567 },
563 error: function(jqXHR, textStatus, errorThrown) {
568 error: function(jqXHR, textStatus, errorThrown) {
564 var prefix = "Error while fetching entries.\n"
569 var prefix = "Error while fetching entries.\n"
565 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
570 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
566 ajaxErrorSwal(message);
571 ajaxErrorSwal(message);
567 }
572 }
568 })
573 })
569
574
570 });
575 });
571
576
572 $pullRequestForm.on('submit', function(e){
577 $pullRequestForm.on('submit', function(e){
573 // Flush changes into textarea
578 // Flush changes into textarea
574 codeMirrorInstance.save();
579 codeMirrorInstance.save();
575 prButtonLock(true, null, 'all');
580 prButtonLock(true, null, 'all');
576 $pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
581 $pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
577 });
582 });
578
583
579 prButtonLock(true, "${_('Please select source and target')}", 'all');
584 prButtonLock(true, "${_('Please select source and target')}", 'all');
580
585
581 // auto-load on init, the target refs select2
586 // auto-load on init, the target refs select2
582 calculateContainerWidth();
587 calculateContainerWidth();
583 targetRepoChanged(defaultTargetRepoData);
588 targetRepoChanged(defaultTargetRepoData);
584
589
585 $('#pullrequest_title').on('keyup', function(e){
590 $('#pullrequest_title').on('keyup', function(e){
586 $(this).removeClass('autogenerated-title');
591 $(this).removeClass('autogenerated-title');
587 });
592 });
588
593
589 % if c.default_source_ref:
594 % if c.default_source_ref:
590 // in case we have a pre-selected value, use it now
595 // in case we have a pre-selected value, use it now
591 $sourceRef.select2('val', '${c.default_source_ref}');
596 $sourceRef.select2('val', '${c.default_source_ref}');
592
597
593
598
594 // default reviewers / observers
599 // default reviewers / observers
595 reviewersController.loadDefaultReviewers(
600 reviewersController.loadDefaultReviewers(
596 sourceRepo(), sourceRef(), targetRepo(), targetRef());
601 sourceRepo(), sourceRef(), targetRepo(), targetRef());
597 % endif
602 % endif
598
603
599 ReviewerAutoComplete('#user', reviewersController);
604 ReviewerAutoComplete('#user', reviewersController);
600 ObserverAutoComplete('#observer', reviewersController);
605 ObserverAutoComplete('#observer', reviewersController);
601
606
602 // TODO, move this to another handler
607 // TODO, move this to another handler
603
608
604 var $reviewersBtn = $('#reviewers-btn');
609 var $reviewersBtn = $('#reviewers-btn');
605 var $reviewersContainer = $('#reviewers-container');
610 var $reviewersContainer = $('#reviewers-container');
606
611
607 var $observersBtn = $('#observers-btn')
612 var $observersBtn = $('#observers-btn')
608 var $observersContainer = $('#observers-container');
613 var $observersContainer = $('#observers-container');
609
614
610 $reviewersBtn.on('click', function (e) {
615 $reviewersBtn.on('click', function (e) {
611
616
612 $observersContainer.hide();
617 $observersContainer.hide();
613 $reviewersContainer.show();
618 $reviewersContainer.show();
614
619
615 $observersBtn.parent().removeClass('active');
620 $observersBtn.parent().removeClass('active');
616 $reviewersBtn.parent().addClass('active');
621 $reviewersBtn.parent().addClass('active');
617 e.preventDefault();
622 e.preventDefault();
618
623
619 })
624 })
620
625
621 $observersBtn.on('click', function (e) {
626 $observersBtn.on('click', function (e) {
622
627
623 $reviewersContainer.hide();
628 $reviewersContainer.hide();
624 $observersContainer.show();
629 $observersContainer.show();
625
630
626 $reviewersBtn.parent().removeClass('active');
631 $reviewersBtn.parent().removeClass('active');
627 $observersBtn.parent().addClass('active');
632 $observersBtn.parent().addClass('active');
628 e.preventDefault();
633 e.preventDefault();
629
634
630 })
635 })
631
636
632 });
637 });
633 </script>
638 </script>
634
639
635 </%def>
640 </%def>
@@ -1,999 +1,1001 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5
5
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15
15
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='showpullrequest')}
23 ${self.repo_menu(active='showpullrequest')}
24 </%def>
24 </%def>
25
25
26
26
27 <%def name="main()">
27 <%def name="main()">
28 ## Container to gather extracted Tickets
28 ## Container to gather extracted Tickets
29 <%
29 <%
30 c.referenced_commit_issues = []
30 c.referenced_commit_issues = []
31 c.referenced_desc_issues = []
31 c.referenced_desc_issues = []
32 %>
32 %>
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
39 </script>
39 </script>
40
40
41 <div class="box">
41 <div class="box">
42
42
43 <div class="box pr-summary">
43 <div class="box pr-summary">
44
44
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <div id="pr-title">
46 <div id="pr-title">
47 % if c.pull_request.is_closed():
47 % if c.pull_request.is_closed():
48 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
48 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
49 % endif
49 % endif
50 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
50 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
51 </div>
51 </div>
52 <div id="pr-title-edit" class="input" style="display: none;">
52 <div id="pr-title-edit" class="input" style="display: none;">
53 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
53 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
54 </div>
54 </div>
55
55
56 <% summary = lambda n:{False:'summary-short'}.get(n) %>
56 <% summary = lambda n:{False:'summary-short'}.get(n) %>
57 <div class="pr-details-title">
57 <div class="pr-details-title">
58 <div class="pull-left">
58 <div class="pull-left">
59 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
59 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
60 ${_('Created on')}
60 ${_('Created on')}
61 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
61 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
62 <span class="pr-details-title-author-pref">${_('by')}</span>
62 <span class="pr-details-title-author-pref">${_('by')}</span>
63 </div>
63 </div>
64
64
65 <div class="pull-left">
65 <div class="pull-left">
66 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
66 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
67 </div>
67 </div>
68
68
69 %if c.allowed_to_update:
69 %if c.allowed_to_update:
70 <div class="pull-right">
70 <div class="pull-right">
71 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
71 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
72 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
72 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
73 % if c.allowed_to_delete:
73 % if c.allowed_to_delete:
74 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
74 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
75 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
75 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
76 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
76 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
77 type="submit" value="${_('Delete pull request')}">
77 type="submit" value="${_('Delete pull request')}">
78 ${h.end_form()}
78 ${h.end_form()}
79 % else:
79 % else:
80 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
80 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
81 % endif
81 % endif
82 </div>
82 </div>
83 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
83 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
84 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
84 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
85 </div>
85 </div>
86
86
87 %endif
87 %endif
88 </div>
88 </div>
89
89
90 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
90 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
91 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
91 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
92 </div>
92 </div>
93
93
94 <div id="pr-desc-edit" class="input textarea" style="display: none;">
94 <div id="pr-desc-edit" class="input textarea" style="display: none;">
95 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
95 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
96 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
96 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
97 </div>
97 </div>
98
98
99 <div id="summary" class="fields pr-details-content">
99 <div id="summary" class="fields pr-details-content">
100
100
101 ## source
101 ## source
102 <div class="field">
102 <div class="field">
103 <div class="label-pr-detail">
103 <div class="label-pr-detail">
104 <label>${_('Commit flow')}:</label>
104 <label>${_('Commit flow')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 <div class="pr-commit-flow">
107 <div class="pr-commit-flow">
108 ## Source
108 ## Source
109 %if c.pull_request.source_ref_parts.type == 'branch':
109 %if c.pull_request.source_ref_parts.type == 'branch':
110 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
110 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
111 %else:
111 %else:
112 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
112 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
113 %endif
113 %endif
114 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
114 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
115 &rarr;
115 &rarr;
116 ## Target
116 ## Target
117 %if c.pull_request.target_ref_parts.type == 'branch':
117 %if c.pull_request.target_ref_parts.type == 'branch':
118 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
118 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
119 %else:
119 %else:
120 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
120 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
121 %endif
121 %endif
122
122
123 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
123 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
124
124
125 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
125 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
126 <i class="icon-angle-down">more details</i>
126 <i class="icon-angle-down">more details</i>
127 </a>
127 </a>
128
128
129 </div>
129 </div>
130
130
131 <div class="source-details" style="display: none">
131 <div class="source-details" style="display: none">
132
132
133 <ul>
133 <ul>
134
134
135 ## common ancestor
135 ## common ancestor
136 <li>
136 <li>
137 ${_('Common ancestor')}:
137 ${_('Common ancestor')}:
138 % if c.ancestor_commit:
138 % if c.ancestor_commit:
139 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
139 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
140 % else:
140 % else:
141 ${_('not available')}
141 ${_('not available')}
142 % endif
142 % endif
143 </li>
143 </li>
144
144
145 ## pull url
145 ## pull url
146 <li>
146 <li>
147 %if h.is_hg(c.pull_request.source_repo):
147 %if h.is_hg(c.pull_request.source_repo):
148 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
148 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
149 %elif h.is_git(c.pull_request.source_repo):
149 %elif h.is_git(c.pull_request.source_repo):
150 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
150 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
151 %endif
151 %endif
152
152
153 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
153 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
154 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
154 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
155 </li>
155 </li>
156
156
157 ## Shadow repo
157 ## Shadow repo
158 <li>
158 <li>
159 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
159 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
160 %if h.is_hg(c.pull_request.target_repo):
160 %if h.is_hg(c.pull_request.target_repo):
161 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
161 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
162 %elif h.is_git(c.pull_request.target_repo):
162 %elif h.is_git(c.pull_request.target_repo):
163 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
163 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
164 %endif
164 %endif
165
165
166 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
166 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
167 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
167 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
168
168
169 % else:
169 % else:
170 <div class="">
170 <div class="">
171 ${_('Shadow repository data not available')}.
171 ${_('Shadow repository data not available')}.
172 </div>
172 </div>
173 % endif
173 % endif
174 </li>
174 </li>
175
175
176 </ul>
176 </ul>
177
177
178 </div>
178 </div>
179
179
180 </div>
180 </div>
181
181
182 </div>
182 </div>
183
183
184 ## versions
184 ## versions
185 <div class="field">
185 <div class="field">
186 <div class="label-pr-detail">
186 <div class="label-pr-detail">
187 <label>${_('Versions')}:</label>
187 <label>${_('Versions')}:</label>
188 </div>
188 </div>
189
189
190 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
190 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
191 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
191 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
192
192
193 <div class="pr-versions">
193 <div class="pr-versions">
194 % if c.show_version_changes:
194 % if c.show_version_changes:
195 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
195 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
196 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
196 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
197 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
197 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
198 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
198 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
199 data-toggle-on="${_('show versions')}."
199 data-toggle-on="${_('show versions')}."
200 data-toggle-off="${_('hide versions')}.">
200 data-toggle-off="${_('hide versions')}.">
201 ${_('show versions')}.
201 ${_('show versions')}.
202 </a>
202 </a>
203 <table>
203 <table>
204 ## SHOW ALL VERSIONS OF PR
204 ## SHOW ALL VERSIONS OF PR
205 <% ver_pr = None %>
205 <% ver_pr = None %>
206
206
207 % for data in reversed(list(enumerate(c.versions, 1))):
207 % for data in reversed(list(enumerate(c.versions, 1))):
208 <% ver_pos = data[0] %>
208 <% ver_pos = data[0] %>
209 <% ver = data[1] %>
209 <% ver = data[1] %>
210 <% ver_pr = ver.pull_request_version_id %>
210 <% ver_pr = ver.pull_request_version_id %>
211 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
211 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
212
212
213 <tr class="version-pr" style="display: ${display_row}">
213 <tr class="version-pr" style="display: ${display_row}">
214 <td>
214 <td>
215 <code>
215 <code>
216 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
216 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
217 </code>
217 </code>
218 </td>
218 </td>
219 <td>
219 <td>
220 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
220 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
221 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
221 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
222 </td>
222 </td>
223 <td>
223 <td>
224 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
224 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
225 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
225 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
226
226
227 </td>
227 </td>
228 <td>
228 <td>
229 % if c.at_version_num != ver_pr:
229 % if c.at_version_num != ver_pr:
230 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
230 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
231 <code>
231 <code>
232 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
232 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
233 </code>
233 </code>
234 % endif
234 % endif
235 </td>
235 </td>
236 <td>
236 <td>
237 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
237 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
238 </td>
238 </td>
239 <td>
239 <td>
240 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
240 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
241 </td>
241 </td>
242 </tr>
242 </tr>
243 % endfor
243 % endfor
244
244
245 <tr>
245 <tr>
246 <td colspan="6">
246 <td colspan="6">
247 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
247 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
248 data-label-text-locked="${_('select versions to show changes')}"
248 data-label-text-locked="${_('select versions to show changes')}"
249 data-label-text-diff="${_('show changes between versions')}"
249 data-label-text-diff="${_('show changes between versions')}"
250 data-label-text-show="${_('show pull request for this version')}"
250 data-label-text-show="${_('show pull request for this version')}"
251 >
251 >
252 ${_('select versions to show changes')}
252 ${_('select versions to show changes')}
253 </button>
253 </button>
254 </td>
254 </td>
255 </tr>
255 </tr>
256 </table>
256 </table>
257 % else:
257 % else:
258 <div>
258 <div>
259 ${_('Pull request versions not available')}.
259 ${_('Pull request versions not available')}.
260 </div>
260 </div>
261 % endif
261 % endif
262 </div>
262 </div>
263 </div>
263 </div>
264
264
265 </div>
265 </div>
266
266
267 </div>
267 </div>
268
268
269
269
270 </div>
270 </div>
271
271
272 </div>
272 </div>
273
273
274 <div class="box">
274 <div class="box">
275
275
276 % if c.state_progressing:
276 % if c.state_progressing:
277
277
278 <h2 style="text-align: center">
278 <h2 style="text-align: center">
279 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
279 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
280
280
281 % if c.is_super_admin:
281 % if c.is_super_admin:
282 <br/>
282 <br/>
283 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
283 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
284 % endif
284 % endif
285 </h2>
285 </h2>
286
286
287 % else:
287 % else:
288
288
289 ## Diffs rendered here
289 ## Diffs rendered here
290 <div class="table" >
290 <div class="table" >
291 <div id="changeset_compare_view_content">
291 <div id="changeset_compare_view_content">
292 ##CS
292 ##CS
293 % if c.missing_requirements:
293 % if c.missing_requirements:
294 <div class="box">
294 <div class="box">
295 <div class="alert alert-warning">
295 <div class="alert alert-warning">
296 <div>
296 <div>
297 <strong>${_('Missing requirements:')}</strong>
297 <strong>${_('Missing requirements:')}</strong>
298 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
298 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
299 </div>
299 </div>
300 </div>
300 </div>
301 </div>
301 </div>
302 % elif c.missing_commits:
302 % elif c.missing_commits:
303 <div class="box">
303 <div class="box">
304 <div class="alert alert-warning">
304 <div class="alert alert-warning">
305 <div>
305 <div>
306 <strong>${_('Missing commits')}:</strong>
306 <strong>${_('Missing commits')}:</strong>
307 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
307 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
308 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
308 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
309 ${_('Consider doing a `force update commits` in case you think this is an error.')}
309 ${_('Consider doing a `force update commits` in case you think this is an error.')}
310 </div>
310 </div>
311 </div>
311 </div>
312 </div>
312 </div>
313 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
313 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
314 <div class="box">
314 <div class="box">
315 <div class="alert alert-info">
315 <div class="alert alert-info">
316 <div>
316 <div>
317 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
317 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
318 </div>
318 </div>
319 </div>
319 </div>
320 </div>
320 </div>
321 % endif
321 % endif
322
322
323 <div class="compare_view_commits_title">
323 <div class="compare_view_commits_title">
324 % if not c.compare_mode:
324 % if not c.compare_mode:
325
325
326 % if c.at_version_index:
326 % if c.at_version_index:
327 <h4>
327 <h4>
328 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
328 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
329 </h4>
329 </h4>
330 % endif
330 % endif
331
331
332 <div class="pull-left">
332 <div class="pull-left">
333 <div class="btn-group">
333 <div class="btn-group">
334 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
334 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
335 % if c.collapse_all_commits:
335 % if c.collapse_all_commits:
336 <i class="icon-plus-squared-alt icon-no-margin"></i>
336 <i class="icon-plus-squared-alt icon-no-margin"></i>
337 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
337 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
338 % else:
338 % else:
339 <i class="icon-minus-squared-alt icon-no-margin"></i>
339 <i class="icon-minus-squared-alt icon-no-margin"></i>
340 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
340 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
341 % endif
341 % endif
342 </a>
342 </a>
343 </div>
343 </div>
344 </div>
344 </div>
345
345
346 <div class="pull-right">
346 <div class="pull-right">
347 % if c.allowed_to_update and not c.pull_request.is_closed():
347 % if c.allowed_to_update and not c.pull_request.is_closed():
348
348
349 <div class="btn-group btn-group-actions">
349 <div class="btn-group btn-group-actions">
350 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
350 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
351 ${_('Update commits')}
351 ${_('Update commits')}
352 </a>
352 </a>
353
353
354 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
354 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
355 <i class="icon-down"></i>
355 <i class="icon-down"></i>
356 </a>
356 </a>
357
357
358 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
358 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
359 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
359 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
360 <li>
360 <li>
361 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
361 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
362 ${_('Force update commits')}
362 ${_('Force update commits')}
363 </a>
363 </a>
364 <div class="action-help-block">
364 <div class="action-help-block">
365 ${_('Update commits and force refresh this pull request.')}
365 ${_('Update commits and force refresh this pull request.')}
366 </div>
366 </div>
367 </li>
367 </li>
368 </ul>
368 </ul>
369 </div>
369 </div>
370 </div>
370 </div>
371
371
372 % else:
372 % else:
373 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
373 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
374 % endif
374 % endif
375
375
376 </div>
376 </div>
377 % endif
377 % endif
378 </div>
378 </div>
379
379
380 % if not c.missing_commits:
380 % if not c.missing_commits:
381 ## COMPARE RANGE DIFF MODE
381 ## COMPARE RANGE DIFF MODE
382 % if c.compare_mode:
382 % if c.compare_mode:
383 % if c.at_version:
383 % if c.at_version:
384 <h4>
384 <h4>
385 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
385 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
386 </h4>
386 </h4>
387
387
388 <div class="subtitle-compare">
388 <div class="subtitle-compare">
389 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
389 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
390 </div>
390 </div>
391
391
392 <div class="container">
392 <div class="container">
393 <table class="rctable compare_view_commits">
393 <table class="rctable compare_view_commits">
394 <tr>
394 <tr>
395 <th></th>
395 <th></th>
396 <th>${_('Time')}</th>
396 <th>${_('Time')}</th>
397 <th>${_('Author')}</th>
397 <th>${_('Author')}</th>
398 <th>${_('Commit')}</th>
398 <th>${_('Commit')}</th>
399 <th></th>
399 <th></th>
400 <th>${_('Description')}</th>
400 <th>${_('Description')}</th>
401 </tr>
401 </tr>
402
402
403 % for c_type, commit in c.commit_changes:
403 % for c_type, commit in c.commit_changes:
404 % if c_type in ['a', 'r']:
404 % if c_type in ['a', 'r']:
405 <%
405 <%
406 if c_type == 'a':
406 if c_type == 'a':
407 cc_title = _('Commit added in displayed changes')
407 cc_title = _('Commit added in displayed changes')
408 elif c_type == 'r':
408 elif c_type == 'r':
409 cc_title = _('Commit removed in displayed changes')
409 cc_title = _('Commit removed in displayed changes')
410 else:
410 else:
411 cc_title = ''
411 cc_title = ''
412 %>
412 %>
413 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
413 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
414 <td>
414 <td>
415 <div class="commit-change-indicator color-${c_type}-border">
415 <div class="commit-change-indicator color-${c_type}-border">
416 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
416 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
417 ${c_type.upper()}
417 ${c_type.upper()}
418 </div>
418 </div>
419 </div>
419 </div>
420 </td>
420 </td>
421 <td class="td-time">
421 <td class="td-time">
422 ${h.age_component(commit.date)}
422 ${h.age_component(commit.date)}
423 </td>
423 </td>
424 <td class="td-user">
424 <td class="td-user">
425 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
425 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
426 </td>
426 </td>
427 <td class="td-hash">
427 <td class="td-hash">
428 <code>
428 <code>
429 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
429 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
430 r${commit.idx}:${h.short_id(commit.raw_id)}
430 r${commit.idx}:${h.short_id(commit.raw_id)}
431 </a>
431 </a>
432 ${h.hidden('revisions', commit.raw_id)}
432 ${h.hidden('revisions', commit.raw_id)}
433 </code>
433 </code>
434 </td>
434 </td>
435 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
435 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
436 <i class="icon-expand-linked"></i>
436 <i class="icon-expand-linked"></i>
437 </td>
437 </td>
438 <td class="mid td-description">
438 <td class="mid td-description">
439 <div class="log-container truncate-wrap">
439 <div class="log-container truncate-wrap">
440 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
440 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
441 </div>
441 </div>
442 </td>
442 </td>
443 </tr>
443 </tr>
444 % endif
444 % endif
445 % endfor
445 % endfor
446 </table>
446 </table>
447 </div>
447 </div>
448
448
449 % endif
449 % endif
450
450
451 ## Regular DIFF
451 ## Regular DIFF
452 % else:
452 % else:
453 <%include file="/compare/compare_commits.mako" />
453 <%include file="/compare/compare_commits.mako" />
454 % endif
454 % endif
455
455
456 <div class="cs_files">
456 <div class="cs_files">
457 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
457 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
458
458
459 <%
459 <%
460 pr_menu_data = {
460 pr_menu_data = {
461 'outdated_comm_count_ver': outdated_comm_count_ver,
461 'outdated_comm_count_ver': outdated_comm_count_ver,
462 'pull_request': c.pull_request
462 'pull_request': c.pull_request
463 }
463 }
464 %>
464 %>
465
465
466 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
466 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
467
467
468 % if c.range_diff_on:
468 % if c.range_diff_on:
469 % for commit in c.commit_ranges:
469 % for commit in c.commit_ranges:
470 ${cbdiffs.render_diffset(
470 ${cbdiffs.render_diffset(
471 c.changes[commit.raw_id],
471 c.changes[commit.raw_id],
472 commit=commit, use_comments=True,
472 commit=commit, use_comments=True,
473 collapse_when_files_over=5,
473 collapse_when_files_over=5,
474 disable_new_comments=True,
474 disable_new_comments=True,
475 deleted_files_comments=c.deleted_files_comments,
475 deleted_files_comments=c.deleted_files_comments,
476 inline_comments=c.inline_comments,
476 inline_comments=c.inline_comments,
477 pull_request_menu=pr_menu_data, show_todos=False)}
477 pull_request_menu=pr_menu_data, show_todos=False)}
478 % endfor
478 % endfor
479 % else:
479 % else:
480 ${cbdiffs.render_diffset(
480 ${cbdiffs.render_diffset(
481 c.diffset, use_comments=True,
481 c.diffset, use_comments=True,
482 collapse_when_files_over=30,
482 collapse_when_files_over=30,
483 disable_new_comments=not c.allowed_to_comment,
483 disable_new_comments=not c.allowed_to_comment,
484 deleted_files_comments=c.deleted_files_comments,
484 deleted_files_comments=c.deleted_files_comments,
485 inline_comments=c.inline_comments,
485 inline_comments=c.inline_comments,
486 pull_request_menu=pr_menu_data, show_todos=False)}
486 pull_request_menu=pr_menu_data, show_todos=False)}
487 % endif
487 % endif
488
488
489 </div>
489 </div>
490 % else:
490 % else:
491 ## skipping commits we need to clear the view for missing commits
491 ## skipping commits we need to clear the view for missing commits
492 <div style="clear:both;"></div>
492 <div style="clear:both;"></div>
493 % endif
493 % endif
494
494
495 </div>
495 </div>
496 </div>
496 </div>
497
497
498 ## template for inline comment form
498 ## template for inline comment form
499 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
499 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
500
500
501 ## comments heading with count
501 ## comments heading with count
502 <div class="comments-heading">
502 <div class="comments-heading">
503 <i class="icon-comment"></i>
503 <i class="icon-comment"></i>
504 ${_('General Comments')} ${len(c.comments)}
504 ${_('General Comments')} ${len(c.comments)}
505 </div>
505 </div>
506
506
507 ## render general comments
507 ## render general comments
508 <div id="comment-tr-show">
508 <div id="comment-tr-show">
509 % if general_outdated_comm_count_ver:
509 % if general_outdated_comm_count_ver:
510 <div class="info-box">
510 <div class="info-box">
511 % if general_outdated_comm_count_ver == 1:
511 % if general_outdated_comm_count_ver == 1:
512 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
512 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
513 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
513 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
514 % else:
514 % else:
515 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
515 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
516 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
516 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
517 % endif
517 % endif
518 </div>
518 </div>
519 % endif
519 % endif
520 </div>
520 </div>
521
521
522 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
522 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
523
523
524 % if not c.pull_request.is_closed():
524 % if not c.pull_request.is_closed():
525 ## main comment form and it status
525 ## main comment form and it status
526 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
526 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
527 pull_request_id=c.pull_request.pull_request_id),
527 pull_request_id=c.pull_request.pull_request_id),
528 c.pull_request_review_status,
528 c.pull_request_review_status,
529 is_pull_request=True, change_status=c.allowed_to_change_status)}
529 is_pull_request=True, change_status=c.allowed_to_change_status)}
530
530
531 ## merge status, and merge action
531 ## merge status, and merge action
532 <div class="pull-request-merge">
532 <div class="pull-request-merge">
533 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
533 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
534 </div>
534 </div>
535
535
536 %endif
536 %endif
537
537
538 % endif
538 % endif
539 </div>
539 </div>
540
540
541
541
542 ### NAV SIDEBAR
542 ### NAV SIDEBAR
543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
544 <div class="sidenav navbar__inner" >
544 <div class="sidenav navbar__inner" >
545 ## TOGGLE
545 ## TOGGLE
546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
547 <a href="#toggleSidebar" class="grey-link-action">
547 <a href="#toggleSidebar" class="grey-link-action">
548
548
549 </a>
549 </a>
550 </div>
550 </div>
551
551
552 ## CONTENT
552 ## CONTENT
553 <div class="sidebar-content">
553 <div class="sidebar-content">
554
554
555 ## RULES SUMMARY/RULES
555 ## RULES SUMMARY/RULES
556 <div class="sidebar-element clear-both">
556 <div class="sidebar-element clear-both">
557 <% vote_title = _ungettext(
557 <% vote_title = _ungettext(
558 'Status calculated based on votes from {} reviewer',
558 'Status calculated based on votes from {} reviewer',
559 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
559 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
560 %>
560 %>
561
561
562 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
562 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
563 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
563 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
564 ${c.reviewers_count}
564 ${c.reviewers_count}
565 </div>
565 </div>
566
566
567 ## REVIEW RULES
567 ## REVIEW RULES
568 <div id="review_rules" style="display: none" class="">
568 <div id="review_rules" style="display: none" class="">
569 <div class="right-sidebar-expanded-state pr-details-title">
569 <div class="right-sidebar-expanded-state pr-details-title">
570 <span class="sidebar-heading">
570 <span class="sidebar-heading">
571 ${_('Reviewer rules')}
571 ${_('Reviewer rules')}
572 </span>
572 </span>
573
573
574 </div>
574 </div>
575 <div class="pr-reviewer-rules">
575 <div class="pr-reviewer-rules">
576 ## review rules will be appended here, by default reviewers logic
576 ## review rules will be appended here, by default reviewers logic
577 </div>
577 </div>
578 <input id="review_data" type="hidden" name="review_data" value="">
578 <input id="review_data" type="hidden" name="review_data" value="">
579 </div>
579 </div>
580
580
581 ## REVIEWERS
581 ## REVIEWERS
582 <div class="right-sidebar-expanded-state pr-details-title">
582 <div class="right-sidebar-expanded-state pr-details-title">
583 <span class="tooltip sidebar-heading" title="${vote_title}">
583 <span class="tooltip sidebar-heading" title="${vote_title}">
584 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
584 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
585 ${_('Reviewers')}
585 ${_('Reviewers')}
586 </span>
586 </span>
587 %if c.allowed_to_update:
587 %if c.allowed_to_update:
588 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
588 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
589 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
589 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
590 %else:
590 %else:
591 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
591 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
592 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
592 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
593 %endif
593 %endif
594 </div>
594 </div>
595
595
596 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
596 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
597
597
598 ## members redering block
598 ## members redering block
599 <input type="hidden" name="__start__" value="review_members:sequence">
599 <input type="hidden" name="__start__" value="review_members:sequence">
600
600
601 <table id="review_members" class="group_members">
601 <table id="review_members" class="group_members">
602 ## This content is loaded via JS and ReviewersPanel
602 ## This content is loaded via JS and ReviewersPanel
603 </table>
603 </table>
604
604
605 <input type="hidden" name="__end__" value="review_members:sequence">
605 <input type="hidden" name="__end__" value="review_members:sequence">
606 ## end members redering block
606 ## end members redering block
607
607
608 %if not c.pull_request.is_closed():
608 %if not c.pull_request.is_closed():
609 <div id="add_reviewer" class="ac" style="display: none;">
609 <div id="add_reviewer" class="ac" style="display: none;">
610 %if c.allowed_to_update:
610 %if c.allowed_to_update:
611 % if not c.forbid_adding_reviewers:
611 % if not c.forbid_adding_reviewers:
612 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
612 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
613 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
613 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
614 <div id="reviewers_container"></div>
614 <div id="reviewers_container"></div>
615 </div>
615 </div>
616 % endif
616 % endif
617 <div class="pull-right" style="margin-bottom: 15px">
617 <div class="pull-right" style="margin-bottom: 15px">
618 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
618 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
619 </div>
619 </div>
620 %endif
620 %endif
621 </div>
621 </div>
622 %endif
622 %endif
623 </div>
623 </div>
624 </div>
624 </div>
625
625
626 ## OBSERVERS
626 ## OBSERVERS
627 % if c.rhodecode_edition_id == 'EE':
627 <div class="sidebar-element clear-both">
628 <div class="sidebar-element clear-both">
628 <% vote_title = _ungettext(
629 <% vote_title = _ungettext(
629 '{} observer without voting right.',
630 '{} observer without voting right.',
630 '{} observers without voting right.', c.observers_count).format(c.observers_count)
631 '{} observers without voting right.', c.observers_count).format(c.observers_count)
631 %>
632 %>
632
633
633 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
634 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
634 <i class="icon-circle-thin"></i>
635 <i class="icon-circle-thin"></i>
635 ${c.observers_count}
636 ${c.observers_count}
636 </div>
637 </div>
637
638
638 <div class="right-sidebar-expanded-state pr-details-title">
639 <div class="right-sidebar-expanded-state pr-details-title">
639 <span class="tooltip sidebar-heading" title="${vote_title}">
640 <span class="tooltip sidebar-heading" title="${vote_title}">
640 <i class="icon-circle-thin"></i>
641 <i class="icon-circle-thin"></i>
641 ${_('Observers')}
642 ${_('Observers')}
642 </span>
643 </span>
643 %if c.allowed_to_update:
644 %if c.allowed_to_update:
644 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
645 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
645 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
646 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
646 %endif
647 %endif
647 </div>
648 </div>
648
649
649 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
650 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
650 ## members redering block
651 ## members redering block
651 <input type="hidden" name="__start__" value="observer_members:sequence">
652 <input type="hidden" name="__start__" value="observer_members:sequence">
652
653
653 <table id="observer_members" class="group_members">
654 <table id="observer_members" class="group_members">
654 ## This content is loaded via JS and ReviewersPanel
655 ## This content is loaded via JS and ReviewersPanel
655 </table>
656 </table>
656
657
657 <input type="hidden" name="__end__" value="observer_members:sequence">
658 <input type="hidden" name="__end__" value="observer_members:sequence">
658 ## end members redering block
659 ## end members redering block
659
660
660 %if not c.pull_request.is_closed():
661 %if not c.pull_request.is_closed():
661 <div id="add_observer" class="ac" style="display: none;">
662 <div id="add_observer" class="ac" style="display: none;">
662 %if c.allowed_to_update:
663 %if c.allowed_to_update:
663 % if not c.forbid_adding_reviewers or 1:
664 % if not c.forbid_adding_reviewers or 1:
664 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
665 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
665 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
666 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
666 <div id="observers_container"></div>
667 <div id="observers_container"></div>
667 </div>
668 </div>
668 % endif
669 % endif
669 <div class="pull-right" style="margin-bottom: 15px">
670 <div class="pull-right" style="margin-bottom: 15px">
670 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
671 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
671 </div>
672 </div>
672 %endif
673 %endif
673 </div>
674 </div>
674 %endif
675 %endif
675 </div>
676 </div>
676 </div>
677 </div>
678 % endif
677
679
678 ## TODOs
680 ## TODOs
679 <div class="sidebar-element clear-both">
681 <div class="sidebar-element clear-both">
680 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
682 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
681 <i class="icon-flag-filled"></i>
683 <i class="icon-flag-filled"></i>
682 <span id="todos-count">${len(c.unresolved_comments)}</span>
684 <span id="todos-count">${len(c.unresolved_comments)}</span>
683 </div>
685 </div>
684
686
685 <div class="right-sidebar-expanded-state pr-details-title">
687 <div class="right-sidebar-expanded-state pr-details-title">
686 ## Only show unresolved, that is only what matters
688 ## Only show unresolved, that is only what matters
687 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
689 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
688 <i class="icon-flag-filled"></i>
690 <i class="icon-flag-filled"></i>
689 TODOs
691 TODOs
690 </span>
692 </span>
691
693
692 % if not c.at_version:
694 % if not c.at_version:
693 % if c.resolved_comments:
695 % if c.resolved_comments:
694 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
696 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
695 % else:
697 % else:
696 <span class="block-right last-item noselect">Show resolved</span>
698 <span class="block-right last-item noselect">Show resolved</span>
697 % endif
699 % endif
698 % endif
700 % endif
699 </div>
701 </div>
700
702
701 <div class="right-sidebar-expanded-state pr-details-content">
703 <div class="right-sidebar-expanded-state pr-details-content">
702
704
703 % if c.at_version:
705 % if c.at_version:
704 <table>
706 <table>
705 <tr>
707 <tr>
706 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
708 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
707 </tr>
709 </tr>
708 </table>
710 </table>
709 % else:
711 % else:
710 % if c.unresolved_comments + c.resolved_comments:
712 % if c.unresolved_comments + c.resolved_comments:
711 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
713 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
712 % else:
714 % else:
713 <table>
715 <table>
714 <tr>
716 <tr>
715 <td>
717 <td>
716 ${_('No TODOs yet')}
718 ${_('No TODOs yet')}
717 </td>
719 </td>
718 </tr>
720 </tr>
719 </table>
721 </table>
720 % endif
722 % endif
721 % endif
723 % endif
722 </div>
724 </div>
723 </div>
725 </div>
724
726
725 ## COMMENTS
727 ## COMMENTS
726 <div class="sidebar-element clear-both">
728 <div class="sidebar-element clear-both">
727 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
729 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
728 <i class="icon-comment" style="color: #949494"></i>
730 <i class="icon-comment" style="color: #949494"></i>
729 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
731 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
730 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
732 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
731 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
733 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
732 </div>
734 </div>
733
735
734 <div class="right-sidebar-expanded-state pr-details-title">
736 <div class="right-sidebar-expanded-state pr-details-title">
735 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
737 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
736 <i class="icon-comment" style="color: #949494"></i>
738 <i class="icon-comment" style="color: #949494"></i>
737 ${_('Comments')}
739 ${_('Comments')}
738
740
739 ## % if outdated_comm_count_ver:
741 ## % if outdated_comm_count_ver:
740 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
742 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
741 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
743 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
742 ## </a>
744 ## </a>
743 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
745 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
744 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
746 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
745
747
746 ## % else:
748 ## % else:
747 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
749 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
748 ## % endif
750 ## % endif
749
751
750 </span>
752 </span>
751
753
752 % if outdated_comm_count_ver:
754 % if outdated_comm_count_ver:
753 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
755 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
754 % else:
756 % else:
755 <span class="block-right last-item noselect">Show hidden</span>
757 <span class="block-right last-item noselect">Show hidden</span>
756 % endif
758 % endif
757
759
758 </div>
760 </div>
759
761
760 <div class="right-sidebar-expanded-state pr-details-content">
762 <div class="right-sidebar-expanded-state pr-details-content">
761 % if c.inline_comments_flat + c.comments:
763 % if c.inline_comments_flat + c.comments:
762 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
764 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
763 % else:
765 % else:
764 <table>
766 <table>
765 <tr>
767 <tr>
766 <td>
768 <td>
767 ${_('No Comments yet')}
769 ${_('No Comments yet')}
768 </td>
770 </td>
769 </tr>
771 </tr>
770 </table>
772 </table>
771 % endif
773 % endif
772 </div>
774 </div>
773
775
774 </div>
776 </div>
775
777
776 ## Referenced Tickets
778 ## Referenced Tickets
777 <div class="sidebar-element clear-both">
779 <div class="sidebar-element clear-both">
778 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
780 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
779 <i class="icon-info-circled"></i>
781 <i class="icon-info-circled"></i>
780 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
782 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
781 </div>
783 </div>
782
784
783 <div class="right-sidebar-expanded-state pr-details-title">
785 <div class="right-sidebar-expanded-state pr-details-title">
784 <span class="sidebar-heading">
786 <span class="sidebar-heading">
785 <i class="icon-info-circled"></i>
787 <i class="icon-info-circled"></i>
786 ${_('Referenced Tickets')}
788 ${_('Referenced Tickets')}
787 </span>
789 </span>
788 </div>
790 </div>
789 <div class="right-sidebar-expanded-state pr-details-content">
791 <div class="right-sidebar-expanded-state pr-details-content">
790 <table>
792 <table>
791
793
792 <tr><td><code>${_('In pull request description')}:</code></td></tr>
794 <tr><td><code>${_('In pull request description')}:</code></td></tr>
793 % if c.referenced_desc_issues:
795 % if c.referenced_desc_issues:
794 % for ticket_dict in sorted(c.referenced_desc_issues):
796 % for ticket_dict in sorted(c.referenced_desc_issues):
795 <tr>
797 <tr>
796 <td>
798 <td>
797 <a href="${ticket_dict.get('url')}">
799 <a href="${ticket_dict.get('url')}">
798 ${ticket_dict.get('id')}
800 ${ticket_dict.get('id')}
799 </a>
801 </a>
800 </td>
802 </td>
801 </tr>
803 </tr>
802 % endfor
804 % endfor
803 % else:
805 % else:
804 <tr>
806 <tr>
805 <td>
807 <td>
806 ${_('No Ticket data found.')}
808 ${_('No Ticket data found.')}
807 </td>
809 </td>
808 </tr>
810 </tr>
809 % endif
811 % endif
810
812
811 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
813 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
812 % if c.referenced_commit_issues:
814 % if c.referenced_commit_issues:
813 % for ticket_dict in sorted(c.referenced_commit_issues):
815 % for ticket_dict in sorted(c.referenced_commit_issues):
814 <tr>
816 <tr>
815 <td>
817 <td>
816 <a href="${ticket_dict.get('url')}">
818 <a href="${ticket_dict.get('url')}">
817 ${ticket_dict.get('id')}
819 ${ticket_dict.get('id')}
818 </a>
820 </a>
819 </td>
821 </td>
820 </tr>
822 </tr>
821 % endfor
823 % endfor
822 % else:
824 % else:
823 <tr>
825 <tr>
824 <td>
826 <td>
825 ${_('No Ticket data found.')}
827 ${_('No Ticket data found.')}
826 </td>
828 </td>
827 </tr>
829 </tr>
828 % endif
830 % endif
829 </table>
831 </table>
830
832
831 </div>
833 </div>
832 </div>
834 </div>
833
835
834 </div>
836 </div>
835
837
836 </div>
838 </div>
837 </aside>
839 </aside>
838
840
839 ## This JS needs to be at the end
841 ## This JS needs to be at the end
840 <script type="text/javascript">
842 <script type="text/javascript">
841
843
842 versionController = new VersionController();
844 versionController = new VersionController();
843 versionController.init();
845 versionController.init();
844
846
845 reviewersController = new ReviewersController();
847 reviewersController = new ReviewersController();
846 commitsController = new CommitsController();
848 commitsController = new CommitsController();
847
849
848 updateController = new UpdatePrController();
850 updateController = new UpdatePrController();
849
851
850 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
852 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
851 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
853 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
852 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
854 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
853
855
854 (function () {
856 (function () {
855 "use strict";
857 "use strict";
856
858
857 // custom code mirror
859 // custom code mirror
858 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
860 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
859
861
860 PRDetails.init();
862 PRDetails.init();
861 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
863 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
862 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
864 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
863
865
864 window.showOutdated = function (self) {
866 window.showOutdated = function (self) {
865 $('.comment-inline.comment-outdated').show();
867 $('.comment-inline.comment-outdated').show();
866 $('.filediff-outdated').show();
868 $('.filediff-outdated').show();
867 $('.showOutdatedComments').hide();
869 $('.showOutdatedComments').hide();
868 $('.hideOutdatedComments').show();
870 $('.hideOutdatedComments').show();
869 };
871 };
870
872
871 window.hideOutdated = function (self) {
873 window.hideOutdated = function (self) {
872 $('.comment-inline.comment-outdated').hide();
874 $('.comment-inline.comment-outdated').hide();
873 $('.filediff-outdated').hide();
875 $('.filediff-outdated').hide();
874 $('.hideOutdatedComments').hide();
876 $('.hideOutdatedComments').hide();
875 $('.showOutdatedComments').show();
877 $('.showOutdatedComments').show();
876 };
878 };
877
879
878 window.refreshMergeChecks = function () {
880 window.refreshMergeChecks = function () {
879 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
881 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
880 $('.pull-request-merge').css('opacity', 0.3);
882 $('.pull-request-merge').css('opacity', 0.3);
881 $('.action-buttons-extra').css('opacity', 0.3);
883 $('.action-buttons-extra').css('opacity', 0.3);
882
884
883 $('.pull-request-merge').load(
885 $('.pull-request-merge').load(
884 loadUrl, function () {
886 loadUrl, function () {
885 $('.pull-request-merge').css('opacity', 1);
887 $('.pull-request-merge').css('opacity', 1);
886
888
887 $('.action-buttons-extra').css('opacity', 1);
889 $('.action-buttons-extra').css('opacity', 1);
888 }
890 }
889 );
891 );
890 };
892 };
891
893
892 window.closePullRequest = function (status) {
894 window.closePullRequest = function (status) {
893 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
895 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
894 return false;
896 return false;
895 }
897 }
896 // inject closing flag
898 // inject closing flag
897 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
899 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
898 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
900 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
899 $(generalCommentForm.submitForm).submit();
901 $(generalCommentForm.submitForm).submit();
900 };
902 };
901
903
902 //TODO this functionality is now missing
904 //TODO this functionality is now missing
903 $('#show-outdated-comments').on('click', function (e) {
905 $('#show-outdated-comments').on('click', function (e) {
904 var button = $(this);
906 var button = $(this);
905 var outdated = $('.comment-outdated');
907 var outdated = $('.comment-outdated');
906
908
907 if (button.html() === "(Show)") {
909 if (button.html() === "(Show)") {
908 button.html("(Hide)");
910 button.html("(Hide)");
909 outdated.show();
911 outdated.show();
910 } else {
912 } else {
911 button.html("(Show)");
913 button.html("(Show)");
912 outdated.hide();
914 outdated.hide();
913 }
915 }
914 });
916 });
915
917
916 $('#merge_pull_request_form').submit(function () {
918 $('#merge_pull_request_form').submit(function () {
917 if (!$('#merge_pull_request').attr('disabled')) {
919 if (!$('#merge_pull_request').attr('disabled')) {
918 $('#merge_pull_request').attr('disabled', 'disabled');
920 $('#merge_pull_request').attr('disabled', 'disabled');
919 }
921 }
920 return true;
922 return true;
921 });
923 });
922
924
923 $('#edit_pull_request').on('click', function (e) {
925 $('#edit_pull_request').on('click', function (e) {
924 var title = $('#pr-title-input').val();
926 var title = $('#pr-title-input').val();
925 var description = codeMirrorInstance.getValue();
927 var description = codeMirrorInstance.getValue();
926 var renderer = $('#pr-renderer-input').val();
928 var renderer = $('#pr-renderer-input').val();
927 editPullRequest(
929 editPullRequest(
928 "${c.repo_name}", "${c.pull_request.pull_request_id}",
930 "${c.repo_name}", "${c.pull_request.pull_request_id}",
929 title, description, renderer);
931 title, description, renderer);
930 });
932 });
931
933
932 var $updateButtons = $('#update_reviewers,#update_observers');
934 var $updateButtons = $('#update_reviewers,#update_observers');
933 $updateButtons.on('click', function (e) {
935 $updateButtons.on('click', function (e) {
934 var role = $(this).data('role');
936 var role = $(this).data('role');
935 $updateButtons.attr('disabled', 'disabled');
937 $updateButtons.attr('disabled', 'disabled');
936 $updateButtons.addClass('disabled');
938 $updateButtons.addClass('disabled');
937 $updateButtons.html(_gettext('Saving...'));
939 $updateButtons.html(_gettext('Saving...'));
938 reviewersController.updateReviewers(
940 reviewersController.updateReviewers(
939 templateContext.repo_name,
941 templateContext.repo_name,
940 templateContext.pull_request_data.pull_request_id,
942 templateContext.pull_request_data.pull_request_id,
941 role
943 role
942 );
944 );
943 });
945 });
944
946
945 // fixing issue with caches on firefox
947 // fixing issue with caches on firefox
946 $('#update_commits').removeAttr("disabled");
948 $('#update_commits').removeAttr("disabled");
947
949
948 $('.show-inline-comments').on('click', function (e) {
950 $('.show-inline-comments').on('click', function (e) {
949 var boxid = $(this).attr('data-comment-id');
951 var boxid = $(this).attr('data-comment-id');
950 var button = $(this);
952 var button = $(this);
951
953
952 if (button.hasClass("comments-visible")) {
954 if (button.hasClass("comments-visible")) {
953 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
955 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
954 $(this).hide();
956 $(this).hide();
955 });
957 });
956 button.removeClass("comments-visible");
958 button.removeClass("comments-visible");
957 } else {
959 } else {
958 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
960 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
959 $(this).show();
961 $(this).show();
960 });
962 });
961 button.addClass("comments-visible");
963 button.addClass("comments-visible");
962 }
964 }
963 });
965 });
964
966
965 $('.show-inline-comments').on('change', function (e) {
967 $('.show-inline-comments').on('change', function (e) {
966 var show = 'none';
968 var show = 'none';
967 var target = e.currentTarget;
969 var target = e.currentTarget;
968 if (target.checked) {
970 if (target.checked) {
969 show = ''
971 show = ''
970 }
972 }
971 var boxid = $(target).attr('id_for');
973 var boxid = $(target).attr('id_for');
972 var comments = $('#{0} .inline-comments'.format(boxid));
974 var comments = $('#{0} .inline-comments'.format(boxid));
973 var fn_display = function (idx) {
975 var fn_display = function (idx) {
974 $(this).css('display', show);
976 $(this).css('display', show);
975 };
977 };
976 $(comments).each(fn_display);
978 $(comments).each(fn_display);
977 var btns = $('#{0} .inline-comments-button'.format(boxid));
979 var btns = $('#{0} .inline-comments-button'.format(boxid));
978 $(btns).each(fn_display);
980 $(btns).each(fn_display);
979 });
981 });
980
982
981 // register submit callback on commentForm form to track TODOs
983 // register submit callback on commentForm form to track TODOs
982 window.commentFormGlobalSubmitSuccessCallback = function () {
984 window.commentFormGlobalSubmitSuccessCallback = function () {
983 refreshMergeChecks();
985 refreshMergeChecks();
984 };
986 };
985
987
986 ReviewerAutoComplete('#user', reviewersController);
988 ReviewerAutoComplete('#user', reviewersController);
987 ObserverAutoComplete('#observer', reviewersController);
989 ObserverAutoComplete('#observer', reviewersController);
988
990
989 })();
991 })();
990
992
991 $(document).ready(function () {
993 $(document).ready(function () {
992
994
993 var channel = '${c.pr_broadcast_channel}';
995 var channel = '${c.pr_broadcast_channel}';
994 new ReviewerPresenceController(channel)
996 new ReviewerPresenceController(channel)
995
997
996 })
998 })
997 </script>
999 </script>
998
1000
999 </%def>
1001 </%def>
General Comments 0
You need to be logged in to leave comments. Login now