##// END OF EJS Templates
emails: added reply link to comment type emails...
dan -
r4050:b3fe0fcc default
parent child Browse files
Show More
@@ -1,715 +1,719 b''
1
1
2
2
3 ################################################################################
3 ################################################################################
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 ################################################################################
5 ################################################################################
6
6
7 [DEFAULT]
7 [DEFAULT]
8 ## Debug flag sets all loggers to debug, and enables request tracking
8 ## Debug flag sets all loggers to debug, and enables request tracking
9 debug = false
9 debug = false
10
10
11 ################################################################################
11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 ## EMAIL CONFIGURATION ##
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
19 #email_prefix = [RhodeCode]
20
20
21 ## email FROM address all mails will be sent
21 ## email FROM address all mails will be sent
22 #app_email_from = rhodecode-noreply@localhost
22 #app_email_from = rhodecode-noreply@localhost
23
23
24 #smtp_server = mail.server.com
24 #smtp_server = mail.server.com
25 #smtp_username =
25 #smtp_username =
26 #smtp_password =
26 #smtp_password =
27 #smtp_port =
27 #smtp_port =
28 #smtp_use_tls = false
28 #smtp_use_tls = false
29 #smtp_use_ssl = true
29 #smtp_use_ssl = true
30
30
31 [server:main]
31 [server:main]
32 ## COMMON ##
32 ## COMMON ##
33 host = 127.0.0.1
33 host = 127.0.0.1
34 port = 5000
34 port = 5000
35
35
36 ###########################################################
36 ###########################################################
37 ## WAITRESS WSGI SERVER - Recommended for Development ####
37 ## WAITRESS WSGI SERVER - Recommended for Development ####
38 ###########################################################
38 ###########################################################
39
39
40 #use = egg:waitress#main
40 #use = egg:waitress#main
41 ## number of worker threads
41 ## number of worker threads
42 #threads = 5
42 #threads = 5
43 ## MAX BODY SIZE 100GB
43 ## MAX BODY SIZE 100GB
44 #max_request_body_size = 107374182400
44 #max_request_body_size = 107374182400
45 ## Use poll instead of select, fixes file descriptors limits problems.
45 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## May not work on old windows systems.
46 ## May not work on old windows systems.
47 #asyncore_use_poll = true
47 #asyncore_use_poll = true
48
48
49
49
50 ##########################
50 ##########################
51 ## GUNICORN WSGI SERVER ##
51 ## GUNICORN WSGI SERVER ##
52 ##########################
52 ##########################
53 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
53 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
54
54
55 use = egg:gunicorn#main
55 use = egg:gunicorn#main
56 ## Sets the number of process workers. More workers means more concurrent connections
56 ## Sets the number of process workers. More workers means more concurrent connections
57 ## RhodeCode can handle at the same time. Each additional worker also it increases
57 ## RhodeCode can handle at the same time. Each additional worker also it increases
58 ## memory usage as each has it's own set of caches.
58 ## memory usage as each has it's own set of caches.
59 ## Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
59 ## Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
60 ## than 8-10 unless for really big deployments .e.g 700-1000 users.
60 ## than 8-10 unless for really big deployments .e.g 700-1000 users.
61 ## `instance_id = *` must be set in the [app:main] section below (which is the default)
61 ## `instance_id = *` must be set in the [app:main] section below (which is the default)
62 ## when using more than 1 worker.
62 ## when using more than 1 worker.
63 workers = 2
63 workers = 2
64 ## process name visible in process list
64 ## process name visible in process list
65 proc_name = rhodecode
65 proc_name = rhodecode
66 ## type of worker class, one of sync, gevent
66 ## type of worker class, one of sync, gevent
67 ## recommended for bigger setup is using of of other than sync one
67 ## recommended for bigger setup is using of of other than sync one
68 worker_class = gevent
68 worker_class = gevent
69 ## The maximum number of simultaneous clients. Valid only for Gevent
69 ## The maximum number of simultaneous clients. Valid only for Gevent
70 worker_connections = 10
70 worker_connections = 10
71 ## max number of requests that worker will handle before being gracefully
71 ## max number of requests that worker will handle before being gracefully
72 ## restarted, could prevent memory leaks
72 ## restarted, could prevent memory leaks
73 max_requests = 1000
73 max_requests = 1000
74 max_requests_jitter = 30
74 max_requests_jitter = 30
75 ## amount of time a worker can spend with handling a request before it
75 ## amount of time a worker can spend with handling a request before it
76 ## gets killed and restarted. Set to 6hrs
76 ## gets killed and restarted. Set to 6hrs
77 timeout = 21600
77 timeout = 21600
78
78
79
79
80 ## prefix middleware for RhodeCode.
80 ## prefix middleware for RhodeCode.
81 ## recommended when using proxy setup.
81 ## recommended when using proxy setup.
82 ## allows to set RhodeCode under a prefix in server.
82 ## allows to set RhodeCode under a prefix in server.
83 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
83 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
84 ## And set your prefix like: `prefix = /custom_prefix`
84 ## And set your prefix like: `prefix = /custom_prefix`
85 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
85 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
86 ## to make your cookies only work on prefix url
86 ## to make your cookies only work on prefix url
87 [filter:proxy-prefix]
87 [filter:proxy-prefix]
88 use = egg:PasteDeploy#prefix
88 use = egg:PasteDeploy#prefix
89 prefix = /
89 prefix = /
90
90
91 [app:main]
91 [app:main]
92 ## The %(here)s variable will be replaced with the absolute path of parent directory
92 ## The %(here)s variable will be replaced with the absolute path of parent directory
93 ## of this file
93 ## of this file
94 ## In addition ENVIRONMENT variables usage is possible, e.g
94 ## In addition ENVIRONMENT variables usage is possible, e.g
95 ## sqlalchemy.db1.url = {ENV_RC_DB_URL}
95 ## sqlalchemy.db1.url = {ENV_RC_DB_URL}
96
96
97 use = egg:rhodecode-enterprise-ce
97 use = egg:rhodecode-enterprise-ce
98
98
99 ## enable proxy prefix middleware, defined above
99 ## enable proxy prefix middleware, defined above
100 #filter-with = proxy-prefix
100 #filter-with = proxy-prefix
101
101
102 ## encryption key used to encrypt social plugin tokens,
102 ## encryption key used to encrypt social plugin tokens,
103 ## remote_urls with credentials etc, if not set it defaults to
103 ## remote_urls with credentials etc, if not set it defaults to
104 ## `beaker.session.secret`
104 ## `beaker.session.secret`
105 #rhodecode.encrypted_values.secret =
105 #rhodecode.encrypted_values.secret =
106
106
107 ## decryption strict mode (enabled by default). It controls if decryption raises
107 ## decryption strict mode (enabled by default). It controls if decryption raises
108 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
108 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
109 #rhodecode.encrypted_values.strict = false
109 #rhodecode.encrypted_values.strict = false
110
110
111 ## Pick algorithm for encryption. Either fernet (more secure) or aes (default)
111 ## Pick algorithm for encryption. Either fernet (more secure) or aes (default)
112 ## fernet is safer, and we strongly recommend switching to it.
112 ## fernet is safer, and we strongly recommend switching to it.
113 ## Due to backward compatibility aes is used as default.
113 ## Due to backward compatibility aes is used as default.
114 #rhodecode.encrypted_values.algorithm = fernet
114 #rhodecode.encrypted_values.algorithm = fernet
115
115
116 ## return gzipped responses from RhodeCode (static files/application)
116 ## return gzipped responses from RhodeCode (static files/application)
117 gzip_responses = false
117 gzip_responses = false
118
118
119 ## auto-generate javascript routes file on startup
119 ## auto-generate javascript routes file on startup
120 generate_js_files = false
120 generate_js_files = false
121
121
122 ## System global default language.
122 ## System global default language.
123 ## All available languages: en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
123 ## All available languages: en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
124 lang = en
124 lang = en
125
125
126 ## Perform a full repository scan and import on each server start.
126 ## Perform a full repository scan and import on each server start.
127 ## Settings this to true could lead to very long startup time.
127 ## Settings this to true could lead to very long startup time.
128 startup.import_repos = false
128 startup.import_repos = false
129
129
130 ## Uncomment and set this path to use archive download cache.
130 ## Uncomment and set this path to use archive download cache.
131 ## Once enabled, generated archives will be cached at this location
131 ## Once enabled, generated archives will be cached at this location
132 ## and served from the cache during subsequent requests for the same archive of
132 ## and served from the cache during subsequent requests for the same archive of
133 ## the repository.
133 ## the repository.
134 #archive_cache_dir = /tmp/tarballcache
134 #archive_cache_dir = /tmp/tarballcache
135
135
136 ## URL at which the application is running. This is used for Bootstrapping
136 ## URL at which the application is running. This is used for Bootstrapping
137 ## requests in context when no web request is available. Used in ishell, or
137 ## requests in context when no web request is available. Used in ishell, or
138 ## SSH calls. Set this for events to receive proper url for SSH calls.
138 ## SSH calls. Set this for events to receive proper url for SSH calls.
139 app.base_url = http://rhodecode.local
139 app.base_url = http://rhodecode.local
140
140
141 ## Unique application ID. Should be a random unique string for security.
141 ## Unique application ID. Should be a random unique string for security.
142 app_instance_uuid = rc-production
142 app_instance_uuid = rc-production
143
143
144 ## Cut off limit for large diffs (size in bytes). If overall diff size on
144 ## Cut off limit for large diffs (size in bytes). If overall diff size on
145 ## commit, or pull request exceeds this limit this diff will be displayed
145 ## commit, or pull request exceeds this limit this diff will be displayed
146 ## partially. E.g 512000 == 512Kb
146 ## partially. E.g 512000 == 512Kb
147 cut_off_limit_diff = 512000
147 cut_off_limit_diff = 512000
148
148
149 ## Cut off limit for large files inside diffs (size in bytes). Each individual
149 ## Cut off limit for large files inside diffs (size in bytes). Each individual
150 ## file inside diff which exceeds this limit will be displayed partially.
150 ## file inside diff which exceeds this limit will be displayed partially.
151 ## E.g 128000 == 128Kb
151 ## E.g 128000 == 128Kb
152 cut_off_limit_file = 128000
152 cut_off_limit_file = 128000
153
153
154 ## use cached version of vcs repositories everywhere. Recommended to be `true`
154 ## use cached version of vcs repositories everywhere. Recommended to be `true`
155 vcs_full_cache = true
155 vcs_full_cache = true
156
156
157 ## Force https in RhodeCode, fixes https redirects, assumes it's always https.
157 ## Force https in RhodeCode, fixes https redirects, assumes it's always https.
158 ## Normally this is controlled by proper http flags sent from http server
158 ## Normally this is controlled by proper http flags sent from http server
159 force_https = false
159 force_https = false
160
160
161 ## use Strict-Transport-Security headers
161 ## use Strict-Transport-Security headers
162 use_htsts = false
162 use_htsts = false
163
163
164 # Set to true if your repos are exposed using the dumb protocol
164 # Set to true if your repos are exposed using the dumb protocol
165 git_update_server_info = false
165 git_update_server_info = false
166
166
167 ## RSS/ATOM feed options
167 ## RSS/ATOM feed options
168 rss_cut_off_limit = 256000
168 rss_cut_off_limit = 256000
169 rss_items_per_page = 10
169 rss_items_per_page = 10
170 rss_include_diff = false
170 rss_include_diff = false
171
171
172 ## gist URL alias, used to create nicer urls for gist. This should be an
172 ## gist URL alias, used to create nicer urls for gist. This should be an
173 ## url that does rewrites to _admin/gists/{gistid}.
173 ## url that does rewrites to _admin/gists/{gistid}.
174 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
174 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
175 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
175 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
176 gist_alias_url =
176 gist_alias_url =
177
177
178 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
178 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
179 ## used for access.
179 ## used for access.
180 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
180 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
181 ## came from the the logged in user who own this authentication token.
181 ## came from the the logged in user who own this authentication token.
182 ## Additionally @TOKEN syntax can be used to bound the view to specific
182 ## Additionally @TOKEN syntax can be used to bound the view to specific
183 ## authentication token. Such view would be only accessible when used together
183 ## authentication token. Such view would be only accessible when used together
184 ## with this authentication token
184 ## with this authentication token
185 ##
185 ##
186 ## list of all views can be found under `/_admin/permissions/auth_token_access`
186 ## list of all views can be found under `/_admin/permissions/auth_token_access`
187 ## The list should be "," separated and on a single line.
187 ## The list should be "," separated and on a single line.
188 ##
188 ##
189 ## Most common views to enable:
189 ## Most common views to enable:
190 # RepoCommitsView:repo_commit_download
190 # RepoCommitsView:repo_commit_download
191 # RepoCommitsView:repo_commit_patch
191 # RepoCommitsView:repo_commit_patch
192 # RepoCommitsView:repo_commit_raw
192 # RepoCommitsView:repo_commit_raw
193 # RepoCommitsView:repo_commit_raw@TOKEN
193 # RepoCommitsView:repo_commit_raw@TOKEN
194 # RepoFilesView:repo_files_diff
194 # RepoFilesView:repo_files_diff
195 # RepoFilesView:repo_archivefile
195 # RepoFilesView:repo_archivefile
196 # RepoFilesView:repo_file_raw
196 # RepoFilesView:repo_file_raw
197 # GistView:*
197 # GistView:*
198 api_access_controllers_whitelist =
198 api_access_controllers_whitelist =
199
199
200 ## Default encoding used to convert from and to unicode
200 ## Default encoding used to convert from and to unicode
201 ## can be also a comma separated list of encoding in case of mixed encodings
201 ## can be also a comma separated list of encoding in case of mixed encodings
202 default_encoding = UTF-8
202 default_encoding = UTF-8
203
203
204 ## instance-id prefix
204 ## instance-id prefix
205 ## a prefix key for this instance used for cache invalidation when running
205 ## a prefix key for this instance used for cache invalidation when running
206 ## multiple instances of RhodeCode, make sure it's globally unique for
206 ## multiple instances of RhodeCode, make sure it's globally unique for
207 ## all running RhodeCode instances. Leave empty if you don't use it
207 ## all running RhodeCode instances. Leave empty if you don't use it
208 instance_id =
208 instance_id =
209
209
210 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
210 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
211 ## of an authentication plugin also if it is disabled by it's settings.
211 ## of an authentication plugin also if it is disabled by it's settings.
212 ## This could be useful if you are unable to log in to the system due to broken
212 ## This could be useful if you are unable to log in to the system due to broken
213 ## authentication settings. Then you can enable e.g. the internal RhodeCode auth
213 ## authentication settings. Then you can enable e.g. the internal RhodeCode auth
214 ## module to log in again and fix the settings.
214 ## module to log in again and fix the settings.
215 ##
215 ##
216 ## Available builtin plugin IDs (hash is part of the ID):
216 ## Available builtin plugin IDs (hash is part of the ID):
217 ## egg:rhodecode-enterprise-ce#rhodecode
217 ## egg:rhodecode-enterprise-ce#rhodecode
218 ## egg:rhodecode-enterprise-ce#pam
218 ## egg:rhodecode-enterprise-ce#pam
219 ## egg:rhodecode-enterprise-ce#ldap
219 ## egg:rhodecode-enterprise-ce#ldap
220 ## egg:rhodecode-enterprise-ce#jasig_cas
220 ## egg:rhodecode-enterprise-ce#jasig_cas
221 ## egg:rhodecode-enterprise-ce#headers
221 ## egg:rhodecode-enterprise-ce#headers
222 ## egg:rhodecode-enterprise-ce#crowd
222 ## egg:rhodecode-enterprise-ce#crowd
223 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
223 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
224
224
225 ## alternative return HTTP header for failed authentication. Default HTTP
225 ## alternative return HTTP header for failed authentication. Default HTTP
226 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
226 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
227 ## handling that causing a series of failed authentication calls.
227 ## handling that causing a series of failed authentication calls.
228 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
228 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
229 ## This will be served instead of default 401 on bad authentication
229 ## This will be served instead of default 401 on bad authentication
230 auth_ret_code =
230 auth_ret_code =
231
231
232 ## use special detection method when serving auth_ret_code, instead of serving
232 ## use special detection method when serving auth_ret_code, instead of serving
233 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
233 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
234 ## and then serve auth_ret_code to clients
234 ## and then serve auth_ret_code to clients
235 auth_ret_code_detection = false
235 auth_ret_code_detection = false
236
236
237 ## locking return code. When repository is locked return this HTTP code. 2XX
237 ## locking return code. When repository is locked return this HTTP code. 2XX
238 ## codes don't break the transactions while 4XX codes do
238 ## codes don't break the transactions while 4XX codes do
239 lock_ret_code = 423
239 lock_ret_code = 423
240
240
241 ## allows to change the repository location in settings page
241 ## allows to change the repository location in settings page
242 allow_repo_location_change = true
242 allow_repo_location_change = true
243
243
244 ## allows to setup custom hooks in settings page
244 ## allows to setup custom hooks in settings page
245 allow_custom_hooks_settings = true
245 allow_custom_hooks_settings = true
246
246
247 ## Generated license token required for EE edition license.
247 ## Generated license token required for EE edition license.
248 ## New generated token value can be found in Admin > settings > license page.
248 ## New generated token value can be found in Admin > settings > license page.
249 license_token =
249 license_token =
250
250
251 ## This flag would hide sensitive information on the license page
251 ## This flag would hide sensitive information on the license page
252 license.hide_license_info = false
252 license.hide_license_info = false
253
253
254 ## supervisor connection uri, for managing supervisor and logs.
254 ## supervisor connection uri, for managing supervisor and logs.
255 supervisor.uri =
255 supervisor.uri =
256 ## supervisord group name/id we only want this RC instance to handle
256 ## supervisord group name/id we only want this RC instance to handle
257 supervisor.group_id = prod
257 supervisor.group_id = prod
258
258
259 ## Display extended labs settings
259 ## Display extended labs settings
260 labs_settings_active = true
260 labs_settings_active = true
261
261
262 ## Custom exception store path, defaults to TMPDIR
262 ## Custom exception store path, defaults to TMPDIR
263 ## This is used to store exception from RhodeCode in shared directory
263 ## This is used to store exception from RhodeCode in shared directory
264 #exception_tracker.store_path =
264 #exception_tracker.store_path =
265
265
266 ## File store configuration. This is used to store and serve uploaded files
266 ## File store configuration. This is used to store and serve uploaded files
267 file_store.enabled = true
267 file_store.enabled = true
268 ## Storage backend, available options are: local
268 ## Storage backend, available options are: local
269 file_store.backend = local
269 file_store.backend = local
270 ## path to store the uploaded binaries
270 ## path to store the uploaded binaries
271 file_store.storage_path = %(here)s/data/file_store
271 file_store.storage_path = %(here)s/data/file_store
272
272
273
273
274 ####################################
274 ####################################
275 ### CELERY CONFIG ####
275 ### CELERY CONFIG ####
276 ####################################
276 ####################################
277 ## run: /path/to/celery worker \
277 ## run: /path/to/celery worker \
278 ## -E --beat --app rhodecode.lib.celerylib.loader \
278 ## -E --beat --app rhodecode.lib.celerylib.loader \
279 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
279 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
280 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
280 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
281
281
282 use_celery = false
282 use_celery = false
283
283
284 ## connection url to the message broker (default redis)
284 ## connection url to the message broker (default redis)
285 celery.broker_url = redis://localhost:6379/8
285 celery.broker_url = redis://localhost:6379/8
286
286
287 ## rabbitmq example
287 ## rabbitmq example
288 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
288 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
289
289
290 ## maximum tasks to execute before worker restart
290 ## maximum tasks to execute before worker restart
291 celery.max_tasks_per_child = 100
291 celery.max_tasks_per_child = 100
292
292
293 ## tasks will never be sent to the queue, but executed locally instead.
293 ## tasks will never be sent to the queue, but executed locally instead.
294 celery.task_always_eager = false
294 celery.task_always_eager = false
295
295
296 #####################################
296 #####################################
297 ### DOGPILE CACHE ####
297 ### DOGPILE CACHE ####
298 #####################################
298 #####################################
299 ## Default cache dir for caches. Putting this into a ramdisk
299 ## Default cache dir for caches. Putting this into a ramdisk
300 ## can boost performance, eg. /tmpfs/data_ramdisk, however this directory might require
300 ## can boost performance, eg. /tmpfs/data_ramdisk, however this directory might require
301 ## large amount of space
301 ## large amount of space
302 cache_dir = %(here)s/data
302 cache_dir = %(here)s/data
303
303
304 ## `cache_perms` cache settings for permission tree, auth TTL.
304 ## `cache_perms` cache settings for permission tree, auth TTL.
305 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
305 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
306 rc_cache.cache_perms.expiration_time = 300
306 rc_cache.cache_perms.expiration_time = 300
307
307
308 ## alternative `cache_perms` redis backend with distributed lock
308 ## alternative `cache_perms` redis backend with distributed lock
309 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
309 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
310 #rc_cache.cache_perms.expiration_time = 300
310 #rc_cache.cache_perms.expiration_time = 300
311 ## redis_expiration_time needs to be greater then expiration_time
311 ## redis_expiration_time needs to be greater then expiration_time
312 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
312 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
313 #rc_cache.cache_perms.arguments.socket_timeout = 30
313 #rc_cache.cache_perms.arguments.socket_timeout = 30
314 #rc_cache.cache_perms.arguments.host = localhost
314 #rc_cache.cache_perms.arguments.host = localhost
315 #rc_cache.cache_perms.arguments.port = 6379
315 #rc_cache.cache_perms.arguments.port = 6379
316 #rc_cache.cache_perms.arguments.db = 0
316 #rc_cache.cache_perms.arguments.db = 0
317 ## more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
317 ## more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
318 #rc_cache.cache_perms.arguments.distributed_lock = true
318 #rc_cache.cache_perms.arguments.distributed_lock = true
319
319
320 ## `cache_repo` cache settings for FileTree, Readme, RSS FEEDS
320 ## `cache_repo` cache settings for FileTree, Readme, RSS FEEDS
321 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
321 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
322 rc_cache.cache_repo.expiration_time = 2592000
322 rc_cache.cache_repo.expiration_time = 2592000
323
323
324 ## alternative `cache_repo` redis backend with distributed lock
324 ## alternative `cache_repo` redis backend with distributed lock
325 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
325 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
326 #rc_cache.cache_repo.expiration_time = 2592000
326 #rc_cache.cache_repo.expiration_time = 2592000
327 ## redis_expiration_time needs to be greater then expiration_time
327 ## redis_expiration_time needs to be greater then expiration_time
328 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
328 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
329 #rc_cache.cache_repo.arguments.socket_timeout = 30
329 #rc_cache.cache_repo.arguments.socket_timeout = 30
330 #rc_cache.cache_repo.arguments.host = localhost
330 #rc_cache.cache_repo.arguments.host = localhost
331 #rc_cache.cache_repo.arguments.port = 6379
331 #rc_cache.cache_repo.arguments.port = 6379
332 #rc_cache.cache_repo.arguments.db = 1
332 #rc_cache.cache_repo.arguments.db = 1
333 ## more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
333 ## more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
334 #rc_cache.cache_repo.arguments.distributed_lock = true
334 #rc_cache.cache_repo.arguments.distributed_lock = true
335
335
336 ## cache settings for SQL queries, this needs to use memory type backend
336 ## cache settings for SQL queries, this needs to use memory type backend
337 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
337 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
338 rc_cache.sql_cache_short.expiration_time = 30
338 rc_cache.sql_cache_short.expiration_time = 30
339
339
340 ## `cache_repo_longterm` cache for repo object instances, this needs to use memory
340 ## `cache_repo_longterm` cache for repo object instances, this needs to use memory
341 ## type backend as the objects kept are not pickle serializable
341 ## type backend as the objects kept are not pickle serializable
342 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
342 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
343 ## by default we use 96H, this is using invalidation on push anyway
343 ## by default we use 96H, this is using invalidation on push anyway
344 rc_cache.cache_repo_longterm.expiration_time = 345600
344 rc_cache.cache_repo_longterm.expiration_time = 345600
345 ## max items in LRU cache, reduce this number to save memory, and expire last used
345 ## max items in LRU cache, reduce this number to save memory, and expire last used
346 ## cached objects
346 ## cached objects
347 rc_cache.cache_repo_longterm.max_size = 10000
347 rc_cache.cache_repo_longterm.max_size = 10000
348
348
349
349
350 ####################################
350 ####################################
351 ### BEAKER SESSION ####
351 ### BEAKER SESSION ####
352 ####################################
352 ####################################
353
353
354 ## .session.type is type of storage options for the session, current allowed
354 ## .session.type is type of storage options for the session, current allowed
355 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
355 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
356 beaker.session.type = file
356 beaker.session.type = file
357 beaker.session.data_dir = %(here)s/data/sessions
357 beaker.session.data_dir = %(here)s/data/sessions
358
358
359 ## redis sessions
359 ## redis sessions
360 #beaker.session.type = ext:redis
360 #beaker.session.type = ext:redis
361 #beaker.session.url = redis://127.0.0.1:6379/2
361 #beaker.session.url = redis://127.0.0.1:6379/2
362
362
363 ## db based session, fast, and allows easy management over logged in users
363 ## db based session, fast, and allows easy management over logged in users
364 #beaker.session.type = ext:database
364 #beaker.session.type = ext:database
365 #beaker.session.table_name = db_session
365 #beaker.session.table_name = db_session
366 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
366 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
367 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
367 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
368 #beaker.session.sa.pool_recycle = 3600
368 #beaker.session.sa.pool_recycle = 3600
369 #beaker.session.sa.echo = false
369 #beaker.session.sa.echo = false
370
370
371 beaker.session.key = rhodecode
371 beaker.session.key = rhodecode
372 beaker.session.secret = production-rc-uytcxaz
372 beaker.session.secret = production-rc-uytcxaz
373 beaker.session.lock_dir = %(here)s/data/sessions/lock
373 beaker.session.lock_dir = %(here)s/data/sessions/lock
374
374
375 ## Secure encrypted cookie. Requires AES and AES python libraries
375 ## Secure encrypted cookie. Requires AES and AES python libraries
376 ## you must disable beaker.session.secret to use this
376 ## you must disable beaker.session.secret to use this
377 #beaker.session.encrypt_key = key_for_encryption
377 #beaker.session.encrypt_key = key_for_encryption
378 #beaker.session.validate_key = validation_key
378 #beaker.session.validate_key = validation_key
379
379
380 ## sets session as invalid(also logging out user) if it haven not been
380 ## sets session as invalid(also logging out user) if it haven not been
381 ## accessed for given amount of time in seconds
381 ## accessed for given amount of time in seconds
382 beaker.session.timeout = 2592000
382 beaker.session.timeout = 2592000
383 beaker.session.httponly = true
383 beaker.session.httponly = true
384 ## Path to use for the cookie. Set to prefix if you use prefix middleware
384 ## Path to use for the cookie. Set to prefix if you use prefix middleware
385 #beaker.session.cookie_path = /custom_prefix
385 #beaker.session.cookie_path = /custom_prefix
386
386
387 ## uncomment for https secure cookie
387 ## uncomment for https secure cookie
388 beaker.session.secure = false
388 beaker.session.secure = false
389
389
390 ## auto save the session to not to use .save()
390 ## auto save the session to not to use .save()
391 beaker.session.auto = false
391 beaker.session.auto = false
392
392
393 ## default cookie expiration time in seconds, set to `true` to set expire
393 ## default cookie expiration time in seconds, set to `true` to set expire
394 ## at browser close
394 ## at browser close
395 #beaker.session.cookie_expires = 3600
395 #beaker.session.cookie_expires = 3600
396
396
397 ###################################
397 ###################################
398 ## SEARCH INDEXING CONFIGURATION ##
398 ## SEARCH INDEXING CONFIGURATION ##
399 ###################################
399 ###################################
400 ## Full text search indexer is available in rhodecode-tools under
400 ## Full text search indexer is available in rhodecode-tools under
401 ## `rhodecode-tools index` command
401 ## `rhodecode-tools index` command
402
402
403 ## WHOOSH Backend, doesn't require additional services to run
403 ## WHOOSH Backend, doesn't require additional services to run
404 ## it works good with few dozen repos
404 ## it works good with few dozen repos
405 search.module = rhodecode.lib.index.whoosh
405 search.module = rhodecode.lib.index.whoosh
406 search.location = %(here)s/data/index
406 search.location = %(here)s/data/index
407
407
408 ########################################
408 ########################################
409 ### CHANNELSTREAM CONFIG ####
409 ### CHANNELSTREAM CONFIG ####
410 ########################################
410 ########################################
411 ## channelstream enables persistent connections and live notification
411 ## channelstream enables persistent connections and live notification
412 ## in the system. It's also used by the chat system
412 ## in the system. It's also used by the chat system
413
413
414 channelstream.enabled = false
414 channelstream.enabled = false
415
415
416 ## server address for channelstream server on the backend
416 ## server address for channelstream server on the backend
417 channelstream.server = 127.0.0.1:9800
417 channelstream.server = 127.0.0.1:9800
418
418
419 ## location of the channelstream server from outside world
419 ## location of the channelstream server from outside world
420 ## use ws:// for http or wss:// for https. This address needs to be handled
420 ## use ws:// for http or wss:// for https. This address needs to be handled
421 ## by external HTTP server such as Nginx or Apache
421 ## by external HTTP server such as Nginx or Apache
422 ## see Nginx/Apache configuration examples in our docs
422 ## see Nginx/Apache configuration examples in our docs
423 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
423 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
424 channelstream.secret = secret
424 channelstream.secret = secret
425 channelstream.history.location = %(here)s/channelstream_history
425 channelstream.history.location = %(here)s/channelstream_history
426
426
427 ## Internal application path that Javascript uses to connect into.
427 ## Internal application path that Javascript uses to connect into.
428 ## If you use proxy-prefix the prefix should be added before /_channelstream
428 ## If you use proxy-prefix the prefix should be added before /_channelstream
429 channelstream.proxy_path = /_channelstream
429 channelstream.proxy_path = /_channelstream
430
430
431 ## Live chat for commits/pull requests. Requires CHANNELSTREAM to be enabled
432 ## and configured. (EE edition only)
433 chat.enabled = true
434
431
435
432 ###################################
436 ###################################
433 ## APPENLIGHT CONFIG ##
437 ## APPENLIGHT CONFIG ##
434 ###################################
438 ###################################
435
439
436 ## Appenlight is tailored to work with RhodeCode, see
440 ## Appenlight is tailored to work with RhodeCode, see
437 ## http://appenlight.com for details how to obtain an account
441 ## http://appenlight.com for details how to obtain an account
438
442
439 ## Appenlight integration enabled
443 ## Appenlight integration enabled
440 appenlight = false
444 appenlight = false
441
445
442 appenlight.server_url = https://api.appenlight.com
446 appenlight.server_url = https://api.appenlight.com
443 appenlight.api_key = YOUR_API_KEY
447 appenlight.api_key = YOUR_API_KEY
444 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
448 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
445
449
446 ## used for JS client
450 ## used for JS client
447 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
451 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
448
452
449 ## TWEAK AMOUNT OF INFO SENT HERE
453 ## TWEAK AMOUNT OF INFO SENT HERE
450
454
451 ## enables 404 error logging (default False)
455 ## enables 404 error logging (default False)
452 appenlight.report_404 = false
456 appenlight.report_404 = false
453
457
454 ## time in seconds after request is considered being slow (default 1)
458 ## time in seconds after request is considered being slow (default 1)
455 appenlight.slow_request_time = 1
459 appenlight.slow_request_time = 1
456
460
457 ## record slow requests in application
461 ## record slow requests in application
458 ## (needs to be enabled for slow datastore recording and time tracking)
462 ## (needs to be enabled for slow datastore recording and time tracking)
459 appenlight.slow_requests = true
463 appenlight.slow_requests = true
460
464
461 ## enable hooking to application loggers
465 ## enable hooking to application loggers
462 appenlight.logging = true
466 appenlight.logging = true
463
467
464 ## minimum log level for log capture
468 ## minimum log level for log capture
465 appenlight.logging.level = WARNING
469 appenlight.logging.level = WARNING
466
470
467 ## send logs only from erroneous/slow requests
471 ## send logs only from erroneous/slow requests
468 ## (saves API quota for intensive logging)
472 ## (saves API quota for intensive logging)
469 appenlight.logging_on_error = false
473 appenlight.logging_on_error = false
470
474
471 ## list of additional keywords that should be grabbed from environ object
475 ## list of additional keywords that should be grabbed from environ object
472 ## can be string with comma separated list of words in lowercase
476 ## can be string with comma separated list of words in lowercase
473 ## (by default client will always send following info:
477 ## (by default client will always send following info:
474 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
478 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
475 ## start with HTTP* this list be extended with additional keywords here
479 ## start with HTTP* this list be extended with additional keywords here
476 appenlight.environ_keys_whitelist =
480 appenlight.environ_keys_whitelist =
477
481
478 ## list of keywords that should be blanked from request object
482 ## list of keywords that should be blanked from request object
479 ## can be string with comma separated list of words in lowercase
483 ## can be string with comma separated list of words in lowercase
480 ## (by default client will always blank keys that contain following words
484 ## (by default client will always blank keys that contain following words
481 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
485 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
482 ## this list be extended with additional keywords set here
486 ## this list be extended with additional keywords set here
483 appenlight.request_keys_blacklist =
487 appenlight.request_keys_blacklist =
484
488
485 ## list of namespaces that should be ignores when gathering log entries
489 ## list of namespaces that should be ignores when gathering log entries
486 ## can be string with comma separated list of namespaces
490 ## can be string with comma separated list of namespaces
487 ## (by default the client ignores own entries: appenlight_client.client)
491 ## (by default the client ignores own entries: appenlight_client.client)
488 appenlight.log_namespace_blacklist =
492 appenlight.log_namespace_blacklist =
489
493
490
494
491 ###########################################
495 ###########################################
492 ### MAIN RHODECODE DATABASE CONFIG ###
496 ### MAIN RHODECODE DATABASE CONFIG ###
493 ###########################################
497 ###########################################
494 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
498 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
495 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
499 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
496 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
500 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
497 # pymysql is an alternative driver for MySQL, use in case of problems with default one
501 # pymysql is an alternative driver for MySQL, use in case of problems with default one
498 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
502 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
499
503
500 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
504 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
501
505
502 # see sqlalchemy docs for other advanced settings
506 # see sqlalchemy docs for other advanced settings
503
507
504 ## print the sql statements to output
508 ## print the sql statements to output
505 sqlalchemy.db1.echo = false
509 sqlalchemy.db1.echo = false
506 ## recycle the connections after this amount of seconds
510 ## recycle the connections after this amount of seconds
507 sqlalchemy.db1.pool_recycle = 3600
511 sqlalchemy.db1.pool_recycle = 3600
508
512
509 ## the number of connections to keep open inside the connection pool.
513 ## the number of connections to keep open inside the connection pool.
510 ## 0 indicates no limit
514 ## 0 indicates no limit
511 #sqlalchemy.db1.pool_size = 5
515 #sqlalchemy.db1.pool_size = 5
512
516
513 ## the number of connections to allow in connection pool "overflow", that is
517 ## the number of connections to allow in connection pool "overflow", that is
514 ## connections that can be opened above and beyond the pool_size setting,
518 ## connections that can be opened above and beyond the pool_size setting,
515 ## which defaults to five.
519 ## which defaults to five.
516 #sqlalchemy.db1.max_overflow = 10
520 #sqlalchemy.db1.max_overflow = 10
517
521
518 ## Connection check ping, used to detect broken database connections
522 ## Connection check ping, used to detect broken database connections
519 ## could be enabled to better handle cases if MySQL has gone away errors
523 ## could be enabled to better handle cases if MySQL has gone away errors
520 #sqlalchemy.db1.ping_connection = true
524 #sqlalchemy.db1.ping_connection = true
521
525
522 ##################
526 ##################
523 ### VCS CONFIG ###
527 ### VCS CONFIG ###
524 ##################
528 ##################
525 vcs.server.enable = true
529 vcs.server.enable = true
526 vcs.server = localhost:9900
530 vcs.server = localhost:9900
527
531
528 ## Web server connectivity protocol, responsible for web based VCS operations
532 ## Web server connectivity protocol, responsible for web based VCS operations
529 ## Available protocols are:
533 ## Available protocols are:
530 ## `http` - use http-rpc backend (default)
534 ## `http` - use http-rpc backend (default)
531 vcs.server.protocol = http
535 vcs.server.protocol = http
532
536
533 ## Push/Pull operations protocol, available options are:
537 ## Push/Pull operations protocol, available options are:
534 ## `http` - use http-rpc backend (default)
538 ## `http` - use http-rpc backend (default)
535 vcs.scm_app_implementation = http
539 vcs.scm_app_implementation = http
536
540
537 ## Push/Pull operations hooks protocol, available options are:
541 ## Push/Pull operations hooks protocol, available options are:
538 ## `http` - use http-rpc backend (default)
542 ## `http` - use http-rpc backend (default)
539 vcs.hooks.protocol = http
543 vcs.hooks.protocol = http
540
544
541 ## Host on which this instance is listening for hooks. If vcsserver is in other location
545 ## Host on which this instance is listening for hooks. If vcsserver is in other location
542 ## this should be adjusted.
546 ## this should be adjusted.
543 vcs.hooks.host = 127.0.0.1
547 vcs.hooks.host = 127.0.0.1
544
548
545 vcs.server.log_level = info
549 vcs.server.log_level = info
546 ## Start VCSServer with this instance as a subprocess, useful for development
550 ## Start VCSServer with this instance as a subprocess, useful for development
547 vcs.start_server = false
551 vcs.start_server = false
548
552
549 ## List of enabled VCS backends, available options are:
553 ## List of enabled VCS backends, available options are:
550 ## `hg` - mercurial
554 ## `hg` - mercurial
551 ## `git` - git
555 ## `git` - git
552 ## `svn` - subversion
556 ## `svn` - subversion
553 vcs.backends = hg, git, svn
557 vcs.backends = hg, git, svn
554
558
555 vcs.connection_timeout = 3600
559 vcs.connection_timeout = 3600
556 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
560 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
557 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
561 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
558 #vcs.svn.compatible_version = pre-1.8-compatible
562 #vcs.svn.compatible_version = pre-1.8-compatible
559
563
560
564
561 ############################################################
565 ############################################################
562 ### Subversion proxy support (mod_dav_svn) ###
566 ### Subversion proxy support (mod_dav_svn) ###
563 ### Maps RhodeCode repo groups into SVN paths for Apache ###
567 ### Maps RhodeCode repo groups into SVN paths for Apache ###
564 ############################################################
568 ############################################################
565 ## Enable or disable the config file generation.
569 ## Enable or disable the config file generation.
566 svn.proxy.generate_config = false
570 svn.proxy.generate_config = false
567 ## Generate config file with `SVNListParentPath` set to `On`.
571 ## Generate config file with `SVNListParentPath` set to `On`.
568 svn.proxy.list_parent_path = true
572 svn.proxy.list_parent_path = true
569 ## Set location and file name of generated config file.
573 ## Set location and file name of generated config file.
570 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
574 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
571 ## alternative mod_dav config template. This needs to be a mako template
575 ## alternative mod_dav config template. This needs to be a mako template
572 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
576 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
573 ## Used as a prefix to the `Location` block in the generated config file.
577 ## Used as a prefix to the `Location` block in the generated config file.
574 ## In most cases it should be set to `/`.
578 ## In most cases it should be set to `/`.
575 svn.proxy.location_root = /
579 svn.proxy.location_root = /
576 ## Command to reload the mod dav svn configuration on change.
580 ## Command to reload the mod dav svn configuration on change.
577 ## Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
581 ## Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
578 ## Make sure user who runs RhodeCode process is allowed to reload Apache
582 ## Make sure user who runs RhodeCode process is allowed to reload Apache
579 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
583 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
580 ## If the timeout expires before the reload command finishes, the command will
584 ## If the timeout expires before the reload command finishes, the command will
581 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
585 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
582 #svn.proxy.reload_timeout = 10
586 #svn.proxy.reload_timeout = 10
583
587
584 ############################################################
588 ############################################################
585 ### SSH Support Settings ###
589 ### SSH Support Settings ###
586 ############################################################
590 ############################################################
587
591
588 ## Defines if a custom authorized_keys file should be created and written on
592 ## Defines if a custom authorized_keys file should be created and written on
589 ## any change user ssh keys. Setting this to false also disables possibility
593 ## any change user ssh keys. Setting this to false also disables possibility
590 ## of adding SSH keys by users from web interface. Super admins can still
594 ## of adding SSH keys by users from web interface. Super admins can still
591 ## manage SSH Keys.
595 ## manage SSH Keys.
592 ssh.generate_authorized_keyfile = false
596 ssh.generate_authorized_keyfile = false
593
597
594 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
598 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
595 # ssh.authorized_keys_ssh_opts =
599 # ssh.authorized_keys_ssh_opts =
596
600
597 ## Path to the authorized_keys file where the generate entries are placed.
601 ## Path to the authorized_keys file where the generate entries are placed.
598 ## It is possible to have multiple key files specified in `sshd_config` e.g.
602 ## It is possible to have multiple key files specified in `sshd_config` e.g.
599 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
603 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
600 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
604 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
601
605
602 ## Command to execute the SSH wrapper. The binary is available in the
606 ## Command to execute the SSH wrapper. The binary is available in the
603 ## RhodeCode installation directory.
607 ## RhodeCode installation directory.
604 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
608 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
605 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
609 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
606
610
607 ## Allow shell when executing the ssh-wrapper command
611 ## Allow shell when executing the ssh-wrapper command
608 ssh.wrapper_cmd_allow_shell = false
612 ssh.wrapper_cmd_allow_shell = false
609
613
610 ## Enables logging, and detailed output send back to the client during SSH
614 ## Enables logging, and detailed output send back to the client during SSH
611 ## operations. Useful for debugging, shouldn't be used in production.
615 ## operations. Useful for debugging, shouldn't be used in production.
612 ssh.enable_debug_logging = false
616 ssh.enable_debug_logging = false
613
617
614 ## Paths to binary executable, by default they are the names, but we can
618 ## Paths to binary executable, by default they are the names, but we can
615 ## override them if we want to use a custom one
619 ## override them if we want to use a custom one
616 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
620 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
617 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
621 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
618 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
622 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
619
623
620 ## Enables SSH key generator web interface. Disabling this still allows users
624 ## Enables SSH key generator web interface. Disabling this still allows users
621 ## to add their own keys.
625 ## to add their own keys.
622 ssh.enable_ui_key_generator = true
626 ssh.enable_ui_key_generator = true
623
627
624
628
625 ## Dummy marker to add new entries after.
629 ## Dummy marker to add new entries after.
626 ## Add any custom entries below. Please don't remove.
630 ## Add any custom entries below. Please don't remove.
627 custom.conf = 1
631 custom.conf = 1
628
632
629
633
630 ################################
634 ################################
631 ### LOGGING CONFIGURATION ####
635 ### LOGGING CONFIGURATION ####
632 ################################
636 ################################
633 [loggers]
637 [loggers]
634 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
638 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
635
639
636 [handlers]
640 [handlers]
637 keys = console, console_sql
641 keys = console, console_sql
638
642
639 [formatters]
643 [formatters]
640 keys = generic, color_formatter, color_formatter_sql
644 keys = generic, color_formatter, color_formatter_sql
641
645
642 #############
646 #############
643 ## LOGGERS ##
647 ## LOGGERS ##
644 #############
648 #############
645 [logger_root]
649 [logger_root]
646 level = NOTSET
650 level = NOTSET
647 handlers = console
651 handlers = console
648
652
649 [logger_sqlalchemy]
653 [logger_sqlalchemy]
650 level = INFO
654 level = INFO
651 handlers = console_sql
655 handlers = console_sql
652 qualname = sqlalchemy.engine
656 qualname = sqlalchemy.engine
653 propagate = 0
657 propagate = 0
654
658
655 [logger_beaker]
659 [logger_beaker]
656 level = DEBUG
660 level = DEBUG
657 handlers =
661 handlers =
658 qualname = beaker.container
662 qualname = beaker.container
659 propagate = 1
663 propagate = 1
660
664
661 [logger_rhodecode]
665 [logger_rhodecode]
662 level = DEBUG
666 level = DEBUG
663 handlers =
667 handlers =
664 qualname = rhodecode
668 qualname = rhodecode
665 propagate = 1
669 propagate = 1
666
670
667 [logger_ssh_wrapper]
671 [logger_ssh_wrapper]
668 level = DEBUG
672 level = DEBUG
669 handlers =
673 handlers =
670 qualname = ssh_wrapper
674 qualname = ssh_wrapper
671 propagate = 1
675 propagate = 1
672
676
673 [logger_celery]
677 [logger_celery]
674 level = DEBUG
678 level = DEBUG
675 handlers =
679 handlers =
676 qualname = celery
680 qualname = celery
677
681
678
682
679 ##############
683 ##############
680 ## HANDLERS ##
684 ## HANDLERS ##
681 ##############
685 ##############
682
686
683 [handler_console]
687 [handler_console]
684 class = StreamHandler
688 class = StreamHandler
685 args = (sys.stderr, )
689 args = (sys.stderr, )
686 level = INFO
690 level = INFO
687 formatter = generic
691 formatter = generic
688
692
689 [handler_console_sql]
693 [handler_console_sql]
690 # "level = DEBUG" logs SQL queries and results.
694 # "level = DEBUG" logs SQL queries and results.
691 # "level = INFO" logs SQL queries.
695 # "level = INFO" logs SQL queries.
692 # "level = WARN" logs neither. (Recommended for production systems.)
696 # "level = WARN" logs neither. (Recommended for production systems.)
693 class = StreamHandler
697 class = StreamHandler
694 args = (sys.stderr, )
698 args = (sys.stderr, )
695 level = WARN
699 level = WARN
696 formatter = generic
700 formatter = generic
697
701
698 ################
702 ################
699 ## FORMATTERS ##
703 ## FORMATTERS ##
700 ################
704 ################
701
705
702 [formatter_generic]
706 [formatter_generic]
703 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
707 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
704 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
708 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
705 datefmt = %Y-%m-%d %H:%M:%S
709 datefmt = %Y-%m-%d %H:%M:%S
706
710
707 [formatter_color_formatter]
711 [formatter_color_formatter]
708 class = rhodecode.lib.logging_formatter.ColorFormatter
712 class = rhodecode.lib.logging_formatter.ColorFormatter
709 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
713 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
710 datefmt = %Y-%m-%d %H:%M:%S
714 datefmt = %Y-%m-%d %H:%M:%S
711
715
712 [formatter_color_formatter_sql]
716 [formatter_color_formatter_sql]
713 class = rhodecode.lib.logging_formatter.ColorFormatterSql
717 class = rhodecode.lib.logging_formatter.ColorFormatterSql
714 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
718 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
715 datefmt = %Y-%m-%d %H:%M:%S
719 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,338 +1,347 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23 import datetime
23 import datetime
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.renderers import render_to_response
26 from pyramid.renderers import render_to_response
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib.celerylib import run_task, tasks
28 from rhodecode.lib.celerylib import run_task, tasks
29 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.lib.utils2 import AttributeDict
30 from rhodecode.model.db import User
30 from rhodecode.model.db import User
31 from rhodecode.model.notification import EmailNotificationModel
31 from rhodecode.model.notification import EmailNotificationModel
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class DebugStyleView(BaseAppView):
36 class DebugStyleView(BaseAppView):
37 def load_default_context(self):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
38 c = self._get_local_tmpl_context()
39
39
40 return c
40 return c
41
41
42 @view_config(
42 @view_config(
43 route_name='debug_style_home', request_method='GET',
43 route_name='debug_style_home', request_method='GET',
44 renderer=None)
44 renderer=None)
45 def index(self):
45 def index(self):
46 c = self.load_default_context()
46 c = self.load_default_context()
47 c.active = 'index'
47 c.active = 'index'
48
48
49 return render_to_response(
49 return render_to_response(
50 'debug_style/index.html', self._get_template_context(c),
50 'debug_style/index.html', self._get_template_context(c),
51 request=self.request)
51 request=self.request)
52
52
53 @view_config(
53 @view_config(
54 route_name='debug_style_email', request_method='GET',
54 route_name='debug_style_email', request_method='GET',
55 renderer=None)
55 renderer=None)
56 @view_config(
56 @view_config(
57 route_name='debug_style_email_plain_rendered', request_method='GET',
57 route_name='debug_style_email_plain_rendered', request_method='GET',
58 renderer=None)
58 renderer=None)
59 def render_email(self):
59 def render_email(self):
60 c = self.load_default_context()
60 c = self.load_default_context()
61 email_id = self.request.matchdict['email_id']
61 email_id = self.request.matchdict['email_id']
62 c.active = 'emails'
62 c.active = 'emails'
63
63
64 pr = AttributeDict(
64 pr = AttributeDict(
65 pull_request_id=123,
65 pull_request_id=123,
66 title='digital_ocean: fix redis, elastic search start on boot, '
66 title='digital_ocean: fix redis, elastic search start on boot, '
67 'fix fd limits on supervisor, set postgres 11 version',
67 'fix fd limits on supervisor, set postgres 11 version',
68 description='''
68 description='''
69 Check if we should use full-topic or mini-topic.
69 Check if we should use full-topic or mini-topic.
70
70
71 - full topic produces some problems with merge states etc
71 - full topic produces some problems with merge states etc
72 - server-mini-topic needs probably tweeks.
72 - server-mini-topic needs probably tweeks.
73 ''',
73 ''',
74 repo_name='foobar',
74 repo_name='foobar',
75 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
75 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
76 target_ref_parts=AttributeDict(type='branch', name='master'),
76 target_ref_parts=AttributeDict(type='branch', name='master'),
77 )
77 )
78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
81
81
82 email_kwargs = {
82 email_kwargs = {
83 'test': {},
83 'test': {},
84 'message': {
84 'message': {
85 'body': 'message body !'
85 'body': 'message body !'
86 },
86 },
87 'email_test': {
87 'email_test': {
88 'user': user,
88 'user': user,
89 'date': datetime.datetime.now(),
89 'date': datetime.datetime.now(),
90 'rhodecode_version': c.rhodecode_version
90 'rhodecode_version': c.rhodecode_version
91 },
91 },
92 'password_reset': {
92 'password_reset': {
93 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
93 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
94
94
95 'user': user,
95 'user': user,
96 'date': datetime.datetime.now(),
96 'date': datetime.datetime.now(),
97 'email': 'test@rhodecode.com',
97 'email': 'test@rhodecode.com',
98 'first_admin_email': User.get_first_super_admin().email
98 'first_admin_email': User.get_first_super_admin().email
99 },
99 },
100 'password_reset_confirmation': {
100 'password_reset_confirmation': {
101 'new_password': 'new-password-example',
101 'new_password': 'new-password-example',
102 'user': user,
102 'user': user,
103 'date': datetime.datetime.now(),
103 'date': datetime.datetime.now(),
104 'email': 'test@rhodecode.com',
104 'email': 'test@rhodecode.com',
105 'first_admin_email': User.get_first_super_admin().email
105 'first_admin_email': User.get_first_super_admin().email
106 },
106 },
107 'registration': {
107 'registration': {
108 'user': user,
108 'user': user,
109 'date': datetime.datetime.now(),
109 'date': datetime.datetime.now(),
110 },
110 },
111
111
112 'pull_request_comment': {
112 'pull_request_comment': {
113 'user': user,
113 'user': user,
114
114
115 'status_change': None,
115 'status_change': None,
116 'status_change_type': None,
116 'status_change_type': None,
117
117
118 'pull_request': pr,
118 'pull_request': pr,
119 'pull_request_commits': [],
119 'pull_request_commits': [],
120
120
121 'pull_request_target_repo': target_repo,
121 'pull_request_target_repo': target_repo,
122 'pull_request_target_repo_url': 'http://target-repo/url',
122 'pull_request_target_repo_url': 'http://target-repo/url',
123
123
124 'pull_request_source_repo': source_repo,
124 'pull_request_source_repo': source_repo,
125 'pull_request_source_repo_url': 'http://source-repo/url',
125 'pull_request_source_repo_url': 'http://source-repo/url',
126
126
127 'pull_request_url': 'http://localhost/pr1',
127 'pull_request_url': 'http://localhost/pr1',
128 'pr_comment_url': 'http://comment-url',
128 'pr_comment_url': 'http://comment-url',
129 'pr_comment_reply_url': 'http://comment-url#reply',
129
130
130 'comment_file': None,
131 'comment_file': None,
131 'comment_line': None,
132 'comment_line': None,
132 'comment_type': 'note',
133 'comment_type': 'note',
133 'comment_body': 'This is my comment body. *I like !*',
134 'comment_body': 'This is my comment body. *I like !*',
134
135 'comment_id': 2048,
135 'renderer_type': 'markdown',
136 'renderer_type': 'markdown',
136 'mention': True,
137 'mention': True,
137
138
138 },
139 },
139 'pull_request_comment+status': {
140 'pull_request_comment+status': {
140 'user': user,
141 'user': user,
141
142
142 'status_change': 'approved',
143 'status_change': 'approved',
143 'status_change_type': 'approved',
144 'status_change_type': 'approved',
144
145
145 'pull_request': pr,
146 'pull_request': pr,
146 'pull_request_commits': [],
147 'pull_request_commits': [],
147
148
148 'pull_request_target_repo': target_repo,
149 'pull_request_target_repo': target_repo,
149 'pull_request_target_repo_url': 'http://target-repo/url',
150 'pull_request_target_repo_url': 'http://target-repo/url',
150
151
151 'pull_request_source_repo': source_repo,
152 'pull_request_source_repo': source_repo,
152 'pull_request_source_repo_url': 'http://source-repo/url',
153 'pull_request_source_repo_url': 'http://source-repo/url',
153
154
154 'pull_request_url': 'http://localhost/pr1',
155 'pull_request_url': 'http://localhost/pr1',
155 'pr_comment_url': 'http://comment-url',
156 'pr_comment_url': 'http://comment-url',
157 'pr_comment_reply_url': 'http://comment-url#reply',
156
158
157 'comment_type': 'todo',
159 'comment_type': 'todo',
158 'comment_file': None,
160 'comment_file': None,
159 'comment_line': None,
161 'comment_line': None,
160 'comment_body': '''
162 'comment_body': '''
161 I think something like this would be better
163 I think something like this would be better
162
164
163 ```py
165 ```py
164
166
165 def db():
167 def db():
166 global connection
168 global connection
167 return connection
169 return connection
168
170
169 ```
171 ```
170
172
171 ''',
173 ''',
172
174 'comment_id': 2048,
173 'renderer_type': 'markdown',
175 'renderer_type': 'markdown',
174 'mention': True,
176 'mention': True,
175
177
176 },
178 },
177 'pull_request_comment+file': {
179 'pull_request_comment+file': {
178 'user': user,
180 'user': user,
179
181
180 'status_change': None,
182 'status_change': None,
181 'status_change_type': None,
183 'status_change_type': None,
182
184
183 'pull_request': pr,
185 'pull_request': pr,
184 'pull_request_commits': [],
186 'pull_request_commits': [],
185
187
186 'pull_request_target_repo': target_repo,
188 'pull_request_target_repo': target_repo,
187 'pull_request_target_repo_url': 'http://target-repo/url',
189 'pull_request_target_repo_url': 'http://target-repo/url',
188
190
189 'pull_request_source_repo': source_repo,
191 'pull_request_source_repo': source_repo,
190 'pull_request_source_repo_url': 'http://source-repo/url',
192 'pull_request_source_repo_url': 'http://source-repo/url',
191
193
192 'pull_request_url': 'http://localhost/pr1',
194 'pull_request_url': 'http://localhost/pr1',
193
195
194 'pr_comment_url': 'http://comment-url',
196 'pr_comment_url': 'http://comment-url',
197 'pr_comment_reply_url': 'http://comment-url#reply',
195
198
196 'comment_file': 'rhodecode/model/db.py',
199 'comment_file': 'rhodecode/model/db.py',
197 'comment_line': 'o1210',
200 'comment_line': 'o1210',
198 'comment_type': 'todo',
201 'comment_type': 'todo',
199 'comment_body': '''
202 'comment_body': '''
200 I like this !
203 I like this !
201
204
202 But please check this code::
205 But please check this code::
203
206
204 def main():
207 def main():
205 print 'ok'
208 print 'ok'
206
209
207 This should work better !
210 This should work better !
208 ''',
211 ''',
209
212 'comment_id': 2048,
210 'renderer_type': 'rst',
213 'renderer_type': 'rst',
211 'mention': True,
214 'mention': True,
212
215
213 },
216 },
214
217
215 'cs_comment': {
218 'cs_comment': {
216 'user': user,
219 'user': user,
217 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
220 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
218 'status_change': None,
221 'status_change': None,
219 'status_change_type': None,
222 'status_change_type': None,
220
223
221 'commit_target_repo_url': 'http://foo.example.com/#comment1',
224 'commit_target_repo_url': 'http://foo.example.com/#comment1',
222 'repo_name': 'test-repo',
225 'repo_name': 'test-repo',
223 'comment_type': 'note',
226 'comment_type': 'note',
224 'comment_file': None,
227 'comment_file': None,
225 'comment_line': None,
228 'comment_line': None,
226 'commit_comment_url': 'http://comment-url',
229 'commit_comment_url': 'http://comment-url',
230 'commit_comment_reply_url': 'http://comment-url#reply',
227 'comment_body': 'This is my comment body. *I like !*',
231 'comment_body': 'This is my comment body. *I like !*',
232 'comment_id': 2048,
228 'renderer_type': 'markdown',
233 'renderer_type': 'markdown',
229 'mention': True,
234 'mention': True,
230 },
235 },
231 'cs_comment+status': {
236 'cs_comment+status': {
232 'user': user,
237 'user': user,
233 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
238 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
234 'status_change': 'approved',
239 'status_change': 'approved',
235 'status_change_type': 'approved',
240 'status_change_type': 'approved',
236
241
237 'commit_target_repo_url': 'http://foo.example.com/#comment1',
242 'commit_target_repo_url': 'http://foo.example.com/#comment1',
238 'repo_name': 'test-repo',
243 'repo_name': 'test-repo',
239 'comment_type': 'note',
244 'comment_type': 'note',
240 'comment_file': None,
245 'comment_file': None,
241 'comment_line': None,
246 'comment_line': None,
242 'commit_comment_url': 'http://comment-url',
247 'commit_comment_url': 'http://comment-url',
248 'commit_comment_reply_url': 'http://comment-url#reply',
243 'comment_body': '''
249 'comment_body': '''
244 Hello **world**
250 Hello **world**
245
251
246 This is a multiline comment :)
252 This is a multiline comment :)
247
253
248 - list
254 - list
249 - list2
255 - list2
250 ''',
256 ''',
257 'comment_id': 2048,
251 'renderer_type': 'markdown',
258 'renderer_type': 'markdown',
252 'mention': True,
259 'mention': True,
253 },
260 },
254 'cs_comment+file': {
261 'cs_comment+file': {
255 'user': user,
262 'user': user,
256 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
263 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
257 'status_change': None,
264 'status_change': None,
258 'status_change_type': None,
265 'status_change_type': None,
259
266
260 'commit_target_repo_url': 'http://foo.example.com/#comment1',
267 'commit_target_repo_url': 'http://foo.example.com/#comment1',
261 'repo_name': 'test-repo',
268 'repo_name': 'test-repo',
262
269
263 'comment_type': 'note',
270 'comment_type': 'note',
264 'comment_file': 'test-file.py',
271 'comment_file': 'test-file.py',
265 'comment_line': 'n100',
272 'comment_line': 'n100',
266
273
267 'commit_comment_url': 'http://comment-url',
274 'commit_comment_url': 'http://comment-url',
275 'commit_comment_reply_url': 'http://comment-url#reply',
268 'comment_body': 'This is my comment body. *I like !*',
276 'comment_body': 'This is my comment body. *I like !*',
277 'comment_id': 2048,
269 'renderer_type': 'markdown',
278 'renderer_type': 'markdown',
270 'mention': True,
279 'mention': True,
271 },
280 },
272
281
273 'pull_request': {
282 'pull_request': {
274 'user': user,
283 'user': user,
275 'pull_request': pr,
284 'pull_request': pr,
276 'pull_request_commits': [
285 'pull_request_commits': [
277 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
286 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
278 my-account: moved email closer to profile as it's similar data just moved outside.
287 my-account: moved email closer to profile as it's similar data just moved outside.
279 '''),
288 '''),
280 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
289 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
281 users: description edit fixes
290 users: description edit fixes
282
291
283 - tests
292 - tests
284 - added metatags info
293 - added metatags info
285 '''),
294 '''),
286 ],
295 ],
287
296
288 'pull_request_target_repo': target_repo,
297 'pull_request_target_repo': target_repo,
289 'pull_request_target_repo_url': 'http://target-repo/url',
298 'pull_request_target_repo_url': 'http://target-repo/url',
290
299
291 'pull_request_source_repo': source_repo,
300 'pull_request_source_repo': source_repo,
292 'pull_request_source_repo_url': 'http://source-repo/url',
301 'pull_request_source_repo_url': 'http://source-repo/url',
293
302
294 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
303 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
295 }
304 }
296
305
297 }
306 }
298
307
299 template_type = email_id.split('+')[0]
308 template_type = email_id.split('+')[0]
300 (c.subject, c.headers, c.email_body,
309 (c.subject, c.headers, c.email_body,
301 c.email_body_plaintext) = EmailNotificationModel().render_email(
310 c.email_body_plaintext) = EmailNotificationModel().render_email(
302 template_type, **email_kwargs.get(email_id, {}))
311 template_type, **email_kwargs.get(email_id, {}))
303
312
304 test_email = self.request.GET.get('email')
313 test_email = self.request.GET.get('email')
305 if test_email:
314 if test_email:
306 recipients = [test_email]
315 recipients = [test_email]
307 run_task(tasks.send_email, recipients, c.subject,
316 run_task(tasks.send_email, recipients, c.subject,
308 c.email_body_plaintext, c.email_body)
317 c.email_body_plaintext, c.email_body)
309
318
310 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
319 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
311 template = 'debug_style/email_plain_rendered.mako'
320 template = 'debug_style/email_plain_rendered.mako'
312 else:
321 else:
313 template = 'debug_style/email.mako'
322 template = 'debug_style/email.mako'
314 return render_to_response(
323 return render_to_response(
315 template, self._get_template_context(c),
324 template, self._get_template_context(c),
316 request=self.request)
325 request=self.request)
317
326
318 @view_config(
327 @view_config(
319 route_name='debug_style_template', request_method='GET',
328 route_name='debug_style_template', request_method='GET',
320 renderer=None)
329 renderer=None)
321 def template(self):
330 def template(self):
322 t_path = self.request.matchdict['t_path']
331 t_path = self.request.matchdict['t_path']
323 c = self.load_default_context()
332 c = self.load_default_context()
324 c.active = os.path.splitext(t_path)[0]
333 c.active = os.path.splitext(t_path)[0]
325 c.came_from = ''
334 c.came_from = ''
326 c.email_types = {
335 c.email_types = {
327 'cs_comment+file': {},
336 'cs_comment+file': {},
328 'cs_comment+status': {},
337 'cs_comment+status': {},
329
338
330 'pull_request_comment+file': {},
339 'pull_request_comment+file': {},
331 'pull_request_comment+status': {},
340 'pull_request_comment+status': {},
332 }
341 }
333 c.email_types.update(EmailNotificationModel.email_types)
342 c.email_types.update(EmailNotificationModel.email_types)
334
343
335 return render_to_response(
344 return render_to_response(
336 'debug_style/' + t_path, self._get_template_context(c),
345 'debug_style/' + t_path, self._get_template_context(c),
337 request=self.request)
346 request=self.request)
338
347
@@ -1,746 +1,754 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 comments model for RhodeCode
22 comments model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import collections
27 import collections
28
28
29 from pyramid.threadlocal import get_current_registry, get_current_request
29 from pyramid.threadlocal import get_current_registry, get_current_request
30 from sqlalchemy.sql.expression import null
30 from sqlalchemy.sql.expression import null
31 from sqlalchemy.sql.functions import coalesce
31 from sqlalchemy.sql.functions import coalesce
32
32
33 from rhodecode.lib import helpers as h, diffs, channelstream
33 from rhodecode.lib import helpers as h, diffs, channelstream
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
35 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
36 from rhodecode.model import BaseModel
36 from rhodecode.model import BaseModel
37 from rhodecode.model.db import (
37 from rhodecode.model.db import (
38 ChangesetComment, User, Notification, PullRequest, AttributeDict)
38 ChangesetComment, User, Notification, PullRequest, AttributeDict)
39 from rhodecode.model.notification import NotificationModel
39 from rhodecode.model.notification import NotificationModel
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.settings import VcsSettingsModel
41 from rhodecode.model.settings import VcsSettingsModel
42 from rhodecode.model.notification import EmailNotificationModel
42 from rhodecode.model.notification import EmailNotificationModel
43 from rhodecode.model.validation_schema.schemas import comment_schema
43 from rhodecode.model.validation_schema.schemas import comment_schema
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class CommentsModel(BaseModel):
49 class CommentsModel(BaseModel):
50
50
51 cls = ChangesetComment
51 cls = ChangesetComment
52
52
53 DIFF_CONTEXT_BEFORE = 3
53 DIFF_CONTEXT_BEFORE = 3
54 DIFF_CONTEXT_AFTER = 3
54 DIFF_CONTEXT_AFTER = 3
55
55
56 def __get_commit_comment(self, changeset_comment):
56 def __get_commit_comment(self, changeset_comment):
57 return self._get_instance(ChangesetComment, changeset_comment)
57 return self._get_instance(ChangesetComment, changeset_comment)
58
58
59 def __get_pull_request(self, pull_request):
59 def __get_pull_request(self, pull_request):
60 return self._get_instance(PullRequest, pull_request)
60 return self._get_instance(PullRequest, pull_request)
61
61
62 def _extract_mentions(self, s):
62 def _extract_mentions(self, s):
63 user_objects = []
63 user_objects = []
64 for username in extract_mentioned_users(s):
64 for username in extract_mentioned_users(s):
65 user_obj = User.get_by_username(username, case_insensitive=True)
65 user_obj = User.get_by_username(username, case_insensitive=True)
66 if user_obj:
66 if user_obj:
67 user_objects.append(user_obj)
67 user_objects.append(user_obj)
68 return user_objects
68 return user_objects
69
69
70 def _get_renderer(self, global_renderer='rst', request=None):
70 def _get_renderer(self, global_renderer='rst', request=None):
71 request = request or get_current_request()
71 request = request or get_current_request()
72
72
73 try:
73 try:
74 global_renderer = request.call_context.visual.default_renderer
74 global_renderer = request.call_context.visual.default_renderer
75 except AttributeError:
75 except AttributeError:
76 log.debug("Renderer not set, falling back "
76 log.debug("Renderer not set, falling back "
77 "to default renderer '%s'", global_renderer)
77 "to default renderer '%s'", global_renderer)
78 except Exception:
78 except Exception:
79 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
80 return global_renderer
80 return global_renderer
81
81
82 def aggregate_comments(self, comments, versions, show_version, inline=False):
82 def aggregate_comments(self, comments, versions, show_version, inline=False):
83 # group by versions, and count until, and display objects
83 # group by versions, and count until, and display objects
84
84
85 comment_groups = collections.defaultdict(list)
85 comment_groups = collections.defaultdict(list)
86 [comment_groups[
86 [comment_groups[
87 _co.pull_request_version_id].append(_co) for _co in comments]
87 _co.pull_request_version_id].append(_co) for _co in comments]
88
88
89 def yield_comments(pos):
89 def yield_comments(pos):
90 for co in comment_groups[pos]:
90 for co in comment_groups[pos]:
91 yield co
91 yield co
92
92
93 comment_versions = collections.defaultdict(
93 comment_versions = collections.defaultdict(
94 lambda: collections.defaultdict(list))
94 lambda: collections.defaultdict(list))
95 prev_prvid = -1
95 prev_prvid = -1
96 # fake last entry with None, to aggregate on "latest" version which
96 # fake last entry with None, to aggregate on "latest" version which
97 # doesn't have an pull_request_version_id
97 # doesn't have an pull_request_version_id
98 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
98 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
99 prvid = ver.pull_request_version_id
99 prvid = ver.pull_request_version_id
100 if prev_prvid == -1:
100 if prev_prvid == -1:
101 prev_prvid = prvid
101 prev_prvid = prvid
102
102
103 for co in yield_comments(prvid):
103 for co in yield_comments(prvid):
104 comment_versions[prvid]['at'].append(co)
104 comment_versions[prvid]['at'].append(co)
105
105
106 # save until
106 # save until
107 current = comment_versions[prvid]['at']
107 current = comment_versions[prvid]['at']
108 prev_until = comment_versions[prev_prvid]['until']
108 prev_until = comment_versions[prev_prvid]['until']
109 cur_until = prev_until + current
109 cur_until = prev_until + current
110 comment_versions[prvid]['until'].extend(cur_until)
110 comment_versions[prvid]['until'].extend(cur_until)
111
111
112 # save outdated
112 # save outdated
113 if inline:
113 if inline:
114 outdated = [x for x in cur_until
114 outdated = [x for x in cur_until
115 if x.outdated_at_version(show_version)]
115 if x.outdated_at_version(show_version)]
116 else:
116 else:
117 outdated = [x for x in cur_until
117 outdated = [x for x in cur_until
118 if x.older_than_version(show_version)]
118 if x.older_than_version(show_version)]
119 display = [x for x in cur_until if x not in outdated]
119 display = [x for x in cur_until if x not in outdated]
120
120
121 comment_versions[prvid]['outdated'] = outdated
121 comment_versions[prvid]['outdated'] = outdated
122 comment_versions[prvid]['display'] = display
122 comment_versions[prvid]['display'] = display
123
123
124 prev_prvid = prvid
124 prev_prvid = prvid
125
125
126 return comment_versions
126 return comment_versions
127
127
128 def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None):
128 def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None):
129 qry = Session().query(ChangesetComment) \
129 qry = Session().query(ChangesetComment) \
130 .filter(ChangesetComment.repo == repo)
130 .filter(ChangesetComment.repo == repo)
131
131
132 if comment_type and comment_type in ChangesetComment.COMMENT_TYPES:
132 if comment_type and comment_type in ChangesetComment.COMMENT_TYPES:
133 qry = qry.filter(ChangesetComment.comment_type == comment_type)
133 qry = qry.filter(ChangesetComment.comment_type == comment_type)
134
134
135 if user:
135 if user:
136 user = self._get_user(user)
136 user = self._get_user(user)
137 if user:
137 if user:
138 qry = qry.filter(ChangesetComment.user_id == user.user_id)
138 qry = qry.filter(ChangesetComment.user_id == user.user_id)
139
139
140 if commit_id:
140 if commit_id:
141 qry = qry.filter(ChangesetComment.revision == commit_id)
141 qry = qry.filter(ChangesetComment.revision == commit_id)
142
142
143 qry = qry.order_by(ChangesetComment.created_on)
143 qry = qry.order_by(ChangesetComment.created_on)
144 return qry.all()
144 return qry.all()
145
145
146 def get_repository_unresolved_todos(self, repo):
146 def get_repository_unresolved_todos(self, repo):
147 todos = Session().query(ChangesetComment) \
147 todos = Session().query(ChangesetComment) \
148 .filter(ChangesetComment.repo == repo) \
148 .filter(ChangesetComment.repo == repo) \
149 .filter(ChangesetComment.resolved_by == None) \
149 .filter(ChangesetComment.resolved_by == None) \
150 .filter(ChangesetComment.comment_type
150 .filter(ChangesetComment.comment_type
151 == ChangesetComment.COMMENT_TYPE_TODO)
151 == ChangesetComment.COMMENT_TYPE_TODO)
152 todos = todos.all()
152 todos = todos.all()
153
153
154 return todos
154 return todos
155
155
156 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
156 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
157
157
158 todos = Session().query(ChangesetComment) \
158 todos = Session().query(ChangesetComment) \
159 .filter(ChangesetComment.pull_request == pull_request) \
159 .filter(ChangesetComment.pull_request == pull_request) \
160 .filter(ChangesetComment.resolved_by == None) \
160 .filter(ChangesetComment.resolved_by == None) \
161 .filter(ChangesetComment.comment_type
161 .filter(ChangesetComment.comment_type
162 == ChangesetComment.COMMENT_TYPE_TODO)
162 == ChangesetComment.COMMENT_TYPE_TODO)
163
163
164 if not show_outdated:
164 if not show_outdated:
165 todos = todos.filter(
165 todos = todos.filter(
166 coalesce(ChangesetComment.display_state, '') !=
166 coalesce(ChangesetComment.display_state, '') !=
167 ChangesetComment.COMMENT_OUTDATED)
167 ChangesetComment.COMMENT_OUTDATED)
168
168
169 todos = todos.all()
169 todos = todos.all()
170
170
171 return todos
171 return todos
172
172
173 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
173 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
174
174
175 todos = Session().query(ChangesetComment) \
175 todos = Session().query(ChangesetComment) \
176 .filter(ChangesetComment.pull_request == pull_request) \
176 .filter(ChangesetComment.pull_request == pull_request) \
177 .filter(ChangesetComment.resolved_by != None) \
177 .filter(ChangesetComment.resolved_by != None) \
178 .filter(ChangesetComment.comment_type
178 .filter(ChangesetComment.comment_type
179 == ChangesetComment.COMMENT_TYPE_TODO)
179 == ChangesetComment.COMMENT_TYPE_TODO)
180
180
181 if not show_outdated:
181 if not show_outdated:
182 todos = todos.filter(
182 todos = todos.filter(
183 coalesce(ChangesetComment.display_state, '') !=
183 coalesce(ChangesetComment.display_state, '') !=
184 ChangesetComment.COMMENT_OUTDATED)
184 ChangesetComment.COMMENT_OUTDATED)
185
185
186 todos = todos.all()
186 todos = todos.all()
187
187
188 return todos
188 return todos
189
189
190 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
190 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
191
191
192 todos = Session().query(ChangesetComment) \
192 todos = Session().query(ChangesetComment) \
193 .filter(ChangesetComment.revision == commit_id) \
193 .filter(ChangesetComment.revision == commit_id) \
194 .filter(ChangesetComment.resolved_by == None) \
194 .filter(ChangesetComment.resolved_by == None) \
195 .filter(ChangesetComment.comment_type
195 .filter(ChangesetComment.comment_type
196 == ChangesetComment.COMMENT_TYPE_TODO)
196 == ChangesetComment.COMMENT_TYPE_TODO)
197
197
198 if not show_outdated:
198 if not show_outdated:
199 todos = todos.filter(
199 todos = todos.filter(
200 coalesce(ChangesetComment.display_state, '') !=
200 coalesce(ChangesetComment.display_state, '') !=
201 ChangesetComment.COMMENT_OUTDATED)
201 ChangesetComment.COMMENT_OUTDATED)
202
202
203 todos = todos.all()
203 todos = todos.all()
204
204
205 return todos
205 return todos
206
206
207 def get_commit_resolved_todos(self, commit_id, show_outdated=True):
207 def get_commit_resolved_todos(self, commit_id, show_outdated=True):
208
208
209 todos = Session().query(ChangesetComment) \
209 todos = Session().query(ChangesetComment) \
210 .filter(ChangesetComment.revision == commit_id) \
210 .filter(ChangesetComment.revision == commit_id) \
211 .filter(ChangesetComment.resolved_by != None) \
211 .filter(ChangesetComment.resolved_by != None) \
212 .filter(ChangesetComment.comment_type
212 .filter(ChangesetComment.comment_type
213 == ChangesetComment.COMMENT_TYPE_TODO)
213 == ChangesetComment.COMMENT_TYPE_TODO)
214
214
215 if not show_outdated:
215 if not show_outdated:
216 todos = todos.filter(
216 todos = todos.filter(
217 coalesce(ChangesetComment.display_state, '') !=
217 coalesce(ChangesetComment.display_state, '') !=
218 ChangesetComment.COMMENT_OUTDATED)
218 ChangesetComment.COMMENT_OUTDATED)
219
219
220 todos = todos.all()
220 todos = todos.all()
221
221
222 return todos
222 return todos
223
223
224 def _log_audit_action(self, action, action_data, auth_user, comment):
224 def _log_audit_action(self, action, action_data, auth_user, comment):
225 audit_logger.store(
225 audit_logger.store(
226 action=action,
226 action=action,
227 action_data=action_data,
227 action_data=action_data,
228 user=auth_user,
228 user=auth_user,
229 repo=comment.repo)
229 repo=comment.repo)
230
230
231 def create(self, text, repo, user, commit_id=None, pull_request=None,
231 def create(self, text, repo, user, commit_id=None, pull_request=None,
232 f_path=None, line_no=None, status_change=None,
232 f_path=None, line_no=None, status_change=None,
233 status_change_type=None, comment_type=None,
233 status_change_type=None, comment_type=None,
234 resolves_comment_id=None, closing_pr=False, send_email=True,
234 resolves_comment_id=None, closing_pr=False, send_email=True,
235 renderer=None, auth_user=None, extra_recipients=None):
235 renderer=None, auth_user=None, extra_recipients=None):
236 """
236 """
237 Creates new comment for commit or pull request.
237 Creates new comment for commit or pull request.
238 IF status_change is not none this comment is associated with a
238 IF status_change is not none this comment is associated with a
239 status change of commit or commit associated with pull request
239 status change of commit or commit associated with pull request
240
240
241 :param text:
241 :param text:
242 :param repo:
242 :param repo:
243 :param user:
243 :param user:
244 :param commit_id:
244 :param commit_id:
245 :param pull_request:
245 :param pull_request:
246 :param f_path:
246 :param f_path:
247 :param line_no:
247 :param line_no:
248 :param status_change: Label for status change
248 :param status_change: Label for status change
249 :param comment_type: Type of comment
249 :param comment_type: Type of comment
250 :param resolves_comment_id: id of comment which this one will resolve
250 :param resolves_comment_id: id of comment which this one will resolve
251 :param status_change_type: type of status change
251 :param status_change_type: type of status change
252 :param closing_pr:
252 :param closing_pr:
253 :param send_email:
253 :param send_email:
254 :param renderer: pick renderer for this comment
254 :param renderer: pick renderer for this comment
255 :param auth_user: current authenticated user calling this method
255 :param auth_user: current authenticated user calling this method
256 :param extra_recipients: list of extra users to be added to recipients
256 :param extra_recipients: list of extra users to be added to recipients
257 """
257 """
258
258
259 if not text:
259 if not text:
260 log.warning('Missing text for comment, skipping...')
260 log.warning('Missing text for comment, skipping...')
261 return
261 return
262 request = get_current_request()
262 request = get_current_request()
263 _ = request.translate
263 _ = request.translate
264
264
265 if not renderer:
265 if not renderer:
266 renderer = self._get_renderer(request=request)
266 renderer = self._get_renderer(request=request)
267
267
268 repo = self._get_repo(repo)
268 repo = self._get_repo(repo)
269 user = self._get_user(user)
269 user = self._get_user(user)
270 auth_user = auth_user or user
270 auth_user = auth_user or user
271
271
272 schema = comment_schema.CommentSchema()
272 schema = comment_schema.CommentSchema()
273 validated_kwargs = schema.deserialize(dict(
273 validated_kwargs = schema.deserialize(dict(
274 comment_body=text,
274 comment_body=text,
275 comment_type=comment_type,
275 comment_type=comment_type,
276 comment_file=f_path,
276 comment_file=f_path,
277 comment_line=line_no,
277 comment_line=line_no,
278 renderer_type=renderer,
278 renderer_type=renderer,
279 status_change=status_change_type,
279 status_change=status_change_type,
280 resolves_comment_id=resolves_comment_id,
280 resolves_comment_id=resolves_comment_id,
281 repo=repo.repo_id,
281 repo=repo.repo_id,
282 user=user.user_id,
282 user=user.user_id,
283 ))
283 ))
284
284
285 comment = ChangesetComment()
285 comment = ChangesetComment()
286 comment.renderer = validated_kwargs['renderer_type']
286 comment.renderer = validated_kwargs['renderer_type']
287 comment.text = validated_kwargs['comment_body']
287 comment.text = validated_kwargs['comment_body']
288 comment.f_path = validated_kwargs['comment_file']
288 comment.f_path = validated_kwargs['comment_file']
289 comment.line_no = validated_kwargs['comment_line']
289 comment.line_no = validated_kwargs['comment_line']
290 comment.comment_type = validated_kwargs['comment_type']
290 comment.comment_type = validated_kwargs['comment_type']
291
291
292 comment.repo = repo
292 comment.repo = repo
293 comment.author = user
293 comment.author = user
294 resolved_comment = self.__get_commit_comment(
294 resolved_comment = self.__get_commit_comment(
295 validated_kwargs['resolves_comment_id'])
295 validated_kwargs['resolves_comment_id'])
296 # check if the comment actually belongs to this PR
296 # check if the comment actually belongs to this PR
297 if resolved_comment and resolved_comment.pull_request and \
297 if resolved_comment and resolved_comment.pull_request and \
298 resolved_comment.pull_request != pull_request:
298 resolved_comment.pull_request != pull_request:
299 log.warning('Comment tried to resolved unrelated todo comment: %s',
299 log.warning('Comment tried to resolved unrelated todo comment: %s',
300 resolved_comment)
300 resolved_comment)
301 # comment not bound to this pull request, forbid
301 # comment not bound to this pull request, forbid
302 resolved_comment = None
302 resolved_comment = None
303
303
304 elif resolved_comment and resolved_comment.repo and \
304 elif resolved_comment and resolved_comment.repo and \
305 resolved_comment.repo != repo:
305 resolved_comment.repo != repo:
306 log.warning('Comment tried to resolved unrelated todo comment: %s',
306 log.warning('Comment tried to resolved unrelated todo comment: %s',
307 resolved_comment)
307 resolved_comment)
308 # comment not bound to this repo, forbid
308 # comment not bound to this repo, forbid
309 resolved_comment = None
309 resolved_comment = None
310
310
311 comment.resolved_comment = resolved_comment
311 comment.resolved_comment = resolved_comment
312
312
313 pull_request_id = pull_request
313 pull_request_id = pull_request
314
314
315 commit_obj = None
315 commit_obj = None
316 pull_request_obj = None
316 pull_request_obj = None
317
317
318 if commit_id:
318 if commit_id:
319 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
319 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
320 # do a lookup, so we don't pass something bad here
320 # do a lookup, so we don't pass something bad here
321 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
321 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
322 comment.revision = commit_obj.raw_id
322 comment.revision = commit_obj.raw_id
323
323
324 elif pull_request_id:
324 elif pull_request_id:
325 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
325 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
326 pull_request_obj = self.__get_pull_request(pull_request_id)
326 pull_request_obj = self.__get_pull_request(pull_request_id)
327 comment.pull_request = pull_request_obj
327 comment.pull_request = pull_request_obj
328 else:
328 else:
329 raise Exception('Please specify commit or pull_request_id')
329 raise Exception('Please specify commit or pull_request_id')
330
330
331 Session().add(comment)
331 Session().add(comment)
332 Session().flush()
332 Session().flush()
333 kwargs = {
333 kwargs = {
334 'user': user,
334 'user': user,
335 'renderer_type': renderer,
335 'renderer_type': renderer,
336 'repo_name': repo.repo_name,
336 'repo_name': repo.repo_name,
337 'status_change': status_change,
337 'status_change': status_change,
338 'status_change_type': status_change_type,
338 'status_change_type': status_change_type,
339 'comment_body': text,
339 'comment_body': text,
340 'comment_file': f_path,
340 'comment_file': f_path,
341 'comment_line': line_no,
341 'comment_line': line_no,
342 'comment_type': comment_type or 'note'
342 'comment_type': comment_type or 'note',
343 'comment_id': comment.comment_id
343 }
344 }
344
345
345 if commit_obj:
346 if commit_obj:
346 recipients = ChangesetComment.get_users(
347 recipients = ChangesetComment.get_users(
347 revision=commit_obj.raw_id)
348 revision=commit_obj.raw_id)
348 # add commit author if it's in RhodeCode system
349 # add commit author if it's in RhodeCode system
349 cs_author = User.get_from_cs_author(commit_obj.author)
350 cs_author = User.get_from_cs_author(commit_obj.author)
350 if not cs_author:
351 if not cs_author:
351 # use repo owner if we cannot extract the author correctly
352 # use repo owner if we cannot extract the author correctly
352 cs_author = repo.user
353 cs_author = repo.user
353 recipients += [cs_author]
354 recipients += [cs_author]
354
355
355 commit_comment_url = self.get_url(comment, request=request)
356 commit_comment_url = self.get_url(comment, request=request)
357 commit_comment_reply_url = self.get_url(
358 comment, request=request,
359 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
356
360
357 target_repo_url = h.link_to(
361 target_repo_url = h.link_to(
358 repo.repo_name,
362 repo.repo_name,
359 h.route_url('repo_summary', repo_name=repo.repo_name))
363 h.route_url('repo_summary', repo_name=repo.repo_name))
360
364
361 # commit specifics
365 # commit specifics
362 kwargs.update({
366 kwargs.update({
363 'commit': commit_obj,
367 'commit': commit_obj,
364 'commit_message': commit_obj.message,
368 'commit_message': commit_obj.message,
365 'commit_target_repo_url': target_repo_url,
369 'commit_target_repo_url': target_repo_url,
366 'commit_comment_url': commit_comment_url,
370 'commit_comment_url': commit_comment_url,
371 'commit_comment_reply_url': commit_comment_reply_url
367 })
372 })
368
373
369 elif pull_request_obj:
374 elif pull_request_obj:
370 # get the current participants of this pull request
375 # get the current participants of this pull request
371 recipients = ChangesetComment.get_users(
376 recipients = ChangesetComment.get_users(
372 pull_request_id=pull_request_obj.pull_request_id)
377 pull_request_id=pull_request_obj.pull_request_id)
373 # add pull request author
378 # add pull request author
374 recipients += [pull_request_obj.author]
379 recipients += [pull_request_obj.author]
375
380
376 # add the reviewers to notification
381 # add the reviewers to notification
377 recipients += [x.user for x in pull_request_obj.reviewers]
382 recipients += [x.user for x in pull_request_obj.reviewers]
378
383
379 pr_target_repo = pull_request_obj.target_repo
384 pr_target_repo = pull_request_obj.target_repo
380 pr_source_repo = pull_request_obj.source_repo
385 pr_source_repo = pull_request_obj.source_repo
381
386
382 pr_comment_url = h.route_url(
387 pr_comment_url = self.get_url(comment, request=request)
383 'pullrequest_show',
388 pr_comment_reply_url = self.get_url(
384 repo_name=pr_target_repo.repo_name,
389 comment, request=request,
385 pull_request_id=pull_request_obj.pull_request_id,
390 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
386 _anchor='comment-%s' % comment.comment_id)
387
391
388 pr_url = h.route_url(
392 pr_url = h.route_url(
389 'pullrequest_show',
393 'pullrequest_show',
390 repo_name=pr_target_repo.repo_name,
394 repo_name=pr_target_repo.repo_name,
391 pull_request_id=pull_request_obj.pull_request_id, )
395 pull_request_id=pull_request_obj.pull_request_id, )
392
396
393 # set some variables for email notification
397 # set some variables for email notification
394 pr_target_repo_url = h.route_url(
398 pr_target_repo_url = h.route_url(
395 'repo_summary', repo_name=pr_target_repo.repo_name)
399 'repo_summary', repo_name=pr_target_repo.repo_name)
396
400
397 pr_source_repo_url = h.route_url(
401 pr_source_repo_url = h.route_url(
398 'repo_summary', repo_name=pr_source_repo.repo_name)
402 'repo_summary', repo_name=pr_source_repo.repo_name)
399
403
400 # pull request specifics
404 # pull request specifics
401 kwargs.update({
405 kwargs.update({
402 'pull_request': pull_request_obj,
406 'pull_request': pull_request_obj,
403 'pr_id': pull_request_obj.pull_request_id,
407 'pr_id': pull_request_obj.pull_request_id,
404 'pull_request_url': pr_url,
408 'pull_request_url': pr_url,
405 'pull_request_target_repo': pr_target_repo,
409 'pull_request_target_repo': pr_target_repo,
406 'pull_request_target_repo_url': pr_target_repo_url,
410 'pull_request_target_repo_url': pr_target_repo_url,
407 'pull_request_source_repo': pr_source_repo,
411 'pull_request_source_repo': pr_source_repo,
408 'pull_request_source_repo_url': pr_source_repo_url,
412 'pull_request_source_repo_url': pr_source_repo_url,
409 'pr_comment_url': pr_comment_url,
413 'pr_comment_url': pr_comment_url,
414 'pr_comment_reply_url': pr_comment_reply_url,
410 'pr_closing': closing_pr,
415 'pr_closing': closing_pr,
411 })
416 })
412
417
413 recipients += [self._get_user(u) for u in (extra_recipients or [])]
418 recipients += [self._get_user(u) for u in (extra_recipients or [])]
414
419
415 if send_email:
420 if send_email:
416 # pre-generate the subject for notification itself
421 # pre-generate the subject for notification itself
417 (subject,
422 (subject,
418 _h, _e, # we don't care about those
423 _h, _e, # we don't care about those
419 body_plaintext) = EmailNotificationModel().render_email(
424 body_plaintext) = EmailNotificationModel().render_email(
420 notification_type, **kwargs)
425 notification_type, **kwargs)
421
426
422 mention_recipients = set(
427 mention_recipients = set(
423 self._extract_mentions(text)).difference(recipients)
428 self._extract_mentions(text)).difference(recipients)
424
429
425 # create notification objects, and emails
430 # create notification objects, and emails
426 NotificationModel().create(
431 NotificationModel().create(
427 created_by=user,
432 created_by=user,
428 notification_subject=subject,
433 notification_subject=subject,
429 notification_body=body_plaintext,
434 notification_body=body_plaintext,
430 notification_type=notification_type,
435 notification_type=notification_type,
431 recipients=recipients,
436 recipients=recipients,
432 mention_recipients=mention_recipients,
437 mention_recipients=mention_recipients,
433 email_kwargs=kwargs,
438 email_kwargs=kwargs,
434 )
439 )
435
440
436 Session().flush()
441 Session().flush()
437 if comment.pull_request:
442 if comment.pull_request:
438 action = 'repo.pull_request.comment.create'
443 action = 'repo.pull_request.comment.create'
439 else:
444 else:
440 action = 'repo.commit.comment.create'
445 action = 'repo.commit.comment.create'
441
446
442 comment_data = comment.get_api_data()
447 comment_data = comment.get_api_data()
443 self._log_audit_action(
448 self._log_audit_action(
444 action, {'data': comment_data}, auth_user, comment)
449 action, {'data': comment_data}, auth_user, comment)
445
450
446 msg_url = ''
451 msg_url = ''
447 channel = None
452 channel = None
448 if commit_obj:
453 if commit_obj:
449 msg_url = commit_comment_url
454 msg_url = commit_comment_url
450 repo_name = repo.repo_name
455 repo_name = repo.repo_name
451 channel = u'/repo${}$/commit/{}'.format(
456 channel = u'/repo${}$/commit/{}'.format(
452 repo_name,
457 repo_name,
453 commit_obj.raw_id
458 commit_obj.raw_id
454 )
459 )
455 elif pull_request_obj:
460 elif pull_request_obj:
456 msg_url = pr_comment_url
461 msg_url = pr_comment_url
457 repo_name = pr_target_repo.repo_name
462 repo_name = pr_target_repo.repo_name
458 channel = u'/repo${}$/pr/{}'.format(
463 channel = u'/repo${}$/pr/{}'.format(
459 repo_name,
464 repo_name,
460 pull_request_id
465 pull_request_id
461 )
466 )
462
467
463 message = '<strong>{}</strong> {} - ' \
468 message = '<strong>{}</strong> {} - ' \
464 '<a onclick="window.location=\'{}\';' \
469 '<a onclick="window.location=\'{}\';' \
465 'window.location.reload()">' \
470 'window.location.reload()">' \
466 '<strong>{}</strong></a>'
471 '<strong>{}</strong></a>'
467 message = message.format(
472 message = message.format(
468 user.username, _('made a comment'), msg_url,
473 user.username, _('made a comment'), msg_url,
469 _('Show it now'))
474 _('Show it now'))
470
475
471 channelstream.post_message(
476 channelstream.post_message(
472 channel, message, user.username,
477 channel, message, user.username,
473 registry=get_current_registry())
478 registry=get_current_registry())
474
479
475 return comment
480 return comment
476
481
477 def delete(self, comment, auth_user):
482 def delete(self, comment, auth_user):
478 """
483 """
479 Deletes given comment
484 Deletes given comment
480 """
485 """
481 comment = self.__get_commit_comment(comment)
486 comment = self.__get_commit_comment(comment)
482 old_data = comment.get_api_data()
487 old_data = comment.get_api_data()
483 Session().delete(comment)
488 Session().delete(comment)
484
489
485 if comment.pull_request:
490 if comment.pull_request:
486 action = 'repo.pull_request.comment.delete'
491 action = 'repo.pull_request.comment.delete'
487 else:
492 else:
488 action = 'repo.commit.comment.delete'
493 action = 'repo.commit.comment.delete'
489
494
490 self._log_audit_action(
495 self._log_audit_action(
491 action, {'old_data': old_data}, auth_user, comment)
496 action, {'old_data': old_data}, auth_user, comment)
492
497
493 return comment
498 return comment
494
499
495 def get_all_comments(self, repo_id, revision=None, pull_request=None):
500 def get_all_comments(self, repo_id, revision=None, pull_request=None):
496 q = ChangesetComment.query()\
501 q = ChangesetComment.query()\
497 .filter(ChangesetComment.repo_id == repo_id)
502 .filter(ChangesetComment.repo_id == repo_id)
498 if revision:
503 if revision:
499 q = q.filter(ChangesetComment.revision == revision)
504 q = q.filter(ChangesetComment.revision == revision)
500 elif pull_request:
505 elif pull_request:
501 pull_request = self.__get_pull_request(pull_request)
506 pull_request = self.__get_pull_request(pull_request)
502 q = q.filter(ChangesetComment.pull_request == pull_request)
507 q = q.filter(ChangesetComment.pull_request == pull_request)
503 else:
508 else:
504 raise Exception('Please specify commit or pull_request')
509 raise Exception('Please specify commit or pull_request')
505 q = q.order_by(ChangesetComment.created_on)
510 q = q.order_by(ChangesetComment.created_on)
506 return q.all()
511 return q.all()
507
512
508 def get_url(self, comment, request=None, permalink=False):
513 def get_url(self, comment, request=None, permalink=False, anchor=None):
509 if not request:
514 if not request:
510 request = get_current_request()
515 request = get_current_request()
511
516
512 comment = self.__get_commit_comment(comment)
517 comment = self.__get_commit_comment(comment)
518 if anchor is None:
519 anchor = 'comment-{}'.format(comment.comment_id)
520
513 if comment.pull_request:
521 if comment.pull_request:
514 pull_request = comment.pull_request
522 pull_request = comment.pull_request
515 if permalink:
523 if permalink:
516 return request.route_url(
524 return request.route_url(
517 'pull_requests_global',
525 'pull_requests_global',
518 pull_request_id=pull_request.pull_request_id,
526 pull_request_id=pull_request.pull_request_id,
519 _anchor='comment-%s' % comment.comment_id)
527 _anchor=anchor)
520 else:
528 else:
521 return request.route_url(
529 return request.route_url(
522 'pullrequest_show',
530 'pullrequest_show',
523 repo_name=safe_str(pull_request.target_repo.repo_name),
531 repo_name=safe_str(pull_request.target_repo.repo_name),
524 pull_request_id=pull_request.pull_request_id,
532 pull_request_id=pull_request.pull_request_id,
525 _anchor='comment-%s' % comment.comment_id)
533 _anchor=anchor)
526
534
527 else:
535 else:
528 repo = comment.repo
536 repo = comment.repo
529 commit_id = comment.revision
537 commit_id = comment.revision
530
538
531 if permalink:
539 if permalink:
532 return request.route_url(
540 return request.route_url(
533 'repo_commit', repo_name=safe_str(repo.repo_id),
541 'repo_commit', repo_name=safe_str(repo.repo_id),
534 commit_id=commit_id,
542 commit_id=commit_id,
535 _anchor='comment-%s' % comment.comment_id)
543 _anchor=anchor)
536
544
537 else:
545 else:
538 return request.route_url(
546 return request.route_url(
539 'repo_commit', repo_name=safe_str(repo.repo_name),
547 'repo_commit', repo_name=safe_str(repo.repo_name),
540 commit_id=commit_id,
548 commit_id=commit_id,
541 _anchor='comment-%s' % comment.comment_id)
549 _anchor=anchor)
542
550
543 def get_comments(self, repo_id, revision=None, pull_request=None):
551 def get_comments(self, repo_id, revision=None, pull_request=None):
544 """
552 """
545 Gets main comments based on revision or pull_request_id
553 Gets main comments based on revision or pull_request_id
546
554
547 :param repo_id:
555 :param repo_id:
548 :param revision:
556 :param revision:
549 :param pull_request:
557 :param pull_request:
550 """
558 """
551
559
552 q = ChangesetComment.query()\
560 q = ChangesetComment.query()\
553 .filter(ChangesetComment.repo_id == repo_id)\
561 .filter(ChangesetComment.repo_id == repo_id)\
554 .filter(ChangesetComment.line_no == None)\
562 .filter(ChangesetComment.line_no == None)\
555 .filter(ChangesetComment.f_path == None)
563 .filter(ChangesetComment.f_path == None)
556 if revision:
564 if revision:
557 q = q.filter(ChangesetComment.revision == revision)
565 q = q.filter(ChangesetComment.revision == revision)
558 elif pull_request:
566 elif pull_request:
559 pull_request = self.__get_pull_request(pull_request)
567 pull_request = self.__get_pull_request(pull_request)
560 q = q.filter(ChangesetComment.pull_request == pull_request)
568 q = q.filter(ChangesetComment.pull_request == pull_request)
561 else:
569 else:
562 raise Exception('Please specify commit or pull_request')
570 raise Exception('Please specify commit or pull_request')
563 q = q.order_by(ChangesetComment.created_on)
571 q = q.order_by(ChangesetComment.created_on)
564 return q.all()
572 return q.all()
565
573
566 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
574 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
567 q = self._get_inline_comments_query(repo_id, revision, pull_request)
575 q = self._get_inline_comments_query(repo_id, revision, pull_request)
568 return self._group_comments_by_path_and_line_number(q)
576 return self._group_comments_by_path_and_line_number(q)
569
577
570 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
578 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
571 version=None):
579 version=None):
572 inline_cnt = 0
580 inline_cnt = 0
573 for fname, per_line_comments in inline_comments.iteritems():
581 for fname, per_line_comments in inline_comments.iteritems():
574 for lno, comments in per_line_comments.iteritems():
582 for lno, comments in per_line_comments.iteritems():
575 for comm in comments:
583 for comm in comments:
576 if not comm.outdated_at_version(version) and skip_outdated:
584 if not comm.outdated_at_version(version) and skip_outdated:
577 inline_cnt += 1
585 inline_cnt += 1
578
586
579 return inline_cnt
587 return inline_cnt
580
588
581 def get_outdated_comments(self, repo_id, pull_request):
589 def get_outdated_comments(self, repo_id, pull_request):
582 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
590 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
583 # of a pull request.
591 # of a pull request.
584 q = self._all_inline_comments_of_pull_request(pull_request)
592 q = self._all_inline_comments_of_pull_request(pull_request)
585 q = q.filter(
593 q = q.filter(
586 ChangesetComment.display_state ==
594 ChangesetComment.display_state ==
587 ChangesetComment.COMMENT_OUTDATED
595 ChangesetComment.COMMENT_OUTDATED
588 ).order_by(ChangesetComment.comment_id.asc())
596 ).order_by(ChangesetComment.comment_id.asc())
589
597
590 return self._group_comments_by_path_and_line_number(q)
598 return self._group_comments_by_path_and_line_number(q)
591
599
592 def _get_inline_comments_query(self, repo_id, revision, pull_request):
600 def _get_inline_comments_query(self, repo_id, revision, pull_request):
593 # TODO: johbo: Split this into two methods: One for PR and one for
601 # TODO: johbo: Split this into two methods: One for PR and one for
594 # commit.
602 # commit.
595 if revision:
603 if revision:
596 q = Session().query(ChangesetComment).filter(
604 q = Session().query(ChangesetComment).filter(
597 ChangesetComment.repo_id == repo_id,
605 ChangesetComment.repo_id == repo_id,
598 ChangesetComment.line_no != null(),
606 ChangesetComment.line_no != null(),
599 ChangesetComment.f_path != null(),
607 ChangesetComment.f_path != null(),
600 ChangesetComment.revision == revision)
608 ChangesetComment.revision == revision)
601
609
602 elif pull_request:
610 elif pull_request:
603 pull_request = self.__get_pull_request(pull_request)
611 pull_request = self.__get_pull_request(pull_request)
604 if not CommentsModel.use_outdated_comments(pull_request):
612 if not CommentsModel.use_outdated_comments(pull_request):
605 q = self._visible_inline_comments_of_pull_request(pull_request)
613 q = self._visible_inline_comments_of_pull_request(pull_request)
606 else:
614 else:
607 q = self._all_inline_comments_of_pull_request(pull_request)
615 q = self._all_inline_comments_of_pull_request(pull_request)
608
616
609 else:
617 else:
610 raise Exception('Please specify commit or pull_request_id')
618 raise Exception('Please specify commit or pull_request_id')
611 q = q.order_by(ChangesetComment.comment_id.asc())
619 q = q.order_by(ChangesetComment.comment_id.asc())
612 return q
620 return q
613
621
614 def _group_comments_by_path_and_line_number(self, q):
622 def _group_comments_by_path_and_line_number(self, q):
615 comments = q.all()
623 comments = q.all()
616 paths = collections.defaultdict(lambda: collections.defaultdict(list))
624 paths = collections.defaultdict(lambda: collections.defaultdict(list))
617 for co in comments:
625 for co in comments:
618 paths[co.f_path][co.line_no].append(co)
626 paths[co.f_path][co.line_no].append(co)
619 return paths
627 return paths
620
628
621 @classmethod
629 @classmethod
622 def needed_extra_diff_context(cls):
630 def needed_extra_diff_context(cls):
623 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
631 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
624
632
625 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
633 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
626 if not CommentsModel.use_outdated_comments(pull_request):
634 if not CommentsModel.use_outdated_comments(pull_request):
627 return
635 return
628
636
629 comments = self._visible_inline_comments_of_pull_request(pull_request)
637 comments = self._visible_inline_comments_of_pull_request(pull_request)
630 comments_to_outdate = comments.all()
638 comments_to_outdate = comments.all()
631
639
632 for comment in comments_to_outdate:
640 for comment in comments_to_outdate:
633 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
641 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
634
642
635 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
643 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
636 diff_line = _parse_comment_line_number(comment.line_no)
644 diff_line = _parse_comment_line_number(comment.line_no)
637
645
638 try:
646 try:
639 old_context = old_diff_proc.get_context_of_line(
647 old_context = old_diff_proc.get_context_of_line(
640 path=comment.f_path, diff_line=diff_line)
648 path=comment.f_path, diff_line=diff_line)
641 new_context = new_diff_proc.get_context_of_line(
649 new_context = new_diff_proc.get_context_of_line(
642 path=comment.f_path, diff_line=diff_line)
650 path=comment.f_path, diff_line=diff_line)
643 except (diffs.LineNotInDiffException,
651 except (diffs.LineNotInDiffException,
644 diffs.FileNotInDiffException):
652 diffs.FileNotInDiffException):
645 comment.display_state = ChangesetComment.COMMENT_OUTDATED
653 comment.display_state = ChangesetComment.COMMENT_OUTDATED
646 return
654 return
647
655
648 if old_context == new_context:
656 if old_context == new_context:
649 return
657 return
650
658
651 if self._should_relocate_diff_line(diff_line):
659 if self._should_relocate_diff_line(diff_line):
652 new_diff_lines = new_diff_proc.find_context(
660 new_diff_lines = new_diff_proc.find_context(
653 path=comment.f_path, context=old_context,
661 path=comment.f_path, context=old_context,
654 offset=self.DIFF_CONTEXT_BEFORE)
662 offset=self.DIFF_CONTEXT_BEFORE)
655 if not new_diff_lines:
663 if not new_diff_lines:
656 comment.display_state = ChangesetComment.COMMENT_OUTDATED
664 comment.display_state = ChangesetComment.COMMENT_OUTDATED
657 else:
665 else:
658 new_diff_line = self._choose_closest_diff_line(
666 new_diff_line = self._choose_closest_diff_line(
659 diff_line, new_diff_lines)
667 diff_line, new_diff_lines)
660 comment.line_no = _diff_to_comment_line_number(new_diff_line)
668 comment.line_no = _diff_to_comment_line_number(new_diff_line)
661 else:
669 else:
662 comment.display_state = ChangesetComment.COMMENT_OUTDATED
670 comment.display_state = ChangesetComment.COMMENT_OUTDATED
663
671
664 def _should_relocate_diff_line(self, diff_line):
672 def _should_relocate_diff_line(self, diff_line):
665 """
673 """
666 Checks if relocation shall be tried for the given `diff_line`.
674 Checks if relocation shall be tried for the given `diff_line`.
667
675
668 If a comment points into the first lines, then we can have a situation
676 If a comment points into the first lines, then we can have a situation
669 that after an update another line has been added on top. In this case
677 that after an update another line has been added on top. In this case
670 we would find the context still and move the comment around. This
678 we would find the context still and move the comment around. This
671 would be wrong.
679 would be wrong.
672 """
680 """
673 should_relocate = (
681 should_relocate = (
674 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
682 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
675 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
683 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
676 return should_relocate
684 return should_relocate
677
685
678 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
686 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
679 candidate = new_diff_lines[0]
687 candidate = new_diff_lines[0]
680 best_delta = _diff_line_delta(diff_line, candidate)
688 best_delta = _diff_line_delta(diff_line, candidate)
681 for new_diff_line in new_diff_lines[1:]:
689 for new_diff_line in new_diff_lines[1:]:
682 delta = _diff_line_delta(diff_line, new_diff_line)
690 delta = _diff_line_delta(diff_line, new_diff_line)
683 if delta < best_delta:
691 if delta < best_delta:
684 candidate = new_diff_line
692 candidate = new_diff_line
685 best_delta = delta
693 best_delta = delta
686 return candidate
694 return candidate
687
695
688 def _visible_inline_comments_of_pull_request(self, pull_request):
696 def _visible_inline_comments_of_pull_request(self, pull_request):
689 comments = self._all_inline_comments_of_pull_request(pull_request)
697 comments = self._all_inline_comments_of_pull_request(pull_request)
690 comments = comments.filter(
698 comments = comments.filter(
691 coalesce(ChangesetComment.display_state, '') !=
699 coalesce(ChangesetComment.display_state, '') !=
692 ChangesetComment.COMMENT_OUTDATED)
700 ChangesetComment.COMMENT_OUTDATED)
693 return comments
701 return comments
694
702
695 def _all_inline_comments_of_pull_request(self, pull_request):
703 def _all_inline_comments_of_pull_request(self, pull_request):
696 comments = Session().query(ChangesetComment)\
704 comments = Session().query(ChangesetComment)\
697 .filter(ChangesetComment.line_no != None)\
705 .filter(ChangesetComment.line_no != None)\
698 .filter(ChangesetComment.f_path != None)\
706 .filter(ChangesetComment.f_path != None)\
699 .filter(ChangesetComment.pull_request == pull_request)
707 .filter(ChangesetComment.pull_request == pull_request)
700 return comments
708 return comments
701
709
702 def _all_general_comments_of_pull_request(self, pull_request):
710 def _all_general_comments_of_pull_request(self, pull_request):
703 comments = Session().query(ChangesetComment)\
711 comments = Session().query(ChangesetComment)\
704 .filter(ChangesetComment.line_no == None)\
712 .filter(ChangesetComment.line_no == None)\
705 .filter(ChangesetComment.f_path == None)\
713 .filter(ChangesetComment.f_path == None)\
706 .filter(ChangesetComment.pull_request == pull_request)
714 .filter(ChangesetComment.pull_request == pull_request)
707 return comments
715 return comments
708
716
709 @staticmethod
717 @staticmethod
710 def use_outdated_comments(pull_request):
718 def use_outdated_comments(pull_request):
711 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
719 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
712 settings = settings_model.get_general_settings()
720 settings = settings_model.get_general_settings()
713 return settings.get('rhodecode_use_outdated_comments', False)
721 return settings.get('rhodecode_use_outdated_comments', False)
714
722
715
723
716 def _parse_comment_line_number(line_no):
724 def _parse_comment_line_number(line_no):
717 """
725 """
718 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
726 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
719 """
727 """
720 old_line = None
728 old_line = None
721 new_line = None
729 new_line = None
722 if line_no.startswith('o'):
730 if line_no.startswith('o'):
723 old_line = int(line_no[1:])
731 old_line = int(line_no[1:])
724 elif line_no.startswith('n'):
732 elif line_no.startswith('n'):
725 new_line = int(line_no[1:])
733 new_line = int(line_no[1:])
726 else:
734 else:
727 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
735 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
728 return diffs.DiffLineNumber(old_line, new_line)
736 return diffs.DiffLineNumber(old_line, new_line)
729
737
730
738
731 def _diff_to_comment_line_number(diff_line):
739 def _diff_to_comment_line_number(diff_line):
732 if diff_line.new is not None:
740 if diff_line.new is not None:
733 return u'n{}'.format(diff_line.new)
741 return u'n{}'.format(diff_line.new)
734 elif diff_line.old is not None:
742 elif diff_line.old is not None:
735 return u'o{}'.format(diff_line.old)
743 return u'o{}'.format(diff_line.old)
736 return u''
744 return u''
737
745
738
746
739 def _diff_line_delta(a, b):
747 def _diff_line_delta(a, b):
740 if None not in (a.new, b.new):
748 if None not in (a.new, b.new):
741 return abs(a.new - b.new)
749 return abs(a.new - b.new)
742 elif None not in (a.old, b.old):
750 elif None not in (a.old, b.old):
743 return abs(a.old - b.old)
751 return abs(a.old - b.old)
744 else:
752 else:
745 raise ValueError(
753 raise ValueError(
746 "Cannot compute delta between {} and {}".format(a, b))
754 "Cannot compute delta between {} and {}".format(a, b))
@@ -1,1289 +1,1293 b''
1 // Default styles
1 // Default styles
2
2
3 .diff-collapse {
3 .diff-collapse {
4 margin: @padding 0;
4 margin: @padding 0;
5 text-align: right;
5 text-align: right;
6 }
6 }
7
7
8 .diff-container {
8 .diff-container {
9 margin-bottom: @space;
9 margin-bottom: @space;
10
10
11 .diffblock {
11 .diffblock {
12 margin-bottom: @space;
12 margin-bottom: @space;
13 }
13 }
14
14
15 &.hidden {
15 &.hidden {
16 display: none;
16 display: none;
17 overflow: hidden;
17 overflow: hidden;
18 }
18 }
19 }
19 }
20
20
21
21
22 div.diffblock .sidebyside {
22 div.diffblock .sidebyside {
23 background: #ffffff;
23 background: #ffffff;
24 }
24 }
25
25
26 div.diffblock {
26 div.diffblock {
27 overflow-x: auto;
27 overflow-x: auto;
28 overflow-y: hidden;
28 overflow-y: hidden;
29 clear: both;
29 clear: both;
30 padding: 0px;
30 padding: 0px;
31 background: @grey6;
31 background: @grey6;
32 border: @border-thickness solid @grey5;
32 border: @border-thickness solid @grey5;
33 -webkit-border-radius: @border-radius @border-radius 0px 0px;
33 -webkit-border-radius: @border-radius @border-radius 0px 0px;
34 border-radius: @border-radius @border-radius 0px 0px;
34 border-radius: @border-radius @border-radius 0px 0px;
35
35
36
36
37 .comments-number {
37 .comments-number {
38 float: right;
38 float: right;
39 }
39 }
40
40
41 // BEGIN CODE-HEADER STYLES
41 // BEGIN CODE-HEADER STYLES
42
42
43 .code-header {
43 .code-header {
44 background: @grey6;
44 background: @grey6;
45 padding: 10px 0 10px 0;
45 padding: 10px 0 10px 0;
46 height: auto;
46 height: auto;
47 width: 100%;
47 width: 100%;
48
48
49 .hash {
49 .hash {
50 float: left;
50 float: left;
51 padding: 2px 0 0 2px;
51 padding: 2px 0 0 2px;
52 }
52 }
53
53
54 .date {
54 .date {
55 float: left;
55 float: left;
56 text-transform: uppercase;
56 text-transform: uppercase;
57 padding: 4px 0px 0px 2px;
57 padding: 4px 0px 0px 2px;
58 }
58 }
59
59
60 div {
60 div {
61 margin-left: 4px;
61 margin-left: 4px;
62 }
62 }
63
63
64 div.compare_header {
64 div.compare_header {
65 min-height: 40px;
65 min-height: 40px;
66 margin: 0;
66 margin: 0;
67 padding: 0 @padding;
67 padding: 0 @padding;
68
68
69 .drop-menu {
69 .drop-menu {
70 float:left;
70 float:left;
71 display: block;
71 display: block;
72 margin:0 0 @padding 0;
72 margin:0 0 @padding 0;
73 }
73 }
74
74
75 .compare-label {
75 .compare-label {
76 float: left;
76 float: left;
77 clear: both;
77 clear: both;
78 display: inline-block;
78 display: inline-block;
79 min-width: 5em;
79 min-width: 5em;
80 margin: 0;
80 margin: 0;
81 padding: @button-padding @button-padding @button-padding 0;
81 padding: @button-padding @button-padding @button-padding 0;
82 font-weight: @text-semibold-weight;
82 font-weight: @text-semibold-weight;
83 font-family: @text-semibold;
83 font-family: @text-semibold;
84 }
84 }
85
85
86 .compare-buttons {
86 .compare-buttons {
87 float: left;
87 float: left;
88 margin: 0;
88 margin: 0;
89 padding: 0 0 @padding;
89 padding: 0 0 @padding;
90
90
91 .btn {
91 .btn {
92 margin: 0 @padding 0 0;
92 margin: 0 @padding 0 0;
93 }
93 }
94 }
94 }
95 }
95 }
96
96
97 }
97 }
98
98
99 .parents {
99 .parents {
100 float: left;
100 float: left;
101 width: 100px;
101 width: 100px;
102 font-weight: 400;
102 font-weight: 400;
103 vertical-align: middle;
103 vertical-align: middle;
104 padding: 0px 2px 0px 2px;
104 padding: 0px 2px 0px 2px;
105 background-color: @grey6;
105 background-color: @grey6;
106
106
107 #parent_link {
107 #parent_link {
108 margin: 00px 2px;
108 margin: 00px 2px;
109
109
110 &.double {
110 &.double {
111 margin: 0px 2px;
111 margin: 0px 2px;
112 }
112 }
113
113
114 &.disabled{
114 &.disabled{
115 margin-right: @padding;
115 margin-right: @padding;
116 }
116 }
117 }
117 }
118 }
118 }
119
119
120 .children {
120 .children {
121 float: right;
121 float: right;
122 width: 100px;
122 width: 100px;
123 font-weight: 400;
123 font-weight: 400;
124 vertical-align: middle;
124 vertical-align: middle;
125 text-align: right;
125 text-align: right;
126 padding: 0px 2px 0px 2px;
126 padding: 0px 2px 0px 2px;
127 background-color: @grey6;
127 background-color: @grey6;
128
128
129 #child_link {
129 #child_link {
130 margin: 0px 2px;
130 margin: 0px 2px;
131
131
132 &.double {
132 &.double {
133 margin: 0px 2px;
133 margin: 0px 2px;
134 }
134 }
135
135
136 &.disabled{
136 &.disabled{
137 margin-right: @padding;
137 margin-right: @padding;
138 }
138 }
139 }
139 }
140 }
140 }
141
141
142 .changeset_header {
142 .changeset_header {
143 height: 16px;
143 height: 16px;
144
144
145 & > div{
145 & > div{
146 margin-right: @padding;
146 margin-right: @padding;
147 }
147 }
148 }
148 }
149
149
150 .changeset_file {
150 .changeset_file {
151 text-align: left;
151 text-align: left;
152 float: left;
152 float: left;
153 padding: 0;
153 padding: 0;
154
154
155 a{
155 a{
156 display: inline-block;
156 display: inline-block;
157 margin-right: 0.5em;
157 margin-right: 0.5em;
158 }
158 }
159
159
160 #selected_mode{
160 #selected_mode{
161 margin-left: 0;
161 margin-left: 0;
162 }
162 }
163 }
163 }
164
164
165 .diff-menu-wrapper {
165 .diff-menu-wrapper {
166 float: left;
166 float: left;
167 }
167 }
168
168
169 .diff-menu {
169 .diff-menu {
170 position: absolute;
170 position: absolute;
171 background: none repeat scroll 0 0 #FFFFFF;
171 background: none repeat scroll 0 0 #FFFFFF;
172 border-color: #003367 @grey3 @grey3;
172 border-color: #003367 @grey3 @grey3;
173 border-right: 1px solid @grey3;
173 border-right: 1px solid @grey3;
174 border-style: solid solid solid;
174 border-style: solid solid solid;
175 border-width: @border-thickness;
175 border-width: @border-thickness;
176 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
176 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
177 margin-top: 5px;
177 margin-top: 5px;
178 margin-left: 1px;
178 margin-left: 1px;
179 }
179 }
180
180
181 .diff-actions, .editor-actions {
181 .diff-actions, .editor-actions {
182 float: left;
182 float: left;
183
183
184 input{
184 input{
185 margin: 0 0.5em 0 0;
185 margin: 0 0.5em 0 0;
186 }
186 }
187 }
187 }
188
188
189 // END CODE-HEADER STYLES
189 // END CODE-HEADER STYLES
190
190
191 // BEGIN CODE-BODY STYLES
191 // BEGIN CODE-BODY STYLES
192
192
193 .code-body {
193 .code-body {
194 padding: 0;
194 padding: 0;
195 background-color: #ffffff;
195 background-color: #ffffff;
196 position: relative;
196 position: relative;
197 max-width: none;
197 max-width: none;
198 box-sizing: border-box;
198 box-sizing: border-box;
199 // TODO: johbo: Parent has overflow: auto, this forces the child here
199 // TODO: johbo: Parent has overflow: auto, this forces the child here
200 // to have the intended size and to scroll. Should be simplified.
200 // to have the intended size and to scroll. Should be simplified.
201 width: 100%;
201 width: 100%;
202 overflow-x: auto;
202 overflow-x: auto;
203 }
203 }
204
204
205 pre.raw {
205 pre.raw {
206 background: white;
206 background: white;
207 color: @grey1;
207 color: @grey1;
208 }
208 }
209 // END CODE-BODY STYLES
209 // END CODE-BODY STYLES
210
210
211 }
211 }
212
212
213
213
214 table.code-difftable {
214 table.code-difftable {
215 border-collapse: collapse;
215 border-collapse: collapse;
216 width: 99%;
216 width: 99%;
217 border-radius: 0px !important;
217 border-radius: 0px !important;
218
218
219 td {
219 td {
220 padding: 0 !important;
220 padding: 0 !important;
221 background: none !important;
221 background: none !important;
222 border: 0 !important;
222 border: 0 !important;
223 }
223 }
224
224
225 .context {
225 .context {
226 background: none repeat scroll 0 0 #DDE7EF;
226 background: none repeat scroll 0 0 #DDE7EF;
227 }
227 }
228
228
229 .add {
229 .add {
230 background: none repeat scroll 0 0 #DDFFDD;
230 background: none repeat scroll 0 0 #DDFFDD;
231
231
232 ins {
232 ins {
233 background: none repeat scroll 0 0 #AAFFAA;
233 background: none repeat scroll 0 0 #AAFFAA;
234 text-decoration: none;
234 text-decoration: none;
235 }
235 }
236 }
236 }
237
237
238 .del {
238 .del {
239 background: none repeat scroll 0 0 #FFDDDD;
239 background: none repeat scroll 0 0 #FFDDDD;
240
240
241 del {
241 del {
242 background: none repeat scroll 0 0 #FFAAAA;
242 background: none repeat scroll 0 0 #FFAAAA;
243 text-decoration: none;
243 text-decoration: none;
244 }
244 }
245 }
245 }
246
246
247 /** LINE NUMBERS **/
247 /** LINE NUMBERS **/
248 .lineno {
248 .lineno {
249 padding-left: 2px !important;
249 padding-left: 2px !important;
250 padding-right: 2px;
250 padding-right: 2px;
251 text-align: right;
251 text-align: right;
252 width: 32px;
252 width: 32px;
253 -moz-user-select: none;
253 -moz-user-select: none;
254 -webkit-user-select: none;
254 -webkit-user-select: none;
255 border-right: @border-thickness solid @grey5 !important;
255 border-right: @border-thickness solid @grey5 !important;
256 border-left: 0px solid #CCC !important;
256 border-left: 0px solid #CCC !important;
257 border-top: 0px solid #CCC !important;
257 border-top: 0px solid #CCC !important;
258 border-bottom: none !important;
258 border-bottom: none !important;
259
259
260 a {
260 a {
261 &:extend(pre);
261 &:extend(pre);
262 text-align: right;
262 text-align: right;
263 padding-right: 2px;
263 padding-right: 2px;
264 cursor: pointer;
264 cursor: pointer;
265 display: block;
265 display: block;
266 width: 32px;
266 width: 32px;
267 }
267 }
268 }
268 }
269
269
270 .context {
270 .context {
271 cursor: auto;
271 cursor: auto;
272 &:extend(pre);
272 &:extend(pre);
273 }
273 }
274
274
275 .lineno-inline {
275 .lineno-inline {
276 background: none repeat scroll 0 0 #FFF !important;
276 background: none repeat scroll 0 0 #FFF !important;
277 padding-left: 2px;
277 padding-left: 2px;
278 padding-right: 2px;
278 padding-right: 2px;
279 text-align: right;
279 text-align: right;
280 width: 30px;
280 width: 30px;
281 -moz-user-select: none;
281 -moz-user-select: none;
282 -webkit-user-select: none;
282 -webkit-user-select: none;
283 }
283 }
284
284
285 /** CODE **/
285 /** CODE **/
286 .code {
286 .code {
287 display: block;
287 display: block;
288 width: 100%;
288 width: 100%;
289
289
290 td {
290 td {
291 margin: 0;
291 margin: 0;
292 padding: 0;
292 padding: 0;
293 }
293 }
294
294
295 pre {
295 pre {
296 margin: 0;
296 margin: 0;
297 padding: 0;
297 padding: 0;
298 margin-left: .5em;
298 margin-left: .5em;
299 }
299 }
300 }
300 }
301 }
301 }
302
302
303
303
304 // Comments
304 // Comments
305
305 .comment-selected-hl {
306 div.comment:target {
307 border-left: 6px solid @comment-highlight-color !important;
306 border-left: 6px solid @comment-highlight-color !important;
308 padding-left: 3px;
307 padding-left: 3px !important;
309 margin-left: -9px;
308 margin-left: -7px !important;
309 }
310
311 div.comment:target,
312 div.comment-outdated:target {
313 .comment-selected-hl;
310 }
314 }
311
315
312 //TODO: anderson: can't get an absolute number out of anything, so had to put the
316 //TODO: anderson: can't get an absolute number out of anything, so had to put the
313 //current values that might change. But to make it clear I put as a calculation
317 //current values that might change. But to make it clear I put as a calculation
314 @comment-max-width: 1065px;
318 @comment-max-width: 1065px;
315 @pr-extra-margin: 34px;
319 @pr-extra-margin: 34px;
316 @pr-border-spacing: 4px;
320 @pr-border-spacing: 4px;
317 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
321 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
318
322
319 // Pull Request
323 // Pull Request
320 .cs_files .code-difftable {
324 .cs_files .code-difftable {
321 border: @border-thickness solid @grey5; //borders only on PRs
325 border: @border-thickness solid @grey5; //borders only on PRs
322
326
323 .comment-inline-form,
327 .comment-inline-form,
324 div.comment {
328 div.comment {
325 width: @pr-comment-width;
329 width: @pr-comment-width;
326 }
330 }
327 }
331 }
328
332
329 // Changeset
333 // Changeset
330 .code-difftable {
334 .code-difftable {
331 .comment-inline-form,
335 .comment-inline-form,
332 div.comment {
336 div.comment {
333 width: @comment-max-width;
337 width: @comment-max-width;
334 }
338 }
335 }
339 }
336
340
337 //Style page
341 //Style page
338 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
342 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
339 #style-page .code-difftable{
343 #style-page .code-difftable{
340 .comment-inline-form,
344 .comment-inline-form,
341 div.comment {
345 div.comment {
342 width: @comment-max-width - @style-extra-margin;
346 width: @comment-max-width - @style-extra-margin;
343 }
347 }
344 }
348 }
345
349
346 #context-bar > h2 {
350 #context-bar > h2 {
347 font-size: 20px;
351 font-size: 20px;
348 }
352 }
349
353
350 #context-bar > h2> a {
354 #context-bar > h2> a {
351 font-size: 20px;
355 font-size: 20px;
352 }
356 }
353 // end of defaults
357 // end of defaults
354
358
355 .file_diff_buttons {
359 .file_diff_buttons {
356 padding: 0 0 @padding;
360 padding: 0 0 @padding;
357
361
358 .drop-menu {
362 .drop-menu {
359 float: left;
363 float: left;
360 margin: 0 @padding 0 0;
364 margin: 0 @padding 0 0;
361 }
365 }
362 .btn {
366 .btn {
363 margin: 0 @padding 0 0;
367 margin: 0 @padding 0 0;
364 }
368 }
365 }
369 }
366
370
367 .code-body.textarea.editor {
371 .code-body.textarea.editor {
368 max-width: none;
372 max-width: none;
369 padding: 15px;
373 padding: 15px;
370 }
374 }
371
375
372 td.injected_diff{
376 td.injected_diff{
373 max-width: 1178px;
377 max-width: 1178px;
374 overflow-x: auto;
378 overflow-x: auto;
375 overflow-y: hidden;
379 overflow-y: hidden;
376
380
377 div.diff-container,
381 div.diff-container,
378 div.diffblock{
382 div.diffblock{
379 max-width: 100%;
383 max-width: 100%;
380 }
384 }
381
385
382 div.code-body {
386 div.code-body {
383 max-width: 1124px;
387 max-width: 1124px;
384 overflow-x: auto;
388 overflow-x: auto;
385 overflow-y: hidden;
389 overflow-y: hidden;
386 padding: 0;
390 padding: 0;
387 }
391 }
388 div.diffblock {
392 div.diffblock {
389 border: none;
393 border: none;
390 }
394 }
391
395
392 &.inline-form {
396 &.inline-form {
393 width: 99%
397 width: 99%
394 }
398 }
395 }
399 }
396
400
397
401
398 table.code-difftable {
402 table.code-difftable {
399 width: 100%;
403 width: 100%;
400 }
404 }
401
405
402 /** PYGMENTS COLORING **/
406 /** PYGMENTS COLORING **/
403 div.codeblock {
407 div.codeblock {
404
408
405 // TODO: johbo: Added interim to get rid of the margin around
409 // TODO: johbo: Added interim to get rid of the margin around
406 // Select2 widgets. This needs further cleanup.
410 // Select2 widgets. This needs further cleanup.
407 overflow: auto;
411 overflow: auto;
408 padding: 0px;
412 padding: 0px;
409 border: @border-thickness solid @grey6;
413 border: @border-thickness solid @grey6;
410 .border-radius(@border-radius);
414 .border-radius(@border-radius);
411
415
412 #remove_gist {
416 #remove_gist {
413 float: right;
417 float: right;
414 }
418 }
415
419
416 .gist_url {
420 .gist_url {
417 padding: 0px 0px 10px 0px;
421 padding: 0px 0px 10px 0px;
418 }
422 }
419
423
420 .author {
424 .author {
421 clear: both;
425 clear: both;
422 vertical-align: middle;
426 vertical-align: middle;
423 font-weight: @text-bold-weight;
427 font-weight: @text-bold-weight;
424 font-family: @text-bold;
428 font-family: @text-bold;
425 }
429 }
426
430
427 .btn-mini {
431 .btn-mini {
428 float: left;
432 float: left;
429 margin: 0 5px 0 0;
433 margin: 0 5px 0 0;
430 }
434 }
431
435
432 .code-header {
436 .code-header {
433 padding: @padding;
437 padding: @padding;
434 border-bottom: @border-thickness solid @grey5;
438 border-bottom: @border-thickness solid @grey5;
435
439
436 .rc-user {
440 .rc-user {
437 min-width: 0;
441 min-width: 0;
438 margin-right: .5em;
442 margin-right: .5em;
439 }
443 }
440
444
441 .stats {
445 .stats {
442 clear: both;
446 clear: both;
443 margin: 0 0 @padding 0;
447 margin: 0 0 @padding 0;
444 padding: 0;
448 padding: 0;
445 .left {
449 .left {
446 float: left;
450 float: left;
447 clear: left;
451 clear: left;
448 max-width: 75%;
452 max-width: 75%;
449 margin: 0 0 @padding 0;
453 margin: 0 0 @padding 0;
450
454
451 &.item {
455 &.item {
452 margin-right: @padding;
456 margin-right: @padding;
453 &.last { border-right: none; }
457 &.last { border-right: none; }
454 }
458 }
455 }
459 }
456 .buttons { float: right; }
460 .buttons { float: right; }
457 .author {
461 .author {
458 height: 25px; margin-left: 15px; font-weight: bold;
462 height: 25px; margin-left: 15px; font-weight: bold;
459 }
463 }
460 }
464 }
461
465
462 .commit {
466 .commit {
463 margin: 5px 0 0 26px;
467 margin: 5px 0 0 26px;
464 font-weight: normal;
468 font-weight: normal;
465 white-space: pre-wrap;
469 white-space: pre-wrap;
466 }
470 }
467 }
471 }
468
472
469 .message {
473 .message {
470 position: relative;
474 position: relative;
471 margin: @padding;
475 margin: @padding;
472
476
473 .codeblock-label {
477 .codeblock-label {
474 margin: 0 0 1em 0;
478 margin: 0 0 1em 0;
475 }
479 }
476 }
480 }
477
481
478 .code-body {
482 .code-body {
479 padding: 0.8em 1em;
483 padding: 0.8em 1em;
480 background-color: #ffffff;
484 background-color: #ffffff;
481 min-width: 100%;
485 min-width: 100%;
482 box-sizing: border-box;
486 box-sizing: border-box;
483 // TODO: johbo: Parent has overflow: auto, this forces the child here
487 // TODO: johbo: Parent has overflow: auto, this forces the child here
484 // to have the intended size and to scroll. Should be simplified.
488 // to have the intended size and to scroll. Should be simplified.
485 width: 100%;
489 width: 100%;
486 overflow-x: auto;
490 overflow-x: auto;
487
491
488 img.rendered-binary {
492 img.rendered-binary {
489 height: auto;
493 height: auto;
490 width: 100%;
494 width: 100%;
491 }
495 }
492
496
493 .markdown-block {
497 .markdown-block {
494 padding: 1em 0;
498 padding: 1em 0;
495 }
499 }
496 }
500 }
497
501
498 .codeblock-header {
502 .codeblock-header {
499 background: @grey7;
503 background: @grey7;
500 height: 36px;
504 height: 36px;
501 }
505 }
502
506
503 .path {
507 .path {
504 border-bottom: 1px solid @grey6;
508 border-bottom: 1px solid @grey6;
505 padding: .65em 1em;
509 padding: .65em 1em;
506 height: 18px;
510 height: 18px;
507 }
511 }
508 }
512 }
509
513
510 .code-highlighttable,
514 .code-highlighttable,
511 div.codeblock {
515 div.codeblock {
512
516
513 &.readme {
517 &.readme {
514 background-color: white;
518 background-color: white;
515 }
519 }
516
520
517 .markdown-block table {
521 .markdown-block table {
518 border-collapse: collapse;
522 border-collapse: collapse;
519
523
520 th,
524 th,
521 td {
525 td {
522 padding: .5em;
526 padding: .5em;
523 border: @border-thickness solid @border-default-color;
527 border: @border-thickness solid @border-default-color;
524 }
528 }
525 }
529 }
526
530
527 table {
531 table {
528 border: 0px;
532 border: 0px;
529 margin: 0;
533 margin: 0;
530 letter-spacing: normal;
534 letter-spacing: normal;
531
535
532
536
533 td {
537 td {
534 border: 0px;
538 border: 0px;
535 vertical-align: top;
539 vertical-align: top;
536 }
540 }
537 }
541 }
538 }
542 }
539
543
540 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
544 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
541 div.search-code-body {
545 div.search-code-body {
542 background-color: #ffffff; padding: 5px 0 5px 10px;
546 background-color: #ffffff; padding: 5px 0 5px 10px;
543 pre {
547 pre {
544 .match { background-color: #faffa6;}
548 .match { background-color: #faffa6;}
545 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
549 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
546 }
550 }
547 .code-highlighttable {
551 .code-highlighttable {
548 border-collapse: collapse;
552 border-collapse: collapse;
549
553
550 tr:hover {
554 tr:hover {
551 background: #fafafa;
555 background: #fafafa;
552 }
556 }
553 td.code {
557 td.code {
554 padding-left: 10px;
558 padding-left: 10px;
555 }
559 }
556 td.line {
560 td.line {
557 border-right: 1px solid #ccc !important;
561 border-right: 1px solid #ccc !important;
558 padding-right: 10px;
562 padding-right: 10px;
559 text-align: right;
563 text-align: right;
560 font-family: @text-monospace;
564 font-family: @text-monospace;
561 span {
565 span {
562 white-space: pre-wrap;
566 white-space: pre-wrap;
563 color: #666666;
567 color: #666666;
564 }
568 }
565 }
569 }
566 }
570 }
567 }
571 }
568
572
569 div.annotatediv { margin-left: 2px; margin-right: 4px; }
573 div.annotatediv { margin-left: 2px; margin-right: 4px; }
570 .code-highlight {
574 .code-highlight {
571 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
575 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
572 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
576 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
573 pre div:target {background-color: @comment-highlight-color !important;}
577 pre div:target {background-color: @comment-highlight-color !important;}
574 }
578 }
575
579
576 .linenos a { text-decoration: none; }
580 .linenos a { text-decoration: none; }
577
581
578 .CodeMirror-selected { background: @rchighlightblue; }
582 .CodeMirror-selected { background: @rchighlightblue; }
579 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
583 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
580 .CodeMirror ::selection { background: @rchighlightblue; }
584 .CodeMirror ::selection { background: @rchighlightblue; }
581 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
585 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
582
586
583 .code { display: block; border:0px !important; }
587 .code { display: block; border:0px !important; }
584 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
588 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
585 /* This can be generated with `pygmentize -S default -f html` */
589 /* This can be generated with `pygmentize -S default -f html` */
586 .codehilite {
590 .codehilite {
587 .c-ElasticMatch { background-color: #faffa6; padding: 0.2em;}
591 .c-ElasticMatch { background-color: #faffa6; padding: 0.2em;}
588 .hll { background-color: #ffffcc }
592 .hll { background-color: #ffffcc }
589 .c { color: #408080; font-style: italic } /* Comment */
593 .c { color: #408080; font-style: italic } /* Comment */
590 .err, .codehilite .err { border: none } /* Error */
594 .err, .codehilite .err { border: none } /* Error */
591 .k { color: #008000; font-weight: bold } /* Keyword */
595 .k { color: #008000; font-weight: bold } /* Keyword */
592 .o { color: #666666 } /* Operator */
596 .o { color: #666666 } /* Operator */
593 .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
597 .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
594 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
598 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
595 .cp { color: #BC7A00 } /* Comment.Preproc */
599 .cp { color: #BC7A00 } /* Comment.Preproc */
596 .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
600 .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
597 .c1 { color: #408080; font-style: italic } /* Comment.Single */
601 .c1 { color: #408080; font-style: italic } /* Comment.Single */
598 .cs { color: #408080; font-style: italic } /* Comment.Special */
602 .cs { color: #408080; font-style: italic } /* Comment.Special */
599 .gd { color: #A00000 } /* Generic.Deleted */
603 .gd { color: #A00000 } /* Generic.Deleted */
600 .ge { font-style: italic } /* Generic.Emph */
604 .ge { font-style: italic } /* Generic.Emph */
601 .gr { color: #FF0000 } /* Generic.Error */
605 .gr { color: #FF0000 } /* Generic.Error */
602 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
606 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
603 .gi { color: #00A000 } /* Generic.Inserted */
607 .gi { color: #00A000 } /* Generic.Inserted */
604 .go { color: #888888 } /* Generic.Output */
608 .go { color: #888888 } /* Generic.Output */
605 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
609 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
606 .gs { font-weight: bold } /* Generic.Strong */
610 .gs { font-weight: bold } /* Generic.Strong */
607 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
611 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
608 .gt { color: #0044DD } /* Generic.Traceback */
612 .gt { color: #0044DD } /* Generic.Traceback */
609 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
613 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
610 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
614 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
611 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
615 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
612 .kp { color: #008000 } /* Keyword.Pseudo */
616 .kp { color: #008000 } /* Keyword.Pseudo */
613 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
617 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
614 .kt { color: #B00040 } /* Keyword.Type */
618 .kt { color: #B00040 } /* Keyword.Type */
615 .m { color: #666666 } /* Literal.Number */
619 .m { color: #666666 } /* Literal.Number */
616 .s { color: #BA2121 } /* Literal.String */
620 .s { color: #BA2121 } /* Literal.String */
617 .na { color: #7D9029 } /* Name.Attribute */
621 .na { color: #7D9029 } /* Name.Attribute */
618 .nb { color: #008000 } /* Name.Builtin */
622 .nb { color: #008000 } /* Name.Builtin */
619 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
623 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
620 .no { color: #880000 } /* Name.Constant */
624 .no { color: #880000 } /* Name.Constant */
621 .nd { color: #AA22FF } /* Name.Decorator */
625 .nd { color: #AA22FF } /* Name.Decorator */
622 .ni { color: #999999; font-weight: bold } /* Name.Entity */
626 .ni { color: #999999; font-weight: bold } /* Name.Entity */
623 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
627 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
624 .nf { color: #0000FF } /* Name.Function */
628 .nf { color: #0000FF } /* Name.Function */
625 .nl { color: #A0A000 } /* Name.Label */
629 .nl { color: #A0A000 } /* Name.Label */
626 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
630 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
627 .nt { color: #008000; font-weight: bold } /* Name.Tag */
631 .nt { color: #008000; font-weight: bold } /* Name.Tag */
628 .nv { color: #19177C } /* Name.Variable */
632 .nv { color: #19177C } /* Name.Variable */
629 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
633 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
630 .w { color: #bbbbbb } /* Text.Whitespace */
634 .w { color: #bbbbbb } /* Text.Whitespace */
631 .mb { color: #666666 } /* Literal.Number.Bin */
635 .mb { color: #666666 } /* Literal.Number.Bin */
632 .mf { color: #666666 } /* Literal.Number.Float */
636 .mf { color: #666666 } /* Literal.Number.Float */
633 .mh { color: #666666 } /* Literal.Number.Hex */
637 .mh { color: #666666 } /* Literal.Number.Hex */
634 .mi { color: #666666 } /* Literal.Number.Integer */
638 .mi { color: #666666 } /* Literal.Number.Integer */
635 .mo { color: #666666 } /* Literal.Number.Oct */
639 .mo { color: #666666 } /* Literal.Number.Oct */
636 .sa { color: #BA2121 } /* Literal.String.Affix */
640 .sa { color: #BA2121 } /* Literal.String.Affix */
637 .sb { color: #BA2121 } /* Literal.String.Backtick */
641 .sb { color: #BA2121 } /* Literal.String.Backtick */
638 .sc { color: #BA2121 } /* Literal.String.Char */
642 .sc { color: #BA2121 } /* Literal.String.Char */
639 .dl { color: #BA2121 } /* Literal.String.Delimiter */
643 .dl { color: #BA2121 } /* Literal.String.Delimiter */
640 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
644 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
641 .s2 { color: #BA2121 } /* Literal.String.Double */
645 .s2 { color: #BA2121 } /* Literal.String.Double */
642 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
646 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
643 .sh { color: #BA2121 } /* Literal.String.Heredoc */
647 .sh { color: #BA2121 } /* Literal.String.Heredoc */
644 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
648 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
645 .sx { color: #008000 } /* Literal.String.Other */
649 .sx { color: #008000 } /* Literal.String.Other */
646 .sr { color: #BB6688 } /* Literal.String.Regex */
650 .sr { color: #BB6688 } /* Literal.String.Regex */
647 .s1 { color: #BA2121 } /* Literal.String.Single */
651 .s1 { color: #BA2121 } /* Literal.String.Single */
648 .ss { color: #19177C } /* Literal.String.Symbol */
652 .ss { color: #19177C } /* Literal.String.Symbol */
649 .bp { color: #008000 } /* Name.Builtin.Pseudo */
653 .bp { color: #008000 } /* Name.Builtin.Pseudo */
650 .fm { color: #0000FF } /* Name.Function.Magic */
654 .fm { color: #0000FF } /* Name.Function.Magic */
651 .vc { color: #19177C } /* Name.Variable.Class */
655 .vc { color: #19177C } /* Name.Variable.Class */
652 .vg { color: #19177C } /* Name.Variable.Global */
656 .vg { color: #19177C } /* Name.Variable.Global */
653 .vi { color: #19177C } /* Name.Variable.Instance */
657 .vi { color: #19177C } /* Name.Variable.Instance */
654 .vm { color: #19177C } /* Name.Variable.Magic */
658 .vm { color: #19177C } /* Name.Variable.Magic */
655 .il { color: #666666 } /* Literal.Number.Integer.Long */
659 .il { color: #666666 } /* Literal.Number.Integer.Long */
656
660
657 }
661 }
658
662
659 /* customized pre blocks for markdown/rst */
663 /* customized pre blocks for markdown/rst */
660 pre.literal-block, .codehilite pre{
664 pre.literal-block, .codehilite pre{
661 padding: @padding;
665 padding: @padding;
662 border: 1px solid @grey6;
666 border: 1px solid @grey6;
663 .border-radius(@border-radius);
667 .border-radius(@border-radius);
664 background-color: @grey7;
668 background-color: @grey7;
665 }
669 }
666
670
667
671
668 /* START NEW CODE BLOCK CSS */
672 /* START NEW CODE BLOCK CSS */
669
673
670 @cb-line-height: 18px;
674 @cb-line-height: 18px;
671 @cb-line-code-padding: 10px;
675 @cb-line-code-padding: 10px;
672 @cb-text-padding: 5px;
676 @cb-text-padding: 5px;
673
677
674 @pill-padding: 2px 7px;
678 @pill-padding: 2px 7px;
675 @pill-padding-small: 2px 2px 1px 2px;
679 @pill-padding-small: 2px 2px 1px 2px;
676
680
677 input.filediff-collapse-state {
681 input.filediff-collapse-state {
678 display: none;
682 display: none;
679
683
680 &:checked + .filediff { /* file diff is collapsed */
684 &:checked + .filediff { /* file diff is collapsed */
681 .cb {
685 .cb {
682 display: none
686 display: none
683 }
687 }
684 .filediff-collapse-indicator {
688 .filediff-collapse-indicator {
685 float: left;
689 float: left;
686 cursor: pointer;
690 cursor: pointer;
687 margin: 1px -5px;
691 margin: 1px -5px;
688 }
692 }
689 .filediff-collapse-indicator:before {
693 .filediff-collapse-indicator:before {
690 content: '\f105';
694 content: '\f105';
691 }
695 }
692
696
693 .filediff-menu {
697 .filediff-menu {
694 display: none;
698 display: none;
695 }
699 }
696
700
697 }
701 }
698
702
699 &+ .filediff { /* file diff is expanded */
703 &+ .filediff { /* file diff is expanded */
700
704
701 .filediff-collapse-indicator {
705 .filediff-collapse-indicator {
702 float: left;
706 float: left;
703 cursor: pointer;
707 cursor: pointer;
704 margin: 1px -5px;
708 margin: 1px -5px;
705 }
709 }
706 .filediff-collapse-indicator:before {
710 .filediff-collapse-indicator:before {
707 content: '\f107';
711 content: '\f107';
708 }
712 }
709
713
710 .filediff-menu {
714 .filediff-menu {
711 display: block;
715 display: block;
712 }
716 }
713
717
714 margin: 10px 0;
718 margin: 10px 0;
715 &:nth-child(2) {
719 &:nth-child(2) {
716 margin: 0;
720 margin: 0;
717 }
721 }
718 }
722 }
719 }
723 }
720
724
721 .filediffs .anchor {
725 .filediffs .anchor {
722 display: block;
726 display: block;
723 height: 40px;
727 height: 40px;
724 margin-top: -40px;
728 margin-top: -40px;
725 visibility: hidden;
729 visibility: hidden;
726 }
730 }
727
731
728 .filediffs .anchor:nth-of-type(1) {
732 .filediffs .anchor:nth-of-type(1) {
729 display: block;
733 display: block;
730 height: 80px;
734 height: 80px;
731 margin-top: -80px;
735 margin-top: -80px;
732 visibility: hidden;
736 visibility: hidden;
733 }
737 }
734
738
735 .cs_files {
739 .cs_files {
736 clear: both;
740 clear: both;
737 }
741 }
738
742
739 #diff-file-sticky{
743 #diff-file-sticky{
740 will-change: min-height;
744 will-change: min-height;
741 height: 80px;
745 height: 80px;
742 }
746 }
743
747
744 .sidebar__inner{
748 .sidebar__inner{
745 transform: translate(0, 0); /* For browsers don't support translate3d. */
749 transform: translate(0, 0); /* For browsers don't support translate3d. */
746 transform: translate3d(0, 0, 0);
750 transform: translate3d(0, 0, 0);
747 will-change: position, transform;
751 will-change: position, transform;
748 height: 65px;
752 height: 65px;
749 background-color: #fff;
753 background-color: #fff;
750 padding: 5px 0px;
754 padding: 5px 0px;
751 }
755 }
752
756
753 .sidebar__bar {
757 .sidebar__bar {
754 padding: 5px 0px 0px 0px
758 padding: 5px 0px 0px 0px
755 }
759 }
756
760
757 .fpath-placeholder {
761 .fpath-placeholder {
758 clear: both;
762 clear: both;
759 visibility: hidden
763 visibility: hidden
760 }
764 }
761
765
762 .is-affixed {
766 .is-affixed {
763
767
764 .sidebar__inner {
768 .sidebar__inner {
765 z-index: 30;
769 z-index: 30;
766 }
770 }
767
771
768 .sidebar_inner_shadow {
772 .sidebar_inner_shadow {
769 position: fixed;
773 position: fixed;
770 top: 75px;
774 top: 75px;
771 right: -100%;
775 right: -100%;
772 left: -100%;
776 left: -100%;
773 z-index: 30;
777 z-index: 30;
774 display: block;
778 display: block;
775 height: 5px;
779 height: 5px;
776 content: "";
780 content: "";
777 background: linear-gradient(rgba(0, 0, 0, 0.075), rgba(0, 0, 0, 0.001)) repeat-x 0 0;
781 background: linear-gradient(rgba(0, 0, 0, 0.075), rgba(0, 0, 0, 0.001)) repeat-x 0 0;
778 border-top: 1px solid rgba(0, 0, 0, 0.15);
782 border-top: 1px solid rgba(0, 0, 0, 0.15);
779 }
783 }
780
784
781 .fpath-placeholder {
785 .fpath-placeholder {
782 visibility: visible !important;
786 visibility: visible !important;
783 }
787 }
784 }
788 }
785
789
786 .diffset-menu {
790 .diffset-menu {
787
791
788 }
792 }
789
793
790 #todo-box {
794 #todo-box {
791 clear:both;
795 clear:both;
792 display: none;
796 display: none;
793 text-align: right
797 text-align: right
794 }
798 }
795
799
796 .diffset {
800 .diffset {
797 margin: 0px auto;
801 margin: 0px auto;
798 .diffset-heading {
802 .diffset-heading {
799 border: 1px solid @grey5;
803 border: 1px solid @grey5;
800 margin-bottom: -1px;
804 margin-bottom: -1px;
801 // margin-top: 20px;
805 // margin-top: 20px;
802 h2 {
806 h2 {
803 margin: 0;
807 margin: 0;
804 line-height: 38px;
808 line-height: 38px;
805 padding-left: 10px;
809 padding-left: 10px;
806 }
810 }
807 .btn {
811 .btn {
808 margin: 0;
812 margin: 0;
809 }
813 }
810 background: @grey6;
814 background: @grey6;
811 display: block;
815 display: block;
812 padding: 5px;
816 padding: 5px;
813 }
817 }
814 .diffset-heading-warning {
818 .diffset-heading-warning {
815 background: @alert3-inner;
819 background: @alert3-inner;
816 border: 1px solid @alert3;
820 border: 1px solid @alert3;
817 }
821 }
818 &.diffset-comments-disabled {
822 &.diffset-comments-disabled {
819 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
823 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
820 display: none !important;
824 display: none !important;
821 }
825 }
822 }
826 }
823 }
827 }
824
828
825 .filelist {
829 .filelist {
826 .pill {
830 .pill {
827 display: block;
831 display: block;
828 float: left;
832 float: left;
829 padding: @pill-padding-small;
833 padding: @pill-padding-small;
830 }
834 }
831 }
835 }
832
836
833 .pill {
837 .pill {
834 display: block;
838 display: block;
835 float: left;
839 float: left;
836 padding: @pill-padding;
840 padding: @pill-padding;
837 }
841 }
838
842
839 .pill-group {
843 .pill-group {
840 .pill {
844 .pill {
841 opacity: .8;
845 opacity: .8;
842 margin-right: 3px;
846 margin-right: 3px;
843 font-size: 12px;
847 font-size: 12px;
844 font-weight: normal;
848 font-weight: normal;
845 min-width: 30px;
849 min-width: 30px;
846 text-align: center;
850 text-align: center;
847
851
848 &:first-child {
852 &:first-child {
849 border-radius: @border-radius 0 0 @border-radius;
853 border-radius: @border-radius 0 0 @border-radius;
850 }
854 }
851 &:last-child {
855 &:last-child {
852 border-radius: 0 @border-radius @border-radius 0;
856 border-radius: 0 @border-radius @border-radius 0;
853 }
857 }
854 &:only-child {
858 &:only-child {
855 border-radius: @border-radius;
859 border-radius: @border-radius;
856 margin-right: 0;
860 margin-right: 0;
857 }
861 }
858 }
862 }
859 }
863 }
860
864
861 /* Main comments*/
865 /* Main comments*/
862 #comments {
866 #comments {
863 .comment-selected {
867 .comment-selected {
864 border-left: 6px solid @comment-highlight-color;
868 border-left: 6px solid @comment-highlight-color;
865 padding-left: 3px;
869 padding-left: 3px;
866 margin-left: -9px;
870 margin-left: -9px;
867 }
871 }
868 }
872 }
869
873
870 .filediff {
874 .filediff {
871 border: 1px solid @grey5;
875 border: 1px solid @grey5;
872
876
873 /* START OVERRIDES */
877 /* START OVERRIDES */
874 .code-highlight {
878 .code-highlight {
875 border: none; // TODO: remove this border from the global
879 border: none; // TODO: remove this border from the global
876 // .code-highlight, it doesn't belong there
880 // .code-highlight, it doesn't belong there
877 }
881 }
878 label {
882 label {
879 margin: 0; // TODO: remove this margin definition from global label
883 margin: 0; // TODO: remove this margin definition from global label
880 // it doesn't belong there - if margin on labels
884 // it doesn't belong there - if margin on labels
881 // are needed for a form they should be defined
885 // are needed for a form they should be defined
882 // in the form's class
886 // in the form's class
883 }
887 }
884 /* END OVERRIDES */
888 /* END OVERRIDES */
885
889
886 * {
890 * {
887 box-sizing: border-box;
891 box-sizing: border-box;
888 }
892 }
889 .filediff-anchor {
893 .filediff-anchor {
890 visibility: hidden;
894 visibility: hidden;
891 }
895 }
892 &:hover {
896 &:hover {
893 .filediff-anchor {
897 .filediff-anchor {
894 visibility: visible;
898 visibility: visible;
895 }
899 }
896 }
900 }
897
901
898 .filediff-heading {
902 .filediff-heading {
899 cursor: pointer;
903 cursor: pointer;
900 display: block;
904 display: block;
901 padding: 10px 10px;
905 padding: 10px 10px;
902 }
906 }
903 .filediff-heading:after {
907 .filediff-heading:after {
904 content: "";
908 content: "";
905 display: table;
909 display: table;
906 clear: both;
910 clear: both;
907 }
911 }
908 .filediff-heading:hover {
912 .filediff-heading:hover {
909 background: #e1e9f4 !important;
913 background: #e1e9f4 !important;
910 }
914 }
911
915
912 .filediff-menu {
916 .filediff-menu {
913 text-align: right;
917 text-align: right;
914 padding: 5px 5px 5px 0px;
918 padding: 5px 5px 5px 0px;
915 background: @grey7;
919 background: @grey7;
916
920
917 &> a,
921 &> a,
918 &> span {
922 &> span {
919 padding: 1px;
923 padding: 1px;
920 }
924 }
921 }
925 }
922
926
923 .filediff-collapse-button, .filediff-expand-button {
927 .filediff-collapse-button, .filediff-expand-button {
924 cursor: pointer;
928 cursor: pointer;
925 }
929 }
926 .filediff-collapse-button {
930 .filediff-collapse-button {
927 display: inline;
931 display: inline;
928 }
932 }
929 .filediff-expand-button {
933 .filediff-expand-button {
930 display: none;
934 display: none;
931 }
935 }
932 .filediff-collapsed .filediff-collapse-button {
936 .filediff-collapsed .filediff-collapse-button {
933 display: none;
937 display: none;
934 }
938 }
935 .filediff-collapsed .filediff-expand-button {
939 .filediff-collapsed .filediff-expand-button {
936 display: inline;
940 display: inline;
937 }
941 }
938
942
939 /**** COMMENTS ****/
943 /**** COMMENTS ****/
940
944
941 .filediff-menu {
945 .filediff-menu {
942 .show-comment-button {
946 .show-comment-button {
943 display: none;
947 display: none;
944 }
948 }
945 }
949 }
946 &.hide-comments {
950 &.hide-comments {
947 .inline-comments {
951 .inline-comments {
948 display: none;
952 display: none;
949 }
953 }
950 .filediff-menu {
954 .filediff-menu {
951 .show-comment-button {
955 .show-comment-button {
952 display: inline;
956 display: inline;
953 }
957 }
954 .hide-comment-button {
958 .hide-comment-button {
955 display: none;
959 display: none;
956 }
960 }
957 }
961 }
958 }
962 }
959
963
960 .hide-line-comments {
964 .hide-line-comments {
961 .inline-comments {
965 .inline-comments {
962 display: none;
966 display: none;
963 }
967 }
964 }
968 }
965
969
966 /**** END COMMENTS ****/
970 /**** END COMMENTS ****/
967
971
968 }
972 }
969
973
970
974
971 .op-added {
975 .op-added {
972 color: @alert1;
976 color: @alert1;
973 }
977 }
974
978
975 .op-deleted {
979 .op-deleted {
976 color: @alert2;
980 color: @alert2;
977 }
981 }
978
982
979 .filediff, .filelist {
983 .filediff, .filelist {
980
984
981 .pill {
985 .pill {
982 &[op="name"] {
986 &[op="name"] {
983 background: none;
987 background: none;
984 opacity: 1;
988 opacity: 1;
985 color: white;
989 color: white;
986 }
990 }
987 &[op="limited"] {
991 &[op="limited"] {
988 background: @grey2;
992 background: @grey2;
989 color: white;
993 color: white;
990 }
994 }
991 &[op="binary"] {
995 &[op="binary"] {
992 background: @color7;
996 background: @color7;
993 color: white;
997 color: white;
994 }
998 }
995 &[op="modified"] {
999 &[op="modified"] {
996 background: @alert1;
1000 background: @alert1;
997 color: white;
1001 color: white;
998 }
1002 }
999 &[op="renamed"] {
1003 &[op="renamed"] {
1000 background: @color4;
1004 background: @color4;
1001 color: white;
1005 color: white;
1002 }
1006 }
1003 &[op="copied"] {
1007 &[op="copied"] {
1004 background: @color4;
1008 background: @color4;
1005 color: white;
1009 color: white;
1006 }
1010 }
1007 &[op="mode"] {
1011 &[op="mode"] {
1008 background: @grey3;
1012 background: @grey3;
1009 color: white;
1013 color: white;
1010 }
1014 }
1011 &[op="symlink"] {
1015 &[op="symlink"] {
1012 background: @color8;
1016 background: @color8;
1013 color: white;
1017 color: white;
1014 }
1018 }
1015
1019
1016 &[op="added"] { /* added lines */
1020 &[op="added"] { /* added lines */
1017 background: @alert1;
1021 background: @alert1;
1018 color: white;
1022 color: white;
1019 }
1023 }
1020 &[op="deleted"] { /* deleted lines */
1024 &[op="deleted"] { /* deleted lines */
1021 background: @alert2;
1025 background: @alert2;
1022 color: white;
1026 color: white;
1023 }
1027 }
1024
1028
1025 &[op="created"] { /* created file */
1029 &[op="created"] { /* created file */
1026 background: @alert1;
1030 background: @alert1;
1027 color: white;
1031 color: white;
1028 }
1032 }
1029 &[op="removed"] { /* deleted file */
1033 &[op="removed"] { /* deleted file */
1030 background: @color5;
1034 background: @color5;
1031 color: white;
1035 color: white;
1032 }
1036 }
1033 }
1037 }
1034 }
1038 }
1035
1039
1036
1040
1037 .filediff-outdated {
1041 .filediff-outdated {
1038 padding: 8px 0;
1042 padding: 8px 0;
1039
1043
1040 .filediff-heading {
1044 .filediff-heading {
1041 opacity: .5;
1045 opacity: .5;
1042 }
1046 }
1043 }
1047 }
1044
1048
1045 table.cb {
1049 table.cb {
1046 width: 100%;
1050 width: 100%;
1047 border-collapse: collapse;
1051 border-collapse: collapse;
1048
1052
1049 .cb-text {
1053 .cb-text {
1050 padding: @cb-text-padding;
1054 padding: @cb-text-padding;
1051 }
1055 }
1052 .cb-hunk {
1056 .cb-hunk {
1053 padding: @cb-text-padding;
1057 padding: @cb-text-padding;
1054 }
1058 }
1055 .cb-expand {
1059 .cb-expand {
1056 display: none;
1060 display: none;
1057 }
1061 }
1058 .cb-collapse {
1062 .cb-collapse {
1059 display: inline;
1063 display: inline;
1060 }
1064 }
1061 &.cb-collapsed {
1065 &.cb-collapsed {
1062 .cb-line {
1066 .cb-line {
1063 display: none;
1067 display: none;
1064 }
1068 }
1065 .cb-expand {
1069 .cb-expand {
1066 display: inline;
1070 display: inline;
1067 }
1071 }
1068 .cb-collapse {
1072 .cb-collapse {
1069 display: none;
1073 display: none;
1070 }
1074 }
1071 .cb-hunk {
1075 .cb-hunk {
1072 display: none;
1076 display: none;
1073 }
1077 }
1074 }
1078 }
1075
1079
1076 /* intentionally general selector since .cb-line-selected must override it
1080 /* intentionally general selector since .cb-line-selected must override it
1077 and they both use !important since the td itself may have a random color
1081 and they both use !important since the td itself may have a random color
1078 generated by annotation blocks. TLDR: if you change it, make sure
1082 generated by annotation blocks. TLDR: if you change it, make sure
1079 annotated block selection and line selection in file view still work */
1083 annotated block selection and line selection in file view still work */
1080 .cb-line-fresh .cb-content {
1084 .cb-line-fresh .cb-content {
1081 background: white !important;
1085 background: white !important;
1082 }
1086 }
1083 .cb-warning {
1087 .cb-warning {
1084 background: #fff4dd;
1088 background: #fff4dd;
1085 }
1089 }
1086
1090
1087 &.cb-diff-sideside {
1091 &.cb-diff-sideside {
1088 td {
1092 td {
1089 &.cb-content {
1093 &.cb-content {
1090 width: 50%;
1094 width: 50%;
1091 }
1095 }
1092 }
1096 }
1093 }
1097 }
1094
1098
1095 tr {
1099 tr {
1096 &.cb-annotate {
1100 &.cb-annotate {
1097 border-top: 1px solid #eee;
1101 border-top: 1px solid #eee;
1098 }
1102 }
1099
1103
1100 &.cb-comment-info {
1104 &.cb-comment-info {
1101 border-top: 1px solid #eee;
1105 border-top: 1px solid #eee;
1102 color: rgba(0, 0, 0, 0.3);
1106 color: rgba(0, 0, 0, 0.3);
1103 background: #edf2f9;
1107 background: #edf2f9;
1104
1108
1105 td {
1109 td {
1106
1110
1107 }
1111 }
1108 }
1112 }
1109
1113
1110 &.cb-hunk {
1114 &.cb-hunk {
1111 font-family: @text-monospace;
1115 font-family: @text-monospace;
1112 color: rgba(0, 0, 0, 0.3);
1116 color: rgba(0, 0, 0, 0.3);
1113
1117
1114 td {
1118 td {
1115 &:first-child {
1119 &:first-child {
1116 background: #edf2f9;
1120 background: #edf2f9;
1117 }
1121 }
1118 &:last-child {
1122 &:last-child {
1119 background: #f4f7fb;
1123 background: #f4f7fb;
1120 }
1124 }
1121 }
1125 }
1122 }
1126 }
1123 }
1127 }
1124
1128
1125
1129
1126 td {
1130 td {
1127 vertical-align: top;
1131 vertical-align: top;
1128 padding: 0;
1132 padding: 0;
1129
1133
1130 &.cb-content {
1134 &.cb-content {
1131 font-size: 12.35px;
1135 font-size: 12.35px;
1132
1136
1133 &.cb-line-selected .cb-code {
1137 &.cb-line-selected .cb-code {
1134 background: @comment-highlight-color !important;
1138 background: @comment-highlight-color !important;
1135 }
1139 }
1136
1140
1137 span.cb-code {
1141 span.cb-code {
1138 line-height: @cb-line-height;
1142 line-height: @cb-line-height;
1139 padding-left: @cb-line-code-padding;
1143 padding-left: @cb-line-code-padding;
1140 padding-right: @cb-line-code-padding;
1144 padding-right: @cb-line-code-padding;
1141 display: block;
1145 display: block;
1142 white-space: pre-wrap;
1146 white-space: pre-wrap;
1143 font-family: @text-monospace;
1147 font-family: @text-monospace;
1144 word-break: break-all;
1148 word-break: break-all;
1145 .nonl {
1149 .nonl {
1146 color: @color5;
1150 color: @color5;
1147 }
1151 }
1148 .cb-action {
1152 .cb-action {
1149 &:before {
1153 &:before {
1150 content: " ";
1154 content: " ";
1151 }
1155 }
1152 &.cb-deletion:before {
1156 &.cb-deletion:before {
1153 content: "- ";
1157 content: "- ";
1154 }
1158 }
1155 &.cb-addition:before {
1159 &.cb-addition:before {
1156 content: "+ ";
1160 content: "+ ";
1157 }
1161 }
1158 }
1162 }
1159 }
1163 }
1160
1164
1161 &> button.cb-comment-box-opener {
1165 &> button.cb-comment-box-opener {
1162
1166
1163 padding: 2px 2px 1px 3px;
1167 padding: 2px 2px 1px 3px;
1164 margin-left: -6px;
1168 margin-left: -6px;
1165 margin-top: -1px;
1169 margin-top: -1px;
1166
1170
1167 border-radius: @border-radius;
1171 border-radius: @border-radius;
1168 position: absolute;
1172 position: absolute;
1169 display: none;
1173 display: none;
1170 }
1174 }
1171 .cb-comment {
1175 .cb-comment {
1172 margin-top: 10px;
1176 margin-top: 10px;
1173 white-space: normal;
1177 white-space: normal;
1174 }
1178 }
1175 }
1179 }
1176 &:hover {
1180 &:hover {
1177 button.cb-comment-box-opener {
1181 button.cb-comment-box-opener {
1178 display: block;
1182 display: block;
1179 }
1183 }
1180 &+ td button.cb-comment-box-opener {
1184 &+ td button.cb-comment-box-opener {
1181 display: block
1185 display: block
1182 }
1186 }
1183 }
1187 }
1184
1188
1185 &.cb-data {
1189 &.cb-data {
1186 text-align: right;
1190 text-align: right;
1187 width: 30px;
1191 width: 30px;
1188 font-family: @text-monospace;
1192 font-family: @text-monospace;
1189
1193
1190 .icon-comment {
1194 .icon-comment {
1191 cursor: pointer;
1195 cursor: pointer;
1192 }
1196 }
1193 &.cb-line-selected {
1197 &.cb-line-selected {
1194 background: @comment-highlight-color !important;
1198 background: @comment-highlight-color !important;
1195 }
1199 }
1196 &.cb-line-selected > div {
1200 &.cb-line-selected > div {
1197 display: block;
1201 display: block;
1198 background: @comment-highlight-color !important;
1202 background: @comment-highlight-color !important;
1199 line-height: @cb-line-height;
1203 line-height: @cb-line-height;
1200 color: rgba(0, 0, 0, 0.3);
1204 color: rgba(0, 0, 0, 0.3);
1201 }
1205 }
1202 }
1206 }
1203
1207
1204 &.cb-lineno {
1208 &.cb-lineno {
1205 padding: 0;
1209 padding: 0;
1206 width: 50px;
1210 width: 50px;
1207 color: rgba(0, 0, 0, 0.3);
1211 color: rgba(0, 0, 0, 0.3);
1208 text-align: right;
1212 text-align: right;
1209 border-right: 1px solid #eee;
1213 border-right: 1px solid #eee;
1210 font-family: @text-monospace;
1214 font-family: @text-monospace;
1211 -webkit-user-select: none;
1215 -webkit-user-select: none;
1212 -moz-user-select: none;
1216 -moz-user-select: none;
1213 user-select: none;
1217 user-select: none;
1214
1218
1215 a::before {
1219 a::before {
1216 content: attr(data-line-no);
1220 content: attr(data-line-no);
1217 }
1221 }
1218 &.cb-line-selected {
1222 &.cb-line-selected {
1219 background: @comment-highlight-color !important;
1223 background: @comment-highlight-color !important;
1220 }
1224 }
1221
1225
1222 a {
1226 a {
1223 display: block;
1227 display: block;
1224 padding-right: @cb-line-code-padding;
1228 padding-right: @cb-line-code-padding;
1225 padding-left: @cb-line-code-padding;
1229 padding-left: @cb-line-code-padding;
1226 line-height: @cb-line-height;
1230 line-height: @cb-line-height;
1227 color: rgba(0, 0, 0, 0.3);
1231 color: rgba(0, 0, 0, 0.3);
1228 }
1232 }
1229 }
1233 }
1230
1234
1231 &.cb-empty {
1235 &.cb-empty {
1232 background: @grey7;
1236 background: @grey7;
1233 }
1237 }
1234
1238
1235 ins {
1239 ins {
1236 color: black;
1240 color: black;
1237 background: #a6f3a6;
1241 background: #a6f3a6;
1238 text-decoration: none;
1242 text-decoration: none;
1239 }
1243 }
1240 del {
1244 del {
1241 color: black;
1245 color: black;
1242 background: #f8cbcb;
1246 background: #f8cbcb;
1243 text-decoration: none;
1247 text-decoration: none;
1244 }
1248 }
1245 &.cb-addition {
1249 &.cb-addition {
1246 background: #ecffec;
1250 background: #ecffec;
1247
1251
1248 &.blob-lineno {
1252 &.blob-lineno {
1249 background: #ddffdd;
1253 background: #ddffdd;
1250 }
1254 }
1251 }
1255 }
1252 &.cb-deletion {
1256 &.cb-deletion {
1253 background: #ffecec;
1257 background: #ffecec;
1254
1258
1255 &.blob-lineno {
1259 &.blob-lineno {
1256 background: #ffdddd;
1260 background: #ffdddd;
1257 }
1261 }
1258 }
1262 }
1259 &.cb-annotate-message-spacer {
1263 &.cb-annotate-message-spacer {
1260 width:8px;
1264 width:8px;
1261 padding: 1px 0px 0px 3px;
1265 padding: 1px 0px 0px 3px;
1262 }
1266 }
1263 &.cb-annotate-info {
1267 &.cb-annotate-info {
1264 width: 320px;
1268 width: 320px;
1265 min-width: 320px;
1269 min-width: 320px;
1266 max-width: 320px;
1270 max-width: 320px;
1267 padding: 5px 2px;
1271 padding: 5px 2px;
1268 font-size: 13px;
1272 font-size: 13px;
1269
1273
1270 .cb-annotate-message {
1274 .cb-annotate-message {
1271 padding: 2px 0px 0px 0px;
1275 padding: 2px 0px 0px 0px;
1272 white-space: pre-line;
1276 white-space: pre-line;
1273 overflow: hidden;
1277 overflow: hidden;
1274 }
1278 }
1275 .rc-user {
1279 .rc-user {
1276 float: none;
1280 float: none;
1277 padding: 0 6px 0 17px;
1281 padding: 0 6px 0 17px;
1278 min-width: unset;
1282 min-width: unset;
1279 min-height: unset;
1283 min-height: unset;
1280 }
1284 }
1281 }
1285 }
1282
1286
1283 &.cb-annotate-revision {
1287 &.cb-annotate-revision {
1284 cursor: pointer;
1288 cursor: pointer;
1285 text-align: right;
1289 text-align: right;
1286 padding: 1px 3px 0px 3px;
1290 padding: 1px 3px 0px 3px;
1287 }
1291 }
1288 }
1292 }
1289 }
1293 }
@@ -1,669 +1,691 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 RhodeCode JS Files
20 RhodeCode JS Files
21 **/
21 **/
22
22
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
24 console = { log: function() {} }
24 console = { log: function() {} }
25 }
25 }
26
26
27 // TODO: move the following function to submodules
27 // TODO: move the following function to submodules
28
28
29 /**
29 /**
30 * show more
30 * show more
31 */
31 */
32 var show_more_event = function(){
32 var show_more_event = function(){
33 $('table .show_more').click(function(e) {
33 $('table .show_more').click(function(e) {
34 var cid = e.target.id.substring(1);
34 var cid = e.target.id.substring(1);
35 var button = $(this);
35 var button = $(this);
36 if (button.hasClass('open')) {
36 if (button.hasClass('open')) {
37 $('#'+cid).hide();
37 $('#'+cid).hide();
38 button.removeClass('open');
38 button.removeClass('open');
39 } else {
39 } else {
40 $('#'+cid).show();
40 $('#'+cid).show();
41 button.addClass('open one');
41 button.addClass('open one');
42 }
42 }
43 });
43 });
44 };
44 };
45
45
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
47 $('#compare_action').on('click', function(e){
47 $('#compare_action').on('click', function(e){
48 e.preventDefault();
48 e.preventDefault();
49
49
50 var source = $('input[name=compare_source]:checked').val();
50 var source = $('input[name=compare_source]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
52 if(source && target){
52 if(source && target){
53 var url_data = {
53 var url_data = {
54 repo_name: repo_name,
54 repo_name: repo_name,
55 source_ref: source,
55 source_ref: source,
56 source_ref_type: compare_ref_type,
56 source_ref_type: compare_ref_type,
57 target_ref: target,
57 target_ref: target,
58 target_ref_type: compare_ref_type,
58 target_ref_type: compare_ref_type,
59 merge: 1
59 merge: 1
60 };
60 };
61 window.location = pyroutes.url('repo_compare', url_data);
61 window.location = pyroutes.url('repo_compare', url_data);
62 }
62 }
63 });
63 });
64 $('.compare-radio-button').on('click', function(e){
64 $('.compare-radio-button').on('click', function(e){
65 var source = $('input[name=compare_source]:checked').val();
65 var source = $('input[name=compare_source]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
67 if(source && target){
67 if(source && target){
68 $('#compare_action').removeAttr("disabled");
68 $('#compare_action').removeAttr("disabled");
69 $('#compare_action').removeClass("disabled");
69 $('#compare_action').removeClass("disabled");
70 }
70 }
71 })
71 })
72 };
72 };
73
73
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
75 var container = $('#' + target);
75 var container = $('#' + target);
76 var url = pyroutes.url('repo_stats',
76 var url = pyroutes.url('repo_stats',
77 {"repo_name": repo_name, "commit_id": commit_id});
77 {"repo_name": repo_name, "commit_id": commit_id});
78
78
79 container.show();
79 container.show();
80 if (!container.hasClass('loaded')) {
80 if (!container.hasClass('loaded')) {
81 $.ajax({url: url})
81 $.ajax({url: url})
82 .complete(function (data) {
82 .complete(function (data) {
83 var responseJSON = data.responseJSON;
83 var responseJSON = data.responseJSON;
84 container.addClass('loaded');
84 container.addClass('loaded');
85 container.html(responseJSON.size);
85 container.html(responseJSON.size);
86 callback(responseJSON.code_stats)
86 callback(responseJSON.code_stats)
87 })
87 })
88 .fail(function (data) {
88 .fail(function (data) {
89 console.log('failed to load repo stats');
89 console.log('failed to load repo stats');
90 });
90 });
91 }
91 }
92
92
93 };
93 };
94
94
95 var showRepoStats = function(target, data){
95 var showRepoStats = function(target, data){
96 var container = $('#' + target);
96 var container = $('#' + target);
97
97
98 if (container.hasClass('loaded')) {
98 if (container.hasClass('loaded')) {
99 return
99 return
100 }
100 }
101
101
102 var total = 0;
102 var total = 0;
103 var no_data = true;
103 var no_data = true;
104 var tbl = document.createElement('table');
104 var tbl = document.createElement('table');
105 tbl.setAttribute('class', 'trending_language_tbl rctable');
105 tbl.setAttribute('class', 'trending_language_tbl rctable');
106
106
107 $.each(data, function(key, val){
107 $.each(data, function(key, val){
108 total += val.count;
108 total += val.count;
109 });
109 });
110
110
111 var sortedStats = [];
111 var sortedStats = [];
112 for (var obj in data){
112 for (var obj in data){
113 sortedStats.push([obj, data[obj]])
113 sortedStats.push([obj, data[obj]])
114 }
114 }
115 var sortedData = sortedStats.sort(function (a, b) {
115 var sortedData = sortedStats.sort(function (a, b) {
116 return b[1].count - a[1].count
116 return b[1].count - a[1].count
117 });
117 });
118 var cnt = 0;
118 var cnt = 0;
119 $.each(sortedData, function(idx, val){
119 $.each(sortedData, function(idx, val){
120 cnt += 1;
120 cnt += 1;
121 no_data = false;
121 no_data = false;
122
122
123 var tr = document.createElement('tr');
123 var tr = document.createElement('tr');
124
124
125 var key = val[0];
125 var key = val[0];
126 var obj = {"desc": val[1].desc, "count": val[1].count};
126 var obj = {"desc": val[1].desc, "count": val[1].count};
127
127
128 // meta language names
128 // meta language names
129 var td1 = document.createElement('td');
129 var td1 = document.createElement('td');
130 var trending_language_label = document.createElement('div');
130 var trending_language_label = document.createElement('div');
131 trending_language_label.innerHTML = obj.desc;
131 trending_language_label.innerHTML = obj.desc;
132 td1.appendChild(trending_language_label);
132 td1.appendChild(trending_language_label);
133
133
134 // extensions
134 // extensions
135 var td2 = document.createElement('td');
135 var td2 = document.createElement('td');
136 var extension = document.createElement('div');
136 var extension = document.createElement('div');
137 extension.innerHTML = ".{0}".format(key)
137 extension.innerHTML = ".{0}".format(key)
138 td2.appendChild(extension);
138 td2.appendChild(extension);
139
139
140 // number of files
140 // number of files
141 var td3 = document.createElement('td');
141 var td3 = document.createElement('td');
142 var file_count = document.createElement('div');
142 var file_count = document.createElement('div');
143 var percentage_num = Math.round((obj.count / total * 100), 2);
143 var percentage_num = Math.round((obj.count / total * 100), 2);
144 var label = _ngettext('file', 'files', obj.count);
144 var label = _ngettext('file', 'files', obj.count);
145 file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ;
145 file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ;
146 td3.appendChild(file_count);
146 td3.appendChild(file_count);
147
147
148 // percentage
148 // percentage
149 var td4 = document.createElement('td');
149 var td4 = document.createElement('td');
150 td4.setAttribute("class", 'trending_language');
150 td4.setAttribute("class", 'trending_language');
151
151
152 var percentage = document.createElement('div');
152 var percentage = document.createElement('div');
153 percentage.setAttribute('class', 'lang-bar');
153 percentage.setAttribute('class', 'lang-bar');
154 percentage.innerHTML = "&nbsp;";
154 percentage.innerHTML = "&nbsp;";
155 percentage.style.width = percentage_num + '%';
155 percentage.style.width = percentage_num + '%';
156 td4.appendChild(percentage);
156 td4.appendChild(percentage);
157
157
158 tr.appendChild(td1);
158 tr.appendChild(td1);
159 tr.appendChild(td2);
159 tr.appendChild(td2);
160 tr.appendChild(td3);
160 tr.appendChild(td3);
161 tr.appendChild(td4);
161 tr.appendChild(td4);
162 tbl.appendChild(tr);
162 tbl.appendChild(tr);
163
163
164 });
164 });
165
165
166 $(container).html(tbl);
166 $(container).html(tbl);
167 $(container).addClass('loaded');
167 $(container).addClass('loaded');
168
168
169 $('#code_stats_show_more').on('click', function (e) {
169 $('#code_stats_show_more').on('click', function (e) {
170 e.preventDefault();
170 e.preventDefault();
171 $('.stats_hidden').each(function (idx) {
171 $('.stats_hidden').each(function (idx) {
172 $(this).css("display", "");
172 $(this).css("display", "");
173 });
173 });
174 $('#code_stats_show_more').hide();
174 $('#code_stats_show_more').hide();
175 });
175 });
176
176
177 };
177 };
178
178
179 // returns a node from given html;
179 // returns a node from given html;
180 var fromHTML = function(html){
180 var fromHTML = function(html){
181 var _html = document.createElement('element');
181 var _html = document.createElement('element');
182 _html.innerHTML = html;
182 _html.innerHTML = html;
183 return _html;
183 return _html;
184 };
184 };
185
185
186 // Toggle Collapsable Content
186 // Toggle Collapsable Content
187 function collapsableContent() {
187 function collapsableContent() {
188
188
189 $('.collapsable-content').not('.no-hide').hide();
189 $('.collapsable-content').not('.no-hide').hide();
190
190
191 $('.btn-collapse').unbind(); //in case we've been here before
191 $('.btn-collapse').unbind(); //in case we've been here before
192 $('.btn-collapse').click(function() {
192 $('.btn-collapse').click(function() {
193 var button = $(this);
193 var button = $(this);
194 var togglename = $(this).data("toggle");
194 var togglename = $(this).data("toggle");
195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
196 if ($(this).html()=="Show Less")
196 if ($(this).html()=="Show Less")
197 $(this).html("Show More");
197 $(this).html("Show More");
198 else
198 else
199 $(this).html("Show Less");
199 $(this).html("Show Less");
200 });
200 });
201 };
201 };
202
202
203 var timeagoActivate = function() {
203 var timeagoActivate = function() {
204 $("time.timeago").timeago();
204 $("time.timeago").timeago();
205 };
205 };
206
206
207
207
208 var clipboardActivate = function() {
208 var clipboardActivate = function() {
209 /*
209 /*
210 *
210 *
211 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
211 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
212 * */
212 * */
213 var clipboard = new ClipboardJS('.clipboard-action');
213 var clipboard = new ClipboardJS('.clipboard-action');
214
214
215 clipboard.on('success', function(e) {
215 clipboard.on('success', function(e) {
216 var callback = function () {
216 var callback = function () {
217 $(e.trigger).animate({'opacity': 1.00}, 200)
217 $(e.trigger).animate({'opacity': 1.00}, 200)
218 };
218 };
219 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
219 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
220 e.clearSelection();
220 e.clearSelection();
221 });
221 });
222 };
222 };
223
223
224 var tooltipActivate = function () {
224 var tooltipActivate = function () {
225 var delay = 50;
225 var delay = 50;
226 var animation = 'fade';
226 var animation = 'fade';
227 var theme = 'tooltipster-shadow';
227 var theme = 'tooltipster-shadow';
228 var debug = false;
228 var debug = false;
229
229
230 $('.tooltip').tooltipster({
230 $('.tooltip').tooltipster({
231 debug: debug,
231 debug: debug,
232 theme: theme,
232 theme: theme,
233 animation: animation,
233 animation: animation,
234 delay: delay,
234 delay: delay,
235 contentCloning: true,
235 contentCloning: true,
236 contentAsHTML: true,
236 contentAsHTML: true,
237
237
238 functionBefore: function (instance, helper) {
238 functionBefore: function (instance, helper) {
239 var $origin = $(helper.origin);
239 var $origin = $(helper.origin);
240 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content());
240 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content());
241 instance.content(data);
241 instance.content(data);
242 }
242 }
243 });
243 });
244 var hovercardCache = {};
244 var hovercardCache = {};
245
245
246 var loadHoverCard = function (url, altHovercard, callback) {
246 var loadHoverCard = function (url, altHovercard, callback) {
247 var id = url;
247 var id = url;
248
248
249 if (hovercardCache[id] !== undefined) {
249 if (hovercardCache[id] !== undefined) {
250 callback(hovercardCache[id]);
250 callback(hovercardCache[id]);
251 return true;
251 return true;
252 }
252 }
253
253
254 hovercardCache[id] = undefined;
254 hovercardCache[id] = undefined;
255 $.get(url, function (data) {
255 $.get(url, function (data) {
256 hovercardCache[id] = data;
256 hovercardCache[id] = data;
257 callback(hovercardCache[id]);
257 callback(hovercardCache[id]);
258 return true;
258 return true;
259 }).fail(function (data, textStatus, errorThrown) {
259 }).fail(function (data, textStatus, errorThrown) {
260
260
261 if (parseInt(data.status) === 404) {
261 if (parseInt(data.status) === 404) {
262 var msg = "<p>{0}</p>".format(altHovercard || "No Data exists for this hovercard");
262 var msg = "<p>{0}</p>".format(altHovercard || "No Data exists for this hovercard");
263 } else {
263 } else {
264 var msg = "<p class='error-message'>Error while fetching hovercard.\nError code {0} ({1}).</p>".format(data.status,data.statusText);
264 var msg = "<p class='error-message'>Error while fetching hovercard.\nError code {0} ({1}).</p>".format(data.status,data.statusText);
265 }
265 }
266 callback(msg);
266 callback(msg);
267 return false
267 return false
268 });
268 });
269 };
269 };
270
270
271 $('.tooltip-hovercard').tooltipster({
271 $('.tooltip-hovercard').tooltipster({
272 debug: debug,
272 debug: debug,
273 theme: theme,
273 theme: theme,
274 animation: animation,
274 animation: animation,
275 delay: delay,
275 delay: delay,
276 interactive: true,
276 interactive: true,
277 contentCloning: true,
277 contentCloning: true,
278
278
279 trigger: 'custom',
279 trigger: 'custom',
280 triggerOpen: {
280 triggerOpen: {
281 mouseenter: true,
281 mouseenter: true,
282 },
282 },
283 triggerClose: {
283 triggerClose: {
284 mouseleave: true,
284 mouseleave: true,
285 originClick: true,
285 originClick: true,
286 touchleave: true
286 touchleave: true
287 },
287 },
288 content: _gettext('Loading...'),
288 content: _gettext('Loading...'),
289 contentAsHTML: true,
289 contentAsHTML: true,
290 updateAnimation: null,
290 updateAnimation: null,
291
291
292 functionBefore: function (instance, helper) {
292 functionBefore: function (instance, helper) {
293
293
294 var $origin = $(helper.origin);
294 var $origin = $(helper.origin);
295
295
296 // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
296 // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
297 if ($origin.data('loaded') !== true) {
297 if ($origin.data('loaded') !== true) {
298 var hovercardUrl = $origin.data('hovercardUrl');
298 var hovercardUrl = $origin.data('hovercardUrl');
299 var altHovercard =$origin.data('hovercardAlt');
299 var altHovercard =$origin.data('hovercardAlt');
300
300
301 if (hovercardUrl !== undefined && hovercardUrl !== "") {
301 if (hovercardUrl !== undefined && hovercardUrl !== "") {
302 var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) {
302 var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) {
303 instance.content(data);
303 instance.content(data);
304 })
304 })
305 } else {
305 } else {
306 if ($origin.data('hovercardAltHtml')) {
306 if ($origin.data('hovercardAltHtml')) {
307 var data = atob($origin.data('hovercardAltHtml'));
307 var data = atob($origin.data('hovercardAltHtml'));
308 } else {
308 } else {
309 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(altHovercard)
309 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(altHovercard)
310 }
310 }
311 var loaded = true;
311 var loaded = true;
312 instance.content(data);
312 instance.content(data);
313 }
313 }
314
314
315 // to remember that the data has been loaded
315 // to remember that the data has been loaded
316 $origin.data('loaded', loaded);
316 $origin.data('loaded', loaded);
317 }
317 }
318 }
318 }
319 })
319 })
320 };
320 };
321
321
322 // Formatting values in a Select2 dropdown of commit references
322 // Formatting values in a Select2 dropdown of commit references
323 var formatSelect2SelectionRefs = function(commit_ref){
323 var formatSelect2SelectionRefs = function(commit_ref){
324 var tmpl = '';
324 var tmpl = '';
325 if (!commit_ref.text || commit_ref.type === 'sha'){
325 if (!commit_ref.text || commit_ref.type === 'sha'){
326 return commit_ref.text;
326 return commit_ref.text;
327 }
327 }
328 if (commit_ref.type === 'branch'){
328 if (commit_ref.type === 'branch'){
329 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
329 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
330 } else if (commit_ref.type === 'tag'){
330 } else if (commit_ref.type === 'tag'){
331 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
331 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
332 } else if (commit_ref.type === 'book'){
332 } else if (commit_ref.type === 'book'){
333 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
333 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
334 }
334 }
335 return tmpl.concat(escapeHtml(commit_ref.text));
335 return tmpl.concat(escapeHtml(commit_ref.text));
336 };
336 };
337
337
338 // takes a given html element and scrolls it down offset pixels
338 // takes a given html element and scrolls it down offset pixels
339 function offsetScroll(element, offset) {
339 function offsetScroll(element, offset) {
340 setTimeout(function() {
340 setTimeout(function() {
341 var location = element.offset().top;
341 var location = element.offset().top;
342 // some browsers use body, some use html
342 // some browsers use body, some use html
343 $('html, body').animate({ scrollTop: (location - offset) });
343 $('html, body').animate({ scrollTop: (location - offset) });
344 }, 100);
344 }, 100);
345 }
345 }
346
346
347 // scroll an element `percent`% from the top of page in `time` ms
347 // scroll an element `percent`% from the top of page in `time` ms
348 function scrollToElement(element, percent, time) {
348 function scrollToElement(element, percent, time) {
349 percent = (percent === undefined ? 25 : percent);
349 percent = (percent === undefined ? 25 : percent);
350 time = (time === undefined ? 100 : time);
350 time = (time === undefined ? 100 : time);
351
351
352 var $element = $(element);
352 var $element = $(element);
353 if ($element.length == 0) {
353 if ($element.length == 0) {
354 throw('Cannot scroll to {0}'.format(element))
354 throw('Cannot scroll to {0}'.format(element))
355 }
355 }
356 var elOffset = $element.offset().top;
356 var elOffset = $element.offset().top;
357 var elHeight = $element.height();
357 var elHeight = $element.height();
358 var windowHeight = $(window).height();
358 var windowHeight = $(window).height();
359 var offset = elOffset;
359 var offset = elOffset;
360 if (elHeight < windowHeight) {
360 if (elHeight < windowHeight) {
361 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
361 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
362 }
362 }
363 setTimeout(function() {
363 setTimeout(function() {
364 $('html, body').animate({ scrollTop: offset});
364 $('html, body').animate({ scrollTop: offset});
365 }, time);
365 }, time);
366 }
366 }
367
367
368 /**
368 /**
369 * global hooks after DOM is loaded
369 * global hooks after DOM is loaded
370 */
370 */
371 $(document).ready(function() {
371 $(document).ready(function() {
372 firefoxAnchorFix();
372 firefoxAnchorFix();
373
373
374 $('.navigation a.menulink').on('click', function(e){
374 $('.navigation a.menulink').on('click', function(e){
375 var menuitem = $(this).parent('li');
375 var menuitem = $(this).parent('li');
376 if (menuitem.hasClass('open')) {
376 if (menuitem.hasClass('open')) {
377 menuitem.removeClass('open');
377 menuitem.removeClass('open');
378 } else {
378 } else {
379 menuitem.addClass('open');
379 menuitem.addClass('open');
380 $(document).on('click', function(event) {
380 $(document).on('click', function(event) {
381 if (!$(event.target).closest(menuitem).length) {
381 if (!$(event.target).closest(menuitem).length) {
382 menuitem.removeClass('open');
382 menuitem.removeClass('open');
383 }
383 }
384 });
384 });
385 }
385 }
386 });
386 });
387
387
388 $('body').on('click', '.cb-lineno a', function(event) {
388 $('body').on('click', '.cb-lineno a', function(event) {
389 function sortNumber(a,b) {
389 function sortNumber(a,b) {
390 return a - b;
390 return a - b;
391 }
391 }
392
392
393 var lineNo = $(this).data('lineNo');
393 var lineNo = $(this).data('lineNo');
394 var lineName = $(this).attr('name');
394 var lineName = $(this).attr('name');
395
395
396 if (lineNo) {
396 if (lineNo) {
397 var prevLine = $('.cb-line-selected a').data('lineNo');
397 var prevLine = $('.cb-line-selected a').data('lineNo');
398
398
399 // on shift, we do a range selection, if we got previous line
399 // on shift, we do a range selection, if we got previous line
400 if (event.shiftKey && prevLine !== undefined) {
400 if (event.shiftKey && prevLine !== undefined) {
401 var prevLine = parseInt(prevLine);
401 var prevLine = parseInt(prevLine);
402 var nextLine = parseInt(lineNo);
402 var nextLine = parseInt(lineNo);
403 var pos = [prevLine, nextLine].sort(sortNumber);
403 var pos = [prevLine, nextLine].sort(sortNumber);
404 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
404 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
405
405
406 // single click
406 // single click
407 } else {
407 } else {
408 var nextLine = parseInt(lineNo);
408 var nextLine = parseInt(lineNo);
409 var pos = [nextLine, nextLine];
409 var pos = [nextLine, nextLine];
410 var anchor = '#L{0}'.format(pos[0]);
410 var anchor = '#L{0}'.format(pos[0]);
411
411
412 }
412 }
413 // highlight
413 // highlight
414 var range = [];
414 var range = [];
415 for (var i = pos[0]; i <= pos[1]; i++) {
415 for (var i = pos[0]; i <= pos[1]; i++) {
416 range.push(i);
416 range.push(i);
417 }
417 }
418 // clear old selected lines
418 // clear old selected lines
419 $('.cb-line-selected').removeClass('cb-line-selected');
419 $('.cb-line-selected').removeClass('cb-line-selected');
420
420
421 $.each(range, function (i, lineNo) {
421 $.each(range, function (i, lineNo) {
422 var line_td = $('td.cb-lineno#L' + lineNo);
422 var line_td = $('td.cb-lineno#L' + lineNo);
423
423
424 if (line_td.length) {
424 if (line_td.length) {
425 line_td.addClass('cb-line-selected'); // line number td
425 line_td.addClass('cb-line-selected'); // line number td
426 line_td.prev().addClass('cb-line-selected'); // line data
426 line_td.prev().addClass('cb-line-selected'); // line data
427 line_td.next().addClass('cb-line-selected'); // line content
427 line_td.next().addClass('cb-line-selected'); // line content
428 }
428 }
429 });
429 });
430
430
431 } else if (lineName !== undefined) { // lineName only occurs in diffs
431 } else if (lineName !== undefined) { // lineName only occurs in diffs
432 // clear old selected lines
432 // clear old selected lines
433 $('td.cb-line-selected').removeClass('cb-line-selected');
433 $('td.cb-line-selected').removeClass('cb-line-selected');
434 var anchor = '#{0}'.format(lineName);
434 var anchor = '#{0}'.format(lineName);
435 var diffmode = templateContext.session_attrs.diffmode || "sideside";
435 var diffmode = templateContext.session_attrs.diffmode || "sideside";
436
436
437 if (diffmode === "unified") {
437 if (diffmode === "unified") {
438 $(this).closest('tr').find('td').addClass('cb-line-selected');
438 $(this).closest('tr').find('td').addClass('cb-line-selected');
439 } else {
439 } else {
440 var activeTd = $(this).closest('td');
440 var activeTd = $(this).closest('td');
441 activeTd.addClass('cb-line-selected');
441 activeTd.addClass('cb-line-selected');
442 activeTd.next('td').addClass('cb-line-selected');
442 activeTd.next('td').addClass('cb-line-selected');
443 }
443 }
444
444
445 }
445 }
446
446
447 // Replace URL without jumping to it if browser supports.
447 // Replace URL without jumping to it if browser supports.
448 // Default otherwise
448 // Default otherwise
449 if (history.pushState && anchor !== undefined) {
449 if (history.pushState && anchor !== undefined) {
450 var new_location = location.href.rstrip('#');
450 var new_location = location.href.rstrip('#');
451 if (location.hash) {
451 if (location.hash) {
452 // location without hash
452 // location without hash
453 new_location = new_location.replace(location.hash, "");
453 new_location = new_location.replace(location.hash, "");
454 }
454 }
455
455
456 // Make new anchor url
456 // Make new anchor url
457 new_location = new_location + anchor;
457 new_location = new_location + anchor;
458 history.pushState(true, document.title, new_location);
458 history.pushState(true, document.title, new_location);
459
459
460 return false;
460 return false;
461 }
461 }
462
462
463 });
463 });
464
464
465 $('.collapse_file').on('click', function(e) {
465 $('.collapse_file').on('click', function(e) {
466 e.stopPropagation();
466 e.stopPropagation();
467 if ($(e.target).is('a')) { return; }
467 if ($(e.target).is('a')) { return; }
468 var node = $(e.delegateTarget).first();
468 var node = $(e.delegateTarget).first();
469 var icon = $($(node.children().first()).children().first());
469 var icon = $($(node.children().first()).children().first());
470 var id = node.attr('fid');
470 var id = node.attr('fid');
471 var target = $('#'+id);
471 var target = $('#'+id);
472 var tr = $('#tr_'+id);
472 var tr = $('#tr_'+id);
473 var diff = $('#diff_'+id);
473 var diff = $('#diff_'+id);
474 if(node.hasClass('expand_file')){
474 if(node.hasClass('expand_file')){
475 node.removeClass('expand_file');
475 node.removeClass('expand_file');
476 icon.removeClass('expand_file_icon');
476 icon.removeClass('expand_file_icon');
477 node.addClass('collapse_file');
477 node.addClass('collapse_file');
478 icon.addClass('collapse_file_icon');
478 icon.addClass('collapse_file_icon');
479 diff.show();
479 diff.show();
480 tr.show();
480 tr.show();
481 target.show();
481 target.show();
482 } else {
482 } else {
483 node.removeClass('collapse_file');
483 node.removeClass('collapse_file');
484 icon.removeClass('collapse_file_icon');
484 icon.removeClass('collapse_file_icon');
485 node.addClass('expand_file');
485 node.addClass('expand_file');
486 icon.addClass('expand_file_icon');
486 icon.addClass('expand_file_icon');
487 diff.hide();
487 diff.hide();
488 tr.hide();
488 tr.hide();
489 target.hide();
489 target.hide();
490 }
490 }
491 });
491 });
492
492
493 $('#expand_all_files').click(function() {
493 $('#expand_all_files').click(function() {
494 $('.expand_file').each(function() {
494 $('.expand_file').each(function() {
495 var node = $(this);
495 var node = $(this);
496 var icon = $($(node.children().first()).children().first());
496 var icon = $($(node.children().first()).children().first());
497 var id = $(this).attr('fid');
497 var id = $(this).attr('fid');
498 var target = $('#'+id);
498 var target = $('#'+id);
499 var tr = $('#tr_'+id);
499 var tr = $('#tr_'+id);
500 var diff = $('#diff_'+id);
500 var diff = $('#diff_'+id);
501 node.removeClass('expand_file');
501 node.removeClass('expand_file');
502 icon.removeClass('expand_file_icon');
502 icon.removeClass('expand_file_icon');
503 node.addClass('collapse_file');
503 node.addClass('collapse_file');
504 icon.addClass('collapse_file_icon');
504 icon.addClass('collapse_file_icon');
505 diff.show();
505 diff.show();
506 tr.show();
506 tr.show();
507 target.show();
507 target.show();
508 });
508 });
509 });
509 });
510
510
511 $('#collapse_all_files').click(function() {
511 $('#collapse_all_files').click(function() {
512 $('.collapse_file').each(function() {
512 $('.collapse_file').each(function() {
513 var node = $(this);
513 var node = $(this);
514 var icon = $($(node.children().first()).children().first());
514 var icon = $($(node.children().first()).children().first());
515 var id = $(this).attr('fid');
515 var id = $(this).attr('fid');
516 var target = $('#'+id);
516 var target = $('#'+id);
517 var tr = $('#tr_'+id);
517 var tr = $('#tr_'+id);
518 var diff = $('#diff_'+id);
518 var diff = $('#diff_'+id);
519 node.removeClass('collapse_file');
519 node.removeClass('collapse_file');
520 icon.removeClass('collapse_file_icon');
520 icon.removeClass('collapse_file_icon');
521 node.addClass('expand_file');
521 node.addClass('expand_file');
522 icon.addClass('expand_file_icon');
522 icon.addClass('expand_file_icon');
523 diff.hide();
523 diff.hide();
524 tr.hide();
524 tr.hide();
525 target.hide();
525 target.hide();
526 });
526 });
527 });
527 });
528
528
529 // Mouse over behavior for comments and line selection
529 // Mouse over behavior for comments and line selection
530
530
531 // Select the line that comes from the url anchor
531 // Select the line that comes from the url anchor
532 // At the time of development, Chrome didn't seem to support jquery's :target
532 // At the time of development, Chrome didn't seem to support jquery's :target
533 // element, so I had to scroll manually
533 // element, so I had to scroll manually
534
534
535 if (location.hash) {
535 if (location.hash) {
536 var result = splitDelimitedHash(location.hash);
536 var result = splitDelimitedHash(location.hash);
537 var loc = result.loc;
537
538 var loc = result.loc;
539
538 if (loc.length > 1) {
540 if (loc.length > 1) {
539
541
540 var highlightable_line_tds = [];
542 var highlightable_line_tds = [];
541
543
542 // source code line format
544 // source code line format
543 var page_highlights = loc.substring(
545 var page_highlights = loc.substring(loc.indexOf('#') + 1).split('L');
544 loc.indexOf('#') + 1).split('L');
545
546
547 // multi-line HL, for files
546 if (page_highlights.length > 1) {
548 if (page_highlights.length > 1) {
547 var highlight_ranges = page_highlights[1].split(",");
549 var highlight_ranges = page_highlights[1].split(",");
548 var h_lines = [];
550 var h_lines = [];
549 for (var pos in highlight_ranges) {
551 for (var pos in highlight_ranges) {
550 var _range = highlight_ranges[pos].split('-');
552 var _range = highlight_ranges[pos].split('-');
551 if (_range.length === 2) {
553 if (_range.length === 2) {
552 var start = parseInt(_range[0]);
554 var start = parseInt(_range[0]);
553 var end = parseInt(_range[1]);
555 var end = parseInt(_range[1]);
554 if (start < end) {
556 if (start < end) {
555 for (var i = start; i <= end; i++) {
557 for (var i = start; i <= end; i++) {
556 h_lines.push(i);
558 h_lines.push(i);
557 }
559 }
558 }
560 }
559 }
561 } else {
560 else {
561 h_lines.push(parseInt(highlight_ranges[pos]));
562 h_lines.push(parseInt(highlight_ranges[pos]));
562 }
563 }
563 }
564 }
564 for (pos in h_lines) {
565 for (pos in h_lines) {
565 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
566 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
566 if (line_td.length) {
567 if (line_td.length) {
567 highlightable_line_tds.push(line_td);
568 highlightable_line_tds.push(line_td);
568 }
569 }
569 }
570 }
570 }
571 }
571
572
572 // now check a direct id reference (diff page)
573 // now check a direct id reference of line in diff / pull-request page)
573 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
574 if ($(loc).length > 0 && $(loc).hasClass('cb-lineno')) {
574 highlightable_line_tds.push($(loc));
575 highlightable_line_tds.push($(loc));
575 }
576 }
577
578 // mark diff lines as selected
576 $.each(highlightable_line_tds, function (i, $td) {
579 $.each(highlightable_line_tds, function (i, $td) {
577 $td.addClass('cb-line-selected'); // line number td
580 $td.addClass('cb-line-selected'); // line number td
578 $td.prev().addClass('cb-line-selected'); // line data
581 $td.prev().addClass('cb-line-selected'); // line data
579 $td.next().addClass('cb-line-selected'); // line content
582 $td.next().addClass('cb-line-selected'); // line content
580 });
583 });
581
584
582 if (highlightable_line_tds.length) {
585 if (highlightable_line_tds.length > 0) {
583 var $first_line_td = highlightable_line_tds[0];
586 var $first_line_td = highlightable_line_tds[0];
584 scrollToElement($first_line_td);
587 scrollToElement($first_line_td);
585 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
588 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
586 td: $first_line_td,
589 td: $first_line_td,
587 remainder: result.remainder
590 remainder: result.remainder
588 });
591 });
592 } else {
593 // case for direct anchor to comments
594 var $line = $(loc);
595
596 if ($line.hasClass('comment-general')) {
597 $line.show();
598 } else if ($line.hasClass('comment-inline')) {
599 $line.show();
600 var $cb = $line.closest('.cb');
601 $cb.removeClass('cb-collapsed')
602 }
603 if ($line.length > 0) {
604 $line.addClass('comment-selected-hl');
605 offsetScroll($line, 70);
606 }
607 if (!$line.hasClass('comment-outdated') && result.remainder === '/ReplyToComment') {
608 $line.nextAll('.cb-comment-add-button').trigger('click');
609 }
589 }
610 }
611
590 }
612 }
591 }
613 }
592 collapsableContent();
614 collapsableContent();
593 });
615 });
594
616
595 var feedLifetimeOptions = function(query, initialData){
617 var feedLifetimeOptions = function(query, initialData){
596 var data = {results: []};
618 var data = {results: []};
597 var isQuery = typeof query.term !== 'undefined';
619 var isQuery = typeof query.term !== 'undefined';
598
620
599 var section = _gettext('Lifetime');
621 var section = _gettext('Lifetime');
600 var children = [];
622 var children = [];
601
623
602 //filter results
624 //filter results
603 $.each(initialData.results, function(idx, value) {
625 $.each(initialData.results, function(idx, value) {
604
626
605 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
627 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
606 children.push({
628 children.push({
607 'id': this.id,
629 'id': this.id,
608 'text': this.text
630 'text': this.text
609 })
631 })
610 }
632 }
611
633
612 });
634 });
613 data.results.push({
635 data.results.push({
614 'text': section,
636 'text': section,
615 'children': children
637 'children': children
616 });
638 });
617
639
618 if (isQuery) {
640 if (isQuery) {
619
641
620 var now = moment.utc();
642 var now = moment.utc();
621
643
622 var parseQuery = function(entry, now){
644 var parseQuery = function(entry, now){
623 var fmt = 'DD/MM/YYYY H:mm';
645 var fmt = 'DD/MM/YYYY H:mm';
624 var parsed = moment.utc(entry, fmt);
646 var parsed = moment.utc(entry, fmt);
625 var diffInMin = parsed.diff(now, 'minutes');
647 var diffInMin = parsed.diff(now, 'minutes');
626
648
627 if (diffInMin > 0){
649 if (diffInMin > 0){
628 return {
650 return {
629 id: diffInMin,
651 id: diffInMin,
630 text: parsed.format(fmt)
652 text: parsed.format(fmt)
631 }
653 }
632 } else {
654 } else {
633 return {
655 return {
634 id: undefined,
656 id: undefined,
635 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
657 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
636 }
658 }
637 }
659 }
638
660
639
661
640 };
662 };
641
663
642 data.results.push({
664 data.results.push({
643 'text': _gettext('Specified expiration date'),
665 'text': _gettext('Specified expiration date'),
644 'children': [{
666 'children': [{
645 'id': parseQuery(query.term, now).id,
667 'id': parseQuery(query.term, now).id,
646 'text': parseQuery(query.term, now).text
668 'text': parseQuery(query.term, now).text
647 }]
669 }]
648 });
670 });
649 }
671 }
650
672
651 query.callback(data);
673 query.callback(data);
652 };
674 };
653
675
654
676
655 var storeUserSessionAttr = function (key, val) {
677 var storeUserSessionAttr = function (key, val) {
656
678
657 var postData = {
679 var postData = {
658 'key': key,
680 'key': key,
659 'val': val,
681 'val': val,
660 'csrf_token': CSRF_TOKEN
682 'csrf_token': CSRF_TOKEN
661 };
683 };
662
684
663 var success = function(o) {
685 var success = function(o) {
664 return true
686 return true
665 };
687 };
666
688
667 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
689 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
668 return false;
690 return false;
669 };
691 };
@@ -1,314 +1,302 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
7
7
8 <%def name="title()">
8 <%def name="title()">
9 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
9 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='repositories')}
16 ${self.menu_items(active='repositories')}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_subnav()">
19 <%def name="menu_bar_subnav()">
20 ${self.repo_menu(active='commits')}
20 ${self.repo_menu(active='commits')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <script type="text/javascript">
24 <script type="text/javascript">
25 // TODO: marcink switch this to pyroutes
25 // TODO: marcink switch this to pyroutes
26 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
26 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
27 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
27 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
28 </script>
28 </script>
29
29
30 <div class="box">
30 <div class="box">
31
31
32 <div class="summary">
32 <div class="summary">
33
33
34 <div class="fieldset">
34 <div class="fieldset">
35 <div class="left-content">
35 <div class="left-content">
36 <%
36 <%
37 rc_user = h.discover_user(c.commit.author_email)
37 rc_user = h.discover_user(c.commit.author_email)
38 %>
38 %>
39 <div class="left-content-avatar">
39 <div class="left-content-avatar">
40 ${base.gravatar(c.commit.author_email, 30, tooltip=True, user=rc_user)}
40 ${base.gravatar(c.commit.author_email, 30, tooltip=True, user=rc_user)}
41 </div>
41 </div>
42
42
43 <div class="left-content-message">
43 <div class="left-content-message">
44 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
44 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
45 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
45 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
46 </div>
46 </div>
47
47
48 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
48 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
49 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
49 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
50 </div>
50 </div>
51
51
52 <div class="fieldset" data-toggle="summary-details">
52 <div class="fieldset" data-toggle="summary-details">
53 <div class="">
53 <div class="">
54 <table>
54 <table>
55 <tr class="file_author">
55 <tr class="file_author">
56
56
57 <td>
57 <td>
58 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
58 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
59 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
59 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
60 </td>
60 </td>
61
61
62 <td>
62 <td>
63 ## second cell for consistency with files
63 ## second cell for consistency with files
64 </td>
64 </td>
65 </tr>
65 </tr>
66 </table>
66 </table>
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70 </div>
70 </div>
71 </div>
71 </div>
72
72
73 <div class="right-content">
73 <div class="right-content">
74
74
75 <div data-toggle="summary-details">
75 <div data-toggle="summary-details">
76 <div class="tags tags-main">
76 <div class="tags tags-main">
77 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
77 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
78 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
78 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
79 ${file_base.refs(c.commit)}
79 ${file_base.refs(c.commit)}
80
80
81 ## phase
81 ## phase
82 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
82 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
83 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
83 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
84 <i class="icon-info"></i>${c.commit.phase}
84 <i class="icon-info"></i>${c.commit.phase}
85 </span>
85 </span>
86 % endif
86 % endif
87
87
88 ## obsolete commits
88 ## obsolete commits
89 % if getattr(c.commit, 'obsolete', False):
89 % if getattr(c.commit, 'obsolete', False):
90 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
90 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
91 ${_('obsolete')}
91 ${_('obsolete')}
92 </span>
92 </span>
93 % endif
93 % endif
94
94
95 ## hidden commits
95 ## hidden commits
96 % if getattr(c.commit, 'hidden', False):
96 % if getattr(c.commit, 'hidden', False):
97 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
97 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
98 ${_('hidden')}
98 ${_('hidden')}
99 </span>
99 </span>
100 % endif
100 % endif
101 </div>
101 </div>
102
102
103 %if c.statuses:
103 %if c.statuses:
104 <div class="tag status-tag-${c.statuses[0]} pull-right">
104 <div class="tag status-tag-${c.statuses[0]} pull-right">
105 <i class="icon-circle review-status-${c.statuses[0]}"></i>
105 <i class="icon-circle review-status-${c.statuses[0]}"></i>
106 <div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div>
106 <div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div>
107 </div>
107 </div>
108 %endif
108 %endif
109
109
110 </div>
110 </div>
111
111
112 </div>
112 </div>
113 </div>
113 </div>
114
114
115 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
115 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
116 <div class="left-label-summary">
116 <div class="left-label-summary">
117 <p>${_('Commit navigation')}:</p>
117 <p>${_('Commit navigation')}:</p>
118 <div class="right-label-summary">
118 <div class="right-label-summary">
119 <span id="parent_link" class="tag tagtag">
119 <span id="parent_link" class="tag tagtag">
120 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
120 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
121 </span>
121 </span>
122
122
123 <span id="child_link" class="tag tagtag">
123 <span id="child_link" class="tag tagtag">
124 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
124 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
125 </span>
125 </span>
126 </div>
126 </div>
127 </div>
127 </div>
128 </div>
128 </div>
129
129
130 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
130 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
131 <div class="left-label-summary">
131 <div class="left-label-summary">
132 <p>${_('Diff options')}:</p>
132 <p>${_('Diff options')}:</p>
133 <div class="right-label-summary">
133 <div class="right-label-summary">
134 <div class="diff-actions">
134 <div class="diff-actions">
135 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
135 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
136 ${_('Raw Diff')}
136 ${_('Raw Diff')}
137 </a>
137 </a>
138 |
138 |
139 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
139 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
140 ${_('Patch Diff')}
140 ${_('Patch Diff')}
141 </a>
141 </a>
142 |
142 |
143 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
143 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
144 ${_('Download Diff')}
144 ${_('Download Diff')}
145 </a>
145 </a>
146 </div>
146 </div>
147 </div>
147 </div>
148 </div>
148 </div>
149 </div>
149 </div>
150
150
151 <div class="clear-fix"></div>
151 <div class="clear-fix"></div>
152
152
153 <div class="btn-collapse" data-toggle="summary-details">
153 <div class="btn-collapse" data-toggle="summary-details">
154 ${_('Show More')}
154 ${_('Show More')}
155 </div>
155 </div>
156
156
157 </div>
157 </div>
158
158
159 <div class="cs_files">
159 <div class="cs_files">
160 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
160 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
161 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id])}
161 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id])}
162 ${cbdiffs.render_diffset(
162 ${cbdiffs.render_diffset(
163 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
163 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
164 </div>
164 </div>
165
165
166 ## template for inline comment form
166 ## template for inline comment form
167 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
167 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
168
168
169 ## comments heading with count
169 ## comments heading with count
170 <div class="comments-heading">
170 <div class="comments-heading">
171 <i class="icon-comment"></i>
171 <i class="icon-comment"></i>
172 ${_('Comments')} ${len(c.comments)}
172 ${_('Comments')} ${len(c.comments)}
173 </div>
173 </div>
174
174
175 ## render comments
175 ## render comments
176 ${comment.generate_comments(c.comments)}
176 ${comment.generate_comments(c.comments)}
177
177
178 ## main comment form and it status
178 ## main comment form and it status
179 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
179 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
180 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
180 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
181 </div>
181 </div>
182
182
183 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
183 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
184 <script type="text/javascript">
184 <script type="text/javascript">
185
185
186 $(document).ready(function() {
186 $(document).ready(function() {
187
187
188 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
188 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
189 if($('#trimmed_message_box').height() === boxmax){
189 if($('#trimmed_message_box').height() === boxmax){
190 $('#message_expand').show();
190 $('#message_expand').show();
191 }
191 }
192
192
193 $('#message_expand').on('click', function(e){
193 $('#message_expand').on('click', function(e){
194 $('#trimmed_message_box').css('max-height', 'none');
194 $('#trimmed_message_box').css('max-height', 'none');
195 $(this).hide();
195 $(this).hide();
196 });
196 });
197
197
198 $('.show-inline-comments').on('click', function(e){
198 $('.show-inline-comments').on('click', function(e){
199 var boxid = $(this).attr('data-comment-id');
199 var boxid = $(this).attr('data-comment-id');
200 var button = $(this);
200 var button = $(this);
201
201
202 if(button.hasClass("comments-visible")) {
202 if(button.hasClass("comments-visible")) {
203 $('#{0} .inline-comments'.format(boxid)).each(function(index){
203 $('#{0} .inline-comments'.format(boxid)).each(function(index){
204 $(this).hide();
204 $(this).hide();
205 });
205 });
206 button.removeClass("comments-visible");
206 button.removeClass("comments-visible");
207 } else {
207 } else {
208 $('#{0} .inline-comments'.format(boxid)).each(function(index){
208 $('#{0} .inline-comments'.format(boxid)).each(function(index){
209 $(this).show();
209 $(this).show();
210 });
210 });
211 button.addClass("comments-visible");
211 button.addClass("comments-visible");
212 }
212 }
213 });
213 });
214
214
215
216 // next links
215 // next links
217 $('#child_link').on('click', function(e){
216 $('#child_link').on('click', function(e){
218 // fetch via ajax what is going to be the next link, if we have
217 // fetch via ajax what is going to be the next link, if we have
219 // >1 links show them to user to choose
218 // >1 links show them to user to choose
220 if(!$('#child_link').hasClass('disabled')){
219 if(!$('#child_link').hasClass('disabled')){
221 $.ajax({
220 $.ajax({
222 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
221 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
223 success: function(data) {
222 success: function(data) {
224 if(data.results.length === 0){
223 if(data.results.length === 0){
225 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
224 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
226 }
225 }
227 if(data.results.length === 1){
226 if(data.results.length === 1){
228 var commit = data.results[0];
227 var commit = data.results[0];
229 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
228 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
230 }
229 }
231 else if(data.results.length === 2){
230 else if(data.results.length === 2){
232 $('#child_link').addClass('disabled');
231 $('#child_link').addClass('disabled');
233 $('#child_link').addClass('double');
232 $('#child_link').addClass('double');
234
233
235 var _html = '';
234 var _html = '';
236 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
235 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
237 .replace('__branch__', data.results[0].branch)
236 .replace('__branch__', data.results[0].branch)
238 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
237 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
239 .replace('__title__', data.results[0].message)
238 .replace('__title__', data.results[0].message)
240 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
239 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
241 _html +=' | ';
240 _html +=' | ';
242 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
241 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
243 .replace('__branch__', data.results[1].branch)
242 .replace('__branch__', data.results[1].branch)
244 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
243 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
245 .replace('__title__', data.results[1].message)
244 .replace('__title__', data.results[1].message)
246 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
245 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
247 $('#child_link').html(_html);
246 $('#child_link').html(_html);
248 }
247 }
249 }
248 }
250 });
249 });
251 e.preventDefault();
250 e.preventDefault();
252 }
251 }
253 });
252 });
254
253
255 // prev links
254 // prev links
256 $('#parent_link').on('click', function(e){
255 $('#parent_link').on('click', function(e){
257 // fetch via ajax what is going to be the next link, if we have
256 // fetch via ajax what is going to be the next link, if we have
258 // >1 links show them to user to choose
257 // >1 links show them to user to choose
259 if(!$('#parent_link').hasClass('disabled')){
258 if(!$('#parent_link').hasClass('disabled')){
260 $.ajax({
259 $.ajax({
261 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
260 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
262 success: function(data) {
261 success: function(data) {
263 if(data.results.length === 0){
262 if(data.results.length === 0){
264 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
263 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
265 }
264 }
266 if(data.results.length === 1){
265 if(data.results.length === 1){
267 var commit = data.results[0];
266 var commit = data.results[0];
268 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
267 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
269 }
268 }
270 else if(data.results.length === 2){
269 else if(data.results.length === 2){
271 $('#parent_link').addClass('disabled');
270 $('#parent_link').addClass('disabled');
272 $('#parent_link').addClass('double');
271 $('#parent_link').addClass('double');
273
272
274 var _html = '';
273 var _html = '';
275 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
274 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
276 .replace('__branch__', data.results[0].branch)
275 .replace('__branch__', data.results[0].branch)
277 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
276 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
278 .replace('__title__', data.results[0].message)
277 .replace('__title__', data.results[0].message)
279 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
278 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
280 _html +=' | ';
279 _html +=' | ';
281 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
280 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
282 .replace('__branch__', data.results[1].branch)
281 .replace('__branch__', data.results[1].branch)
283 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
282 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
284 .replace('__title__', data.results[1].message)
283 .replace('__title__', data.results[1].message)
285 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
284 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
286 $('#parent_link').html(_html);
285 $('#parent_link').html(_html);
287 }
286 }
288 }
287 }
289 });
288 });
290 e.preventDefault();
289 e.preventDefault();
291 }
290 }
292 });
291 });
293
292
294 if (location.hash) {
295 var result = splitDelimitedHash(location.hash);
296 var line = $('html').find(result.loc);
297 if (line.length > 0){
298 offsetScroll(line, 70);
299 }
300 }
301
302 // browse tree @ revision
293 // browse tree @ revision
303 $('#files_link').on('click', function(e){
294 $('#files_link').on('click', function(e){
304 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
295 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
305 e.preventDefault();
296 e.preventDefault();
306 });
297 });
307
298
308 // inject comments into their proper positions
309 var file_comments = $('.inline-comment-placeholder');
310
311 })
299 })
312 </script>
300 </script>
313
301
314 </%def>
302 </%def>
@@ -1,161 +1,170 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 ## EMAIL SUBJECT
5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim,whitespace_filter">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 <%
7 <%
8 data = {
8 data = {
9 'user': '@'+h.person(user),
9 'user': '@'+h.person(user),
10 'repo_name': repo_name,
10 'repo_name': repo_name,
11 'status': status_change,
11 'status': status_change,
12 'comment_file': comment_file,
12 'comment_file': comment_file,
13 'comment_line': comment_line,
13 'comment_line': comment_line,
14 'comment_type': comment_type,
14 'comment_type': comment_type,
15 'comment_id': comment_id,
15
16
16 'commit_id': h.show_id(commit),
17 'commit_id': h.show_id(commit),
17 }
18 }
18 %>
19 %>
19
20
20
21
21 % if comment_file:
22 % if comment_file:
22 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}`').format(**data)} ${_('in the `{repo_name}` repository').format(**data) |n}
23 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}`').format(**data)} ${_('in the `{repo_name}` repository').format(**data) |n}
23 % else:
24 % else:
24 % if status_change:
25 % if status_change:
25 ${(_('[mention]') if mention else '')} ${_('[status: {status}] {user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
26 ${(_('[mention]') if mention else '')} ${_('[status: {status}] {user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
26 % else:
27 % else:
27 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
28 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on commit `{commit_id}`').format(**data) |n} ${_('in the `{repo_name}` repository').format(**data) |n}
28 % endif
29 % endif
29 % endif
30 % endif
30
31
31 </%def>
32 </%def>
32
33
33 ## PLAINTEXT VERSION OF BODY
34 ## PLAINTEXT VERSION OF BODY
34 <%def name="body_plaintext()" filter="n,trim">
35 <%def name="body_plaintext()" filter="n,trim">
35 <%
36 <%
36 data = {
37 data = {
37 'user': h.person(user),
38 'user': h.person(user),
38 'repo_name': repo_name,
39 'repo_name': repo_name,
39 'status': status_change,
40 'status': status_change,
40 'comment_file': comment_file,
41 'comment_file': comment_file,
41 'comment_line': comment_line,
42 'comment_line': comment_line,
42 'comment_type': comment_type,
43 'comment_type': comment_type,
44 'comment_id': comment_id,
43
45
44 'commit_id': h.show_id(commit),
46 'commit_id': h.show_id(commit),
45 }
47 }
46 %>
48 %>
47
49
48 * ${_('Comment link')}: ${commit_comment_url}
50 * ${_('Comment link')}: ${commit_comment_url}
49
51
50 %if status_change:
52 %if status_change:
51 * ${_('Commit status')}: ${_('Status was changed to')}: *${status_change}*
53 * ${_('Commit status')}: ${_('Status was changed to')}: *${status_change}*
52
54
53 %endif
55 %endif
54 * ${_('Commit')}: ${h.show_id(commit)}
56 * ${_('Commit')}: ${h.show_id(commit)}
55
57
56 * ${_('Commit message')}: ${commit.message}
58 * ${_('Commit message')}: ${commit.message}
57
59
58 %if comment_file:
60 %if comment_file:
59 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
61 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
60
62
61 %endif
63 %endif
62 % if comment_type == 'todo':
64 % if comment_type == 'todo':
63 ${_('`TODO` comment')}:
65 ${_('`TODO` comment')}:
64 % else:
66 % else:
65 ${_('`Note` comment')}:
67 ${_('`Note` comment')}:
66 % endif
68 % endif
67
69
68 ${comment_body |n, trim}
70 ${comment_body |n, trim}
69
71
70 ---
72 ---
71 ${self.plaintext_footer()}
73 ${self.plaintext_footer()}
72 </%def>
74 </%def>
73
75
74
76
75 <%
77 <%
76 data = {
78 data = {
77 'user': h.person(user),
79 'user': h.person(user),
78 'comment_file': comment_file,
80 'comment_file': comment_file,
79 'comment_line': comment_line,
81 'comment_line': comment_line,
80 'comment_type': comment_type,
82 'comment_type': comment_type,
83 'comment_id': comment_id,
81 'renderer_type': renderer_type or 'plain',
84 'renderer_type': renderer_type or 'plain',
82
85
83 'repo': commit_target_repo_url,
86 'repo': commit_target_repo_url,
84 'repo_name': repo_name,
87 'repo_name': repo_name,
85 'commit_id': h.show_id(commit),
88 'commit_id': h.show_id(commit),
86 }
89 }
87 %>
90 %>
88
91
89 <table style="text-align:left;vertical-align:middle;width: 100%">
92 <table style="text-align:left;vertical-align:middle;width: 100%">
90 <tr>
93 <tr>
91 <td style="width:100%;border-bottom:1px solid #dbd9da;">
94 <td style="width:100%;border-bottom:1px solid #dbd9da;">
92
95
93 <h4 style="margin: 0">
96 <h4 style="margin: 0">
94 <div style="margin-bottom: 4px; color:#7E7F7F">
97 <div style="margin-bottom: 4px; color:#7E7F7F">
95 @${h.person(user.username)}
98 @${h.person(user.username)}
96 </div>
99 </div>
97 ${_('left a')}
100 ${_('left a')}
98 <a href="${commit_comment_url}" style="${base.link_css()}">
101 <a href="${commit_comment_url}" style="${base.link_css()}">
99 % if comment_file:
102 % if comment_file:
100 ${_('{comment_type} on file `{comment_file}` in commit.').format(**data)}
103 ${_('{comment_type} on file `{comment_file}` in commit.').format(**data)}
101 % else:
104 % else:
102 ${_('{comment_type} on commit.').format(**data) |n}
105 ${_('{comment_type} on commit.').format(**data) |n}
103 % endif
106 % endif
104 </a>
107 </a>
105 <div style="margin-top: 10px"></div>
108 <div style="margin-top: 10px"></div>
106 ${_('Commit')} <code>${data['commit_id']}</code> ${_('of repository')}: ${data['repo_name']}
109 ${_('Commit')} <code>${data['commit_id']}</code> ${_('of repository')}: ${data['repo_name']}
107 </h4>
110 </h4>
108
111
109 </td>
112 </td>
110 </tr>
113 </tr>
111
114
112 </table>
115 </table>
113
116
114 <table style="text-align:left;vertical-align:middle;width: 100%">
117 <table style="text-align:left;vertical-align:middle;width: 100%">
115
118
116 ## spacing def
119 ## spacing def
117 <tr>
120 <tr>
118 <td style="width: 130px"></td>
121 <td style="width: 130px"></td>
119 <td></td>
122 <td></td>
120 </tr>
123 </tr>
121
124
122 % if status_change:
125 % if status_change:
123 <tr>
126 <tr>
124 <td style="padding-right:20px;">${_('Commit Status')}:</td>
127 <td style="padding-right:20px;">${_('Commit Status')}:</td>
125 <td>
128 <td>
126 ${_('Status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}
129 ${_('Status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}
127 </td>
130 </td>
128 </tr>
131 </tr>
129 % endif
132 % endif
130
133
131 <tr>
134 <tr>
132 <td style="padding-right:20px;">${_('Commit')}:</td>
135 <td style="padding-right:20px;">${_('Commit')}:</td>
133 <td>
136 <td>
134 <a href="${commit_comment_url}" style="${base.link_css()}">${h.show_id(commit)}</a>
137 <a href="${commit_comment_url}" style="${base.link_css()}">${h.show_id(commit)}</a>
135 </td>
138 </td>
136 </tr>
139 </tr>
137 <tr>
140 <tr>
138 <td style="padding-right:20px;">${_('Commit message')}:</td>
141 <td style="padding-right:20px;">${_('Commit message')}:</td>
139 <td style="white-space:pre-wrap">${h.urlify_commit_message(commit.message, repo_name)}</td>
142 <td style="white-space:pre-wrap">${h.urlify_commit_message(commit.message, repo_name)}</td>
140 </tr>
143 </tr>
141
144
142 % if comment_file:
145 % if comment_file:
143 <tr>
146 <tr>
144 <td style="padding-right:20px;">${_('File')}:</td>
147 <td style="padding-right:20px;">${_('File')}:</td>
145 <td><a href="${commit_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
148 <td><a href="${commit_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
146 </tr>
149 </tr>
147 % endif
150 % endif
148
151
149 <tr style="background-image: linear-gradient(to right, black 33%, rgba(255,255,255,0) 0%);background-position: bottom;background-size: 3px 1px;background-repeat: repeat-x;">
152 <tr style="border-bottom:1px solid #dbd9da;">
150 <td colspan="2" style="padding-right:20px;">
153 <td colspan="2" style="padding-right:20px;">
151 % if comment_type == 'todo':
154 % if comment_type == 'todo':
152 ${_('`TODO` comment')}:
155 ${_('`TODO` number')} ${comment_id}:
153 % else:
156 % else:
154 ${_('`Note` comment')}:
157 ${_('`Note` number')} ${comment_id}:
155 % endif
158 % endif
156 </td>
159 </td>
157 </tr>
160 </tr>
158
161
159 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
162 <tr>
163 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
164 </tr>
165
166 <tr>
167 <td><a href="${commit_comment_reply_url}">${_('Reply')}</a></td>
168 <td></td>
160 </tr>
169 </tr>
161 </table>
170 </table>
@@ -1,191 +1,200 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 ## EMAIL SUBJECT
5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim,whitespace_filter">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 <%
7 <%
8 data = {
8 data = {
9 'user': '@'+h.person(user),
9 'user': '@'+h.person(user),
10 'repo_name': repo_name,
10 'repo_name': repo_name,
11 'status': status_change,
11 'status': status_change,
12 'comment_file': comment_file,
12 'comment_file': comment_file,
13 'comment_line': comment_line,
13 'comment_line': comment_line,
14 'comment_type': comment_type,
14 'comment_type': comment_type,
15 'comment_id': comment_id,
15
16
16 'pr_title': pull_request.title,
17 'pr_title': pull_request.title,
17 'pr_id': pull_request.pull_request_id,
18 'pr_id': pull_request.pull_request_id,
18 }
19 }
19 %>
20 %>
20
21
21
22
22 % if comment_file:
23 % if comment_file:
23 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: "{pr_title}"').format(**data) |n}
24 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: "{pr_title}"').format(**data) |n}
24 % else:
25 % else:
25 % if status_change:
26 % if status_change:
26 ${(_('[mention]') if mention else '')} ${_('[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
27 ${(_('[mention]') if mention else '')} ${_('[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
27 % else:
28 % else:
28 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
29 ${(_('[mention]') if mention else '')} ${_('{user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data) |n}
29 % endif
30 % endif
30 % endif
31 % endif
31
32
32 </%def>
33 </%def>
33
34
34 ## PLAINTEXT VERSION OF BODY
35 ## PLAINTEXT VERSION OF BODY
35 <%def name="body_plaintext()" filter="n,trim">
36 <%def name="body_plaintext()" filter="n,trim">
36 <%
37 <%
37 data = {
38 data = {
38 'user': h.person(user),
39 'user': h.person(user),
39 'repo_name': repo_name,
40 'repo_name': repo_name,
40 'status': status_change,
41 'status': status_change,
41 'comment_file': comment_file,
42 'comment_file': comment_file,
42 'comment_line': comment_line,
43 'comment_line': comment_line,
43 'comment_type': comment_type,
44 'comment_type': comment_type,
45 'comment_id': comment_id,
44
46
45 'pr_title': pull_request.title,
47 'pr_title': pull_request.title,
46 'pr_id': pull_request.pull_request_id,
48 'pr_id': pull_request.pull_request_id,
47 'source_ref_type': pull_request.source_ref_parts.type,
49 'source_ref_type': pull_request.source_ref_parts.type,
48 'source_ref_name': pull_request.source_ref_parts.name,
50 'source_ref_name': pull_request.source_ref_parts.name,
49 'target_ref_type': pull_request.target_ref_parts.type,
51 'target_ref_type': pull_request.target_ref_parts.type,
50 'target_ref_name': pull_request.target_ref_parts.name,
52 'target_ref_name': pull_request.target_ref_parts.name,
51 'source_repo': pull_request_source_repo.repo_name,
53 'source_repo': pull_request_source_repo.repo_name,
52 'target_repo': pull_request_target_repo.repo_name,
54 'target_repo': pull_request_target_repo.repo_name,
53 'source_repo_url': pull_request_source_repo_url,
55 'source_repo_url': pull_request_source_repo_url,
54 'target_repo_url': pull_request_target_repo_url,
56 'target_repo_url': pull_request_target_repo_url,
55 }
57 }
56 %>
58 %>
57
59
58 ${h.literal(_('Pull request !{pr_id}: `{pr_title}`').format(**data))}
60 ${h.literal(_('Pull request !{pr_id}: `{pr_title}`').format(**data))}
59
61
60 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
62 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
61
63
62 * ${_('Comment link')}: ${pr_comment_url}
64 * ${_('Comment link')}: ${pr_comment_url}
63
65
64 %if status_change and not closing_pr:
66 %if status_change and not closing_pr:
65 * ${_('{user} submitted pull request !{pr_id} status: *{status}*').format(**data)}
67 * ${_('{user} submitted pull request !{pr_id} status: *{status}*').format(**data)}
66
68
67 %elif status_change and closing_pr:
69 %elif status_change and closing_pr:
68 * ${_('{user} submitted pull request !{pr_id} status: *{status} and closed*').format(**data)}
70 * ${_('{user} submitted pull request !{pr_id} status: *{status} and closed*').format(**data)}
69
71
70 %endif
72 %endif
71 %if comment_file:
73 %if comment_file:
72 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
74 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
73
75
74 %endif
76 %endif
75 % if comment_type == 'todo':
77 % if comment_type == 'todo':
76 ${_('`TODO` comment')}:
78 ${_('`TODO` comment')}:
77 % else:
79 % else:
78 ${_('`Note` comment')}:
80 ${_('`Note` comment')}:
79 % endif
81 % endif
80
82
81 ${comment_body |n, trim}
83 ${comment_body |n, trim}
82
84
83 ---
85 ---
84 ${self.plaintext_footer()}
86 ${self.plaintext_footer()}
85 </%def>
87 </%def>
86
88
87
89
88 <%
90 <%
89 data = {
91 data = {
90 'user': h.person(user),
92 'user': h.person(user),
91 'comment_file': comment_file,
93 'comment_file': comment_file,
92 'comment_line': comment_line,
94 'comment_line': comment_line,
93 'comment_type': comment_type,
95 'comment_type': comment_type,
96 'comment_id': comment_id,
94 'renderer_type': renderer_type or 'plain',
97 'renderer_type': renderer_type or 'plain',
95
98
96 'pr_title': pull_request.title,
99 'pr_title': pull_request.title,
97 'pr_id': pull_request.pull_request_id,
100 'pr_id': pull_request.pull_request_id,
98 'status': status_change,
101 'status': status_change,
99 'source_ref_type': pull_request.source_ref_parts.type,
102 'source_ref_type': pull_request.source_ref_parts.type,
100 'source_ref_name': pull_request.source_ref_parts.name,
103 'source_ref_name': pull_request.source_ref_parts.name,
101 'target_ref_type': pull_request.target_ref_parts.type,
104 'target_ref_type': pull_request.target_ref_parts.type,
102 'target_ref_name': pull_request.target_ref_parts.name,
105 'target_ref_name': pull_request.target_ref_parts.name,
103 'source_repo': pull_request_source_repo.repo_name,
106 'source_repo': pull_request_source_repo.repo_name,
104 'target_repo': pull_request_target_repo.repo_name,
107 'target_repo': pull_request_target_repo.repo_name,
105 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
108 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
106 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
109 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
107 }
110 }
108 %>
111 %>
109
112
110 <table style="text-align:left;vertical-align:middle;width: 100%">
113 <table style="text-align:left;vertical-align:middle;width: 100%">
111 <tr>
114 <tr>
112 <td style="width:100%;border-bottom:1px solid #dbd9da;">
115 <td style="width:100%;border-bottom:1px solid #dbd9da;">
113
116
114 <h4 style="margin: 0">
117 <h4 style="margin: 0">
115 <div style="margin-bottom: 4px; color:#7E7F7F">
118 <div style="margin-bottom: 4px; color:#7E7F7F">
116 @${h.person(user.username)}
119 @${h.person(user.username)}
117 </div>
120 </div>
118 ${_('left a')}
121 ${_('left a')}
119 <a href="${pr_comment_url}" style="${base.link_css()}">
122 <a href="${pr_comment_url}" style="${base.link_css()}">
120 % if comment_file:
123 % if comment_file:
121 ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)}
124 ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)}
122 % else:
125 % else:
123 ${_('{comment_type} on pull request.').format(**data) |n}
126 ${_('{comment_type} on pull request.').format(**data) |n}
124 % endif
127 % endif
125 </a>
128 </a>
126 <div style="margin-top: 10px"></div>
129 <div style="margin-top: 10px"></div>
127 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
130 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
128 </h4>
131 </h4>
129
132
130 </td>
133 </td>
131 </tr>
134 </tr>
132
135
133 </table>
136 </table>
134
137
135 <table style="text-align:left;vertical-align:middle;width: 100%">
138 <table style="text-align:left;vertical-align:middle;width: 100%">
136
139
137 ## spacing def
140 ## spacing def
138 <tr>
141 <tr>
139 <td style="width: 130px"></td>
142 <td style="width: 130px"></td>
140 <td></td>
143 <td></td>
141 </tr>
144 </tr>
142
145
143 % if status_change:
146 % if status_change:
144 <tr>
147 <tr>
145 <td style="padding-right:20px;">${_('Review Status')}:</td>
148 <td style="padding-right:20px;">${_('Review Status')}:</td>
146 <td>
149 <td>
147 % if closing_pr:
150 % if closing_pr:
148 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
151 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
149 % else:
152 % else:
150 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
153 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
151 % endif
154 % endif
152 </td>
155 </td>
153 </tr>
156 </tr>
154 % endif
157 % endif
155
158
156 <tr>
159 <tr>
157 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
160 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
158 <td style="line-height:20px;">
161 <td style="line-height:20px;">
159 ${base.tag_button('{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name))} ${_('of')} ${data['source_repo_url']}
162 ${base.tag_button('{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name))} ${_('of')} ${data['source_repo_url']}
160 &rarr;
163 &rarr;
161 ${base.tag_button('{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name))} ${_('of')} ${data['target_repo_url']}
164 ${base.tag_button('{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name))} ${_('of')} ${data['target_repo_url']}
162 </td>
165 </td>
163 </tr>
166 </tr>
164 <tr>
167 <tr>
165 <td style="padding-right:20px;">${_('Pull request')}:</td>
168 <td style="padding-right:20px;">${_('Pull request')}:</td>
166 <td>
169 <td>
167 <a href="${pull_request_url}" style="${base.link_css()}">
170 <a href="${pull_request_url}" style="${base.link_css()}">
168 !${pull_request.pull_request_id}
171 !${pull_request.pull_request_id}
169 </a>
172 </a>
170 </td>
173 </td>
171 </tr>
174 </tr>
172 % if comment_file:
175 % if comment_file:
173 <tr>
176 <tr>
174 <td style="padding-right:20px;">${_('File')}:</td>
177 <td style="padding-right:20px;">${_('File')}:</td>
175 <td><a href="${pr_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
178 <td><a href="${pr_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
176 </tr>
179 </tr>
177 % endif
180 % endif
178
181
179 <tr style="background-image: linear-gradient(to right, black 33%, rgba(255,255,255,0) 0%);background-position: bottom;background-size: 3px 1px;background-repeat: repeat-x;">
182 <tr style="border-bottom:1px solid #dbd9da;">
180 <td colspan="2" style="padding-right:20px;">
183 <td colspan="2" style="padding-right:20px;">
181 % if comment_type == 'todo':
184 % if comment_type == 'todo':
182 ${_('`TODO` comment')}:
185 ${_('`TODO` number')} ${comment_id}:
183 % else:
186 % else:
184 ${_('`Note` comment')}:
187 ${_('`Note` number')} ${comment_id}:
185 % endif
188 % endif
186 </td>
189 </td>
187 </tr>
190 </tr>
188
191
189 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
192 <tr>
193 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
194 </tr>
195
196 <tr>
197 <td><a href="${pr_comment_reply_url}">${_('Reply')}</a></td>
198 <td></td>
190 </tr>
199 </tr>
191 </table>
200 </table>
@@ -1,804 +1,790 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="breadcrumbs_links()">
12 <%def name="breadcrumbs_links()">
13 <span id="pr-title">
13 <span id="pr-title">
14 ${c.pull_request.title}
14 ${c.pull_request.title}
15 %if c.pull_request.is_closed():
15 %if c.pull_request.is_closed():
16 (${_('Closed')})
16 (${_('Closed')})
17 %endif
17 %endif
18 </span>
18 </span>
19 <div id="pr-title-edit" class="input" style="display: none;">
19 <div id="pr-title-edit" class="input" style="display: none;">
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
21 </div>
21 </div>
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_nav()">
24 <%def name="menu_bar_nav()">
25 ${self.menu_items(active='repositories')}
25 ${self.menu_items(active='repositories')}
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_subnav()">
28 <%def name="menu_bar_subnav()">
29 ${self.repo_menu(active='showpullrequest')}
29 ${self.repo_menu(active='showpullrequest')}
30 </%def>
30 </%def>
31
31
32 <%def name="main()">
32 <%def name="main()">
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 </script>
38 </script>
39 <div class="box">
39 <div class="box">
40
40
41 ${self.breadcrumbs()}
41 ${self.breadcrumbs()}
42
42
43 <div class="box pr-summary">
43 <div class="box pr-summary">
44
44
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
47 <div class="pr-details-title">
47 <div class="pr-details-title">
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 %if c.allowed_to_update:
49 %if c.allowed_to_update:
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 % if c.allowed_to_delete:
51 % if c.allowed_to_delete:
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 % else:
56 % else:
57 ${_('Delete')}
57 ${_('Delete')}
58 % endif
58 % endif
59 </div>
59 </div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 %endif
62 %endif
63 </div>
63 </div>
64
64
65 <div id="summary" class="fields pr-details-content">
65 <div id="summary" class="fields pr-details-content">
66 <div class="field">
66 <div class="field">
67 <div class="label-summary">
67 <div class="label-summary">
68 <label>${_('Source')}:</label>
68 <label>${_('Source')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 <div class="pr-origininfo">
71 <div class="pr-origininfo">
72 ## branch link is only valid if it is a branch
72 ## branch link is only valid if it is a branch
73 <span class="tag">
73 <span class="tag">
74 %if c.pull_request.source_ref_parts.type == 'branch':
74 %if c.pull_request.source_ref_parts.type == 'branch':
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 %else:
76 %else:
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 %endif
78 %endif
79 </span>
79 </span>
80 <span class="clone-url">
80 <span class="clone-url">
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 </span>
82 </span>
83 <br/>
83 <br/>
84 % if c.ancestor_commit:
84 % if c.ancestor_commit:
85 ${_('Common ancestor')}:
85 ${_('Common ancestor')}:
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
87 % endif
87 % endif
88 </div>
88 </div>
89 %if h.is_hg(c.pull_request.source_repo):
89 %if h.is_hg(c.pull_request.source_repo):
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
91 %elif h.is_git(c.pull_request.source_repo):
91 %elif h.is_git(c.pull_request.source_repo):
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
93 %endif
93 %endif
94
94
95 <div class="">
95 <div class="">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
98 </div>
98 </div>
99
99
100 </div>
100 </div>
101 </div>
101 </div>
102 <div class="field">
102 <div class="field">
103 <div class="label-summary">
103 <div class="label-summary">
104 <label>${_('Target')}:</label>
104 <label>${_('Target')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 <div class="pr-targetinfo">
107 <div class="pr-targetinfo">
108 ## branch link is only valid if it is a branch
108 ## branch link is only valid if it is a branch
109 <span class="tag">
109 <span class="tag">
110 %if c.pull_request.target_ref_parts.type == 'branch':
110 %if c.pull_request.target_ref_parts.type == 'branch':
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
112 %else:
112 %else:
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
114 %endif
114 %endif
115 </span>
115 </span>
116 <span class="clone-url">
116 <span class="clone-url">
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
118 </span>
118 </span>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 ## Link to the shadow repository.
123 ## Link to the shadow repository.
124 <div class="field">
124 <div class="field">
125 <div class="label-summary">
125 <div class="label-summary">
126 <label>${_('Merge')}:</label>
126 <label>${_('Merge')}:</label>
127 </div>
127 </div>
128 <div class="input">
128 <div class="input">
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
130 %if h.is_hg(c.pull_request.target_repo):
130 %if h.is_hg(c.pull_request.target_repo):
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
132 %elif h.is_git(c.pull_request.target_repo):
132 %elif h.is_git(c.pull_request.target_repo):
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 %endif
134 %endif
135 <div class="">
135 <div class="">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
138 </div>
138 </div>
139 % else:
139 % else:
140 <div class="">
140 <div class="">
141 ${_('Shadow repository data not available')}.
141 ${_('Shadow repository data not available')}.
142 </div>
142 </div>
143 % endif
143 % endif
144 </div>
144 </div>
145 </div>
145 </div>
146
146
147 <div class="field">
147 <div class="field">
148 <div class="label-summary">
148 <div class="label-summary">
149 <label>${_('Review')}:</label>
149 <label>${_('Review')}:</label>
150 </div>
150 </div>
151 <div class="input">
151 <div class="input">
152 %if c.pull_request_review_status:
152 %if c.pull_request_review_status:
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
154 <span class="changeset-status-lbl tooltip">
154 <span class="changeset-status-lbl tooltip">
155 %if c.pull_request.is_closed():
155 %if c.pull_request.is_closed():
156 ${_('Closed')},
156 ${_('Closed')},
157 %endif
157 %endif
158 ${h.commit_status_lbl(c.pull_request_review_status)}
158 ${h.commit_status_lbl(c.pull_request_review_status)}
159 </span>
159 </span>
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
161 %endif
161 %endif
162 </div>
162 </div>
163 </div>
163 </div>
164 <div class="field">
164 <div class="field">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
166 <label>${_('Description')}:</label>
166 <label>${_('Description')}:</label>
167 </div>
167 </div>
168 <div id="pr-desc" class="input">
168 <div id="pr-desc" class="input">
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}</div>
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}</div>
170 </div>
170 </div>
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
174 </div>
174 </div>
175 </div>
175 </div>
176
176
177 <div class="field">
177 <div class="field">
178 <div class="label-summary">
178 <div class="label-summary">
179 <label>${_('Versions')}:</label>
179 <label>${_('Versions')}:</label>
180 </div>
180 </div>
181
181
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
184
184
185 <div class="pr-versions">
185 <div class="pr-versions">
186 % if c.show_version_changes:
186 % if c.show_version_changes:
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
191 data-toggle-off="${_('Hide all versions of this pull request')}">
191 data-toggle-off="${_('Hide all versions of this pull request')}">
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
193 </a>
193 </a>
194 <table>
194 <table>
195 ## SHOW ALL VERSIONS OF PR
195 ## SHOW ALL VERSIONS OF PR
196 <% ver_pr = None %>
196 <% ver_pr = None %>
197
197
198 % for data in reversed(list(enumerate(c.versions, 1))):
198 % for data in reversed(list(enumerate(c.versions, 1))):
199 <% ver_pos = data[0] %>
199 <% ver_pos = data[0] %>
200 <% ver = data[1] %>
200 <% ver = data[1] %>
201 <% ver_pr = ver.pull_request_version_id %>
201 <% ver_pr = ver.pull_request_version_id %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
203
203
204 <tr class="version-pr" style="display: ${display_row}">
204 <tr class="version-pr" style="display: ${display_row}">
205 <td>
205 <td>
206 <code>
206 <code>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
208 </code>
208 </code>
209 </td>
209 </td>
210 <td>
210 <td>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 </td>
213 </td>
214 <td>
214 <td>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
217 </div>
217 </div>
218 </td>
218 </td>
219 <td>
219 <td>
220 % if c.at_version_num != ver_pr:
220 % if c.at_version_num != ver_pr:
221 <i class="icon-comment"></i>
221 <i class="icon-comment"></i>
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
224 </code>
224 </code>
225 % endif
225 % endif
226 </td>
226 </td>
227 <td>
227 <td>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
229 </td>
229 </td>
230 <td>
230 <td>
231 ${h.age_component(ver.updated_on, time_is_local=True)}
231 ${h.age_component(ver.updated_on, time_is_local=True)}
232 </td>
232 </td>
233 </tr>
233 </tr>
234 % endfor
234 % endfor
235
235
236 <tr>
236 <tr>
237 <td colspan="6">
237 <td colspan="6">
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
239 data-label-text-locked="${_('select versions to show changes')}"
239 data-label-text-locked="${_('select versions to show changes')}"
240 data-label-text-diff="${_('show changes between versions')}"
240 data-label-text-diff="${_('show changes between versions')}"
241 data-label-text-show="${_('show pull request for this version')}"
241 data-label-text-show="${_('show pull request for this version')}"
242 >
242 >
243 ${_('select versions to show changes')}
243 ${_('select versions to show changes')}
244 </button>
244 </button>
245 </td>
245 </td>
246 </tr>
246 </tr>
247 </table>
247 </table>
248 % else:
248 % else:
249 <div class="input">
249 <div class="input">
250 ${_('Pull request versions not available')}.
250 ${_('Pull request versions not available')}.
251 </div>
251 </div>
252 % endif
252 % endif
253 </div>
253 </div>
254 </div>
254 </div>
255
255
256 <div id="pr-save" class="field" style="display: none;">
256 <div id="pr-save" class="field" style="display: none;">
257 <div class="label-summary"></div>
257 <div class="label-summary"></div>
258 <div class="input">
258 <div class="input">
259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
260 </div>
260 </div>
261 </div>
261 </div>
262 </div>
262 </div>
263 </div>
263 </div>
264 <div>
264 <div>
265 ## AUTHOR
265 ## AUTHOR
266 <div class="reviewers-title block-right">
266 <div class="reviewers-title block-right">
267 <div class="pr-details-title">
267 <div class="pr-details-title">
268 ${_('Author of this pull request')}
268 ${_('Author of this pull request')}
269 </div>
269 </div>
270 </div>
270 </div>
271 <div class="block-right pr-details-content reviewers">
271 <div class="block-right pr-details-content reviewers">
272 <ul class="group_members">
272 <ul class="group_members">
273 <li>
273 <li>
274 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
274 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
275 </li>
275 </li>
276 </ul>
276 </ul>
277 </div>
277 </div>
278
278
279 ## REVIEW RULES
279 ## REVIEW RULES
280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
281 <div class="pr-details-title">
281 <div class="pr-details-title">
282 ${_('Reviewer rules')}
282 ${_('Reviewer rules')}
283 %if c.allowed_to_update:
283 %if c.allowed_to_update:
284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
285 %endif
285 %endif
286 </div>
286 </div>
287 <div class="pr-reviewer-rules">
287 <div class="pr-reviewer-rules">
288 ## review rules will be appended here, by default reviewers logic
288 ## review rules will be appended here, by default reviewers logic
289 </div>
289 </div>
290 <input id="review_data" type="hidden" name="review_data" value="">
290 <input id="review_data" type="hidden" name="review_data" value="">
291 </div>
291 </div>
292
292
293 ## REVIEWERS
293 ## REVIEWERS
294 <div class="reviewers-title block-right">
294 <div class="reviewers-title block-right">
295 <div class="pr-details-title">
295 <div class="pr-details-title">
296 ${_('Pull request reviewers')}
296 ${_('Pull request reviewers')}
297 %if c.allowed_to_update:
297 %if c.allowed_to_update:
298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
299 %endif
299 %endif
300 </div>
300 </div>
301 </div>
301 </div>
302 <div id="reviewers" class="block-right pr-details-content reviewers">
302 <div id="reviewers" class="block-right pr-details-content reviewers">
303
303
304 ## members redering block
304 ## members redering block
305 <input type="hidden" name="__start__" value="review_members:sequence">
305 <input type="hidden" name="__start__" value="review_members:sequence">
306 <ul id="review_members" class="group_members">
306 <ul id="review_members" class="group_members">
307
307
308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
309 <script>
309 <script>
310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
314
314
315 var entry = renderTemplate('reviewMemberEntry', {
315 var entry = renderTemplate('reviewMemberEntry', {
316 'member': member,
316 'member': member,
317 'mandatory': member.mandatory,
317 'mandatory': member.mandatory,
318 'reasons': member.reasons,
318 'reasons': member.reasons,
319 'allowed_to_update': allowed_to_update,
319 'allowed_to_update': allowed_to_update,
320 'review_status': status,
320 'review_status': status,
321 'review_status_label': status_lbl,
321 'review_status_label': status_lbl,
322 'user_group': member.user_group,
322 'user_group': member.user_group,
323 'create': false
323 'create': false
324 });
324 });
325 $('#review_members').append(entry)
325 $('#review_members').append(entry)
326 </script>
326 </script>
327
327
328 % endfor
328 % endfor
329
329
330 </ul>
330 </ul>
331
331
332 <input type="hidden" name="__end__" value="review_members:sequence">
332 <input type="hidden" name="__end__" value="review_members:sequence">
333 ## end members redering block
333 ## end members redering block
334
334
335 %if not c.pull_request.is_closed():
335 %if not c.pull_request.is_closed():
336 <div id="add_reviewer" class="ac" style="display: none;">
336 <div id="add_reviewer" class="ac" style="display: none;">
337 %if c.allowed_to_update:
337 %if c.allowed_to_update:
338 % if not c.forbid_adding_reviewers:
338 % if not c.forbid_adding_reviewers:
339 <div id="add_reviewer_input" class="reviewer_ac">
339 <div id="add_reviewer_input" class="reviewer_ac">
340 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
340 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
341 <div id="reviewers_container"></div>
341 <div id="reviewers_container"></div>
342 </div>
342 </div>
343 % endif
343 % endif
344 <div class="pull-right">
344 <div class="pull-right">
345 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
345 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
346 </div>
346 </div>
347 %endif
347 %endif
348 </div>
348 </div>
349 %endif
349 %endif
350 </div>
350 </div>
351 </div>
351 </div>
352 </div>
352 </div>
353 <div class="box">
353 <div class="box">
354 ##DIFF
354 ##DIFF
355 <div class="table" >
355 <div class="table" >
356 <div id="changeset_compare_view_content">
356 <div id="changeset_compare_view_content">
357 ##CS
357 ##CS
358 % if c.missing_requirements:
358 % if c.missing_requirements:
359 <div class="box">
359 <div class="box">
360 <div class="alert alert-warning">
360 <div class="alert alert-warning">
361 <div>
361 <div>
362 <strong>${_('Missing requirements:')}</strong>
362 <strong>${_('Missing requirements:')}</strong>
363 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
363 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
364 </div>
364 </div>
365 </div>
365 </div>
366 </div>
366 </div>
367 % elif c.missing_commits:
367 % elif c.missing_commits:
368 <div class="box">
368 <div class="box">
369 <div class="alert alert-warning">
369 <div class="alert alert-warning">
370 <div>
370 <div>
371 <strong>${_('Missing commits')}:</strong>
371 <strong>${_('Missing commits')}:</strong>
372 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
372 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
373 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
373 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
374 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
374 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
375 </div>
375 </div>
376 </div>
376 </div>
377 </div>
377 </div>
378 % endif
378 % endif
379
379
380 <div class="compare_view_commits_title">
380 <div class="compare_view_commits_title">
381 % if not c.compare_mode:
381 % if not c.compare_mode:
382
382
383 % if c.at_version_pos:
383 % if c.at_version_pos:
384 <h4>
384 <h4>
385 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
385 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
386 </h4>
386 </h4>
387 % endif
387 % endif
388
388
389 <div class="pull-left">
389 <div class="pull-left">
390 <div class="btn-group">
390 <div class="btn-group">
391 <a
391 <a
392 class="btn"
392 class="btn"
393 href="#"
393 href="#"
394 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
394 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
395 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
395 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
396 </a>
396 </a>
397 <a
397 <a
398 class="btn"
398 class="btn"
399 href="#"
399 href="#"
400 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
400 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
401 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
401 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
402 </a>
402 </a>
403 </div>
403 </div>
404 </div>
404 </div>
405
405
406 <div class="pull-right">
406 <div class="pull-right">
407 % if c.allowed_to_update and not c.pull_request.is_closed():
407 % if c.allowed_to_update and not c.pull_request.is_closed():
408 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
408 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
409 % else:
409 % else:
410 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
410 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
411 % endif
411 % endif
412
412
413 </div>
413 </div>
414 % endif
414 % endif
415 </div>
415 </div>
416
416
417 % if not c.missing_commits:
417 % if not c.missing_commits:
418 % if c.compare_mode:
418 % if c.compare_mode:
419 % if c.at_version:
419 % if c.at_version:
420 <h4>
420 <h4>
421 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
421 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
422 </h4>
422 </h4>
423
423
424 <div class="subtitle-compare">
424 <div class="subtitle-compare">
425 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
425 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
426 </div>
426 </div>
427
427
428 <div class="container">
428 <div class="container">
429 <table class="rctable compare_view_commits">
429 <table class="rctable compare_view_commits">
430 <tr>
430 <tr>
431 <th></th>
431 <th></th>
432 <th>${_('Time')}</th>
432 <th>${_('Time')}</th>
433 <th>${_('Author')}</th>
433 <th>${_('Author')}</th>
434 <th>${_('Commit')}</th>
434 <th>${_('Commit')}</th>
435 <th></th>
435 <th></th>
436 <th>${_('Description')}</th>
436 <th>${_('Description')}</th>
437 </tr>
437 </tr>
438
438
439 % for c_type, commit in c.commit_changes:
439 % for c_type, commit in c.commit_changes:
440 % if c_type in ['a', 'r']:
440 % if c_type in ['a', 'r']:
441 <%
441 <%
442 if c_type == 'a':
442 if c_type == 'a':
443 cc_title = _('Commit added in displayed changes')
443 cc_title = _('Commit added in displayed changes')
444 elif c_type == 'r':
444 elif c_type == 'r':
445 cc_title = _('Commit removed in displayed changes')
445 cc_title = _('Commit removed in displayed changes')
446 else:
446 else:
447 cc_title = ''
447 cc_title = ''
448 %>
448 %>
449 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
449 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
450 <td>
450 <td>
451 <div class="commit-change-indicator color-${c_type}-border">
451 <div class="commit-change-indicator color-${c_type}-border">
452 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
452 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
453 ${c_type.upper()}
453 ${c_type.upper()}
454 </div>
454 </div>
455 </div>
455 </div>
456 </td>
456 </td>
457 <td class="td-time">
457 <td class="td-time">
458 ${h.age_component(commit.date)}
458 ${h.age_component(commit.date)}
459 </td>
459 </td>
460 <td class="td-user">
460 <td class="td-user">
461 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
461 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
462 </td>
462 </td>
463 <td class="td-hash">
463 <td class="td-hash">
464 <code>
464 <code>
465 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
465 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
466 r${commit.idx}:${h.short_id(commit.raw_id)}
466 r${commit.idx}:${h.short_id(commit.raw_id)}
467 </a>
467 </a>
468 ${h.hidden('revisions', commit.raw_id)}
468 ${h.hidden('revisions', commit.raw_id)}
469 </code>
469 </code>
470 </td>
470 </td>
471 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
471 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
472 <i class="icon-expand-linked"></i>
472 <i class="icon-expand-linked"></i>
473 </td>
473 </td>
474 <td class="mid td-description">
474 <td class="mid td-description">
475 <div class="log-container truncate-wrap">
475 <div class="log-container truncate-wrap">
476 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
476 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
477 </div>
477 </div>
478 </td>
478 </td>
479 </tr>
479 </tr>
480 % endif
480 % endif
481 % endfor
481 % endfor
482 </table>
482 </table>
483 </div>
483 </div>
484
484
485 % endif
485 % endif
486
486
487 % else:
487 % else:
488 <%include file="/compare/compare_commits.mako" />
488 <%include file="/compare/compare_commits.mako" />
489 % endif
489 % endif
490
490
491 <div class="cs_files">
491 <div class="cs_files">
492 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
492 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
493 % if c.at_version:
493 % if c.at_version:
494 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
494 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
495 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
495 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
496 % else:
496 % else:
497 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
497 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
498 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
498 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
499 % endif
499 % endif
500
500
501 <%
501 <%
502 pr_menu_data = {
502 pr_menu_data = {
503 'outdated_comm_count_ver': outdated_comm_count_ver
503 'outdated_comm_count_ver': outdated_comm_count_ver
504 }
504 }
505 %>
505 %>
506
506
507 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
507 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
508
508
509 % if c.range_diff_on:
509 % if c.range_diff_on:
510 % for commit in c.commit_ranges:
510 % for commit in c.commit_ranges:
511 ${cbdiffs.render_diffset(
511 ${cbdiffs.render_diffset(
512 c.changes[commit.raw_id],
512 c.changes[commit.raw_id],
513 commit=commit, use_comments=True,
513 commit=commit, use_comments=True,
514 collapse_when_files_over=5,
514 collapse_when_files_over=5,
515 disable_new_comments=True,
515 disable_new_comments=True,
516 deleted_files_comments=c.deleted_files_comments,
516 deleted_files_comments=c.deleted_files_comments,
517 inline_comments=c.inline_comments,
517 inline_comments=c.inline_comments,
518 pull_request_menu=pr_menu_data)}
518 pull_request_menu=pr_menu_data)}
519 % endfor
519 % endfor
520 % else:
520 % else:
521 ${cbdiffs.render_diffset(
521 ${cbdiffs.render_diffset(
522 c.diffset, use_comments=True,
522 c.diffset, use_comments=True,
523 collapse_when_files_over=30,
523 collapse_when_files_over=30,
524 disable_new_comments=not c.allowed_to_comment,
524 disable_new_comments=not c.allowed_to_comment,
525 deleted_files_comments=c.deleted_files_comments,
525 deleted_files_comments=c.deleted_files_comments,
526 inline_comments=c.inline_comments,
526 inline_comments=c.inline_comments,
527 pull_request_menu=pr_menu_data)}
527 pull_request_menu=pr_menu_data)}
528 % endif
528 % endif
529
529
530 </div>
530 </div>
531 % else:
531 % else:
532 ## skipping commits we need to clear the view for missing commits
532 ## skipping commits we need to clear the view for missing commits
533 <div style="clear:both;"></div>
533 <div style="clear:both;"></div>
534 % endif
534 % endif
535
535
536 </div>
536 </div>
537 </div>
537 </div>
538
538
539 ## template for inline comment form
539 ## template for inline comment form
540 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
540 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
541
541
542 ## comments heading with count
542 ## comments heading with count
543 <div class="comments-heading">
543 <div class="comments-heading">
544 <i class="icon-comment"></i>
544 <i class="icon-comment"></i>
545 ${_('Comments')} ${len(c.comments)}
545 ${_('Comments')} ${len(c.comments)}
546 </div>
546 </div>
547
547
548 ## render general comments
548 ## render general comments
549 <div id="comment-tr-show">
549 <div id="comment-tr-show">
550 % if general_outdated_comm_count_ver:
550 % if general_outdated_comm_count_ver:
551 <div class="info-box">
551 <div class="info-box">
552 % if general_outdated_comm_count_ver == 1:
552 % if general_outdated_comm_count_ver == 1:
553 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
553 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
554 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
554 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
555 % else:
555 % else:
556 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
556 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
557 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
557 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
558 % endif
558 % endif
559 </div>
559 </div>
560 % endif
560 % endif
561 </div>
561 </div>
562
562
563 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
563 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
564
564
565 % if not c.pull_request.is_closed():
565 % if not c.pull_request.is_closed():
566 ## merge status, and merge action
566 ## merge status, and merge action
567 <div class="pull-request-merge">
567 <div class="pull-request-merge">
568 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
568 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
569 </div>
569 </div>
570
570
571 ## main comment form and it status
571 ## main comment form and it status
572 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
572 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
573 pull_request_id=c.pull_request.pull_request_id),
573 pull_request_id=c.pull_request.pull_request_id),
574 c.pull_request_review_status,
574 c.pull_request_review_status,
575 is_pull_request=True, change_status=c.allowed_to_change_status)}
575 is_pull_request=True, change_status=c.allowed_to_change_status)}
576 %endif
576 %endif
577
577
578 <script type="text/javascript">
578 <script type="text/javascript">
579 if (location.hash) {
580 var result = splitDelimitedHash(location.hash);
581 var line = $('html').find(result.loc);
582 // show hidden comments if we use location.hash
583 if (line.hasClass('comment-general')) {
584 $(line).show();
585 } else if (line.hasClass('comment-inline')) {
586 $(line).show();
587 var $cb = $(line).closest('.cb');
588 $cb.removeClass('cb-collapsed')
589 }
590 if (line.length > 0){
591 offsetScroll(line, 70);
592 }
593 }
594
579
595 versionController = new VersionController();
580 versionController = new VersionController();
596 versionController.init();
581 versionController.init();
597
582
598 reviewersController = new ReviewersController();
583 reviewersController = new ReviewersController();
599 commitsController = new CommitsController();
584 commitsController = new CommitsController();
600
585
601 $(function(){
586 $(function(){
602
587
603 // custom code mirror
588 // custom code mirror
604 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
589 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
605
590
606 var PRDetails = {
591 var PRDetails = {
607 editButton: $('#open_edit_pullrequest'),
592 editButton: $('#open_edit_pullrequest'),
608 closeButton: $('#close_edit_pullrequest'),
593 closeButton: $('#close_edit_pullrequest'),
609 deleteButton: $('#delete_pullrequest'),
594 deleteButton: $('#delete_pullrequest'),
610 viewFields: $('#pr-desc, #pr-title'),
595 viewFields: $('#pr-desc, #pr-title'),
611 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
596 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
612
597
613 init: function() {
598 init: function() {
614 var that = this;
599 var that = this;
615 this.editButton.on('click', function(e) { that.edit(); });
600 this.editButton.on('click', function(e) { that.edit(); });
616 this.closeButton.on('click', function(e) { that.view(); });
601 this.closeButton.on('click', function(e) { that.view(); });
617 },
602 },
618
603
619 edit: function(event) {
604 edit: function(event) {
620 this.viewFields.hide();
605 this.viewFields.hide();
621 this.editButton.hide();
606 this.editButton.hide();
622 this.deleteButton.hide();
607 this.deleteButton.hide();
623 this.closeButton.show();
608 this.closeButton.show();
624 this.editFields.show();
609 this.editFields.show();
625 codeMirrorInstance.refresh();
610 codeMirrorInstance.refresh();
626 },
611 },
627
612
628 view: function(event) {
613 view: function(event) {
629 this.editButton.show();
614 this.editButton.show();
630 this.deleteButton.show();
615 this.deleteButton.show();
631 this.editFields.hide();
616 this.editFields.hide();
632 this.closeButton.hide();
617 this.closeButton.hide();
633 this.viewFields.show();
618 this.viewFields.show();
634 }
619 }
635 };
620 };
636
621
637 var ReviewersPanel = {
622 var ReviewersPanel = {
638 editButton: $('#open_edit_reviewers'),
623 editButton: $('#open_edit_reviewers'),
639 closeButton: $('#close_edit_reviewers'),
624 closeButton: $('#close_edit_reviewers'),
640 addButton: $('#add_reviewer'),
625 addButton: $('#add_reviewer'),
641 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
626 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
642
627
643 init: function() {
628 init: function() {
644 var self = this;
629 var self = this;
645 this.editButton.on('click', function(e) { self.edit(); });
630 this.editButton.on('click', function(e) { self.edit(); });
646 this.closeButton.on('click', function(e) { self.close(); });
631 this.closeButton.on('click', function(e) { self.close(); });
647 },
632 },
648
633
649 edit: function(event) {
634 edit: function(event) {
650 this.editButton.hide();
635 this.editButton.hide();
651 this.closeButton.show();
636 this.closeButton.show();
652 this.addButton.show();
637 this.addButton.show();
653 this.removeButtons.css('visibility', 'visible');
638 this.removeButtons.css('visibility', 'visible');
654 // review rules
639 // review rules
655 reviewersController.loadReviewRules(
640 reviewersController.loadReviewRules(
656 ${c.pull_request.reviewer_data_json | n});
641 ${c.pull_request.reviewer_data_json | n});
657 },
642 },
658
643
659 close: function(event) {
644 close: function(event) {
660 this.editButton.show();
645 this.editButton.show();
661 this.closeButton.hide();
646 this.closeButton.hide();
662 this.addButton.hide();
647 this.addButton.hide();
663 this.removeButtons.css('visibility', 'hidden');
648 this.removeButtons.css('visibility', 'hidden');
664 // hide review rules
649 // hide review rules
665 reviewersController.hideReviewRules()
650 reviewersController.hideReviewRules()
666 }
651 }
667 };
652 };
668
653
669 PRDetails.init();
654 PRDetails.init();
670 ReviewersPanel.init();
655 ReviewersPanel.init();
671
656
672 showOutdated = function(self){
657 showOutdated = function(self){
673 $('.comment-inline.comment-outdated').show();
658 $('.comment-inline.comment-outdated').show();
674 $('.filediff-outdated').show();
659 $('.filediff-outdated').show();
675 $('.showOutdatedComments').hide();
660 $('.showOutdatedComments').hide();
676 $('.hideOutdatedComments').show();
661 $('.hideOutdatedComments').show();
677 };
662 };
678
663
679 hideOutdated = function(self){
664 hideOutdated = function(self){
680 $('.comment-inline.comment-outdated').hide();
665 $('.comment-inline.comment-outdated').hide();
681 $('.filediff-outdated').hide();
666 $('.filediff-outdated').hide();
682 $('.hideOutdatedComments').hide();
667 $('.hideOutdatedComments').hide();
683 $('.showOutdatedComments').show();
668 $('.showOutdatedComments').show();
684 };
669 };
685
670
686 refreshMergeChecks = function(){
671 refreshMergeChecks = function(){
687 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
672 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
688 $('.pull-request-merge').css('opacity', 0.3);
673 $('.pull-request-merge').css('opacity', 0.3);
689 $('.action-buttons-extra').css('opacity', 0.3);
674 $('.action-buttons-extra').css('opacity', 0.3);
690
675
691 $('.pull-request-merge').load(
676 $('.pull-request-merge').load(
692 loadUrl, function() {
677 loadUrl, function() {
693 $('.pull-request-merge').css('opacity', 1);
678 $('.pull-request-merge').css('opacity', 1);
694
679
695 $('.action-buttons-extra').css('opacity', 1);
680 $('.action-buttons-extra').css('opacity', 1);
696 }
681 }
697 );
682 );
698 };
683 };
699
684
700 closePullRequest = function (status) {
685 closePullRequest = function (status) {
701 // inject closing flag
686 // inject closing flag
702 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
687 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
703 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
688 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
704 $(generalCommentForm.submitForm).submit();
689 $(generalCommentForm.submitForm).submit();
705 };
690 };
706
691
707 $('#show-outdated-comments').on('click', function(e){
692 $('#show-outdated-comments').on('click', function(e){
708 var button = $(this);
693 var button = $(this);
709 var outdated = $('.comment-outdated');
694 var outdated = $('.comment-outdated');
710
695
711 if (button.html() === "(Show)") {
696 if (button.html() === "(Show)") {
712 button.html("(Hide)");
697 button.html("(Hide)");
713 outdated.show();
698 outdated.show();
714 } else {
699 } else {
715 button.html("(Show)");
700 button.html("(Show)");
716 outdated.hide();
701 outdated.hide();
717 }
702 }
718 });
703 });
719
704
720 $('.show-inline-comments').on('change', function(e){
705 $('.show-inline-comments').on('change', function(e){
721 var show = 'none';
706 var show = 'none';
722 var target = e.currentTarget;
707 var target = e.currentTarget;
723 if(target.checked){
708 if(target.checked){
724 show = ''
709 show = ''
725 }
710 }
726 var boxid = $(target).attr('id_for');
711 var boxid = $(target).attr('id_for');
727 var comments = $('#{0} .inline-comments'.format(boxid));
712 var comments = $('#{0} .inline-comments'.format(boxid));
728 var fn_display = function(idx){
713 var fn_display = function(idx){
729 $(this).css('display', show);
714 $(this).css('display', show);
730 };
715 };
731 $(comments).each(fn_display);
716 $(comments).each(fn_display);
732 var btns = $('#{0} .inline-comments-button'.format(boxid));
717 var btns = $('#{0} .inline-comments-button'.format(boxid));
733 $(btns).each(fn_display);
718 $(btns).each(fn_display);
734 });
719 });
735
720
736 $('#merge_pull_request_form').submit(function() {
721 $('#merge_pull_request_form').submit(function() {
737 if (!$('#merge_pull_request').attr('disabled')) {
722 if (!$('#merge_pull_request').attr('disabled')) {
738 $('#merge_pull_request').attr('disabled', 'disabled');
723 $('#merge_pull_request').attr('disabled', 'disabled');
739 }
724 }
740 return true;
725 return true;
741 });
726 });
742
727
743 $('#edit_pull_request').on('click', function(e){
728 $('#edit_pull_request').on('click', function(e){
744 var title = $('#pr-title-input').val();
729 var title = $('#pr-title-input').val();
745 var description = codeMirrorInstance.getValue();
730 var description = codeMirrorInstance.getValue();
746 var renderer = $('#pr-renderer-input').val();
731 var renderer = $('#pr-renderer-input').val();
747 editPullRequest(
732 editPullRequest(
748 "${c.repo_name}", "${c.pull_request.pull_request_id}",
733 "${c.repo_name}", "${c.pull_request.pull_request_id}",
749 title, description, renderer);
734 title, description, renderer);
750 });
735 });
751
736
752 $('#update_pull_request').on('click', function(e){
737 $('#update_pull_request').on('click', function(e){
753 $(this).attr('disabled', 'disabled');
738 $(this).attr('disabled', 'disabled');
754 $(this).addClass('disabled');
739 $(this).addClass('disabled');
755 $(this).html(_gettext('Saving...'));
740 $(this).html(_gettext('Saving...'));
756 reviewersController.updateReviewers(
741 reviewersController.updateReviewers(
757 "${c.repo_name}", "${c.pull_request.pull_request_id}");
742 "${c.repo_name}", "${c.pull_request.pull_request_id}");
758 });
743 });
759
744
760 $('#update_commits').on('click', function(e){
745 $('#update_commits').on('click', function(e){
761 var isDisabled = !$(e.currentTarget).attr('disabled');
746 var isDisabled = !$(e.currentTarget).attr('disabled');
762 $(e.currentTarget).attr('disabled', 'disabled');
747 $(e.currentTarget).attr('disabled', 'disabled');
763 $(e.currentTarget).addClass('disabled');
748 $(e.currentTarget).addClass('disabled');
764 $(e.currentTarget).removeClass('btn-primary');
749 $(e.currentTarget).removeClass('btn-primary');
765 $(e.currentTarget).text(_gettext('Updating...'));
750 $(e.currentTarget).text(_gettext('Updating...'));
766 if(isDisabled){
751 if(isDisabled){
767 updateCommits(
752 updateCommits(
768 "${c.repo_name}", "${c.pull_request.pull_request_id}");
753 "${c.repo_name}", "${c.pull_request.pull_request_id}");
769 }
754 }
770 });
755 });
771 // fixing issue with caches on firefox
756 // fixing issue with caches on firefox
772 $('#update_commits').removeAttr("disabled");
757 $('#update_commits').removeAttr("disabled");
773
758
774 $('.show-inline-comments').on('click', function(e){
759 $('.show-inline-comments').on('click', function(e){
775 var boxid = $(this).attr('data-comment-id');
760 var boxid = $(this).attr('data-comment-id');
776 var button = $(this);
761 var button = $(this);
777
762
778 if(button.hasClass("comments-visible")) {
763 if(button.hasClass("comments-visible")) {
779 $('#{0} .inline-comments'.format(boxid)).each(function(index){
764 $('#{0} .inline-comments'.format(boxid)).each(function(index){
780 $(this).hide();
765 $(this).hide();
781 });
766 });
782 button.removeClass("comments-visible");
767 button.removeClass("comments-visible");
783 } else {
768 } else {
784 $('#{0} .inline-comments'.format(boxid)).each(function(index){
769 $('#{0} .inline-comments'.format(boxid)).each(function(index){
785 $(this).show();
770 $(this).show();
786 });
771 });
787 button.addClass("comments-visible");
772 button.addClass("comments-visible");
788 }
773 }
789 });
774 });
790
775
791 // register submit callback on commentForm form to track TODOs
776 // register submit callback on commentForm form to track TODOs
792 window.commentFormGlobalSubmitSuccessCallback = function(){
777 window.commentFormGlobalSubmitSuccessCallback = function(){
793 refreshMergeChecks();
778 refreshMergeChecks();
794 };
779 };
795
780
796 ReviewerAutoComplete('#user');
781 ReviewerAutoComplete('#user');
797
782
798 })
783 })
784
799 </script>
785 </script>
800
786
801 </div>
787 </div>
802 </div>
788 </div>
803
789
804 </%def>
790 </%def>
@@ -1,138 +1,141 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 import collections
22 import collections
23
23
24 from rhodecode.lib.partial_renderer import PyramidPartialRenderer
24 from rhodecode.lib.partial_renderer import PyramidPartialRenderer
25 from rhodecode.lib.utils2 import AttributeDict
25 from rhodecode.lib.utils2 import AttributeDict
26 from rhodecode.model.db import User
26 from rhodecode.model.db import User
27 from rhodecode.model.notification import EmailNotificationModel
27 from rhodecode.model.notification import EmailNotificationModel
28
28
29
29
30 def test_get_template_obj(app, request_stub):
30 def test_get_template_obj(app, request_stub):
31 template = EmailNotificationModel().get_renderer(
31 template = EmailNotificationModel().get_renderer(
32 EmailNotificationModel.TYPE_TEST, request_stub)
32 EmailNotificationModel.TYPE_TEST, request_stub)
33 assert isinstance(template, PyramidPartialRenderer)
33 assert isinstance(template, PyramidPartialRenderer)
34
34
35
35
36 def test_render_email(app, http_host_only_stub):
36 def test_render_email(app, http_host_only_stub):
37 kwargs = {}
37 kwargs = {}
38 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
38 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
39 EmailNotificationModel.TYPE_TEST, **kwargs)
39 EmailNotificationModel.TYPE_TEST, **kwargs)
40
40
41 # subject
41 # subject
42 assert subject == 'Test "Subject" hello "world"'
42 assert subject == 'Test "Subject" hello "world"'
43
43
44 # headers
44 # headers
45 assert headers == 'X=Y'
45 assert headers == 'X=Y'
46
46
47 # body plaintext
47 # body plaintext
48 assert body_plaintext == 'Email Plaintext Body'
48 assert body_plaintext == 'Email Plaintext Body'
49
49
50 # body
50 # body
51 notification_footer1 = 'This is a notification from RhodeCode.'
51 notification_footer1 = 'This is a notification from RhodeCode.'
52 notification_footer2 = 'http://{}/'.format(http_host_only_stub)
52 notification_footer2 = 'http://{}/'.format(http_host_only_stub)
53 assert notification_footer1 in body
53 assert notification_footer1 in body
54 assert notification_footer2 in body
54 assert notification_footer2 in body
55 assert 'Email Body' in body
55 assert 'Email Body' in body
56
56
57
57
58 def test_render_pr_email(app, user_admin):
58 def test_render_pr_email(app, user_admin):
59 ref = collections.namedtuple(
59 ref = collections.namedtuple(
60 'Ref', 'name, type')('fxies123', 'book')
60 'Ref', 'name, type')('fxies123', 'book')
61
61
62 pr = collections.namedtuple('PullRequest',
62 pr = collections.namedtuple('PullRequest',
63 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
63 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
64 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
64 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
65
65
66 source_repo = target_repo = collections.namedtuple(
66 source_repo = target_repo = collections.namedtuple(
67 'Repo', 'type, repo_name')('hg', 'pull_request_1')
67 'Repo', 'type, repo_name')('hg', 'pull_request_1')
68
68
69 kwargs = {
69 kwargs = {
70 'user': User.get_first_super_admin(),
70 'user': User.get_first_super_admin(),
71 'pull_request': pr,
71 'pull_request': pr,
72 'pull_request_commits': [],
72 'pull_request_commits': [],
73
73
74 'pull_request_target_repo': target_repo,
74 'pull_request_target_repo': target_repo,
75 'pull_request_target_repo_url': 'x',
75 'pull_request_target_repo_url': 'x',
76
76
77 'pull_request_source_repo': source_repo,
77 'pull_request_source_repo': source_repo,
78 'pull_request_source_repo_url': 'x',
78 'pull_request_source_repo_url': 'x',
79
79
80 'pull_request_url': 'http://localhost/pr1',
80 'pull_request_url': 'http://localhost/pr1',
81 }
81 }
82
82
83 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
83 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
84 EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
84 EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
85
85
86 # subject
86 # subject
87 assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
87 assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
88
88
89
89
90 @pytest.mark.parametrize('mention', [
90 @pytest.mark.parametrize('mention', [
91 True,
91 True,
92 False
92 False
93 ])
93 ])
94 @pytest.mark.parametrize('email_type', [
94 @pytest.mark.parametrize('email_type', [
95 EmailNotificationModel.TYPE_COMMIT_COMMENT,
95 EmailNotificationModel.TYPE_COMMIT_COMMENT,
96 EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
96 EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
97 ])
97 ])
98 def test_render_comment_subject_no_newlines(app, mention, email_type):
98 def test_render_comment_subject_no_newlines(app, mention, email_type):
99 ref = collections.namedtuple(
99 ref = collections.namedtuple(
100 'Ref', 'name, type')('fxies123', 'book')
100 'Ref', 'name, type')('fxies123', 'book')
101
101
102 pr = collections.namedtuple('PullRequest',
102 pr = collections.namedtuple('PullRequest',
103 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
103 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
104 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
104 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
105
105
106 source_repo = target_repo = collections.namedtuple(
106 source_repo = target_repo = collections.namedtuple(
107 'Repo', 'type, repo_name')('hg', 'pull_request_1')
107 'Repo', 'type, repo_name')('hg', 'pull_request_1')
108
108
109 kwargs = {
109 kwargs = {
110 'user': User.get_first_super_admin(),
110 'user': User.get_first_super_admin(),
111 'commit': AttributeDict(raw_id='a'*40, message='Commit message'),
111 'commit': AttributeDict(raw_id='a'*40, message='Commit message'),
112 'status_change': 'approved',
112 'status_change': 'approved',
113 'commit_target_repo_url': 'http://foo.example.com/#comment1',
113 'commit_target_repo_url': 'http://foo.example.com/#comment1',
114 'repo_name': 'test-repo',
114 'repo_name': 'test-repo',
115 'comment_file': 'test-file.py',
115 'comment_file': 'test-file.py',
116 'comment_line': 'n100',
116 'comment_line': 'n100',
117 'comment_type': 'note',
117 'comment_type': 'note',
118 'comment_id': 2048,
118 'commit_comment_url': 'http://comment-url',
119 'commit_comment_url': 'http://comment-url',
120 'commit_comment_reply_url': 'http://comment-url/#Reply',
119 'instance_url': 'http://rc-instance',
121 'instance_url': 'http://rc-instance',
120 'comment_body': 'hello world',
122 'comment_body': 'hello world',
121 'mention': mention,
123 'mention': mention,
122
124
123 'pr_comment_url': 'http://comment-url',
125 'pr_comment_url': 'http://comment-url',
126 'pr_comment_reply_url': 'http://comment-url/#Reply',
124 'pull_request': pr,
127 'pull_request': pr,
125 'pull_request_commits': [],
128 'pull_request_commits': [],
126
129
127 'pull_request_target_repo': target_repo,
130 'pull_request_target_repo': target_repo,
128 'pull_request_target_repo_url': 'x',
131 'pull_request_target_repo_url': 'x',
129
132
130 'pull_request_source_repo': source_repo,
133 'pull_request_source_repo': source_repo,
131 'pull_request_source_repo_url': 'x',
134 'pull_request_source_repo_url': 'x',
132
135
133 'pull_request_url': 'http://code.rc.com/_pr/123'
136 'pull_request_url': 'http://code.rc.com/_pr/123'
134 }
137 }
135 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
138 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
136 email_type, **kwargs)
139 email_type, **kwargs)
137
140
138 assert '\n' not in subject
141 assert '\n' not in subject
General Comments 0
You need to be logged in to leave comments. Login now