##// END OF EJS Templates
db: get rid of vcs_full_cache - it should always be used...
Mads Kiilerich -
r5739:6afa528e default
parent child Browse files
Show More
@@ -1,597 +1,594 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # Kallithea - Development config: #
3 # Kallithea - Development config: #
4 # listening on *:5000 #
4 # listening on *:5000 #
5 # sqlite and kallithea.db #
5 # sqlite and kallithea.db #
6 # initial_repo_scan = true #
6 # initial_repo_scan = true #
7 # set debug = true #
7 # set debug = true #
8 # verbose and colorful logging #
8 # verbose and colorful logging #
9 # #
9 # #
10 # The %(here)s variable will be replaced with the parent directory of this file#
10 # The %(here)s variable will be replaced with the parent directory of this file#
11 ################################################################################
11 ################################################################################
12 ################################################################################
12 ################################################################################
13
13
14 [DEFAULT]
14 [DEFAULT]
15 debug = true
15 debug = true
16 pdebug = false
16 pdebug = false
17
17
18 ################################################################################
18 ################################################################################
19 ## Email settings ##
19 ## Email settings ##
20 ## ##
20 ## ##
21 ## Refer to the documentation ("Email settings") for more details. ##
21 ## Refer to the documentation ("Email settings") for more details. ##
22 ## ##
22 ## ##
23 ## It is recommended to use a valid sender address that passes access ##
23 ## It is recommended to use a valid sender address that passes access ##
24 ## validation and spam filtering in mail servers. ##
24 ## validation and spam filtering in mail servers. ##
25 ################################################################################
25 ################################################################################
26
26
27 ## 'From' header for application emails. You can optionally add a name.
27 ## 'From' header for application emails. You can optionally add a name.
28 ## Default:
28 ## Default:
29 #app_email_from = Kallithea
29 #app_email_from = Kallithea
30 ## Examples:
30 ## Examples:
31 #app_email_from = Kallithea <kallithea-noreply@example.com>
31 #app_email_from = Kallithea <kallithea-noreply@example.com>
32 #app_email_from = kallithea-noreply@example.com
32 #app_email_from = kallithea-noreply@example.com
33
33
34 ## Subject prefix for application emails.
34 ## Subject prefix for application emails.
35 ## A space between this prefix and the real subject is automatically added.
35 ## A space between this prefix and the real subject is automatically added.
36 ## Default:
36 ## Default:
37 #email_prefix =
37 #email_prefix =
38 ## Example:
38 ## Example:
39 #email_prefix = [Kallithea]
39 #email_prefix = [Kallithea]
40
40
41 ## Recipients for error emails and fallback recipients of application mails.
41 ## Recipients for error emails and fallback recipients of application mails.
42 ## Multiple addresses can be specified, space-separated.
42 ## Multiple addresses can be specified, space-separated.
43 ## Only addresses are allowed, do not add any name part.
43 ## Only addresses are allowed, do not add any name part.
44 ## Default:
44 ## Default:
45 #email_to =
45 #email_to =
46 ## Examples:
46 ## Examples:
47 #email_to = admin@example.com
47 #email_to = admin@example.com
48 #email_to = admin@example.com another_admin@example.com
48 #email_to = admin@example.com another_admin@example.com
49
49
50 ## 'From' header for error emails. You can optionally add a name.
50 ## 'From' header for error emails. You can optionally add a name.
51 ## Default:
51 ## Default:
52 #error_email_from = pylons@yourapp.com
52 #error_email_from = pylons@yourapp.com
53 ## Examples:
53 ## Examples:
54 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
54 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
55 #error_email_from = paste_error@example.com
55 #error_email_from = paste_error@example.com
56
56
57 ## SMTP server settings
57 ## SMTP server settings
58 ## Only smtp_server is mandatory. All other settings take the specified default
58 ## Only smtp_server is mandatory. All other settings take the specified default
59 ## values.
59 ## values.
60 #smtp_server = smtp.example.com
60 #smtp_server = smtp.example.com
61 #smtp_username =
61 #smtp_username =
62 #smtp_password =
62 #smtp_password =
63 #smtp_port = 25
63 #smtp_port = 25
64 #smtp_use_tls = false
64 #smtp_use_tls = false
65 #smtp_use_ssl = false
65 #smtp_use_ssl = false
66 ## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
66 ## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
67 ## If empty, use any of the authentication parameters supported by the server.
67 ## If empty, use any of the authentication parameters supported by the server.
68 #smtp_auth =
68 #smtp_auth =
69
69
70 [server:main]
70 [server:main]
71 ## PASTE ##
71 ## PASTE ##
72 #use = egg:Paste#http
72 #use = egg:Paste#http
73 ## nr of worker threads to spawn
73 ## nr of worker threads to spawn
74 #threadpool_workers = 5
74 #threadpool_workers = 5
75 ## max request before thread respawn
75 ## max request before thread respawn
76 #threadpool_max_requests = 10
76 #threadpool_max_requests = 10
77 ## option to use threads of process
77 ## option to use threads of process
78 #use_threadpool = true
78 #use_threadpool = true
79
79
80 ## WAITRESS ##
80 ## WAITRESS ##
81 use = egg:waitress#main
81 use = egg:waitress#main
82 ## number of worker threads
82 ## number of worker threads
83 threads = 5
83 threads = 5
84 ## MAX BODY SIZE 100GB
84 ## MAX BODY SIZE 100GB
85 max_request_body_size = 107374182400
85 max_request_body_size = 107374182400
86 ## use poll instead of select, fixes fd limits, may not work on old
86 ## use poll instead of select, fixes fd limits, may not work on old
87 ## windows systems.
87 ## windows systems.
88 #asyncore_use_poll = True
88 #asyncore_use_poll = True
89
89
90 ## GUNICORN ##
90 ## GUNICORN ##
91 #use = egg:gunicorn#main
91 #use = egg:gunicorn#main
92 ## number of process workers. You must set `instance_id = *` when this option
92 ## number of process workers. You must set `instance_id = *` when this option
93 ## is set to more than one worker
93 ## is set to more than one worker
94 #workers = 1
94 #workers = 1
95 ## process name
95 ## process name
96 #proc_name = kallithea
96 #proc_name = kallithea
97 ## type of worker class, one of sync, eventlet, gevent, tornado
97 ## type of worker class, one of sync, eventlet, gevent, tornado
98 ## recommended for bigger setup is using of of other than sync one
98 ## recommended for bigger setup is using of of other than sync one
99 #worker_class = sync
99 #worker_class = sync
100 #max_requests = 1000
100 #max_requests = 1000
101 ## ammount of time a worker can handle request before it gets killed and
101 ## ammount of time a worker can handle request before it gets killed and
102 ## restarted
102 ## restarted
103 #timeout = 3600
103 #timeout = 3600
104
104
105 ## UWSGI ##
105 ## UWSGI ##
106 ## run with uwsgi --ini-paste-logged <inifile.ini>
106 ## run with uwsgi --ini-paste-logged <inifile.ini>
107 #[uwsgi]
107 #[uwsgi]
108 #socket = /tmp/uwsgi.sock
108 #socket = /tmp/uwsgi.sock
109 #master = true
109 #master = true
110 #http = 127.0.0.1:5000
110 #http = 127.0.0.1:5000
111
111
112 ## set as deamon and redirect all output to file
112 ## set as deamon and redirect all output to file
113 #daemonize = ./uwsgi_kallithea.log
113 #daemonize = ./uwsgi_kallithea.log
114
114
115 ## master process PID
115 ## master process PID
116 #pidfile = ./uwsgi_kallithea.pid
116 #pidfile = ./uwsgi_kallithea.pid
117
117
118 ## stats server with workers statistics, use uwsgitop
118 ## stats server with workers statistics, use uwsgitop
119 ## for monitoring, `uwsgitop 127.0.0.1:1717`
119 ## for monitoring, `uwsgitop 127.0.0.1:1717`
120 #stats = 127.0.0.1:1717
120 #stats = 127.0.0.1:1717
121 #memory-report = true
121 #memory-report = true
122
122
123 ## log 5XX errors
123 ## log 5XX errors
124 #log-5xx = true
124 #log-5xx = true
125
125
126 ## Set the socket listen queue size.
126 ## Set the socket listen queue size.
127 #listen = 256
127 #listen = 256
128
128
129 ## Gracefully Reload workers after the specified amount of managed requests
129 ## Gracefully Reload workers after the specified amount of managed requests
130 ## (avoid memory leaks).
130 ## (avoid memory leaks).
131 #max-requests = 1000
131 #max-requests = 1000
132
132
133 ## enable large buffers
133 ## enable large buffers
134 #buffer-size = 65535
134 #buffer-size = 65535
135
135
136 ## socket and http timeouts ##
136 ## socket and http timeouts ##
137 #http-timeout = 3600
137 #http-timeout = 3600
138 #socket-timeout = 3600
138 #socket-timeout = 3600
139
139
140 ## Log requests slower than the specified number of milliseconds.
140 ## Log requests slower than the specified number of milliseconds.
141 #log-slow = 10
141 #log-slow = 10
142
142
143 ## Exit if no app can be loaded.
143 ## Exit if no app can be loaded.
144 #need-app = true
144 #need-app = true
145
145
146 ## Set lazy mode (load apps in workers instead of master).
146 ## Set lazy mode (load apps in workers instead of master).
147 #lazy = true
147 #lazy = true
148
148
149 ## scaling ##
149 ## scaling ##
150 ## set cheaper algorithm to use, if not set default will be used
150 ## set cheaper algorithm to use, if not set default will be used
151 #cheaper-algo = spare
151 #cheaper-algo = spare
152
152
153 ## minimum number of workers to keep at all times
153 ## minimum number of workers to keep at all times
154 #cheaper = 1
154 #cheaper = 1
155
155
156 ## number of workers to spawn at startup
156 ## number of workers to spawn at startup
157 #cheaper-initial = 1
157 #cheaper-initial = 1
158
158
159 ## maximum number of workers that can be spawned
159 ## maximum number of workers that can be spawned
160 #workers = 4
160 #workers = 4
161
161
162 ## how many workers should be spawned at a time
162 ## how many workers should be spawned at a time
163 #cheaper-step = 1
163 #cheaper-step = 1
164
164
165 ## COMMON ##
165 ## COMMON ##
166 #host = 127.0.0.1
166 #host = 127.0.0.1
167 host = 0.0.0.0
167 host = 0.0.0.0
168 port = 5000
168 port = 5000
169
169
170 ## middleware for hosting the WSGI application under a URL prefix
170 ## middleware for hosting the WSGI application under a URL prefix
171 #[filter:proxy-prefix]
171 #[filter:proxy-prefix]
172 #use = egg:PasteDeploy#prefix
172 #use = egg:PasteDeploy#prefix
173 #prefix = /<your-prefix>
173 #prefix = /<your-prefix>
174
174
175 [app:main]
175 [app:main]
176 use = egg:kallithea
176 use = egg:kallithea
177 ## enable proxy prefix middleware
177 ## enable proxy prefix middleware
178 #filter-with = proxy-prefix
178 #filter-with = proxy-prefix
179
179
180 full_stack = true
180 full_stack = true
181 static_files = true
181 static_files = true
182 ## Available Languages:
182 ## Available Languages:
183 ## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
183 ## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
184 lang =
184 lang =
185 cache_dir = %(here)s/data
185 cache_dir = %(here)s/data
186 index_dir = %(here)s/data/index
186 index_dir = %(here)s/data/index
187
187
188 ## perform a full repository scan on each server start, this should be
188 ## perform a full repository scan on each server start, this should be
189 ## set to false after first startup, to allow faster server restarts.
189 ## set to false after first startup, to allow faster server restarts.
190 #initial_repo_scan = false
190 #initial_repo_scan = false
191 initial_repo_scan = true
191 initial_repo_scan = true
192
192
193 ## uncomment and set this path to use archive download cache
193 ## uncomment and set this path to use archive download cache
194 archive_cache_dir = %(here)s/tarballcache
194 archive_cache_dir = %(here)s/tarballcache
195
195
196 ## change this to unique ID for security
196 ## change this to unique ID for security
197 app_instance_uuid = development-not-secret
197 app_instance_uuid = development-not-secret
198
198
199 ## cut off limit for large diffs (size in bytes)
199 ## cut off limit for large diffs (size in bytes)
200 cut_off_limit = 256000
200 cut_off_limit = 256000
201
201
202 ## use cache version of scm repo everywhere
203 vcs_full_cache = true
204
205 ## force https in Kallithea, fixes https redirects, assumes it's always https
202 ## force https in Kallithea, fixes https redirects, assumes it's always https
206 force_https = false
203 force_https = false
207
204
208 ## use Strict-Transport-Security headers
205 ## use Strict-Transport-Security headers
209 use_htsts = false
206 use_htsts = false
210
207
211 ## number of commits stats will parse on each iteration
208 ## number of commits stats will parse on each iteration
212 commit_parse_limit = 25
209 commit_parse_limit = 25
213
210
214 ## path to git executable
211 ## path to git executable
215 git_path = git
212 git_path = git
216
213
217 ## git rev filter option, --all is the default filter, if you need to
214 ## git rev filter option, --all is the default filter, if you need to
218 ## hide all refs in changelog switch this to --branches --tags
215 ## hide all refs in changelog switch this to --branches --tags
219 #git_rev_filter = --branches --tags
216 #git_rev_filter = --branches --tags
220
217
221 ## RSS feed options
218 ## RSS feed options
222 rss_cut_off_limit = 256000
219 rss_cut_off_limit = 256000
223 rss_items_per_page = 10
220 rss_items_per_page = 10
224 rss_include_diff = false
221 rss_include_diff = false
225
222
226 ## options for showing and identifying changesets
223 ## options for showing and identifying changesets
227 show_sha_length = 12
224 show_sha_length = 12
228 show_revision_number = false
225 show_revision_number = false
229
226
230 ## gist URL alias, used to create nicer urls for gist. This should be an
227 ## gist URL alias, used to create nicer urls for gist. This should be an
231 ## url that does rewrites to _admin/gists/<gistid>.
228 ## url that does rewrites to _admin/gists/<gistid>.
232 ## example: http://gist.example.com/{gistid}. Empty means use the internal
229 ## example: http://gist.example.com/{gistid}. Empty means use the internal
233 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
230 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
234 gist_alias_url =
231 gist_alias_url =
235
232
236 ## white list of API enabled controllers. This allows to add list of
233 ## white list of API enabled controllers. This allows to add list of
237 ## controllers to which access will be enabled by api_key. eg: to enable
234 ## controllers to which access will be enabled by api_key. eg: to enable
238 ## api access to raw_files put `FilesController:raw`, to enable access to patches
235 ## api access to raw_files put `FilesController:raw`, to enable access to patches
239 ## add `ChangesetController:changeset_patch`. This list should be "," separated
236 ## add `ChangesetController:changeset_patch`. This list should be "," separated
240 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
237 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
241 ## Recommended settings below are commented out:
238 ## Recommended settings below are commented out:
242 api_access_controllers_whitelist =
239 api_access_controllers_whitelist =
243 # ChangesetController:changeset_patch,
240 # ChangesetController:changeset_patch,
244 # ChangesetController:changeset_raw,
241 # ChangesetController:changeset_raw,
245 # FilesController:raw,
242 # FilesController:raw,
246 # FilesController:archivefile
243 # FilesController:archivefile
247
244
248 ## default encoding used to convert from and to unicode
245 ## default encoding used to convert from and to unicode
249 ## can be also a comma seperated list of encoding in case of mixed encodings
246 ## can be also a comma seperated list of encoding in case of mixed encodings
250 default_encoding = utf8
247 default_encoding = utf8
251
248
252 ## issue tracker for Kallithea (leave blank to disable, absent for default)
249 ## issue tracker for Kallithea (leave blank to disable, absent for default)
253 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
250 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
254
251
255 ## issue tracking mapping for commits messages
252 ## issue tracking mapping for commits messages
256 ## comment out issue_pat, issue_server, issue_prefix to enable
253 ## comment out issue_pat, issue_server, issue_prefix to enable
257
254
258 ## pattern to get the issues from commit messages
255 ## pattern to get the issues from commit messages
259 ## default one used here is #<numbers> with a regex passive group for `#`
256 ## default one used here is #<numbers> with a regex passive group for `#`
260 ## {id} will be all groups matched from this pattern
257 ## {id} will be all groups matched from this pattern
261
258
262 issue_pat = (?:\s*#)(\d+)
259 issue_pat = (?:\s*#)(\d+)
263
260
264 ## server url to the issue, each {id} will be replaced with match
261 ## server url to the issue, each {id} will be replaced with match
265 ## fetched from the regex and {repo} is replaced with full repository name
262 ## fetched from the regex and {repo} is replaced with full repository name
266 ## including groups {repo_name} is replaced with just name of repo
263 ## including groups {repo_name} is replaced with just name of repo
267
264
268 issue_server_link = https://issues.example.com/{repo}/issue/{id}
265 issue_server_link = https://issues.example.com/{repo}/issue/{id}
269
266
270 ## prefix to add to link to indicate it's an url
267 ## prefix to add to link to indicate it's an url
271 ## #314 will be replaced by <issue_prefix><id>
268 ## #314 will be replaced by <issue_prefix><id>
272
269
273 issue_prefix = #
270 issue_prefix = #
274
271
275 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
272 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
276 ## multiple patterns, to other issues server, wiki or others
273 ## multiple patterns, to other issues server, wiki or others
277 ## below an example how to create a wiki pattern
274 ## below an example how to create a wiki pattern
278 # wiki-some-id -> https://wiki.example.com/some-id
275 # wiki-some-id -> https://wiki.example.com/some-id
279
276
280 #issue_pat_wiki = (?:wiki-)(.+)
277 #issue_pat_wiki = (?:wiki-)(.+)
281 #issue_server_link_wiki = https://wiki.example.com/{id}
278 #issue_server_link_wiki = https://wiki.example.com/{id}
282 #issue_prefix_wiki = WIKI-
279 #issue_prefix_wiki = WIKI-
283
280
284 ## alternative return HTTP header for failed authentication. Default HTTP
281 ## alternative return HTTP header for failed authentication. Default HTTP
285 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
282 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
286 ## handling that. Set this variable to 403 to return HTTPForbidden
283 ## handling that. Set this variable to 403 to return HTTPForbidden
287 auth_ret_code =
284 auth_ret_code =
288
285
289 ## locking return code. When repository is locked return this HTTP code. 2XX
286 ## locking return code. When repository is locked return this HTTP code. 2XX
290 ## codes don't break the transactions while 4XX codes do
287 ## codes don't break the transactions while 4XX codes do
291 lock_ret_code = 423
288 lock_ret_code = 423
292
289
293 ## allows to change the repository location in settings page
290 ## allows to change the repository location in settings page
294 allow_repo_location_change = True
291 allow_repo_location_change = True
295
292
296 ## allows to setup custom hooks in settings page
293 ## allows to setup custom hooks in settings page
297 allow_custom_hooks_settings = True
294 allow_custom_hooks_settings = True
298
295
299 ## extra extensions for indexing, space separated and without the leading '.'.
296 ## extra extensions for indexing, space separated and without the leading '.'.
300 # index.extensions =
297 # index.extensions =
301 # gemfile
298 # gemfile
302 # lock
299 # lock
303
300
304 ## extra filenames for indexing, space separated
301 ## extra filenames for indexing, space separated
305 # index.filenames =
302 # index.filenames =
306 # .dockerignore
303 # .dockerignore
307 # .editorconfig
304 # .editorconfig
308 # INSTALL
305 # INSTALL
309 # CHANGELOG
306 # CHANGELOG
310
307
311 ####################################
308 ####################################
312 ### CELERY CONFIG ####
309 ### CELERY CONFIG ####
313 ####################################
310 ####################################
314
311
315 use_celery = false
312 use_celery = false
316 broker.host = localhost
313 broker.host = localhost
317 broker.vhost = rabbitmqhost
314 broker.vhost = rabbitmqhost
318 broker.port = 5672
315 broker.port = 5672
319 broker.user = rabbitmq
316 broker.user = rabbitmq
320 broker.password = qweqwe
317 broker.password = qweqwe
321
318
322 celery.imports = kallithea.lib.celerylib.tasks
319 celery.imports = kallithea.lib.celerylib.tasks
323
320
324 celery.result.backend = amqp
321 celery.result.backend = amqp
325 celery.result.dburi = amqp://
322 celery.result.dburi = amqp://
326 celery.result.serialier = json
323 celery.result.serialier = json
327
324
328 #celery.send.task.error.emails = true
325 #celery.send.task.error.emails = true
329 #celery.amqp.task.result.expires = 18000
326 #celery.amqp.task.result.expires = 18000
330
327
331 celeryd.concurrency = 2
328 celeryd.concurrency = 2
332 #celeryd.log.file = celeryd.log
329 #celeryd.log.file = celeryd.log
333 celeryd.log.level = DEBUG
330 celeryd.log.level = DEBUG
334 celeryd.max.tasks.per.child = 1
331 celeryd.max.tasks.per.child = 1
335
332
336 ## tasks will never be sent to the queue, but executed locally instead.
333 ## tasks will never be sent to the queue, but executed locally instead.
337 celery.always.eager = false
334 celery.always.eager = false
338
335
339 ####################################
336 ####################################
340 ### BEAKER CACHE ####
337 ### BEAKER CACHE ####
341 ####################################
338 ####################################
342
339
343 beaker.cache.data_dir = %(here)s/data/cache/data
340 beaker.cache.data_dir = %(here)s/data/cache/data
344 beaker.cache.lock_dir = %(here)s/data/cache/lock
341 beaker.cache.lock_dir = %(here)s/data/cache/lock
345
342
346 beaker.cache.regions = short_term,long_term,sql_cache_short
343 beaker.cache.regions = short_term,long_term,sql_cache_short
347
344
348 beaker.cache.short_term.type = memory
345 beaker.cache.short_term.type = memory
349 beaker.cache.short_term.expire = 60
346 beaker.cache.short_term.expire = 60
350 beaker.cache.short_term.key_length = 256
347 beaker.cache.short_term.key_length = 256
351
348
352 beaker.cache.long_term.type = memory
349 beaker.cache.long_term.type = memory
353 beaker.cache.long_term.expire = 36000
350 beaker.cache.long_term.expire = 36000
354 beaker.cache.long_term.key_length = 256
351 beaker.cache.long_term.key_length = 256
355
352
356 beaker.cache.sql_cache_short.type = memory
353 beaker.cache.sql_cache_short.type = memory
357 beaker.cache.sql_cache_short.expire = 10
354 beaker.cache.sql_cache_short.expire = 10
358 beaker.cache.sql_cache_short.key_length = 256
355 beaker.cache.sql_cache_short.key_length = 256
359
356
360 ####################################
357 ####################################
361 ### BEAKER SESSION ####
358 ### BEAKER SESSION ####
362 ####################################
359 ####################################
363
360
364 ## Name of session cookie. Should be unique for a given host and path, even when running
361 ## Name of session cookie. Should be unique for a given host and path, even when running
365 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
362 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
366 beaker.session.key = kallithea
363 beaker.session.key = kallithea
367 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
364 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
368 beaker.session.httponly = true
365 beaker.session.httponly = true
369 ## Session lifetime. 2592000 seconds is 30 days.
366 ## Session lifetime. 2592000 seconds is 30 days.
370 beaker.session.timeout = 2592000
367 beaker.session.timeout = 2592000
371
368
372 ## Server secret used with HMAC to ensure integrity of cookies.
369 ## Server secret used with HMAC to ensure integrity of cookies.
373 beaker.session.secret = development-not-secret
370 beaker.session.secret = development-not-secret
374 ## Further, encrypt the data with AES.
371 ## Further, encrypt the data with AES.
375 #beaker.session.encrypt_key = <key_for_encryption>
372 #beaker.session.encrypt_key = <key_for_encryption>
376 #beaker.session.validate_key = <validation_key>
373 #beaker.session.validate_key = <validation_key>
377
374
378 ## Type of storage used for the session, current types are
375 ## Type of storage used for the session, current types are
379 ## dbm, file, memcached, database, and memory.
376 ## dbm, file, memcached, database, and memory.
380
377
381 ## File system storage of session data. (default)
378 ## File system storage of session data. (default)
382 #beaker.session.type = file
379 #beaker.session.type = file
383
380
384 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
381 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
385 #beaker.session.type = cookie
382 #beaker.session.type = cookie
386
383
387 ## Database storage of session data.
384 ## Database storage of session data.
388 #beaker.session.type = ext:database
385 #beaker.session.type = ext:database
389 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
386 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
390 #beaker.session.table_name = db_session
387 #beaker.session.table_name = db_session
391
388
392 ############################
389 ############################
393 ## ERROR HANDLING SYSTEMS ##
390 ## ERROR HANDLING SYSTEMS ##
394 ############################
391 ############################
395
392
396 ####################
393 ####################
397 ### [errormator] ###
394 ### [errormator] ###
398 ####################
395 ####################
399
396
400 ## Errormator is tailored to work with Kallithea, see
397 ## Errormator is tailored to work with Kallithea, see
401 ## http://errormator.com for details how to obtain an account
398 ## http://errormator.com for details how to obtain an account
402 ## you must install python package `errormator_client` to make it work
399 ## you must install python package `errormator_client` to make it work
403
400
404 ## errormator enabled
401 ## errormator enabled
405 errormator = false
402 errormator = false
406
403
407 errormator.server_url = https://api.errormator.com
404 errormator.server_url = https://api.errormator.com
408 errormator.api_key = YOUR_API_KEY
405 errormator.api_key = YOUR_API_KEY
409
406
410 ## TWEAK AMOUNT OF INFO SENT HERE
407 ## TWEAK AMOUNT OF INFO SENT HERE
411
408
412 ## enables 404 error logging (default False)
409 ## enables 404 error logging (default False)
413 errormator.report_404 = false
410 errormator.report_404 = false
414
411
415 ## time in seconds after request is considered being slow (default 1)
412 ## time in seconds after request is considered being slow (default 1)
416 errormator.slow_request_time = 1
413 errormator.slow_request_time = 1
417
414
418 ## record slow requests in application
415 ## record slow requests in application
419 ## (needs to be enabled for slow datastore recording and time tracking)
416 ## (needs to be enabled for slow datastore recording and time tracking)
420 errormator.slow_requests = true
417 errormator.slow_requests = true
421
418
422 ## enable hooking to application loggers
419 ## enable hooking to application loggers
423 #errormator.logging = true
420 #errormator.logging = true
424
421
425 ## minimum log level for log capture
422 ## minimum log level for log capture
426 #errormator.logging.level = WARNING
423 #errormator.logging.level = WARNING
427
424
428 ## send logs only from erroneous/slow requests
425 ## send logs only from erroneous/slow requests
429 ## (saves API quota for intensive logging)
426 ## (saves API quota for intensive logging)
430 errormator.logging_on_error = false
427 errormator.logging_on_error = false
431
428
432 ## list of additonal keywords that should be grabbed from environ object
429 ## list of additonal keywords that should be grabbed from environ object
433 ## can be string with comma separated list of words in lowercase
430 ## can be string with comma separated list of words in lowercase
434 ## (by default client will always send following info:
431 ## (by default client will always send following info:
435 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
432 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
436 ## start with HTTP* this list be extended with additional keywords here
433 ## start with HTTP* this list be extended with additional keywords here
437 errormator.environ_keys_whitelist =
434 errormator.environ_keys_whitelist =
438
435
439 ## list of keywords that should be blanked from request object
436 ## list of keywords that should be blanked from request object
440 ## can be string with comma separated list of words in lowercase
437 ## can be string with comma separated list of words in lowercase
441 ## (by default client will always blank keys that contain following words
438 ## (by default client will always blank keys that contain following words
442 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
439 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
443 ## this list be extended with additional keywords set here
440 ## this list be extended with additional keywords set here
444 errormator.request_keys_blacklist =
441 errormator.request_keys_blacklist =
445
442
446 ## list of namespaces that should be ignores when gathering log entries
443 ## list of namespaces that should be ignores when gathering log entries
447 ## can be string with comma separated list of namespaces
444 ## can be string with comma separated list of namespaces
448 ## (by default the client ignores own entries: errormator_client.client)
445 ## (by default the client ignores own entries: errormator_client.client)
449 errormator.log_namespace_blacklist =
446 errormator.log_namespace_blacklist =
450
447
451 ################
448 ################
452 ### [sentry] ###
449 ### [sentry] ###
453 ################
450 ################
454
451
455 ## sentry is a alternative open source error aggregator
452 ## sentry is a alternative open source error aggregator
456 ## you must install python packages `sentry` and `raven` to enable
453 ## you must install python packages `sentry` and `raven` to enable
457
454
458 sentry.dsn = YOUR_DNS
455 sentry.dsn = YOUR_DNS
459 sentry.servers =
456 sentry.servers =
460 sentry.name =
457 sentry.name =
461 sentry.key =
458 sentry.key =
462 sentry.public_key =
459 sentry.public_key =
463 sentry.secret_key =
460 sentry.secret_key =
464 sentry.project =
461 sentry.project =
465 sentry.site =
462 sentry.site =
466 sentry.include_paths =
463 sentry.include_paths =
467 sentry.exclude_paths =
464 sentry.exclude_paths =
468
465
469 ################################################################################
466 ################################################################################
470 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
467 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
471 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
468 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
472 ## execute malicious code after an exception is raised. ##
469 ## execute malicious code after an exception is raised. ##
473 ################################################################################
470 ################################################################################
474 #set debug = false
471 #set debug = false
475 set debug = true
472 set debug = true
476
473
477 ##################################
474 ##################################
478 ### LOGVIEW CONFIG ###
475 ### LOGVIEW CONFIG ###
479 ##################################
476 ##################################
480
477
481 logview.sqlalchemy = #faa
478 logview.sqlalchemy = #faa
482 logview.pylons.templating = #bfb
479 logview.pylons.templating = #bfb
483 logview.pylons.util = #eee
480 logview.pylons.util = #eee
484
481
485 #########################################################
482 #########################################################
486 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
483 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
487 #########################################################
484 #########################################################
488
485
489 # SQLITE [default]
486 # SQLITE [default]
490 sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
487 sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
491
488
492 # POSTGRESQL
489 # POSTGRESQL
493 #sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
490 #sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
494
491
495 # MySQL
492 # MySQL
496 #sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
493 #sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
497
494
498 # see sqlalchemy docs for others
495 # see sqlalchemy docs for others
499
496
500 sqlalchemy.db1.echo = false
497 sqlalchemy.db1.echo = false
501 sqlalchemy.db1.pool_recycle = 3600
498 sqlalchemy.db1.pool_recycle = 3600
502
499
503 ################################
500 ################################
504 ### LOGGING CONFIGURATION ####
501 ### LOGGING CONFIGURATION ####
505 ################################
502 ################################
506
503
507 [loggers]
504 [loggers]
508 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
505 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
509
506
510 [handlers]
507 [handlers]
511 keys = console, console_sql
508 keys = console, console_sql
512
509
513 [formatters]
510 [formatters]
514 keys = generic, color_formatter, color_formatter_sql
511 keys = generic, color_formatter, color_formatter_sql
515
512
516 #############
513 #############
517 ## LOGGERS ##
514 ## LOGGERS ##
518 #############
515 #############
519
516
520 [logger_root]
517 [logger_root]
521 level = NOTSET
518 level = NOTSET
522 handlers = console
519 handlers = console
523
520
524 [logger_routes]
521 [logger_routes]
525 level = DEBUG
522 level = DEBUG
526 handlers =
523 handlers =
527 qualname = routes.middleware
524 qualname = routes.middleware
528 ## "level = DEBUG" logs the route matched and routing variables.
525 ## "level = DEBUG" logs the route matched and routing variables.
529 propagate = 1
526 propagate = 1
530
527
531 [logger_beaker]
528 [logger_beaker]
532 level = DEBUG
529 level = DEBUG
533 handlers =
530 handlers =
534 qualname = beaker.container
531 qualname = beaker.container
535 propagate = 1
532 propagate = 1
536
533
537 [logger_templates]
534 [logger_templates]
538 level = INFO
535 level = INFO
539 handlers =
536 handlers =
540 qualname = pylons.templating
537 qualname = pylons.templating
541 propagate = 1
538 propagate = 1
542
539
543 [logger_kallithea]
540 [logger_kallithea]
544 level = DEBUG
541 level = DEBUG
545 handlers =
542 handlers =
546 qualname = kallithea
543 qualname = kallithea
547 propagate = 1
544 propagate = 1
548
545
549 [logger_sqlalchemy]
546 [logger_sqlalchemy]
550 level = INFO
547 level = INFO
551 handlers = console_sql
548 handlers = console_sql
552 qualname = sqlalchemy.engine
549 qualname = sqlalchemy.engine
553 propagate = 0
550 propagate = 0
554
551
555 [logger_whoosh_indexer]
552 [logger_whoosh_indexer]
556 level = DEBUG
553 level = DEBUG
557 handlers =
554 handlers =
558 qualname = whoosh_indexer
555 qualname = whoosh_indexer
559 propagate = 1
556 propagate = 1
560
557
561 ##############
558 ##############
562 ## HANDLERS ##
559 ## HANDLERS ##
563 ##############
560 ##############
564
561
565 [handler_console]
562 [handler_console]
566 class = StreamHandler
563 class = StreamHandler
567 args = (sys.stderr,)
564 args = (sys.stderr,)
568 #level = INFO
565 #level = INFO
569 level = DEBUG
566 level = DEBUG
570 #formatter = generic
567 #formatter = generic
571 formatter = color_formatter
568 formatter = color_formatter
572
569
573 [handler_console_sql]
570 [handler_console_sql]
574 class = StreamHandler
571 class = StreamHandler
575 args = (sys.stderr,)
572 args = (sys.stderr,)
576 #level = WARN
573 #level = WARN
577 level = DEBUG
574 level = DEBUG
578 #formatter = generic
575 #formatter = generic
579 formatter = color_formatter_sql
576 formatter = color_formatter_sql
580
577
581 ################
578 ################
582 ## FORMATTERS ##
579 ## FORMATTERS ##
583 ################
580 ################
584
581
585 [formatter_generic]
582 [formatter_generic]
586 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
583 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
587 datefmt = %Y-%m-%d %H:%M:%S
584 datefmt = %Y-%m-%d %H:%M:%S
588
585
589 [formatter_color_formatter]
586 [formatter_color_formatter]
590 class = kallithea.lib.colored_formatter.ColorFormatter
587 class = kallithea.lib.colored_formatter.ColorFormatter
591 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
588 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
592 datefmt = %Y-%m-%d %H:%M:%S
589 datefmt = %Y-%m-%d %H:%M:%S
593
590
594 [formatter_color_formatter_sql]
591 [formatter_color_formatter_sql]
595 class = kallithea.lib.colored_formatter.ColorFormatterSql
592 class = kallithea.lib.colored_formatter.ColorFormatterSql
596 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
593 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
597 datefmt = %Y-%m-%d %H:%M:%S
594 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,55 +1,51 b''
1 .. _performance:
1 .. _performance:
2
2
3 ================================
3 ================================
4 Optimizing Kallithea performance
4 Optimizing Kallithea performance
5 ================================
5 ================================
6
6
7 When serving a large amount of big repositories, Kallithea can start
7 When serving a large amount of big repositories, Kallithea can start
8 performing slower than expected. Because of the demanding nature of handling large
8 performing slower than expected. Because of the demanding nature of handling large
9 amounts of data from version control systems, here are some tips on how to get
9 amounts of data from version control systems, here are some tips on how to get
10 the best performance.
10 the best performance.
11
11
12 * Kallithea is often I/O bound, and hence a fast disk (SSD/SAN) is
13 usually more important than a fast CPU.
14
15 * Sluggish loading of the front page can easily be fixed by grouping repositories or by
16 increasing cache size (see below). This includes using the lightweight dashboard
17 option and ``vcs_full_cache`` setting in .ini file.
18
19 Follow these few steps to improve performance of Kallithea system.
12 Follow these few steps to improve performance of Kallithea system.
20
13
21 1. Increase cache
14 1. Kallithea is often I/O bound, and hence a fast disk (SSD/SAN) is
15 usually more important than a fast CPU.
16
17 2. Increase cache
22
18
23 Tweak beaker cache settings in the ini file. The actual effect of that
19 Tweak beaker cache settings in the ini file. The actual effect of that
24 is questionable.
20 is questionable.
25
21
26 2. Switch from SQLite to PostgreSQL or MySQL
22 3. Switch from SQLite to PostgreSQL or MySQL
27
23
28 SQLite is a good option when having a small load on the system. But due to
24 SQLite is a good option when having a small load on the system. But due to
29 locking issues with SQLite, it is not recommended to use it for larger
25 locking issues with SQLite, it is not recommended to use it for larger
30 deployments. Switching to MySQL or PostgreSQL will result in an immediate
26 deployments. Switching to MySQL or PostgreSQL will result in an immediate
31 performance increase. A tool like SQLAlchemyGrate_ can be used for
27 performance increase. A tool like SQLAlchemyGrate_ can be used for
32 migrating to another database platform.
28 migrating to another database platform.
33
29
34 3. Scale Kallithea horizontally
30 4. Scale Kallithea horizontally
35
31
36 Scaling horizontally can give huge performance benefits when dealing with
32 Scaling horizontally can give huge performance benefits when dealing with
37 large amounts of traffic (many users, CI servers, etc.). Kallithea can be
33 large amounts of traffic (many users, CI servers, etc.). Kallithea can be
38 scaled horizontally on one (recommended) or multiple machines. In order
34 scaled horizontally on one (recommended) or multiple machines. In order
39 to scale horizontally you need to do the following:
35 to scale horizontally you need to do the following:
40
36
41 - Each instance's ``data`` storage needs to be configured to be stored on a
37 - Each instance's ``data`` storage needs to be configured to be stored on a
42 shared disk storage, preferably together with repositories. This ``data``
38 shared disk storage, preferably together with repositories. This ``data``
43 dir contains template caches, sessions, whoosh index and is used for
39 dir contains template caches, sessions, whoosh index and is used for
44 task locking (so it is safe across multiple instances). Set the
40 task locking (so it is safe across multiple instances). Set the
45 ``cache_dir``, ``index_dir``, ``beaker.cache.data_dir``, ``beaker.cache.lock_dir``
41 ``cache_dir``, ``index_dir``, ``beaker.cache.data_dir``, ``beaker.cache.lock_dir``
46 variables in each .ini file to a shared location across Kallithea instances
42 variables in each .ini file to a shared location across Kallithea instances
47 - If celery is used each instance should run a separate Celery instance, but
43 - If celery is used each instance should run a separate Celery instance, but
48 the message broker should be common to all of them (e.g., one
44 the message broker should be common to all of them (e.g., one
49 shared RabbitMQ server)
45 shared RabbitMQ server)
50 - Load balance using round robin or IP hash, recommended is writing LB rules
46 - Load balance using round robin or IP hash, recommended is writing LB rules
51 that will separate regular user traffic from automated processes like CI
47 that will separate regular user traffic from automated processes like CI
52 servers or build bots.
48 servers or build bots.
53
49
54
50
55 .. _SQLAlchemyGrate: https://github.com/shazow/sqlalchemygrate
51 .. _SQLAlchemyGrate: https://github.com/shazow/sqlalchemygrate
@@ -1,596 +1,593 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%text>################################################################################</%text>
2 <%text>################################################################################</%text>
3 <%text>################################################################################</%text>
3 <%text>################################################################################</%text>
4 # Kallithea - config file generated with kallithea-config #
4 # Kallithea - config file generated with kallithea-config #
5 <%text>################################################################################</%text>
5 <%text>################################################################################</%text>
6 <%text>################################################################################</%text>
6 <%text>################################################################################</%text>
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11
11
12 <%text>################################################################################</%text>
12 <%text>################################################################################</%text>
13 <%text>## Email settings ##</%text>
13 <%text>## Email settings ##</%text>
14 <%text>## ##</%text>
14 <%text>## ##</%text>
15 <%text>## Refer to the documentation ("Email settings") for more details. ##</%text>
15 <%text>## Refer to the documentation ("Email settings") for more details. ##</%text>
16 <%text>## ##</%text>
16 <%text>## ##</%text>
17 <%text>## It is recommended to use a valid sender address that passes access ##</%text>
17 <%text>## It is recommended to use a valid sender address that passes access ##</%text>
18 <%text>## validation and spam filtering in mail servers. ##</%text>
18 <%text>## validation and spam filtering in mail servers. ##</%text>
19 <%text>################################################################################</%text>
19 <%text>################################################################################</%text>
20
20
21 <%text>## 'From' header for application emails. You can optionally add a name.</%text>
21 <%text>## 'From' header for application emails. You can optionally add a name.</%text>
22 <%text>## Default:</%text>
22 <%text>## Default:</%text>
23 #app_email_from = Kallithea
23 #app_email_from = Kallithea
24 <%text>## Examples:</%text>
24 <%text>## Examples:</%text>
25 #app_email_from = Kallithea <kallithea-noreply@example.com>
25 #app_email_from = Kallithea <kallithea-noreply@example.com>
26 #app_email_from = kallithea-noreply@example.com
26 #app_email_from = kallithea-noreply@example.com
27
27
28 <%text>## Subject prefix for application emails.</%text>
28 <%text>## Subject prefix for application emails.</%text>
29 <%text>## A space between this prefix and the real subject is automatically added.</%text>
29 <%text>## A space between this prefix and the real subject is automatically added.</%text>
30 <%text>## Default:</%text>
30 <%text>## Default:</%text>
31 #email_prefix =
31 #email_prefix =
32 <%text>## Example:</%text>
32 <%text>## Example:</%text>
33 #email_prefix = [Kallithea]
33 #email_prefix = [Kallithea]
34
34
35 <%text>## Recipients for error emails and fallback recipients of application mails.</%text>
35 <%text>## Recipients for error emails and fallback recipients of application mails.</%text>
36 <%text>## Multiple addresses can be specified, space-separated.</%text>
36 <%text>## Multiple addresses can be specified, space-separated.</%text>
37 <%text>## Only addresses are allowed, do not add any name part.</%text>
37 <%text>## Only addresses are allowed, do not add any name part.</%text>
38 <%text>## Default:</%text>
38 <%text>## Default:</%text>
39 #email_to =
39 #email_to =
40 <%text>## Examples:</%text>
40 <%text>## Examples:</%text>
41 #email_to = admin@example.com
41 #email_to = admin@example.com
42 #email_to = admin@example.com another_admin@example.com
42 #email_to = admin@example.com another_admin@example.com
43
43
44 <%text>## 'From' header for error emails. You can optionally add a name.</%text>
44 <%text>## 'From' header for error emails. You can optionally add a name.</%text>
45 <%text>## Default:</%text>
45 <%text>## Default:</%text>
46 #error_email_from = pylons@yourapp.com
46 #error_email_from = pylons@yourapp.com
47 <%text>## Examples:</%text>
47 <%text>## Examples:</%text>
48 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
48 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
49 #error_email_from = paste_error@example.com
49 #error_email_from = paste_error@example.com
50
50
51 <%text>## SMTP server settings</%text>
51 <%text>## SMTP server settings</%text>
52 <%text>## Only smtp_server is mandatory. All other settings take the specified default</%text>
52 <%text>## Only smtp_server is mandatory. All other settings take the specified default</%text>
53 <%text>## values.</%text>
53 <%text>## values.</%text>
54 #smtp_server = smtp.example.com
54 #smtp_server = smtp.example.com
55 #smtp_username =
55 #smtp_username =
56 #smtp_password =
56 #smtp_password =
57 #smtp_port = 25
57 #smtp_port = 25
58 #smtp_use_tls = false
58 #smtp_use_tls = false
59 #smtp_use_ssl = false
59 #smtp_use_ssl = false
60 <%text>## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).</%text>
60 <%text>## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).</%text>
61 <%text>## If empty, use any of the authentication parameters supported by the server.</%text>
61 <%text>## If empty, use any of the authentication parameters supported by the server.</%text>
62 #smtp_auth =
62 #smtp_auth =
63
63
64 [server:main]
64 [server:main]
65 %if http_server == 'paste':
65 %if http_server == 'paste':
66 <%text>## PASTE ##</%text>
66 <%text>## PASTE ##</%text>
67 use = egg:Paste#http
67 use = egg:Paste#http
68 <%text>## nr of worker threads to spawn</%text>
68 <%text>## nr of worker threads to spawn</%text>
69 threadpool_workers = 5
69 threadpool_workers = 5
70 <%text>## max request before thread respawn</%text>
70 <%text>## max request before thread respawn</%text>
71 threadpool_max_requests = 10
71 threadpool_max_requests = 10
72 <%text>## option to use threads of process</%text>
72 <%text>## option to use threads of process</%text>
73 use_threadpool = true
73 use_threadpool = true
74
74
75 %elif http_server == 'waitress':
75 %elif http_server == 'waitress':
76 <%text>## WAITRESS ##</%text>
76 <%text>## WAITRESS ##</%text>
77 use = egg:waitress#main
77 use = egg:waitress#main
78 <%text>## number of worker threads</%text>
78 <%text>## number of worker threads</%text>
79 threads = 5
79 threads = 5
80 <%text>## MAX BODY SIZE 100GB</%text>
80 <%text>## MAX BODY SIZE 100GB</%text>
81 max_request_body_size = 107374182400
81 max_request_body_size = 107374182400
82 <%text>## use poll instead of select, fixes fd limits, may not work on old</%text>
82 <%text>## use poll instead of select, fixes fd limits, may not work on old</%text>
83 <%text>## windows systems.</%text>
83 <%text>## windows systems.</%text>
84 #asyncore_use_poll = True
84 #asyncore_use_poll = True
85
85
86 %elif http_server == 'gunicorn':
86 %elif http_server == 'gunicorn':
87 <%text>## GUNICORN ##</%text>
87 <%text>## GUNICORN ##</%text>
88 use = egg:gunicorn#main
88 use = egg:gunicorn#main
89 <%text>## number of process workers. You must set `instance_id = *` when this option</%text>
89 <%text>## number of process workers. You must set `instance_id = *` when this option</%text>
90 <%text>## is set to more than one worker</%text>
90 <%text>## is set to more than one worker</%text>
91 workers = 1
91 workers = 1
92 <%text>## process name</%text>
92 <%text>## process name</%text>
93 proc_name = kallithea
93 proc_name = kallithea
94 <%text>## type of worker class, one of sync, eventlet, gevent, tornado</%text>
94 <%text>## type of worker class, one of sync, eventlet, gevent, tornado</%text>
95 <%text>## recommended for bigger setup is using of of other than sync one</%text>
95 <%text>## recommended for bigger setup is using of of other than sync one</%text>
96 worker_class = sync
96 worker_class = sync
97 max_requests = 1000
97 max_requests = 1000
98 <%text>## ammount of time a worker can handle request before it gets killed and</%text>
98 <%text>## ammount of time a worker can handle request before it gets killed and</%text>
99 <%text>## restarted</%text>
99 <%text>## restarted</%text>
100 timeout = 3600
100 timeout = 3600
101
101
102 %elif http_server == 'uwsgi':
102 %elif http_server == 'uwsgi':
103 <%text>## UWSGI ##</%text>
103 <%text>## UWSGI ##</%text>
104 <%text>## run with uwsgi --ini-paste-logged <inifile.ini></%text>
104 <%text>## run with uwsgi --ini-paste-logged <inifile.ini></%text>
105 [uwsgi]
105 [uwsgi]
106 socket = /tmp/uwsgi.sock
106 socket = /tmp/uwsgi.sock
107 master = true
107 master = true
108 http = 127.0.0.1:5000
108 http = 127.0.0.1:5000
109
109
110 <%text>## set as deamon and redirect all output to file</%text>
110 <%text>## set as deamon and redirect all output to file</%text>
111 #daemonize = ./uwsgi_kallithea.log
111 #daemonize = ./uwsgi_kallithea.log
112
112
113 <%text>## master process PID</%text>
113 <%text>## master process PID</%text>
114 pidfile = ./uwsgi_kallithea.pid
114 pidfile = ./uwsgi_kallithea.pid
115
115
116 <%text>## stats server with workers statistics, use uwsgitop</%text>
116 <%text>## stats server with workers statistics, use uwsgitop</%text>
117 <%text>## for monitoring, `uwsgitop 127.0.0.1:1717`</%text>
117 <%text>## for monitoring, `uwsgitop 127.0.0.1:1717`</%text>
118 stats = 127.0.0.1:1717
118 stats = 127.0.0.1:1717
119 memory-report = true
119 memory-report = true
120
120
121 <%text>## log 5XX errors</%text>
121 <%text>## log 5XX errors</%text>
122 log-5xx = true
122 log-5xx = true
123
123
124 <%text>## Set the socket listen queue size.</%text>
124 <%text>## Set the socket listen queue size.</%text>
125 listen = 256
125 listen = 256
126
126
127 <%text>## Gracefully Reload workers after the specified amount of managed requests</%text>
127 <%text>## Gracefully Reload workers after the specified amount of managed requests</%text>
128 <%text>## (avoid memory leaks).</%text>
128 <%text>## (avoid memory leaks).</%text>
129 max-requests = 1000
129 max-requests = 1000
130
130
131 <%text>## enable large buffers</%text>
131 <%text>## enable large buffers</%text>
132 buffer-size = 65535
132 buffer-size = 65535
133
133
134 <%text>## socket and http timeouts ##</%text>
134 <%text>## socket and http timeouts ##</%text>
135 http-timeout = 3600
135 http-timeout = 3600
136 socket-timeout = 3600
136 socket-timeout = 3600
137
137
138 <%text>## Log requests slower than the specified number of milliseconds.</%text>
138 <%text>## Log requests slower than the specified number of milliseconds.</%text>
139 log-slow = 10
139 log-slow = 10
140
140
141 <%text>## Exit if no app can be loaded.</%text>
141 <%text>## Exit if no app can be loaded.</%text>
142 need-app = true
142 need-app = true
143
143
144 <%text>## Set lazy mode (load apps in workers instead of master).</%text>
144 <%text>## Set lazy mode (load apps in workers instead of master).</%text>
145 lazy = true
145 lazy = true
146
146
147 <%text>## scaling ##</%text>
147 <%text>## scaling ##</%text>
148 <%text>## set cheaper algorithm to use, if not set default will be used</%text>
148 <%text>## set cheaper algorithm to use, if not set default will be used</%text>
149 cheaper-algo = spare
149 cheaper-algo = spare
150
150
151 <%text>## minimum number of workers to keep at all times</%text>
151 <%text>## minimum number of workers to keep at all times</%text>
152 cheaper = 1
152 cheaper = 1
153
153
154 <%text>## number of workers to spawn at startup</%text>
154 <%text>## number of workers to spawn at startup</%text>
155 cheaper-initial = 1
155 cheaper-initial = 1
156
156
157 <%text>## maximum number of workers that can be spawned</%text>
157 <%text>## maximum number of workers that can be spawned</%text>
158 workers = 4
158 workers = 4
159
159
160 <%text>## how many workers should be spawned at a time</%text>
160 <%text>## how many workers should be spawned at a time</%text>
161 cheaper-step = 1
161 cheaper-step = 1
162
162
163 %endif
163 %endif
164 <%text>## COMMON ##</%text>
164 <%text>## COMMON ##</%text>
165 host = ${host}
165 host = ${host}
166 port = ${port}
166 port = ${port}
167
167
168 <%text>## middleware for hosting the WSGI application under a URL prefix</%text>
168 <%text>## middleware for hosting the WSGI application under a URL prefix</%text>
169 #[filter:proxy-prefix]
169 #[filter:proxy-prefix]
170 #use = egg:PasteDeploy#prefix
170 #use = egg:PasteDeploy#prefix
171 #prefix = /<your-prefix>
171 #prefix = /<your-prefix>
172
172
173 [app:main]
173 [app:main]
174 use = egg:kallithea
174 use = egg:kallithea
175 <%text>## enable proxy prefix middleware</%text>
175 <%text>## enable proxy prefix middleware</%text>
176 #filter-with = proxy-prefix
176 #filter-with = proxy-prefix
177
177
178 full_stack = true
178 full_stack = true
179 static_files = true
179 static_files = true
180 <%text>## Available Languages:</%text>
180 <%text>## Available Languages:</%text>
181 <%text>## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW</%text>
181 <%text>## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW</%text>
182 lang =
182 lang =
183 cache_dir = ${here}/data
183 cache_dir = ${here}/data
184 index_dir = ${here}/data/index
184 index_dir = ${here}/data/index
185
185
186 <%text>## perform a full repository scan on each server start, this should be</%text>
186 <%text>## perform a full repository scan on each server start, this should be</%text>
187 <%text>## set to false after first startup, to allow faster server restarts.</%text>
187 <%text>## set to false after first startup, to allow faster server restarts.</%text>
188 initial_repo_scan = false
188 initial_repo_scan = false
189
189
190 <%text>## uncomment and set this path to use archive download cache</%text>
190 <%text>## uncomment and set this path to use archive download cache</%text>
191 archive_cache_dir = ${here}/tarballcache
191 archive_cache_dir = ${here}/tarballcache
192
192
193 <%text>## change this to unique ID for security</%text>
193 <%text>## change this to unique ID for security</%text>
194 app_instance_uuid = ${uuid()}
194 app_instance_uuid = ${uuid()}
195
195
196 <%text>## cut off limit for large diffs (size in bytes)</%text>
196 <%text>## cut off limit for large diffs (size in bytes)</%text>
197 cut_off_limit = 256000
197 cut_off_limit = 256000
198
198
199 <%text>## use cache version of scm repo everywhere</%text>
200 vcs_full_cache = true
201
202 <%text>## force https in Kallithea, fixes https redirects, assumes it's always https</%text>
199 <%text>## force https in Kallithea, fixes https redirects, assumes it's always https</%text>
203 force_https = false
200 force_https = false
204
201
205 <%text>## use Strict-Transport-Security headers</%text>
202 <%text>## use Strict-Transport-Security headers</%text>
206 use_htsts = false
203 use_htsts = false
207
204
208 <%text>## number of commits stats will parse on each iteration</%text>
205 <%text>## number of commits stats will parse on each iteration</%text>
209 commit_parse_limit = 25
206 commit_parse_limit = 25
210
207
211 <%text>## path to git executable</%text>
208 <%text>## path to git executable</%text>
212 git_path = git
209 git_path = git
213
210
214 <%text>## git rev filter option, --all is the default filter, if you need to</%text>
211 <%text>## git rev filter option, --all is the default filter, if you need to</%text>
215 <%text>## hide all refs in changelog switch this to --branches --tags</%text>
212 <%text>## hide all refs in changelog switch this to --branches --tags</%text>
216 #git_rev_filter = --branches --tags
213 #git_rev_filter = --branches --tags
217
214
218 <%text>## RSS feed options</%text>
215 <%text>## RSS feed options</%text>
219 rss_cut_off_limit = 256000
216 rss_cut_off_limit = 256000
220 rss_items_per_page = 10
217 rss_items_per_page = 10
221 rss_include_diff = false
218 rss_include_diff = false
222
219
223 <%text>## options for showing and identifying changesets</%text>
220 <%text>## options for showing and identifying changesets</%text>
224 show_sha_length = 12
221 show_sha_length = 12
225 show_revision_number = false
222 show_revision_number = false
226
223
227 <%text>## gist URL alias, used to create nicer urls for gist. This should be an</%text>
224 <%text>## gist URL alias, used to create nicer urls for gist. This should be an</%text>
228 <%text>## url that does rewrites to _admin/gists/<gistid>.</%text>
225 <%text>## url that does rewrites to _admin/gists/<gistid>.</%text>
229 <%text>## example: http://gist.example.com/{gistid}. Empty means use the internal</%text>
226 <%text>## example: http://gist.example.com/{gistid}. Empty means use the internal</%text>
230 <%text>## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid></%text>
227 <%text>## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid></%text>
231 gist_alias_url =
228 gist_alias_url =
232
229
233 <%text>## white list of API enabled controllers. This allows to add list of</%text>
230 <%text>## white list of API enabled controllers. This allows to add list of</%text>
234 <%text>## controllers to which access will be enabled by api_key. eg: to enable</%text>
231 <%text>## controllers to which access will be enabled by api_key. eg: to enable</%text>
235 <%text>## api access to raw_files put `FilesController:raw`, to enable access to patches</%text>
232 <%text>## api access to raw_files put `FilesController:raw`, to enable access to patches</%text>
236 <%text>## add `ChangesetController:changeset_patch`. This list should be "," separated</%text>
233 <%text>## add `ChangesetController:changeset_patch`. This list should be "," separated</%text>
237 <%text>## Syntax is <ControllerClass>:<function>. Check debug logs for generated names</%text>
234 <%text>## Syntax is <ControllerClass>:<function>. Check debug logs for generated names</%text>
238 <%text>## Recommended settings below are commented out:</%text>
235 <%text>## Recommended settings below are commented out:</%text>
239 api_access_controllers_whitelist =
236 api_access_controllers_whitelist =
240 # ChangesetController:changeset_patch,
237 # ChangesetController:changeset_patch,
241 # ChangesetController:changeset_raw,
238 # ChangesetController:changeset_raw,
242 # FilesController:raw,
239 # FilesController:raw,
243 # FilesController:archivefile
240 # FilesController:archivefile
244
241
245 <%text>## default encoding used to convert from and to unicode</%text>
242 <%text>## default encoding used to convert from and to unicode</%text>
246 <%text>## can be also a comma seperated list of encoding in case of mixed encodings</%text>
243 <%text>## can be also a comma seperated list of encoding in case of mixed encodings</%text>
247 default_encoding = utf8
244 default_encoding = utf8
248
245
249 <%text>## issue tracker for Kallithea (leave blank to disable, absent for default)</%text>
246 <%text>## issue tracker for Kallithea (leave blank to disable, absent for default)</%text>
250 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
247 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
251
248
252 <%text>## issue tracking mapping for commits messages</%text>
249 <%text>## issue tracking mapping for commits messages</%text>
253 <%text>## comment out issue_pat, issue_server, issue_prefix to enable</%text>
250 <%text>## comment out issue_pat, issue_server, issue_prefix to enable</%text>
254
251
255 <%text>## pattern to get the issues from commit messages</%text>
252 <%text>## pattern to get the issues from commit messages</%text>
256 <%text>## default one used here is #<numbers> with a regex passive group for `#`</%text>
253 <%text>## default one used here is #<numbers> with a regex passive group for `#`</%text>
257 <%text>## {id} will be all groups matched from this pattern</%text>
254 <%text>## {id} will be all groups matched from this pattern</%text>
258
255
259 issue_pat = (?:\s*#)(\d+)
256 issue_pat = (?:\s*#)(\d+)
260
257
261 <%text>## server url to the issue, each {id} will be replaced with match</%text>
258 <%text>## server url to the issue, each {id} will be replaced with match</%text>
262 <%text>## fetched from the regex and {repo} is replaced with full repository name</%text>
259 <%text>## fetched from the regex and {repo} is replaced with full repository name</%text>
263 <%text>## including groups {repo_name} is replaced with just name of repo</%text>
260 <%text>## including groups {repo_name} is replaced with just name of repo</%text>
264
261
265 issue_server_link = https://issues.example.com/{repo}/issue/{id}
262 issue_server_link = https://issues.example.com/{repo}/issue/{id}
266
263
267 <%text>## prefix to add to link to indicate it's an url</%text>
264 <%text>## prefix to add to link to indicate it's an url</%text>
268 <%text>## #314 will be replaced by <issue_prefix><id></%text>
265 <%text>## #314 will be replaced by <issue_prefix><id></%text>
269
266
270 issue_prefix = #
267 issue_prefix = #
271
268
272 <%text>## issue_pat, issue_server_link, issue_prefix can have suffixes to specify</%text>
269 <%text>## issue_pat, issue_server_link, issue_prefix can have suffixes to specify</%text>
273 <%text>## multiple patterns, to other issues server, wiki or others</%text>
270 <%text>## multiple patterns, to other issues server, wiki or others</%text>
274 <%text>## below an example how to create a wiki pattern</%text>
271 <%text>## below an example how to create a wiki pattern</%text>
275 # wiki-some-id -> https://wiki.example.com/some-id
272 # wiki-some-id -> https://wiki.example.com/some-id
276
273
277 #issue_pat_wiki = (?:wiki-)(.+)
274 #issue_pat_wiki = (?:wiki-)(.+)
278 #issue_server_link_wiki = https://wiki.example.com/{id}
275 #issue_server_link_wiki = https://wiki.example.com/{id}
279 #issue_prefix_wiki = WIKI-
276 #issue_prefix_wiki = WIKI-
280
277
281 <%text>## alternative return HTTP header for failed authentication. Default HTTP</%text>
278 <%text>## alternative return HTTP header for failed authentication. Default HTTP</%text>
282 <%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with</%text>
279 <%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with</%text>
283 <%text>## handling that. Set this variable to 403 to return HTTPForbidden</%text>
280 <%text>## handling that. Set this variable to 403 to return HTTPForbidden</%text>
284 auth_ret_code =
281 auth_ret_code =
285
282
286 <%text>## locking return code. When repository is locked return this HTTP code. 2XX</%text>
283 <%text>## locking return code. When repository is locked return this HTTP code. 2XX</%text>
287 <%text>## codes don't break the transactions while 4XX codes do</%text>
284 <%text>## codes don't break the transactions while 4XX codes do</%text>
288 lock_ret_code = 423
285 lock_ret_code = 423
289
286
290 <%text>## allows to change the repository location in settings page</%text>
287 <%text>## allows to change the repository location in settings page</%text>
291 allow_repo_location_change = True
288 allow_repo_location_change = True
292
289
293 <%text>## allows to setup custom hooks in settings page</%text>
290 <%text>## allows to setup custom hooks in settings page</%text>
294 allow_custom_hooks_settings = True
291 allow_custom_hooks_settings = True
295
292
296 <%text>## extra extensions for indexing, space separated and without the leading '.'.</%text>
293 <%text>## extra extensions for indexing, space separated and without the leading '.'.</%text>
297 # index.extensions =
294 # index.extensions =
298 # gemfile
295 # gemfile
299 # lock
296 # lock
300
297
301 <%text>## extra filenames for indexing, space separated</%text>
298 <%text>## extra filenames for indexing, space separated</%text>
302 # index.filenames =
299 # index.filenames =
303 # .dockerignore
300 # .dockerignore
304 # .editorconfig
301 # .editorconfig
305 # INSTALL
302 # INSTALL
306 # CHANGELOG
303 # CHANGELOG
307
304
308 <%text>####################################</%text>
305 <%text>####################################</%text>
309 <%text>### CELERY CONFIG ####</%text>
306 <%text>### CELERY CONFIG ####</%text>
310 <%text>####################################</%text>
307 <%text>####################################</%text>
311
308
312 use_celery = false
309 use_celery = false
313 broker.host = localhost
310 broker.host = localhost
314 broker.vhost = rabbitmqhost
311 broker.vhost = rabbitmqhost
315 broker.port = 5672
312 broker.port = 5672
316 broker.user = rabbitmq
313 broker.user = rabbitmq
317 broker.password = qweqwe
314 broker.password = qweqwe
318
315
319 celery.imports = kallithea.lib.celerylib.tasks
316 celery.imports = kallithea.lib.celerylib.tasks
320
317
321 celery.result.backend = amqp
318 celery.result.backend = amqp
322 celery.result.dburi = amqp://
319 celery.result.dburi = amqp://
323 celery.result.serialier = json
320 celery.result.serialier = json
324
321
325 #celery.send.task.error.emails = true
322 #celery.send.task.error.emails = true
326 #celery.amqp.task.result.expires = 18000
323 #celery.amqp.task.result.expires = 18000
327
324
328 celeryd.concurrency = 2
325 celeryd.concurrency = 2
329 #celeryd.log.file = celeryd.log
326 #celeryd.log.file = celeryd.log
330 celeryd.log.level = DEBUG
327 celeryd.log.level = DEBUG
331 celeryd.max.tasks.per.child = 1
328 celeryd.max.tasks.per.child = 1
332
329
333 <%text>## tasks will never be sent to the queue, but executed locally instead.</%text>
330 <%text>## tasks will never be sent to the queue, but executed locally instead.</%text>
334 celery.always.eager = false
331 celery.always.eager = false
335
332
336 <%text>####################################</%text>
333 <%text>####################################</%text>
337 <%text>### BEAKER CACHE ####</%text>
334 <%text>### BEAKER CACHE ####</%text>
338 <%text>####################################</%text>
335 <%text>####################################</%text>
339
336
340 beaker.cache.data_dir = ${here}/data/cache/data
337 beaker.cache.data_dir = ${here}/data/cache/data
341 beaker.cache.lock_dir = ${here}/data/cache/lock
338 beaker.cache.lock_dir = ${here}/data/cache/lock
342
339
343 beaker.cache.regions = short_term,long_term,sql_cache_short
340 beaker.cache.regions = short_term,long_term,sql_cache_short
344
341
345 beaker.cache.short_term.type = memory
342 beaker.cache.short_term.type = memory
346 beaker.cache.short_term.expire = 60
343 beaker.cache.short_term.expire = 60
347 beaker.cache.short_term.key_length = 256
344 beaker.cache.short_term.key_length = 256
348
345
349 beaker.cache.long_term.type = memory
346 beaker.cache.long_term.type = memory
350 beaker.cache.long_term.expire = 36000
347 beaker.cache.long_term.expire = 36000
351 beaker.cache.long_term.key_length = 256
348 beaker.cache.long_term.key_length = 256
352
349
353 beaker.cache.sql_cache_short.type = memory
350 beaker.cache.sql_cache_short.type = memory
354 beaker.cache.sql_cache_short.expire = 10
351 beaker.cache.sql_cache_short.expire = 10
355 beaker.cache.sql_cache_short.key_length = 256
352 beaker.cache.sql_cache_short.key_length = 256
356
353
357 <%text>####################################</%text>
354 <%text>####################################</%text>
358 <%text>### BEAKER SESSION ####</%text>
355 <%text>### BEAKER SESSION ####</%text>
359 <%text>####################################</%text>
356 <%text>####################################</%text>
360
357
361 <%text>## Name of session cookie. Should be unique for a given host and path, even when running</%text>
358 <%text>## Name of session cookie. Should be unique for a given host and path, even when running</%text>
362 <%text>## on different ports. Otherwise, cookie sessions will be shared and messed up.</%text>
359 <%text>## on different ports. Otherwise, cookie sessions will be shared and messed up.</%text>
363 beaker.session.key = kallithea
360 beaker.session.key = kallithea
364 <%text>## Sessions should always only be accessible by the browser, not directly by JavaScript.</%text>
361 <%text>## Sessions should always only be accessible by the browser, not directly by JavaScript.</%text>
365 beaker.session.httponly = true
362 beaker.session.httponly = true
366 <%text>## Session lifetime. 2592000 seconds is 30 days.</%text>
363 <%text>## Session lifetime. 2592000 seconds is 30 days.</%text>
367 beaker.session.timeout = 2592000
364 beaker.session.timeout = 2592000
368
365
369 <%text>## Server secret used with HMAC to ensure integrity of cookies.</%text>
366 <%text>## Server secret used with HMAC to ensure integrity of cookies.</%text>
370 beaker.session.secret = ${uuid()}
367 beaker.session.secret = ${uuid()}
371 <%text>## Further, encrypt the data with AES.</%text>
368 <%text>## Further, encrypt the data with AES.</%text>
372 #beaker.session.encrypt_key = <key_for_encryption>
369 #beaker.session.encrypt_key = <key_for_encryption>
373 #beaker.session.validate_key = <validation_key>
370 #beaker.session.validate_key = <validation_key>
374
371
375 <%text>## Type of storage used for the session, current types are</%text>
372 <%text>## Type of storage used for the session, current types are</%text>
376 <%text>## dbm, file, memcached, database, and memory.</%text>
373 <%text>## dbm, file, memcached, database, and memory.</%text>
377
374
378 <%text>## File system storage of session data. (default)</%text>
375 <%text>## File system storage of session data. (default)</%text>
379 #beaker.session.type = file
376 #beaker.session.type = file
380
377
381 <%text>## Cookie only, store all session data inside the cookie. Requires secure secrets.</%text>
378 <%text>## Cookie only, store all session data inside the cookie. Requires secure secrets.</%text>
382 #beaker.session.type = cookie
379 #beaker.session.type = cookie
383
380
384 <%text>## Database storage of session data.</%text>
381 <%text>## Database storage of session data.</%text>
385 #beaker.session.type = ext:database
382 #beaker.session.type = ext:database
386 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
383 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
387 #beaker.session.table_name = db_session
384 #beaker.session.table_name = db_session
388
385
389 %if error_aggregation_service == 'errormator':
386 %if error_aggregation_service == 'errormator':
390 <%text>############################</%text>
387 <%text>############################</%text>
391 <%text>## ERROR HANDLING SYSTEMS ##</%text>
388 <%text>## ERROR HANDLING SYSTEMS ##</%text>
392 <%text>############################</%text>
389 <%text>############################</%text>
393
390
394 <%text>####################</%text>
391 <%text>####################</%text>
395 <%text>### [errormator] ###</%text>
392 <%text>### [errormator] ###</%text>
396 <%text>####################</%text>
393 <%text>####################</%text>
397
394
398 <%text>## Errormator is tailored to work with Kallithea, see</%text>
395 <%text>## Errormator is tailored to work with Kallithea, see</%text>
399 <%text>## http://errormator.com for details how to obtain an account</%text>
396 <%text>## http://errormator.com for details how to obtain an account</%text>
400 <%text>## you must install python package `errormator_client` to make it work</%text>
397 <%text>## you must install python package `errormator_client` to make it work</%text>
401
398
402 <%text>## errormator enabled</%text>
399 <%text>## errormator enabled</%text>
403 errormator = false
400 errormator = false
404
401
405 errormator.server_url = https://api.errormator.com
402 errormator.server_url = https://api.errormator.com
406 errormator.api_key = YOUR_API_KEY
403 errormator.api_key = YOUR_API_KEY
407
404
408 <%text>## TWEAK AMOUNT OF INFO SENT HERE</%text>
405 <%text>## TWEAK AMOUNT OF INFO SENT HERE</%text>
409
406
410 <%text>## enables 404 error logging (default False)</%text>
407 <%text>## enables 404 error logging (default False)</%text>
411 errormator.report_404 = false
408 errormator.report_404 = false
412
409
413 <%text>## time in seconds after request is considered being slow (default 1)</%text>
410 <%text>## time in seconds after request is considered being slow (default 1)</%text>
414 errormator.slow_request_time = 1
411 errormator.slow_request_time = 1
415
412
416 <%text>## record slow requests in application</%text>
413 <%text>## record slow requests in application</%text>
417 <%text>## (needs to be enabled for slow datastore recording and time tracking)</%text>
414 <%text>## (needs to be enabled for slow datastore recording and time tracking)</%text>
418 errormator.slow_requests = true
415 errormator.slow_requests = true
419
416
420 <%text>## enable hooking to application loggers</%text>
417 <%text>## enable hooking to application loggers</%text>
421 #errormator.logging = true
418 #errormator.logging = true
422
419
423 <%text>## minimum log level for log capture</%text>
420 <%text>## minimum log level for log capture</%text>
424 #errormator.logging.level = WARNING
421 #errormator.logging.level = WARNING
425
422
426 <%text>## send logs only from erroneous/slow requests</%text>
423 <%text>## send logs only from erroneous/slow requests</%text>
427 <%text>## (saves API quota for intensive logging)</%text>
424 <%text>## (saves API quota for intensive logging)</%text>
428 errormator.logging_on_error = false
425 errormator.logging_on_error = false
429
426
430 <%text>## list of additonal keywords that should be grabbed from environ object</%text>
427 <%text>## list of additonal keywords that should be grabbed from environ object</%text>
431 <%text>## can be string with comma separated list of words in lowercase</%text>
428 <%text>## can be string with comma separated list of words in lowercase</%text>
432 <%text>## (by default client will always send following info:</%text>
429 <%text>## (by default client will always send following info:</%text>
433 <%text>## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that</%text>
430 <%text>## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that</%text>
434 <%text>## start with HTTP* this list be extended with additional keywords here</%text>
431 <%text>## start with HTTP* this list be extended with additional keywords here</%text>
435 errormator.environ_keys_whitelist =
432 errormator.environ_keys_whitelist =
436
433
437 <%text>## list of keywords that should be blanked from request object</%text>
434 <%text>## list of keywords that should be blanked from request object</%text>
438 <%text>## can be string with comma separated list of words in lowercase</%text>
435 <%text>## can be string with comma separated list of words in lowercase</%text>
439 <%text>## (by default client will always blank keys that contain following words</%text>
436 <%text>## (by default client will always blank keys that contain following words</%text>
440 <%text>## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'</%text>
437 <%text>## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'</%text>
441 <%text>## this list be extended with additional keywords set here</%text>
438 <%text>## this list be extended with additional keywords set here</%text>
442 errormator.request_keys_blacklist =
439 errormator.request_keys_blacklist =
443
440
444 <%text>## list of namespaces that should be ignores when gathering log entries</%text>
441 <%text>## list of namespaces that should be ignores when gathering log entries</%text>
445 <%text>## can be string with comma separated list of namespaces</%text>
442 <%text>## can be string with comma separated list of namespaces</%text>
446 <%text>## (by default the client ignores own entries: errormator_client.client)</%text>
443 <%text>## (by default the client ignores own entries: errormator_client.client)</%text>
447 errormator.log_namespace_blacklist =
444 errormator.log_namespace_blacklist =
448
445
449 %elif error_aggregation_service == 'sentry':
446 %elif error_aggregation_service == 'sentry':
450 <%text>################</%text>
447 <%text>################</%text>
451 <%text>### [sentry] ###</%text>
448 <%text>### [sentry] ###</%text>
452 <%text>################</%text>
449 <%text>################</%text>
453
450
454 <%text>## sentry is a alternative open source error aggregator</%text>
451 <%text>## sentry is a alternative open source error aggregator</%text>
455 <%text>## you must install python packages `sentry` and `raven` to enable</%text>
452 <%text>## you must install python packages `sentry` and `raven` to enable</%text>
456
453
457 sentry.dsn = YOUR_DNS
454 sentry.dsn = YOUR_DNS
458 sentry.servers =
455 sentry.servers =
459 sentry.name =
456 sentry.name =
460 sentry.key =
457 sentry.key =
461 sentry.public_key =
458 sentry.public_key =
462 sentry.secret_key =
459 sentry.secret_key =
463 sentry.project =
460 sentry.project =
464 sentry.site =
461 sentry.site =
465 sentry.include_paths =
462 sentry.include_paths =
466 sentry.exclude_paths =
463 sentry.exclude_paths =
467
464
468 %endif
465 %endif
469 <%text>################################################################################</%text>
466 <%text>################################################################################</%text>
470 <%text>## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##</%text>
467 <%text>## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##</%text>
471 <%text>## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##</%text>
468 <%text>## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##</%text>
472 <%text>## execute malicious code after an exception is raised. ##</%text>
469 <%text>## execute malicious code after an exception is raised. ##</%text>
473 <%text>################################################################################</%text>
470 <%text>################################################################################</%text>
474 set debug = false
471 set debug = false
475
472
476 <%text>##################################</%text>
473 <%text>##################################</%text>
477 <%text>### LOGVIEW CONFIG ###</%text>
474 <%text>### LOGVIEW CONFIG ###</%text>
478 <%text>##################################</%text>
475 <%text>##################################</%text>
479
476
480 logview.sqlalchemy = #faa
477 logview.sqlalchemy = #faa
481 logview.pylons.templating = #bfb
478 logview.pylons.templating = #bfb
482 logview.pylons.util = #eee
479 logview.pylons.util = #eee
483
480
484 <%text>#########################################################</%text>
481 <%text>#########################################################</%text>
485 <%text>### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###</%text>
482 <%text>### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###</%text>
486 <%text>#########################################################</%text>
483 <%text>#########################################################</%text>
487
484
488 %if database_engine == 'sqlite':
485 %if database_engine == 'sqlite':
489 # SQLITE [default]
486 # SQLITE [default]
490 sqlalchemy.db1.url = sqlite:///${here}/kallithea.db?timeout=60
487 sqlalchemy.db1.url = sqlite:///${here}/kallithea.db?timeout=60
491
488
492 %elif database_engine == 'postgres':
489 %elif database_engine == 'postgres':
493 # POSTGRESQL
490 # POSTGRESQL
494 sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
491 sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
495
492
496 %elif database_engine == 'mysql':
493 %elif database_engine == 'mysql':
497 # MySQL
494 # MySQL
498 sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
495 sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
499
496
500 %endif
497 %endif
501 # see sqlalchemy docs for others
498 # see sqlalchemy docs for others
502
499
503 sqlalchemy.db1.echo = false
500 sqlalchemy.db1.echo = false
504 sqlalchemy.db1.pool_recycle = 3600
501 sqlalchemy.db1.pool_recycle = 3600
505
502
506 <%text>################################</%text>
503 <%text>################################</%text>
507 <%text>### LOGGING CONFIGURATION ####</%text>
504 <%text>### LOGGING CONFIGURATION ####</%text>
508 <%text>################################</%text>
505 <%text>################################</%text>
509
506
510 [loggers]
507 [loggers]
511 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
508 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
512
509
513 [handlers]
510 [handlers]
514 keys = console, console_sql
511 keys = console, console_sql
515
512
516 [formatters]
513 [formatters]
517 keys = generic, color_formatter, color_formatter_sql
514 keys = generic, color_formatter, color_formatter_sql
518
515
519 <%text>#############</%text>
516 <%text>#############</%text>
520 <%text>## LOGGERS ##</%text>
517 <%text>## LOGGERS ##</%text>
521 <%text>#############</%text>
518 <%text>#############</%text>
522
519
523 [logger_root]
520 [logger_root]
524 level = NOTSET
521 level = NOTSET
525 handlers = console
522 handlers = console
526
523
527 [logger_routes]
524 [logger_routes]
528 level = DEBUG
525 level = DEBUG
529 handlers =
526 handlers =
530 qualname = routes.middleware
527 qualname = routes.middleware
531 <%text>## "level = DEBUG" logs the route matched and routing variables.</%text>
528 <%text>## "level = DEBUG" logs the route matched and routing variables.</%text>
532 propagate = 1
529 propagate = 1
533
530
534 [logger_beaker]
531 [logger_beaker]
535 level = DEBUG
532 level = DEBUG
536 handlers =
533 handlers =
537 qualname = beaker.container
534 qualname = beaker.container
538 propagate = 1
535 propagate = 1
539
536
540 [logger_templates]
537 [logger_templates]
541 level = INFO
538 level = INFO
542 handlers =
539 handlers =
543 qualname = pylons.templating
540 qualname = pylons.templating
544 propagate = 1
541 propagate = 1
545
542
546 [logger_kallithea]
543 [logger_kallithea]
547 level = DEBUG
544 level = DEBUG
548 handlers =
545 handlers =
549 qualname = kallithea
546 qualname = kallithea
550 propagate = 1
547 propagate = 1
551
548
552 [logger_sqlalchemy]
549 [logger_sqlalchemy]
553 level = INFO
550 level = INFO
554 handlers = console_sql
551 handlers = console_sql
555 qualname = sqlalchemy.engine
552 qualname = sqlalchemy.engine
556 propagate = 0
553 propagate = 0
557
554
558 [logger_whoosh_indexer]
555 [logger_whoosh_indexer]
559 level = DEBUG
556 level = DEBUG
560 handlers =
557 handlers =
561 qualname = whoosh_indexer
558 qualname = whoosh_indexer
562 propagate = 1
559 propagate = 1
563
560
564 <%text>##############</%text>
561 <%text>##############</%text>
565 <%text>## HANDLERS ##</%text>
562 <%text>## HANDLERS ##</%text>
566 <%text>##############</%text>
563 <%text>##############</%text>
567
564
568 [handler_console]
565 [handler_console]
569 class = StreamHandler
566 class = StreamHandler
570 args = (sys.stderr,)
567 args = (sys.stderr,)
571 level = INFO
568 level = INFO
572 formatter = generic
569 formatter = generic
573
570
574 [handler_console_sql]
571 [handler_console_sql]
575 class = StreamHandler
572 class = StreamHandler
576 args = (sys.stderr,)
573 args = (sys.stderr,)
577 level = WARN
574 level = WARN
578 formatter = generic
575 formatter = generic
579
576
580 <%text>################</%text>
577 <%text>################</%text>
581 <%text>## FORMATTERS ##</%text>
578 <%text>## FORMATTERS ##</%text>
582 <%text>################</%text>
579 <%text>################</%text>
583
580
584 [formatter_generic]
581 [formatter_generic]
585 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
582 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
586 datefmt = %Y-%m-%d %H:%M:%S
583 datefmt = %Y-%m-%d %H:%M:%S
587
584
588 [formatter_color_formatter]
585 [formatter_color_formatter]
589 class = kallithea.lib.colored_formatter.ColorFormatter
586 class = kallithea.lib.colored_formatter.ColorFormatter
590 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
587 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
591 datefmt = %Y-%m-%d %H:%M:%S
588 datefmt = %Y-%m-%d %H:%M:%S
592
589
593 [formatter_color_formatter_sql]
590 [formatter_color_formatter_sql]
594 class = kallithea.lib.colored_formatter.ColorFormatterSql
591 class = kallithea.lib.colored_formatter.ColorFormatterSql
595 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
592 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
596 datefmt = %Y-%m-%d %H:%M:%S
593 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,585 +1,582 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # Kallithea - Example config #
3 # Kallithea - Example config #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7 ################################################################################
7 ################################################################################
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 pdebug = false
11 pdebug = false
12
12
13 ################################################################################
13 ################################################################################
14 ## Email settings ##
14 ## Email settings ##
15 ## ##
15 ## ##
16 ## Refer to the documentation ("Email settings") for more details. ##
16 ## Refer to the documentation ("Email settings") for more details. ##
17 ## ##
17 ## ##
18 ## It is recommended to use a valid sender address that passes access ##
18 ## It is recommended to use a valid sender address that passes access ##
19 ## validation and spam filtering in mail servers. ##
19 ## validation and spam filtering in mail servers. ##
20 ################################################################################
20 ################################################################################
21
21
22 ## 'From' header for application emails. You can optionally add a name.
22 ## 'From' header for application emails. You can optionally add a name.
23 ## Default:
23 ## Default:
24 #app_email_from = Kallithea
24 #app_email_from = Kallithea
25 ## Examples:
25 ## Examples:
26 #app_email_from = Kallithea <kallithea-noreply@example.com>
26 #app_email_from = Kallithea <kallithea-noreply@example.com>
27 #app_email_from = kallithea-noreply@example.com
27 #app_email_from = kallithea-noreply@example.com
28
28
29 ## Subject prefix for application emails.
29 ## Subject prefix for application emails.
30 ## A space between this prefix and the real subject is automatically added.
30 ## A space between this prefix and the real subject is automatically added.
31 ## Default:
31 ## Default:
32 #email_prefix =
32 #email_prefix =
33 ## Example:
33 ## Example:
34 #email_prefix = [Kallithea]
34 #email_prefix = [Kallithea]
35
35
36 ## Recipients for error emails and fallback recipients of application mails.
36 ## Recipients for error emails and fallback recipients of application mails.
37 ## Multiple addresses can be specified, space-separated.
37 ## Multiple addresses can be specified, space-separated.
38 ## Only addresses are allowed, do not add any name part.
38 ## Only addresses are allowed, do not add any name part.
39 ## Default:
39 ## Default:
40 #email_to =
40 #email_to =
41 ## Examples:
41 ## Examples:
42 #email_to = admin@example.com
42 #email_to = admin@example.com
43 #email_to = admin@example.com another_admin@example.com
43 #email_to = admin@example.com another_admin@example.com
44
44
45 ## 'From' header for error emails. You can optionally add a name.
45 ## 'From' header for error emails. You can optionally add a name.
46 ## Default:
46 ## Default:
47 #error_email_from = pylons@yourapp.com
47 #error_email_from = pylons@yourapp.com
48 ## Examples:
48 ## Examples:
49 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
49 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
50 #error_email_from = paste_error@example.com
50 #error_email_from = paste_error@example.com
51
51
52 ## SMTP server settings
52 ## SMTP server settings
53 ## Only smtp_server is mandatory. All other settings take the specified default
53 ## Only smtp_server is mandatory. All other settings take the specified default
54 ## values.
54 ## values.
55 #smtp_server = smtp.example.com
55 #smtp_server = smtp.example.com
56 #smtp_username =
56 #smtp_username =
57 #smtp_password =
57 #smtp_password =
58 #smtp_port = 25
58 #smtp_port = 25
59 #smtp_use_tls = false
59 #smtp_use_tls = false
60 #smtp_use_ssl = false
60 #smtp_use_ssl = false
61 ## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
61 ## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
62 ## If empty, use any of the authentication parameters supported by the server.
62 ## If empty, use any of the authentication parameters supported by the server.
63 #smtp_auth =
63 #smtp_auth =
64
64
65 [server:main]
65 [server:main]
66 ## PASTE ##
66 ## PASTE ##
67 #use = egg:Paste#http
67 #use = egg:Paste#http
68 ## nr of worker threads to spawn
68 ## nr of worker threads to spawn
69 #threadpool_workers = 5
69 #threadpool_workers = 5
70 ## max request before thread respawn
70 ## max request before thread respawn
71 #threadpool_max_requests = 10
71 #threadpool_max_requests = 10
72 ## option to use threads of process
72 ## option to use threads of process
73 #use_threadpool = true
73 #use_threadpool = true
74
74
75 ## WAITRESS ##
75 ## WAITRESS ##
76 use = egg:waitress#main
76 use = egg:waitress#main
77 ## number of worker threads
77 ## number of worker threads
78 threads = 5
78 threads = 5
79 ## MAX BODY SIZE 100GB
79 ## MAX BODY SIZE 100GB
80 max_request_body_size = 107374182400
80 max_request_body_size = 107374182400
81 ## use poll instead of select, fixes fd limits, may not work on old
81 ## use poll instead of select, fixes fd limits, may not work on old
82 ## windows systems.
82 ## windows systems.
83 #asyncore_use_poll = True
83 #asyncore_use_poll = True
84
84
85 ## GUNICORN ##
85 ## GUNICORN ##
86 #use = egg:gunicorn#main
86 #use = egg:gunicorn#main
87 ## number of process workers. You must set `instance_id = *` when this option
87 ## number of process workers. You must set `instance_id = *` when this option
88 ## is set to more than one worker
88 ## is set to more than one worker
89 #workers = 1
89 #workers = 1
90 ## process name
90 ## process name
91 #proc_name = kallithea
91 #proc_name = kallithea
92 ## type of worker class, one of sync, eventlet, gevent, tornado
92 ## type of worker class, one of sync, eventlet, gevent, tornado
93 ## recommended for bigger setup is using of of other than sync one
93 ## recommended for bigger setup is using of of other than sync one
94 #worker_class = sync
94 #worker_class = sync
95 #max_requests = 1000
95 #max_requests = 1000
96 ## ammount of time a worker can handle request before it gets killed and
96 ## ammount of time a worker can handle request before it gets killed and
97 ## restarted
97 ## restarted
98 #timeout = 3600
98 #timeout = 3600
99
99
100 ## UWSGI ##
100 ## UWSGI ##
101 ## run with uwsgi --ini-paste-logged <inifile.ini>
101 ## run with uwsgi --ini-paste-logged <inifile.ini>
102 #[uwsgi]
102 #[uwsgi]
103 #socket = /tmp/uwsgi.sock
103 #socket = /tmp/uwsgi.sock
104 #master = true
104 #master = true
105 #http = 127.0.0.1:5000
105 #http = 127.0.0.1:5000
106
106
107 ## set as deamon and redirect all output to file
107 ## set as deamon and redirect all output to file
108 #daemonize = ./uwsgi_kallithea.log
108 #daemonize = ./uwsgi_kallithea.log
109
109
110 ## master process PID
110 ## master process PID
111 #pidfile = ./uwsgi_kallithea.pid
111 #pidfile = ./uwsgi_kallithea.pid
112
112
113 ## stats server with workers statistics, use uwsgitop
113 ## stats server with workers statistics, use uwsgitop
114 ## for monitoring, `uwsgitop 127.0.0.1:1717`
114 ## for monitoring, `uwsgitop 127.0.0.1:1717`
115 #stats = 127.0.0.1:1717
115 #stats = 127.0.0.1:1717
116 #memory-report = true
116 #memory-report = true
117
117
118 ## log 5XX errors
118 ## log 5XX errors
119 #log-5xx = true
119 #log-5xx = true
120
120
121 ## Set the socket listen queue size.
121 ## Set the socket listen queue size.
122 #listen = 256
122 #listen = 256
123
123
124 ## Gracefully Reload workers after the specified amount of managed requests
124 ## Gracefully Reload workers after the specified amount of managed requests
125 ## (avoid memory leaks).
125 ## (avoid memory leaks).
126 #max-requests = 1000
126 #max-requests = 1000
127
127
128 ## enable large buffers
128 ## enable large buffers
129 #buffer-size = 65535
129 #buffer-size = 65535
130
130
131 ## socket and http timeouts ##
131 ## socket and http timeouts ##
132 #http-timeout = 3600
132 #http-timeout = 3600
133 #socket-timeout = 3600
133 #socket-timeout = 3600
134
134
135 ## Log requests slower than the specified number of milliseconds.
135 ## Log requests slower than the specified number of milliseconds.
136 #log-slow = 10
136 #log-slow = 10
137
137
138 ## Exit if no app can be loaded.
138 ## Exit if no app can be loaded.
139 #need-app = true
139 #need-app = true
140
140
141 ## Set lazy mode (load apps in workers instead of master).
141 ## Set lazy mode (load apps in workers instead of master).
142 #lazy = true
142 #lazy = true
143
143
144 ## scaling ##
144 ## scaling ##
145 ## set cheaper algorithm to use, if not set default will be used
145 ## set cheaper algorithm to use, if not set default will be used
146 #cheaper-algo = spare
146 #cheaper-algo = spare
147
147
148 ## minimum number of workers to keep at all times
148 ## minimum number of workers to keep at all times
149 #cheaper = 1
149 #cheaper = 1
150
150
151 ## number of workers to spawn at startup
151 ## number of workers to spawn at startup
152 #cheaper-initial = 1
152 #cheaper-initial = 1
153
153
154 ## maximum number of workers that can be spawned
154 ## maximum number of workers that can be spawned
155 #workers = 4
155 #workers = 4
156
156
157 ## how many workers should be spawned at a time
157 ## how many workers should be spawned at a time
158 #cheaper-step = 1
158 #cheaper-step = 1
159
159
160 ## COMMON ##
160 ## COMMON ##
161 host = 127.0.0.1
161 host = 127.0.0.1
162 port = 5000
162 port = 5000
163
163
164 ## middleware for hosting the WSGI application under a URL prefix
164 ## middleware for hosting the WSGI application under a URL prefix
165 #[filter:proxy-prefix]
165 #[filter:proxy-prefix]
166 #use = egg:PasteDeploy#prefix
166 #use = egg:PasteDeploy#prefix
167 #prefix = /<your-prefix>
167 #prefix = /<your-prefix>
168
168
169 [app:main]
169 [app:main]
170 use = egg:kallithea
170 use = egg:kallithea
171 ## enable proxy prefix middleware
171 ## enable proxy prefix middleware
172 #filter-with = proxy-prefix
172 #filter-with = proxy-prefix
173
173
174 full_stack = true
174 full_stack = true
175 static_files = true
175 static_files = true
176 ## Available Languages:
176 ## Available Languages:
177 ## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
177 ## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
178 lang =
178 lang =
179 cache_dir = %(here)s/data
179 cache_dir = %(here)s/data
180 index_dir = %(here)s/data/index
180 index_dir = %(here)s/data/index
181
181
182 ## perform a full repository scan on each server start, this should be
182 ## perform a full repository scan on each server start, this should be
183 ## set to false after first startup, to allow faster server restarts.
183 ## set to false after first startup, to allow faster server restarts.
184 initial_repo_scan = false
184 initial_repo_scan = false
185
185
186 ## uncomment and set this path to use archive download cache
186 ## uncomment and set this path to use archive download cache
187 archive_cache_dir = %(here)s/tarballcache
187 archive_cache_dir = %(here)s/tarballcache
188
188
189 ## change this to unique ID for security
189 ## change this to unique ID for security
190 app_instance_uuid = ${app_instance_uuid}
190 app_instance_uuid = ${app_instance_uuid}
191
191
192 ## cut off limit for large diffs (size in bytes)
192 ## cut off limit for large diffs (size in bytes)
193 cut_off_limit = 256000
193 cut_off_limit = 256000
194
194
195 ## use cache version of scm repo everywhere
196 vcs_full_cache = true
197
198 ## force https in Kallithea, fixes https redirects, assumes it's always https
195 ## force https in Kallithea, fixes https redirects, assumes it's always https
199 force_https = false
196 force_https = false
200
197
201 ## use Strict-Transport-Security headers
198 ## use Strict-Transport-Security headers
202 use_htsts = false
199 use_htsts = false
203
200
204 ## number of commits stats will parse on each iteration
201 ## number of commits stats will parse on each iteration
205 commit_parse_limit = 25
202 commit_parse_limit = 25
206
203
207 ## path to git executable
204 ## path to git executable
208 git_path = git
205 git_path = git
209
206
210 ## git rev filter option, --all is the default filter, if you need to
207 ## git rev filter option, --all is the default filter, if you need to
211 ## hide all refs in changelog switch this to --branches --tags
208 ## hide all refs in changelog switch this to --branches --tags
212 #git_rev_filter = --branches --tags
209 #git_rev_filter = --branches --tags
213
210
214 ## RSS feed options
211 ## RSS feed options
215 rss_cut_off_limit = 256000
212 rss_cut_off_limit = 256000
216 rss_items_per_page = 10
213 rss_items_per_page = 10
217 rss_include_diff = false
214 rss_include_diff = false
218
215
219 ## options for showing and identifying changesets
216 ## options for showing and identifying changesets
220 show_sha_length = 12
217 show_sha_length = 12
221 show_revision_number = false
218 show_revision_number = false
222
219
223 ## gist URL alias, used to create nicer urls for gist. This should be an
220 ## gist URL alias, used to create nicer urls for gist. This should be an
224 ## url that does rewrites to _admin/gists/<gistid>.
221 ## url that does rewrites to _admin/gists/<gistid>.
225 ## example: http://gist.example.com/{gistid}. Empty means use the internal
222 ## example: http://gist.example.com/{gistid}. Empty means use the internal
226 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
223 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
227 gist_alias_url =
224 gist_alias_url =
228
225
229 ## white list of API enabled controllers. This allows to add list of
226 ## white list of API enabled controllers. This allows to add list of
230 ## controllers to which access will be enabled by api_key. eg: to enable
227 ## controllers to which access will be enabled by api_key. eg: to enable
231 ## api access to raw_files put `FilesController:raw`, to enable access to patches
228 ## api access to raw_files put `FilesController:raw`, to enable access to patches
232 ## add `ChangesetController:changeset_patch`. This list should be "," separated
229 ## add `ChangesetController:changeset_patch`. This list should be "," separated
233 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
230 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
234 ## Recommended settings below are commented out:
231 ## Recommended settings below are commented out:
235 api_access_controllers_whitelist =
232 api_access_controllers_whitelist =
236 # ChangesetController:changeset_patch,
233 # ChangesetController:changeset_patch,
237 # ChangesetController:changeset_raw,
234 # ChangesetController:changeset_raw,
238 # FilesController:raw,
235 # FilesController:raw,
239 # FilesController:archivefile
236 # FilesController:archivefile
240
237
241 ## default encoding used to convert from and to unicode
238 ## default encoding used to convert from and to unicode
242 ## can be also a comma seperated list of encoding in case of mixed encodings
239 ## can be also a comma seperated list of encoding in case of mixed encodings
243 default_encoding = utf8
240 default_encoding = utf8
244
241
245 ## issue tracker for Kallithea (leave blank to disable, absent for default)
242 ## issue tracker for Kallithea (leave blank to disable, absent for default)
246 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
243 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
247
244
248 ## issue tracking mapping for commits messages
245 ## issue tracking mapping for commits messages
249 ## comment out issue_pat, issue_server, issue_prefix to enable
246 ## comment out issue_pat, issue_server, issue_prefix to enable
250
247
251 ## pattern to get the issues from commit messages
248 ## pattern to get the issues from commit messages
252 ## default one used here is #<numbers> with a regex passive group for `#`
249 ## default one used here is #<numbers> with a regex passive group for `#`
253 ## {id} will be all groups matched from this pattern
250 ## {id} will be all groups matched from this pattern
254
251
255 issue_pat = (?:\s*#)(\d+)
252 issue_pat = (?:\s*#)(\d+)
256
253
257 ## server url to the issue, each {id} will be replaced with match
254 ## server url to the issue, each {id} will be replaced with match
258 ## fetched from the regex and {repo} is replaced with full repository name
255 ## fetched from the regex and {repo} is replaced with full repository name
259 ## including groups {repo_name} is replaced with just name of repo
256 ## including groups {repo_name} is replaced with just name of repo
260
257
261 issue_server_link = https://issues.example.com/{repo}/issue/{id}
258 issue_server_link = https://issues.example.com/{repo}/issue/{id}
262
259
263 ## prefix to add to link to indicate it's an url
260 ## prefix to add to link to indicate it's an url
264 ## #314 will be replaced by <issue_prefix><id>
261 ## #314 will be replaced by <issue_prefix><id>
265
262
266 issue_prefix = #
263 issue_prefix = #
267
264
268 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
265 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
269 ## multiple patterns, to other issues server, wiki or others
266 ## multiple patterns, to other issues server, wiki or others
270 ## below an example how to create a wiki pattern
267 ## below an example how to create a wiki pattern
271 # wiki-some-id -> https://wiki.example.com/some-id
268 # wiki-some-id -> https://wiki.example.com/some-id
272
269
273 #issue_pat_wiki = (?:wiki-)(.+)
270 #issue_pat_wiki = (?:wiki-)(.+)
274 #issue_server_link_wiki = https://wiki.example.com/{id}
271 #issue_server_link_wiki = https://wiki.example.com/{id}
275 #issue_prefix_wiki = WIKI-
272 #issue_prefix_wiki = WIKI-
276
273
277 ## alternative return HTTP header for failed authentication. Default HTTP
274 ## alternative return HTTP header for failed authentication. Default HTTP
278 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
275 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
279 ## handling that. Set this variable to 403 to return HTTPForbidden
276 ## handling that. Set this variable to 403 to return HTTPForbidden
280 auth_ret_code =
277 auth_ret_code =
281
278
282 ## locking return code. When repository is locked return this HTTP code. 2XX
279 ## locking return code. When repository is locked return this HTTP code. 2XX
283 ## codes don't break the transactions while 4XX codes do
280 ## codes don't break the transactions while 4XX codes do
284 lock_ret_code = 423
281 lock_ret_code = 423
285
282
286 ## allows to change the repository location in settings page
283 ## allows to change the repository location in settings page
287 allow_repo_location_change = True
284 allow_repo_location_change = True
288
285
289 ## allows to setup custom hooks in settings page
286 ## allows to setup custom hooks in settings page
290 allow_custom_hooks_settings = True
287 allow_custom_hooks_settings = True
291
288
292 ## extra extensions for indexing, space separated and without the leading '.'.
289 ## extra extensions for indexing, space separated and without the leading '.'.
293 # index.extensions =
290 # index.extensions =
294 # gemfile
291 # gemfile
295 # lock
292 # lock
296
293
297 ## extra filenames for indexing, space separated
294 ## extra filenames for indexing, space separated
298 # index.filenames =
295 # index.filenames =
299 # .dockerignore
296 # .dockerignore
300 # .editorconfig
297 # .editorconfig
301 # INSTALL
298 # INSTALL
302 # CHANGELOG
299 # CHANGELOG
303
300
304 ####################################
301 ####################################
305 ### CELERY CONFIG ####
302 ### CELERY CONFIG ####
306 ####################################
303 ####################################
307
304
308 use_celery = false
305 use_celery = false
309 broker.host = localhost
306 broker.host = localhost
310 broker.vhost = rabbitmqhost
307 broker.vhost = rabbitmqhost
311 broker.port = 5672
308 broker.port = 5672
312 broker.user = rabbitmq
309 broker.user = rabbitmq
313 broker.password = qweqwe
310 broker.password = qweqwe
314
311
315 celery.imports = kallithea.lib.celerylib.tasks
312 celery.imports = kallithea.lib.celerylib.tasks
316
313
317 celery.result.backend = amqp
314 celery.result.backend = amqp
318 celery.result.dburi = amqp://
315 celery.result.dburi = amqp://
319 celery.result.serialier = json
316 celery.result.serialier = json
320
317
321 #celery.send.task.error.emails = true
318 #celery.send.task.error.emails = true
322 #celery.amqp.task.result.expires = 18000
319 #celery.amqp.task.result.expires = 18000
323
320
324 celeryd.concurrency = 2
321 celeryd.concurrency = 2
325 #celeryd.log.file = celeryd.log
322 #celeryd.log.file = celeryd.log
326 celeryd.log.level = DEBUG
323 celeryd.log.level = DEBUG
327 celeryd.max.tasks.per.child = 1
324 celeryd.max.tasks.per.child = 1
328
325
329 ## tasks will never be sent to the queue, but executed locally instead.
326 ## tasks will never be sent to the queue, but executed locally instead.
330 celery.always.eager = false
327 celery.always.eager = false
331
328
332 ####################################
329 ####################################
333 ### BEAKER CACHE ####
330 ### BEAKER CACHE ####
334 ####################################
331 ####################################
335
332
336 beaker.cache.data_dir = %(here)s/data/cache/data
333 beaker.cache.data_dir = %(here)s/data/cache/data
337 beaker.cache.lock_dir = %(here)s/data/cache/lock
334 beaker.cache.lock_dir = %(here)s/data/cache/lock
338
335
339 beaker.cache.regions = short_term,long_term,sql_cache_short
336 beaker.cache.regions = short_term,long_term,sql_cache_short
340
337
341 beaker.cache.short_term.type = memory
338 beaker.cache.short_term.type = memory
342 beaker.cache.short_term.expire = 60
339 beaker.cache.short_term.expire = 60
343 beaker.cache.short_term.key_length = 256
340 beaker.cache.short_term.key_length = 256
344
341
345 beaker.cache.long_term.type = memory
342 beaker.cache.long_term.type = memory
346 beaker.cache.long_term.expire = 36000
343 beaker.cache.long_term.expire = 36000
347 beaker.cache.long_term.key_length = 256
344 beaker.cache.long_term.key_length = 256
348
345
349 beaker.cache.sql_cache_short.type = memory
346 beaker.cache.sql_cache_short.type = memory
350 beaker.cache.sql_cache_short.expire = 10
347 beaker.cache.sql_cache_short.expire = 10
351 beaker.cache.sql_cache_short.key_length = 256
348 beaker.cache.sql_cache_short.key_length = 256
352
349
353 ####################################
350 ####################################
354 ### BEAKER SESSION ####
351 ### BEAKER SESSION ####
355 ####################################
352 ####################################
356
353
357 ## Name of session cookie. Should be unique for a given host and path, even when running
354 ## Name of session cookie. Should be unique for a given host and path, even when running
358 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
355 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
359 beaker.session.key = kallithea
356 beaker.session.key = kallithea
360 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
357 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
361 beaker.session.httponly = true
358 beaker.session.httponly = true
362 ## Session lifetime. 2592000 seconds is 30 days.
359 ## Session lifetime. 2592000 seconds is 30 days.
363 beaker.session.timeout = 2592000
360 beaker.session.timeout = 2592000
364
361
365 ## Server secret used with HMAC to ensure integrity of cookies.
362 ## Server secret used with HMAC to ensure integrity of cookies.
366 beaker.session.secret = ${app_instance_uuid}
363 beaker.session.secret = ${app_instance_uuid}
367 ## Further, encrypt the data with AES.
364 ## Further, encrypt the data with AES.
368 #beaker.session.encrypt_key = <key_for_encryption>
365 #beaker.session.encrypt_key = <key_for_encryption>
369 #beaker.session.validate_key = <validation_key>
366 #beaker.session.validate_key = <validation_key>
370
367
371 ## Type of storage used for the session, current types are
368 ## Type of storage used for the session, current types are
372 ## dbm, file, memcached, database, and memory.
369 ## dbm, file, memcached, database, and memory.
373
370
374 ## File system storage of session data. (default)
371 ## File system storage of session data. (default)
375 #beaker.session.type = file
372 #beaker.session.type = file
376
373
377 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
374 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
378 #beaker.session.type = cookie
375 #beaker.session.type = cookie
379
376
380 ## Database storage of session data.
377 ## Database storage of session data.
381 #beaker.session.type = ext:database
378 #beaker.session.type = ext:database
382 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
379 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
383 #beaker.session.table_name = db_session
380 #beaker.session.table_name = db_session
384
381
385 ############################
382 ############################
386 ## ERROR HANDLING SYSTEMS ##
383 ## ERROR HANDLING SYSTEMS ##
387 ############################
384 ############################
388
385
389 ####################
386 ####################
390 ### [errormator] ###
387 ### [errormator] ###
391 ####################
388 ####################
392
389
393 ## Errormator is tailored to work with Kallithea, see
390 ## Errormator is tailored to work with Kallithea, see
394 ## http://errormator.com for details how to obtain an account
391 ## http://errormator.com for details how to obtain an account
395 ## you must install python package `errormator_client` to make it work
392 ## you must install python package `errormator_client` to make it work
396
393
397 ## errormator enabled
394 ## errormator enabled
398 errormator = false
395 errormator = false
399
396
400 errormator.server_url = https://api.errormator.com
397 errormator.server_url = https://api.errormator.com
401 errormator.api_key = YOUR_API_KEY
398 errormator.api_key = YOUR_API_KEY
402
399
403 ## TWEAK AMOUNT OF INFO SENT HERE
400 ## TWEAK AMOUNT OF INFO SENT HERE
404
401
405 ## enables 404 error logging (default False)
402 ## enables 404 error logging (default False)
406 errormator.report_404 = false
403 errormator.report_404 = false
407
404
408 ## time in seconds after request is considered being slow (default 1)
405 ## time in seconds after request is considered being slow (default 1)
409 errormator.slow_request_time = 1
406 errormator.slow_request_time = 1
410
407
411 ## record slow requests in application
408 ## record slow requests in application
412 ## (needs to be enabled for slow datastore recording and time tracking)
409 ## (needs to be enabled for slow datastore recording and time tracking)
413 errormator.slow_requests = true
410 errormator.slow_requests = true
414
411
415 ## enable hooking to application loggers
412 ## enable hooking to application loggers
416 #errormator.logging = true
413 #errormator.logging = true
417
414
418 ## minimum log level for log capture
415 ## minimum log level for log capture
419 #errormator.logging.level = WARNING
416 #errormator.logging.level = WARNING
420
417
421 ## send logs only from erroneous/slow requests
418 ## send logs only from erroneous/slow requests
422 ## (saves API quota for intensive logging)
419 ## (saves API quota for intensive logging)
423 errormator.logging_on_error = false
420 errormator.logging_on_error = false
424
421
425 ## list of additonal keywords that should be grabbed from environ object
422 ## list of additonal keywords that should be grabbed from environ object
426 ## can be string with comma separated list of words in lowercase
423 ## can be string with comma separated list of words in lowercase
427 ## (by default client will always send following info:
424 ## (by default client will always send following info:
428 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
425 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
429 ## start with HTTP* this list be extended with additional keywords here
426 ## start with HTTP* this list be extended with additional keywords here
430 errormator.environ_keys_whitelist =
427 errormator.environ_keys_whitelist =
431
428
432 ## list of keywords that should be blanked from request object
429 ## list of keywords that should be blanked from request object
433 ## can be string with comma separated list of words in lowercase
430 ## can be string with comma separated list of words in lowercase
434 ## (by default client will always blank keys that contain following words
431 ## (by default client will always blank keys that contain following words
435 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
432 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
436 ## this list be extended with additional keywords set here
433 ## this list be extended with additional keywords set here
437 errormator.request_keys_blacklist =
434 errormator.request_keys_blacklist =
438
435
439 ## list of namespaces that should be ignores when gathering log entries
436 ## list of namespaces that should be ignores when gathering log entries
440 ## can be string with comma separated list of namespaces
437 ## can be string with comma separated list of namespaces
441 ## (by default the client ignores own entries: errormator_client.client)
438 ## (by default the client ignores own entries: errormator_client.client)
442 errormator.log_namespace_blacklist =
439 errormator.log_namespace_blacklist =
443
440
444 ################
441 ################
445 ### [sentry] ###
442 ### [sentry] ###
446 ################
443 ################
447
444
448 ## sentry is a alternative open source error aggregator
445 ## sentry is a alternative open source error aggregator
449 ## you must install python packages `sentry` and `raven` to enable
446 ## you must install python packages `sentry` and `raven` to enable
450
447
451 sentry.dsn = YOUR_DNS
448 sentry.dsn = YOUR_DNS
452 sentry.servers =
449 sentry.servers =
453 sentry.name =
450 sentry.name =
454 sentry.key =
451 sentry.key =
455 sentry.public_key =
452 sentry.public_key =
456 sentry.secret_key =
453 sentry.secret_key =
457 sentry.project =
454 sentry.project =
458 sentry.site =
455 sentry.site =
459 sentry.include_paths =
456 sentry.include_paths =
460 sentry.exclude_paths =
457 sentry.exclude_paths =
461
458
462 ################################################################################
459 ################################################################################
463 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
460 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
464 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
461 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
465 ## execute malicious code after an exception is raised. ##
462 ## execute malicious code after an exception is raised. ##
466 ################################################################################
463 ################################################################################
467 set debug = false
464 set debug = false
468
465
469 ##################################
466 ##################################
470 ### LOGVIEW CONFIG ###
467 ### LOGVIEW CONFIG ###
471 ##################################
468 ##################################
472
469
473 logview.sqlalchemy = #faa
470 logview.sqlalchemy = #faa
474 logview.pylons.templating = #bfb
471 logview.pylons.templating = #bfb
475 logview.pylons.util = #eee
472 logview.pylons.util = #eee
476
473
477 #########################################################
474 #########################################################
478 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
475 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
479 #########################################################
476 #########################################################
480
477
481 # SQLITE [default]
478 # SQLITE [default]
482 sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
479 sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
483
480
484 # POSTGRESQL
481 # POSTGRESQL
485 #sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
482 #sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
486
483
487 # MySQL
484 # MySQL
488 #sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
485 #sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
489
486
490 # see sqlalchemy docs for others
487 # see sqlalchemy docs for others
491
488
492 sqlalchemy.db1.echo = false
489 sqlalchemy.db1.echo = false
493 sqlalchemy.db1.pool_recycle = 3600
490 sqlalchemy.db1.pool_recycle = 3600
494
491
495 ################################
492 ################################
496 ### LOGGING CONFIGURATION ####
493 ### LOGGING CONFIGURATION ####
497 ################################
494 ################################
498
495
499 [loggers]
496 [loggers]
500 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
497 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
501
498
502 [handlers]
499 [handlers]
503 keys = console, console_sql
500 keys = console, console_sql
504
501
505 [formatters]
502 [formatters]
506 keys = generic, color_formatter, color_formatter_sql
503 keys = generic, color_formatter, color_formatter_sql
507
504
508 #############
505 #############
509 ## LOGGERS ##
506 ## LOGGERS ##
510 #############
507 #############
511
508
512 [logger_root]
509 [logger_root]
513 level = NOTSET
510 level = NOTSET
514 handlers = console
511 handlers = console
515
512
516 [logger_routes]
513 [logger_routes]
517 level = DEBUG
514 level = DEBUG
518 handlers =
515 handlers =
519 qualname = routes.middleware
516 qualname = routes.middleware
520 ## "level = DEBUG" logs the route matched and routing variables.
517 ## "level = DEBUG" logs the route matched and routing variables.
521 propagate = 1
518 propagate = 1
522
519
523 [logger_beaker]
520 [logger_beaker]
524 level = DEBUG
521 level = DEBUG
525 handlers =
522 handlers =
526 qualname = beaker.container
523 qualname = beaker.container
527 propagate = 1
524 propagate = 1
528
525
529 [logger_templates]
526 [logger_templates]
530 level = INFO
527 level = INFO
531 handlers =
528 handlers =
532 qualname = pylons.templating
529 qualname = pylons.templating
533 propagate = 1
530 propagate = 1
534
531
535 [logger_kallithea]
532 [logger_kallithea]
536 level = DEBUG
533 level = DEBUG
537 handlers =
534 handlers =
538 qualname = kallithea
535 qualname = kallithea
539 propagate = 1
536 propagate = 1
540
537
541 [logger_sqlalchemy]
538 [logger_sqlalchemy]
542 level = INFO
539 level = INFO
543 handlers = console_sql
540 handlers = console_sql
544 qualname = sqlalchemy.engine
541 qualname = sqlalchemy.engine
545 propagate = 0
542 propagate = 0
546
543
547 [logger_whoosh_indexer]
544 [logger_whoosh_indexer]
548 level = DEBUG
545 level = DEBUG
549 handlers =
546 handlers =
550 qualname = whoosh_indexer
547 qualname = whoosh_indexer
551 propagate = 1
548 propagate = 1
552
549
553 ##############
550 ##############
554 ## HANDLERS ##
551 ## HANDLERS ##
555 ##############
552 ##############
556
553
557 [handler_console]
554 [handler_console]
558 class = StreamHandler
555 class = StreamHandler
559 args = (sys.stderr,)
556 args = (sys.stderr,)
560 level = INFO
557 level = INFO
561 formatter = generic
558 formatter = generic
562
559
563 [handler_console_sql]
560 [handler_console_sql]
564 class = StreamHandler
561 class = StreamHandler
565 args = (sys.stderr,)
562 args = (sys.stderr,)
566 level = WARN
563 level = WARN
567 formatter = generic
564 formatter = generic
568
565
569 ################
566 ################
570 ## FORMATTERS ##
567 ## FORMATTERS ##
571 ################
568 ################
572
569
573 [formatter_generic]
570 [formatter_generic]
574 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
571 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
575 datefmt = %Y-%m-%d %H:%M:%S
572 datefmt = %Y-%m-%d %H:%M:%S
576
573
577 [formatter_color_formatter]
574 [formatter_color_formatter]
578 class = kallithea.lib.colored_formatter.ColorFormatter
575 class = kallithea.lib.colored_formatter.ColorFormatter
579 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
576 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
580 datefmt = %Y-%m-%d %H:%M:%S
577 datefmt = %Y-%m-%d %H:%M:%S
581
578
582 [formatter_color_formatter_sql]
579 [formatter_color_formatter_sql]
583 class = kallithea.lib.colored_formatter.ColorFormatterSql
580 class = kallithea.lib.colored_formatter.ColorFormatterSql
584 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
581 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
585 datefmt = %Y-%m-%d %H:%M:%S
582 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,2570 +1,2566 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
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 General Public License
12 # You should have received a copy of the GNU 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 kallithea.model.db
15 kallithea.model.db
16 ~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~
17
17
18 Database Models for Kallithea
18 Database Models for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 08, 2010
22 :created_on: Apr 08, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import os
28 import os
29 import time
29 import time
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import traceback
32 import traceback
33 import hashlib
33 import hashlib
34 import collections
34 import collections
35 import functools
35 import functools
36
36
37 import sqlalchemy
37 import sqlalchemy
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.hybrid import hybrid_property
39 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
40 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
41 from beaker.cache import cache_region, region_invalidate
41 from beaker.cache import cache_region, region_invalidate
42 from webob.exc import HTTPNotFound
42 from webob.exc import HTTPNotFound
43
43
44 from pylons.i18n.translation import lazy_ugettext as _
44 from pylons.i18n.translation import lazy_ugettext as _
45
45
46 from kallithea import DB_PREFIX
46 from kallithea import DB_PREFIX
47 from kallithea.lib.exceptions import DefaultUserException
47 from kallithea.lib.exceptions import DefaultUserException
48 from kallithea.lib.vcs import get_backend
48 from kallithea.lib.vcs import get_backend
49 from kallithea.lib.vcs.utils.helpers import get_scm
49 from kallithea.lib.vcs.utils.helpers import get_scm
50 from kallithea.lib.vcs.utils.lazy import LazyProperty
50 from kallithea.lib.vcs.utils.lazy import LazyProperty
51 from kallithea.lib.vcs.backends.base import EmptyChangeset
51 from kallithea.lib.vcs.backends.base import EmptyChangeset
52
52
53 from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
53 from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
54 safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
54 safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
55 get_clone_url, urlreadable
55 get_clone_url, urlreadable
56 from kallithea.lib.compat import json
56 from kallithea.lib.compat import json
57 from kallithea.lib.caching_query import FromCache
57 from kallithea.lib.caching_query import FromCache
58
58
59 from kallithea.model.meta import Base, Session
59 from kallithea.model.meta import Base, Session
60
60
61 URL_SEP = '/'
61 URL_SEP = '/'
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64 #==============================================================================
64 #==============================================================================
65 # BASE CLASSES
65 # BASE CLASSES
66 #==============================================================================
66 #==============================================================================
67
67
68 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
68 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
69
69
70
70
71 class BaseModel(object):
71 class BaseModel(object):
72 """
72 """
73 Base Model for all classes
73 Base Model for all classes
74 """
74 """
75
75
76 @classmethod
76 @classmethod
77 def _get_keys(cls):
77 def _get_keys(cls):
78 """return column names for this model """
78 """return column names for this model """
79 return class_mapper(cls).c.keys()
79 return class_mapper(cls).c.keys()
80
80
81 def get_dict(self):
81 def get_dict(self):
82 """
82 """
83 return dict with keys and values corresponding
83 return dict with keys and values corresponding
84 to this model data """
84 to this model data """
85
85
86 d = {}
86 d = {}
87 for k in self._get_keys():
87 for k in self._get_keys():
88 d[k] = getattr(self, k)
88 d[k] = getattr(self, k)
89
89
90 # also use __json__() if present to get additional fields
90 # also use __json__() if present to get additional fields
91 _json_attr = getattr(self, '__json__', None)
91 _json_attr = getattr(self, '__json__', None)
92 if _json_attr:
92 if _json_attr:
93 # update with attributes from __json__
93 # update with attributes from __json__
94 if callable(_json_attr):
94 if callable(_json_attr):
95 _json_attr = _json_attr()
95 _json_attr = _json_attr()
96 for k, val in _json_attr.iteritems():
96 for k, val in _json_attr.iteritems():
97 d[k] = val
97 d[k] = val
98 return d
98 return d
99
99
100 def get_appstruct(self):
100 def get_appstruct(self):
101 """return list with keys and values tuples corresponding
101 """return list with keys and values tuples corresponding
102 to this model data """
102 to this model data """
103
103
104 l = []
104 l = []
105 for k in self._get_keys():
105 for k in self._get_keys():
106 l.append((k, getattr(self, k),))
106 l.append((k, getattr(self, k),))
107 return l
107 return l
108
108
109 def populate_obj(self, populate_dict):
109 def populate_obj(self, populate_dict):
110 """populate model with data from given populate_dict"""
110 """populate model with data from given populate_dict"""
111
111
112 for k in self._get_keys():
112 for k in self._get_keys():
113 if k in populate_dict:
113 if k in populate_dict:
114 setattr(self, k, populate_dict[k])
114 setattr(self, k, populate_dict[k])
115
115
116 @classmethod
116 @classmethod
117 def query(cls):
117 def query(cls):
118 return Session().query(cls)
118 return Session().query(cls)
119
119
120 @classmethod
120 @classmethod
121 def get(cls, id_):
121 def get(cls, id_):
122 if id_:
122 if id_:
123 return cls.query().get(id_)
123 return cls.query().get(id_)
124
124
125 @classmethod
125 @classmethod
126 def get_or_404(cls, id_):
126 def get_or_404(cls, id_):
127 try:
127 try:
128 id_ = int(id_)
128 id_ = int(id_)
129 except (TypeError, ValueError):
129 except (TypeError, ValueError):
130 raise HTTPNotFound
130 raise HTTPNotFound
131
131
132 res = cls.query().get(id_)
132 res = cls.query().get(id_)
133 if res is None:
133 if res is None:
134 raise HTTPNotFound
134 raise HTTPNotFound
135 return res
135 return res
136
136
137 @classmethod
137 @classmethod
138 def getAll(cls):
138 def getAll(cls):
139 # deprecated and left for backward compatibility
139 # deprecated and left for backward compatibility
140 return cls.get_all()
140 return cls.get_all()
141
141
142 @classmethod
142 @classmethod
143 def get_all(cls):
143 def get_all(cls):
144 return cls.query().all()
144 return cls.query().all()
145
145
146 @classmethod
146 @classmethod
147 def delete(cls, id_):
147 def delete(cls, id_):
148 obj = cls.query().get(id_)
148 obj = cls.query().get(id_)
149 Session().delete(obj)
149 Session().delete(obj)
150
150
151 def __repr__(self):
151 def __repr__(self):
152 if hasattr(self, '__unicode__'):
152 if hasattr(self, '__unicode__'):
153 # python repr needs to return str
153 # python repr needs to return str
154 try:
154 try:
155 return safe_str(self.__unicode__())
155 return safe_str(self.__unicode__())
156 except UnicodeDecodeError:
156 except UnicodeDecodeError:
157 pass
157 pass
158 return '<DB:%s>' % (self.__class__.__name__)
158 return '<DB:%s>' % (self.__class__.__name__)
159
159
160
160
161 _table_args_default_dict = {'extend_existing': True,
161 _table_args_default_dict = {'extend_existing': True,
162 'mysql_engine': 'InnoDB',
162 'mysql_engine': 'InnoDB',
163 'mysql_charset': 'utf8',
163 'mysql_charset': 'utf8',
164 'sqlite_autoincrement': True,
164 'sqlite_autoincrement': True,
165 }
165 }
166
166
167 class Setting(Base, BaseModel):
167 class Setting(Base, BaseModel):
168 __tablename__ = DB_PREFIX + 'settings'
168 __tablename__ = DB_PREFIX + 'settings'
169 __table_args__ = (
169 __table_args__ = (
170 _table_args_default_dict,
170 _table_args_default_dict,
171 )
171 )
172
172
173 SETTINGS_TYPES = {
173 SETTINGS_TYPES = {
174 'str': safe_str,
174 'str': safe_str,
175 'int': safe_int,
175 'int': safe_int,
176 'unicode': safe_unicode,
176 'unicode': safe_unicode,
177 'bool': str2bool,
177 'bool': str2bool,
178 'list': functools.partial(aslist, sep=',')
178 'list': functools.partial(aslist, sep=',')
179 }
179 }
180 DEFAULT_UPDATE_URL = ''
180 DEFAULT_UPDATE_URL = ''
181
181
182 app_settings_id = Column(Integer(), unique=True, primary_key=True)
182 app_settings_id = Column(Integer(), unique=True, primary_key=True)
183 app_settings_name = Column(String(255), nullable=False, unique=True)
183 app_settings_name = Column(String(255), nullable=False, unique=True)
184 _app_settings_value = Column("app_settings_value", Unicode(4096), nullable=False)
184 _app_settings_value = Column("app_settings_value", Unicode(4096), nullable=False)
185 _app_settings_type = Column("app_settings_type", String(255), nullable=True) # FIXME: not nullable?
185 _app_settings_type = Column("app_settings_type", String(255), nullable=True) # FIXME: not nullable?
186
186
187 def __init__(self, key='', val='', type='unicode'):
187 def __init__(self, key='', val='', type='unicode'):
188 self.app_settings_name = key
188 self.app_settings_name = key
189 self.app_settings_value = val
189 self.app_settings_value = val
190 self.app_settings_type = type
190 self.app_settings_type = type
191
191
192 @validates('_app_settings_value')
192 @validates('_app_settings_value')
193 def validate_settings_value(self, key, val):
193 def validate_settings_value(self, key, val):
194 assert type(val) == unicode
194 assert type(val) == unicode
195 return val
195 return val
196
196
197 @hybrid_property
197 @hybrid_property
198 def app_settings_value(self):
198 def app_settings_value(self):
199 v = self._app_settings_value
199 v = self._app_settings_value
200 _type = self.app_settings_type
200 _type = self.app_settings_type
201 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
201 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
202 return converter(v)
202 return converter(v)
203
203
204 @app_settings_value.setter
204 @app_settings_value.setter
205 def app_settings_value(self, val):
205 def app_settings_value(self, val):
206 """
206 """
207 Setter that will always make sure we use unicode in app_settings_value
207 Setter that will always make sure we use unicode in app_settings_value
208
208
209 :param val:
209 :param val:
210 """
210 """
211 self._app_settings_value = safe_unicode(val)
211 self._app_settings_value = safe_unicode(val)
212
212
213 @hybrid_property
213 @hybrid_property
214 def app_settings_type(self):
214 def app_settings_type(self):
215 return self._app_settings_type
215 return self._app_settings_type
216
216
217 @app_settings_type.setter
217 @app_settings_type.setter
218 def app_settings_type(self, val):
218 def app_settings_type(self, val):
219 if val not in self.SETTINGS_TYPES:
219 if val not in self.SETTINGS_TYPES:
220 raise Exception('type must be one of %s got %s'
220 raise Exception('type must be one of %s got %s'
221 % (self.SETTINGS_TYPES.keys(), val))
221 % (self.SETTINGS_TYPES.keys(), val))
222 self._app_settings_type = val
222 self._app_settings_type = val
223
223
224 def __unicode__(self):
224 def __unicode__(self):
225 return u"<%s('%s:%s[%s]')>" % (
225 return u"<%s('%s:%s[%s]')>" % (
226 self.__class__.__name__,
226 self.__class__.__name__,
227 self.app_settings_name, self.app_settings_value, self.app_settings_type
227 self.app_settings_name, self.app_settings_value, self.app_settings_type
228 )
228 )
229
229
230 @classmethod
230 @classmethod
231 def get_by_name(cls, key):
231 def get_by_name(cls, key):
232 return cls.query() \
232 return cls.query() \
233 .filter(cls.app_settings_name == key).scalar()
233 .filter(cls.app_settings_name == key).scalar()
234
234
235 @classmethod
235 @classmethod
236 def get_by_name_or_create(cls, key, val='', type='unicode'):
236 def get_by_name_or_create(cls, key, val='', type='unicode'):
237 res = cls.get_by_name(key)
237 res = cls.get_by_name(key)
238 if res is None:
238 if res is None:
239 res = cls(key, val, type)
239 res = cls(key, val, type)
240 return res
240 return res
241
241
242 @classmethod
242 @classmethod
243 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
243 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
244 """
244 """
245 Creates or updates Kallithea setting. If updates are triggered, it will only
245 Creates or updates Kallithea setting. If updates are triggered, it will only
246 update parameters that are explicitly set. Optional instance will be skipped.
246 update parameters that are explicitly set. Optional instance will be skipped.
247
247
248 :param key:
248 :param key:
249 :param val:
249 :param val:
250 :param type:
250 :param type:
251 :return:
251 :return:
252 """
252 """
253 res = cls.get_by_name(key)
253 res = cls.get_by_name(key)
254 if res is None:
254 if res is None:
255 val = Optional.extract(val)
255 val = Optional.extract(val)
256 type = Optional.extract(type)
256 type = Optional.extract(type)
257 res = cls(key, val, type)
257 res = cls(key, val, type)
258 else:
258 else:
259 res.app_settings_name = key
259 res.app_settings_name = key
260 if not isinstance(val, Optional):
260 if not isinstance(val, Optional):
261 # update if set
261 # update if set
262 res.app_settings_value = val
262 res.app_settings_value = val
263 if not isinstance(type, Optional):
263 if not isinstance(type, Optional):
264 # update if set
264 # update if set
265 res.app_settings_type = type
265 res.app_settings_type = type
266 return res
266 return res
267
267
268 @classmethod
268 @classmethod
269 def get_app_settings(cls, cache=False):
269 def get_app_settings(cls, cache=False):
270
270
271 ret = cls.query()
271 ret = cls.query()
272
272
273 if cache:
273 if cache:
274 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
274 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
275
275
276 if ret is None:
276 if ret is None:
277 raise Exception('Could not get application settings !')
277 raise Exception('Could not get application settings !')
278 settings = {}
278 settings = {}
279 for each in ret:
279 for each in ret:
280 settings[each.app_settings_name] = \
280 settings[each.app_settings_name] = \
281 each.app_settings_value
281 each.app_settings_value
282
282
283 return settings
283 return settings
284
284
285 @classmethod
285 @classmethod
286 def get_auth_plugins(cls, cache=False):
286 def get_auth_plugins(cls, cache=False):
287 auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
287 auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
288 return auth_plugins
288 return auth_plugins
289
289
290 @classmethod
290 @classmethod
291 def get_auth_settings(cls, cache=False):
291 def get_auth_settings(cls, cache=False):
292 ret = cls.query() \
292 ret = cls.query() \
293 .filter(cls.app_settings_name.startswith('auth_')).all()
293 .filter(cls.app_settings_name.startswith('auth_')).all()
294 fd = {}
294 fd = {}
295 for row in ret:
295 for row in ret:
296 fd[row.app_settings_name] = row.app_settings_value
296 fd[row.app_settings_name] = row.app_settings_value
297 return fd
297 return fd
298
298
299 @classmethod
299 @classmethod
300 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
300 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
301 ret = cls.query() \
301 ret = cls.query() \
302 .filter(cls.app_settings_name.startswith('default_')).all()
302 .filter(cls.app_settings_name.startswith('default_')).all()
303 fd = {}
303 fd = {}
304 for row in ret:
304 for row in ret:
305 key = row.app_settings_name
305 key = row.app_settings_name
306 if strip_prefix:
306 if strip_prefix:
307 key = remove_prefix(key, prefix='default_')
307 key = remove_prefix(key, prefix='default_')
308 fd.update({key: row.app_settings_value})
308 fd.update({key: row.app_settings_value})
309
309
310 return fd
310 return fd
311
311
312 @classmethod
312 @classmethod
313 def get_server_info(cls):
313 def get_server_info(cls):
314 import pkg_resources
314 import pkg_resources
315 import platform
315 import platform
316 import kallithea
316 import kallithea
317 from kallithea.lib.utils import check_git_version
317 from kallithea.lib.utils import check_git_version
318 mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
318 mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
319 info = {
319 info = {
320 'modules': sorted(mods, key=lambda k: k[0].lower()),
320 'modules': sorted(mods, key=lambda k: k[0].lower()),
321 'py_version': platform.python_version(),
321 'py_version': platform.python_version(),
322 'platform': safe_unicode(platform.platform()),
322 'platform': safe_unicode(platform.platform()),
323 'kallithea_version': kallithea.__version__,
323 'kallithea_version': kallithea.__version__,
324 'git_version': safe_unicode(check_git_version()),
324 'git_version': safe_unicode(check_git_version()),
325 'git_path': kallithea.CONFIG.get('git_path')
325 'git_path': kallithea.CONFIG.get('git_path')
326 }
326 }
327 return info
327 return info
328
328
329
329
330 class Ui(Base, BaseModel):
330 class Ui(Base, BaseModel):
331 __tablename__ = DB_PREFIX + 'ui'
331 __tablename__ = DB_PREFIX + 'ui'
332 __table_args__ = (
332 __table_args__ = (
333 # FIXME: ui_key as key is wrong and should be removed when the corresponding
333 # FIXME: ui_key as key is wrong and should be removed when the corresponding
334 # Ui.get_by_key has been replaced by the composite key
334 # Ui.get_by_key has been replaced by the composite key
335 UniqueConstraint('ui_key'),
335 UniqueConstraint('ui_key'),
336 UniqueConstraint('ui_section', 'ui_key'),
336 UniqueConstraint('ui_section', 'ui_key'),
337 _table_args_default_dict,
337 _table_args_default_dict,
338 )
338 )
339
339
340 HOOK_UPDATE = 'changegroup.update'
340 HOOK_UPDATE = 'changegroup.update'
341 HOOK_REPO_SIZE = 'changegroup.repo_size'
341 HOOK_REPO_SIZE = 'changegroup.repo_size'
342 HOOK_PUSH = 'changegroup.push_logger'
342 HOOK_PUSH = 'changegroup.push_logger'
343 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
343 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
344 HOOK_PULL = 'outgoing.pull_logger'
344 HOOK_PULL = 'outgoing.pull_logger'
345 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
345 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
346
346
347 ui_id = Column(Integer(), unique=True, primary_key=True)
347 ui_id = Column(Integer(), unique=True, primary_key=True)
348 ui_section = Column(String(255), nullable=False)
348 ui_section = Column(String(255), nullable=False)
349 ui_key = Column(String(255), nullable=False)
349 ui_key = Column(String(255), nullable=False)
350 ui_value = Column(String(255), nullable=True) # FIXME: not nullable?
350 ui_value = Column(String(255), nullable=True) # FIXME: not nullable?
351 ui_active = Column(Boolean(), nullable=False, default=True)
351 ui_active = Column(Boolean(), nullable=False, default=True)
352
352
353 @classmethod
353 @classmethod
354 def get_by_key(cls, section, key):
354 def get_by_key(cls, section, key):
355 """ Return specified Ui object, or None if not found. """
355 """ Return specified Ui object, or None if not found. """
356 return cls.query().filter_by(ui_section=section, ui_key=key).scalar()
356 return cls.query().filter_by(ui_section=section, ui_key=key).scalar()
357
357
358 @classmethod
358 @classmethod
359 def get_or_create(cls, section, key):
359 def get_or_create(cls, section, key):
360 """ Return specified Ui object, creating it if necessary. """
360 """ Return specified Ui object, creating it if necessary. """
361 setting = cls.get_by_key(section, key)
361 setting = cls.get_by_key(section, key)
362 if setting is None:
362 if setting is None:
363 setting = cls(ui_section=section, ui_key=key)
363 setting = cls(ui_section=section, ui_key=key)
364 Session().add(setting)
364 Session().add(setting)
365 return setting
365 return setting
366
366
367 @classmethod
367 @classmethod
368 def get_builtin_hooks(cls):
368 def get_builtin_hooks(cls):
369 q = cls.query()
369 q = cls.query()
370 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
370 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
371 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
371 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
372 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
372 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
373 q = q.filter(cls.ui_section == 'hooks')
373 q = q.filter(cls.ui_section == 'hooks')
374 return q.all()
374 return q.all()
375
375
376 @classmethod
376 @classmethod
377 def get_custom_hooks(cls):
377 def get_custom_hooks(cls):
378 q = cls.query()
378 q = cls.query()
379 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
379 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
380 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
380 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
381 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
381 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
382 q = q.filter(cls.ui_section == 'hooks')
382 q = q.filter(cls.ui_section == 'hooks')
383 return q.all()
383 return q.all()
384
384
385 @classmethod
385 @classmethod
386 def get_repos_location(cls):
386 def get_repos_location(cls):
387 return cls.get_by_key('paths', '/').ui_value
387 return cls.get_by_key('paths', '/').ui_value
388
388
389 @classmethod
389 @classmethod
390 def create_or_update_hook(cls, key, val):
390 def create_or_update_hook(cls, key, val):
391 new_ui = cls.get_or_create('hooks', key)
391 new_ui = cls.get_or_create('hooks', key)
392 new_ui.ui_active = True
392 new_ui.ui_active = True
393 new_ui.ui_value = val
393 new_ui.ui_value = val
394
394
395 def __repr__(self):
395 def __repr__(self):
396 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
396 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
397 self.ui_key, self.ui_value)
397 self.ui_key, self.ui_value)
398
398
399
399
400 class User(Base, BaseModel):
400 class User(Base, BaseModel):
401 __tablename__ = 'users'
401 __tablename__ = 'users'
402 __table_args__ = (
402 __table_args__ = (
403 Index('u_username_idx', 'username'),
403 Index('u_username_idx', 'username'),
404 Index('u_email_idx', 'email'),
404 Index('u_email_idx', 'email'),
405 _table_args_default_dict,
405 _table_args_default_dict,
406 )
406 )
407
407
408 DEFAULT_USER = 'default'
408 DEFAULT_USER = 'default'
409 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
409 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
410
410
411 user_id = Column(Integer(), unique=True, primary_key=True)
411 user_id = Column(Integer(), unique=True, primary_key=True)
412 username = Column(String(255), nullable=False, unique=True)
412 username = Column(String(255), nullable=False, unique=True)
413 password = Column(String(255), nullable=False)
413 password = Column(String(255), nullable=False)
414 active = Column(Boolean(), nullable=False, default=True)
414 active = Column(Boolean(), nullable=False, default=True)
415 admin = Column(Boolean(), nullable=False, default=False)
415 admin = Column(Boolean(), nullable=False, default=False)
416 name = Column("firstname", Unicode(255), nullable=False)
416 name = Column("firstname", Unicode(255), nullable=False)
417 lastname = Column(Unicode(255), nullable=False)
417 lastname = Column(Unicode(255), nullable=False)
418 _email = Column("email", String(255), nullable=True, unique=True) # FIXME: not nullable?
418 _email = Column("email", String(255), nullable=True, unique=True) # FIXME: not nullable?
419 last_login = Column(DateTime(timezone=False), nullable=True)
419 last_login = Column(DateTime(timezone=False), nullable=True)
420 extern_type = Column(String(255), nullable=True) # FIXME: not nullable?
420 extern_type = Column(String(255), nullable=True) # FIXME: not nullable?
421 extern_name = Column(String(255), nullable=True) # FIXME: not nullable?
421 extern_name = Column(String(255), nullable=True) # FIXME: not nullable?
422 api_key = Column(String(255), nullable=False)
422 api_key = Column(String(255), nullable=False)
423 inherit_default_permissions = Column(Boolean(), nullable=False, default=True)
423 inherit_default_permissions = Column(Boolean(), nullable=False, default=True)
424 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
424 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
425 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
425 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
426
426
427 user_log = relationship('UserLog')
427 user_log = relationship('UserLog')
428 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
428 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
429
429
430 repositories = relationship('Repository')
430 repositories = relationship('Repository')
431 repo_groups = relationship('RepoGroup')
431 repo_groups = relationship('RepoGroup')
432 user_groups = relationship('UserGroup')
432 user_groups = relationship('UserGroup')
433 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
433 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
434 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
434 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
435
435
436 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
436 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
437 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
437 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
438
438
439 group_member = relationship('UserGroupMember', cascade='all')
439 group_member = relationship('UserGroupMember', cascade='all')
440
440
441 notifications = relationship('UserNotification', cascade='all')
441 notifications = relationship('UserNotification', cascade='all')
442 # notifications assigned to this user
442 # notifications assigned to this user
443 user_created_notifications = relationship('Notification', cascade='all')
443 user_created_notifications = relationship('Notification', cascade='all')
444 # comments created by this user
444 # comments created by this user
445 user_comments = relationship('ChangesetComment', cascade='all')
445 user_comments = relationship('ChangesetComment', cascade='all')
446 #extra emails for this user
446 #extra emails for this user
447 user_emails = relationship('UserEmailMap', cascade='all')
447 user_emails = relationship('UserEmailMap', cascade='all')
448 #extra API keys
448 #extra API keys
449 user_api_keys = relationship('UserApiKeys', cascade='all')
449 user_api_keys = relationship('UserApiKeys', cascade='all')
450
450
451
451
452 @hybrid_property
452 @hybrid_property
453 def email(self):
453 def email(self):
454 return self._email
454 return self._email
455
455
456 @email.setter
456 @email.setter
457 def email(self, val):
457 def email(self, val):
458 self._email = val.lower() if val else None
458 self._email = val.lower() if val else None
459
459
460 @property
460 @property
461 def firstname(self):
461 def firstname(self):
462 # alias for future
462 # alias for future
463 return self.name
463 return self.name
464
464
465 @property
465 @property
466 def emails(self):
466 def emails(self):
467 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
467 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
468 return [self.email] + [x.email for x in other]
468 return [self.email] + [x.email for x in other]
469
469
470 @property
470 @property
471 def api_keys(self):
471 def api_keys(self):
472 other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
472 other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
473 return [self.api_key] + [x.api_key for x in other]
473 return [self.api_key] + [x.api_key for x in other]
474
474
475 @property
475 @property
476 def ip_addresses(self):
476 def ip_addresses(self):
477 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
477 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
478 return [x.ip_addr for x in ret]
478 return [x.ip_addr for x in ret]
479
479
480 @property
480 @property
481 def full_name(self):
481 def full_name(self):
482 return '%s %s' % (self.firstname, self.lastname)
482 return '%s %s' % (self.firstname, self.lastname)
483
483
484 @property
484 @property
485 def full_name_or_username(self):
485 def full_name_or_username(self):
486 """
486 """
487 Show full name.
487 Show full name.
488 If full name is not set, fall back to username.
488 If full name is not set, fall back to username.
489 """
489 """
490 return ('%s %s' % (self.firstname, self.lastname)
490 return ('%s %s' % (self.firstname, self.lastname)
491 if (self.firstname and self.lastname) else self.username)
491 if (self.firstname and self.lastname) else self.username)
492
492
493 @property
493 @property
494 def full_name_and_username(self):
494 def full_name_and_username(self):
495 """
495 """
496 Show full name and username as 'Firstname Lastname (username)'.
496 Show full name and username as 'Firstname Lastname (username)'.
497 If full name is not set, fall back to username.
497 If full name is not set, fall back to username.
498 """
498 """
499 return ('%s %s (%s)' % (self.firstname, self.lastname, self.username)
499 return ('%s %s (%s)' % (self.firstname, self.lastname, self.username)
500 if (self.firstname and self.lastname) else self.username)
500 if (self.firstname and self.lastname) else self.username)
501
501
502 @property
502 @property
503 def full_contact(self):
503 def full_contact(self):
504 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
504 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
505
505
506 @property
506 @property
507 def short_contact(self):
507 def short_contact(self):
508 return '%s %s' % (self.firstname, self.lastname)
508 return '%s %s' % (self.firstname, self.lastname)
509
509
510 @property
510 @property
511 def is_admin(self):
511 def is_admin(self):
512 return self.admin
512 return self.admin
513
513
514 @property
514 @property
515 def AuthUser(self):
515 def AuthUser(self):
516 """
516 """
517 Returns instance of AuthUser for this user
517 Returns instance of AuthUser for this user
518 """
518 """
519 from kallithea.lib.auth import AuthUser
519 from kallithea.lib.auth import AuthUser
520 return AuthUser(dbuser=self)
520 return AuthUser(dbuser=self)
521
521
522 @hybrid_property
522 @hybrid_property
523 def user_data(self):
523 def user_data(self):
524 if not self._user_data:
524 if not self._user_data:
525 return {}
525 return {}
526
526
527 try:
527 try:
528 return json.loads(self._user_data)
528 return json.loads(self._user_data)
529 except TypeError:
529 except TypeError:
530 return {}
530 return {}
531
531
532 @user_data.setter
532 @user_data.setter
533 def user_data(self, val):
533 def user_data(self, val):
534 try:
534 try:
535 self._user_data = json.dumps(val)
535 self._user_data = json.dumps(val)
536 except Exception:
536 except Exception:
537 log.error(traceback.format_exc())
537 log.error(traceback.format_exc())
538
538
539 def __unicode__(self):
539 def __unicode__(self):
540 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
540 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
541 self.user_id, self.username)
541 self.user_id, self.username)
542
542
543 @classmethod
543 @classmethod
544 def get_or_404(cls, id_, allow_default=True):
544 def get_or_404(cls, id_, allow_default=True):
545 '''
545 '''
546 Overridden version of BaseModel.get_or_404, with an extra check on
546 Overridden version of BaseModel.get_or_404, with an extra check on
547 the default user.
547 the default user.
548 '''
548 '''
549 user = super(User, cls).get_or_404(id_)
549 user = super(User, cls).get_or_404(id_)
550 if allow_default == False:
550 if allow_default == False:
551 if user.username == User.DEFAULT_USER:
551 if user.username == User.DEFAULT_USER:
552 raise DefaultUserException
552 raise DefaultUserException
553 return user
553 return user
554
554
555 @classmethod
555 @classmethod
556 def get_by_username_or_email(cls, username_or_email, case_insensitive=False, cache=False):
556 def get_by_username_or_email(cls, username_or_email, case_insensitive=False, cache=False):
557 """
557 """
558 For anything that looks like an email address, look up by the email address (matching
558 For anything that looks like an email address, look up by the email address (matching
559 case insensitively).
559 case insensitively).
560 For anything else, try to look up by the user name.
560 For anything else, try to look up by the user name.
561
561
562 This assumes no normal username can have '@' symbol.
562 This assumes no normal username can have '@' symbol.
563 """
563 """
564 if '@' in username_or_email:
564 if '@' in username_or_email:
565 return User.get_by_email(username_or_email, cache=cache)
565 return User.get_by_email(username_or_email, cache=cache)
566 else:
566 else:
567 return User.get_by_username(username_or_email, case_insensitive=case_insensitive, cache=cache)
567 return User.get_by_username(username_or_email, case_insensitive=case_insensitive, cache=cache)
568
568
569 @classmethod
569 @classmethod
570 def get_by_username(cls, username, case_insensitive=False, cache=False):
570 def get_by_username(cls, username, case_insensitive=False, cache=False):
571 if case_insensitive:
571 if case_insensitive:
572 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
572 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
573 else:
573 else:
574 q = cls.query().filter(cls.username == username)
574 q = cls.query().filter(cls.username == username)
575
575
576 if cache:
576 if cache:
577 q = q.options(FromCache(
577 q = q.options(FromCache(
578 "sql_cache_short",
578 "sql_cache_short",
579 "get_user_%s" % _hash_key(username)
579 "get_user_%s" % _hash_key(username)
580 )
580 )
581 )
581 )
582 return q.scalar()
582 return q.scalar()
583
583
584 @classmethod
584 @classmethod
585 def get_by_api_key(cls, api_key, cache=False, fallback=True):
585 def get_by_api_key(cls, api_key, cache=False, fallback=True):
586 if len(api_key) != 40 or not api_key.isalnum():
586 if len(api_key) != 40 or not api_key.isalnum():
587 return None
587 return None
588
588
589 q = cls.query().filter(cls.api_key == api_key)
589 q = cls.query().filter(cls.api_key == api_key)
590
590
591 if cache:
591 if cache:
592 q = q.options(FromCache("sql_cache_short",
592 q = q.options(FromCache("sql_cache_short",
593 "get_api_key_%s" % api_key))
593 "get_api_key_%s" % api_key))
594 res = q.scalar()
594 res = q.scalar()
595
595
596 if fallback and not res:
596 if fallback and not res:
597 #fallback to additional keys
597 #fallback to additional keys
598 _res = UserApiKeys.query() \
598 _res = UserApiKeys.query() \
599 .filter(UserApiKeys.api_key == api_key) \
599 .filter(UserApiKeys.api_key == api_key) \
600 .filter(or_(UserApiKeys.expires == -1,
600 .filter(or_(UserApiKeys.expires == -1,
601 UserApiKeys.expires >= time.time())) \
601 UserApiKeys.expires >= time.time())) \
602 .first()
602 .first()
603 if _res:
603 if _res:
604 res = _res.user
604 res = _res.user
605 return res
605 return res
606
606
607 @classmethod
607 @classmethod
608 def get_by_email(cls, email, cache=False):
608 def get_by_email(cls, email, cache=False):
609 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
609 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
610
610
611 if cache:
611 if cache:
612 q = q.options(FromCache("sql_cache_short",
612 q = q.options(FromCache("sql_cache_short",
613 "get_email_key_%s" % email))
613 "get_email_key_%s" % email))
614
614
615 ret = q.scalar()
615 ret = q.scalar()
616 if ret is None:
616 if ret is None:
617 q = UserEmailMap.query()
617 q = UserEmailMap.query()
618 # try fetching in alternate email map
618 # try fetching in alternate email map
619 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
619 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
620 q = q.options(joinedload(UserEmailMap.user))
620 q = q.options(joinedload(UserEmailMap.user))
621 if cache:
621 if cache:
622 q = q.options(FromCache("sql_cache_short",
622 q = q.options(FromCache("sql_cache_short",
623 "get_email_map_key_%s" % email))
623 "get_email_map_key_%s" % email))
624 ret = getattr(q.scalar(), 'user', None)
624 ret = getattr(q.scalar(), 'user', None)
625
625
626 return ret
626 return ret
627
627
628 @classmethod
628 @classmethod
629 def get_from_cs_author(cls, author):
629 def get_from_cs_author(cls, author):
630 """
630 """
631 Tries to get User objects out of commit author string
631 Tries to get User objects out of commit author string
632
632
633 :param author:
633 :param author:
634 """
634 """
635 from kallithea.lib.helpers import email, author_name
635 from kallithea.lib.helpers import email, author_name
636 # Valid email in the attribute passed, see if they're in the system
636 # Valid email in the attribute passed, see if they're in the system
637 _email = email(author)
637 _email = email(author)
638 if _email:
638 if _email:
639 user = cls.get_by_email(_email)
639 user = cls.get_by_email(_email)
640 if user is not None:
640 if user is not None:
641 return user
641 return user
642 # Maybe we can match by username?
642 # Maybe we can match by username?
643 _author = author_name(author)
643 _author = author_name(author)
644 user = cls.get_by_username(_author, case_insensitive=True)
644 user = cls.get_by_username(_author, case_insensitive=True)
645 if user is not None:
645 if user is not None:
646 return user
646 return user
647
647
648 def update_lastlogin(self):
648 def update_lastlogin(self):
649 """Update user lastlogin"""
649 """Update user lastlogin"""
650 self.last_login = datetime.datetime.now()
650 self.last_login = datetime.datetime.now()
651 Session().add(self)
651 Session().add(self)
652 log.debug('updated user %s lastlogin', self.username)
652 log.debug('updated user %s lastlogin', self.username)
653
653
654 @classmethod
654 @classmethod
655 def get_first_admin(cls):
655 def get_first_admin(cls):
656 user = User.query().filter(User.admin == True).first()
656 user = User.query().filter(User.admin == True).first()
657 if user is None:
657 if user is None:
658 raise Exception('Missing administrative account!')
658 raise Exception('Missing administrative account!')
659 return user
659 return user
660
660
661 @classmethod
661 @classmethod
662 def get_default_user(cls, cache=False):
662 def get_default_user(cls, cache=False):
663 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
663 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
664 if user is None:
664 if user is None:
665 raise Exception('Missing default account!')
665 raise Exception('Missing default account!')
666 return user
666 return user
667
667
668 def get_api_data(self, details=False):
668 def get_api_data(self, details=False):
669 """
669 """
670 Common function for generating user related data for API
670 Common function for generating user related data for API
671 """
671 """
672 user = self
672 user = self
673 data = dict(
673 data = dict(
674 user_id=user.user_id,
674 user_id=user.user_id,
675 username=user.username,
675 username=user.username,
676 firstname=user.name,
676 firstname=user.name,
677 lastname=user.lastname,
677 lastname=user.lastname,
678 email=user.email,
678 email=user.email,
679 emails=user.emails,
679 emails=user.emails,
680 active=user.active,
680 active=user.active,
681 admin=user.admin,
681 admin=user.admin,
682 )
682 )
683 if details:
683 if details:
684 data.update(dict(
684 data.update(dict(
685 extern_type=user.extern_type,
685 extern_type=user.extern_type,
686 extern_name=user.extern_name,
686 extern_name=user.extern_name,
687 api_key=user.api_key,
687 api_key=user.api_key,
688 api_keys=user.api_keys,
688 api_keys=user.api_keys,
689 last_login=user.last_login,
689 last_login=user.last_login,
690 ip_addresses=user.ip_addresses
690 ip_addresses=user.ip_addresses
691 ))
691 ))
692 return data
692 return data
693
693
694 def __json__(self):
694 def __json__(self):
695 data = dict(
695 data = dict(
696 full_name=self.full_name,
696 full_name=self.full_name,
697 full_name_or_username=self.full_name_or_username,
697 full_name_or_username=self.full_name_or_username,
698 short_contact=self.short_contact,
698 short_contact=self.short_contact,
699 full_contact=self.full_contact
699 full_contact=self.full_contact
700 )
700 )
701 data.update(self.get_api_data())
701 data.update(self.get_api_data())
702 return data
702 return data
703
703
704
704
705 class UserApiKeys(Base, BaseModel):
705 class UserApiKeys(Base, BaseModel):
706 __tablename__ = 'user_api_keys'
706 __tablename__ = 'user_api_keys'
707 __table_args__ = (
707 __table_args__ = (
708 Index('uak_api_key_idx', 'api_key'),
708 Index('uak_api_key_idx', 'api_key'),
709 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
709 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
710 _table_args_default_dict,
710 _table_args_default_dict,
711 )
711 )
712 __mapper_args__ = {}
712 __mapper_args__ = {}
713
713
714 user_api_key_id = Column(Integer(), unique=True, primary_key=True)
714 user_api_key_id = Column(Integer(), unique=True, primary_key=True)
715 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
715 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
716 api_key = Column(String(255), nullable=False, unique=True)
716 api_key = Column(String(255), nullable=False, unique=True)
717 description = Column(UnicodeText(1024), nullable=False)
717 description = Column(UnicodeText(1024), nullable=False)
718 expires = Column(Float(53), nullable=False)
718 expires = Column(Float(53), nullable=False)
719 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
719 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
720
720
721 user = relationship('User')
721 user = relationship('User')
722
722
723 @property
723 @property
724 def expired(self):
724 def expired(self):
725 if self.expires == -1:
725 if self.expires == -1:
726 return False
726 return False
727 return time.time() > self.expires
727 return time.time() > self.expires
728
728
729
729
730 class UserEmailMap(Base, BaseModel):
730 class UserEmailMap(Base, BaseModel):
731 __tablename__ = 'user_email_map'
731 __tablename__ = 'user_email_map'
732 __table_args__ = (
732 __table_args__ = (
733 Index('uem_email_idx', 'email'),
733 Index('uem_email_idx', 'email'),
734 _table_args_default_dict,
734 _table_args_default_dict,
735 )
735 )
736 __mapper_args__ = {}
736 __mapper_args__ = {}
737
737
738 email_id = Column(Integer(), unique=True, primary_key=True)
738 email_id = Column(Integer(), unique=True, primary_key=True)
739 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
739 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
740 _email = Column("email", String(255), nullable=False, unique=True)
740 _email = Column("email", String(255), nullable=False, unique=True)
741 user = relationship('User')
741 user = relationship('User')
742
742
743 @validates('_email')
743 @validates('_email')
744 def validate_email(self, key, email):
744 def validate_email(self, key, email):
745 # check if this email is not main one
745 # check if this email is not main one
746 main_email = Session().query(User).filter(User.email == email).scalar()
746 main_email = Session().query(User).filter(User.email == email).scalar()
747 if main_email is not None:
747 if main_email is not None:
748 raise AttributeError('email %s is present is user table' % email)
748 raise AttributeError('email %s is present is user table' % email)
749 return email
749 return email
750
750
751 @hybrid_property
751 @hybrid_property
752 def email(self):
752 def email(self):
753 return self._email
753 return self._email
754
754
755 @email.setter
755 @email.setter
756 def email(self, val):
756 def email(self, val):
757 self._email = val.lower() if val else None
757 self._email = val.lower() if val else None
758
758
759
759
760 class UserIpMap(Base, BaseModel):
760 class UserIpMap(Base, BaseModel):
761 __tablename__ = 'user_ip_map'
761 __tablename__ = 'user_ip_map'
762 __table_args__ = (
762 __table_args__ = (
763 UniqueConstraint('user_id', 'ip_addr'),
763 UniqueConstraint('user_id', 'ip_addr'),
764 _table_args_default_dict,
764 _table_args_default_dict,
765 )
765 )
766 __mapper_args__ = {}
766 __mapper_args__ = {}
767
767
768 ip_id = Column(Integer(), unique=True, primary_key=True)
768 ip_id = Column(Integer(), unique=True, primary_key=True)
769 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
769 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
770 ip_addr = Column(String(255), nullable=False)
770 ip_addr = Column(String(255), nullable=False)
771 active = Column(Boolean(), nullable=False, default=True)
771 active = Column(Boolean(), nullable=False, default=True)
772 user = relationship('User')
772 user = relationship('User')
773
773
774 @classmethod
774 @classmethod
775 def _get_ip_range(cls, ip_addr):
775 def _get_ip_range(cls, ip_addr):
776 from kallithea.lib import ipaddr
776 from kallithea.lib import ipaddr
777 net = ipaddr.IPNetwork(address=ip_addr)
777 net = ipaddr.IPNetwork(address=ip_addr)
778 return [str(net.network), str(net.broadcast)]
778 return [str(net.network), str(net.broadcast)]
779
779
780 def __json__(self):
780 def __json__(self):
781 return dict(
781 return dict(
782 ip_addr=self.ip_addr,
782 ip_addr=self.ip_addr,
783 ip_range=self._get_ip_range(self.ip_addr)
783 ip_range=self._get_ip_range(self.ip_addr)
784 )
784 )
785
785
786 def __unicode__(self):
786 def __unicode__(self):
787 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
787 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
788 self.user_id, self.ip_addr)
788 self.user_id, self.ip_addr)
789
789
790 class UserLog(Base, BaseModel):
790 class UserLog(Base, BaseModel):
791 __tablename__ = 'user_logs'
791 __tablename__ = 'user_logs'
792 __table_args__ = (
792 __table_args__ = (
793 _table_args_default_dict,
793 _table_args_default_dict,
794 )
794 )
795
795
796 user_log_id = Column(Integer(), unique=True, primary_key=True)
796 user_log_id = Column(Integer(), unique=True, primary_key=True)
797 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
797 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
798 username = Column(String(255), nullable=False)
798 username = Column(String(255), nullable=False)
799 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
799 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
800 repository_name = Column(Unicode(255), nullable=False)
800 repository_name = Column(Unicode(255), nullable=False)
801 user_ip = Column(String(255), nullable=True)
801 user_ip = Column(String(255), nullable=True)
802 action = Column(UnicodeText(1200000), nullable=False)
802 action = Column(UnicodeText(1200000), nullable=False)
803 action_date = Column(DateTime(timezone=False), nullable=False)
803 action_date = Column(DateTime(timezone=False), nullable=False)
804
804
805 def __unicode__(self):
805 def __unicode__(self):
806 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
806 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
807 self.repository_name,
807 self.repository_name,
808 self.action)
808 self.action)
809
809
810 @property
810 @property
811 def action_as_day(self):
811 def action_as_day(self):
812 return datetime.date(*self.action_date.timetuple()[:3])
812 return datetime.date(*self.action_date.timetuple()[:3])
813
813
814 user = relationship('User')
814 user = relationship('User')
815 repository = relationship('Repository', cascade='')
815 repository = relationship('Repository', cascade='')
816
816
817
817
818 class UserGroup(Base, BaseModel):
818 class UserGroup(Base, BaseModel):
819 __tablename__ = 'users_groups'
819 __tablename__ = 'users_groups'
820 __table_args__ = (
820 __table_args__ = (
821 _table_args_default_dict,
821 _table_args_default_dict,
822 )
822 )
823
823
824 users_group_id = Column(Integer(), unique=True, primary_key=True)
824 users_group_id = Column(Integer(), unique=True, primary_key=True)
825 users_group_name = Column(Unicode(255), nullable=False, unique=True)
825 users_group_name = Column(Unicode(255), nullable=False, unique=True)
826 user_group_description = Column(Unicode(10000), nullable=True) # FIXME: not nullable?
826 user_group_description = Column(Unicode(10000), nullable=True) # FIXME: not nullable?
827 users_group_active = Column(Boolean(), nullable=False)
827 users_group_active = Column(Boolean(), nullable=False)
828 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, default=True)
828 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, default=True)
829 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
829 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
830 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
830 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
831 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
831 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
832
832
833 members = relationship('UserGroupMember', cascade="all, delete-orphan")
833 members = relationship('UserGroupMember', cascade="all, delete-orphan")
834 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
834 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
835 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
835 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
836 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
836 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
837 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
837 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
838 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
838 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
839
839
840 user = relationship('User')
840 user = relationship('User')
841
841
842 @hybrid_property
842 @hybrid_property
843 def group_data(self):
843 def group_data(self):
844 if not self._group_data:
844 if not self._group_data:
845 return {}
845 return {}
846
846
847 try:
847 try:
848 return json.loads(self._group_data)
848 return json.loads(self._group_data)
849 except TypeError:
849 except TypeError:
850 return {}
850 return {}
851
851
852 @group_data.setter
852 @group_data.setter
853 def group_data(self, val):
853 def group_data(self, val):
854 try:
854 try:
855 self._group_data = json.dumps(val)
855 self._group_data = json.dumps(val)
856 except Exception:
856 except Exception:
857 log.error(traceback.format_exc())
857 log.error(traceback.format_exc())
858
858
859 def __unicode__(self):
859 def __unicode__(self):
860 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
860 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
861 self.users_group_id,
861 self.users_group_id,
862 self.users_group_name)
862 self.users_group_name)
863
863
864 @classmethod
864 @classmethod
865 def get_by_group_name(cls, group_name, cache=False,
865 def get_by_group_name(cls, group_name, cache=False,
866 case_insensitive=False):
866 case_insensitive=False):
867 if case_insensitive:
867 if case_insensitive:
868 q = cls.query().filter(func.lower(cls.users_group_name) == func.lower(group_name))
868 q = cls.query().filter(func.lower(cls.users_group_name) == func.lower(group_name))
869 else:
869 else:
870 q = cls.query().filter(cls.users_group_name == group_name)
870 q = cls.query().filter(cls.users_group_name == group_name)
871 if cache:
871 if cache:
872 q = q.options(FromCache(
872 q = q.options(FromCache(
873 "sql_cache_short",
873 "sql_cache_short",
874 "get_group_%s" % _hash_key(group_name)
874 "get_group_%s" % _hash_key(group_name)
875 )
875 )
876 )
876 )
877 return q.scalar()
877 return q.scalar()
878
878
879 @classmethod
879 @classmethod
880 def get(cls, user_group_id, cache=False):
880 def get(cls, user_group_id, cache=False):
881 user_group = cls.query()
881 user_group = cls.query()
882 if cache:
882 if cache:
883 user_group = user_group.options(FromCache("sql_cache_short",
883 user_group = user_group.options(FromCache("sql_cache_short",
884 "get_users_group_%s" % user_group_id))
884 "get_users_group_%s" % user_group_id))
885 return user_group.get(user_group_id)
885 return user_group.get(user_group_id)
886
886
887 def get_api_data(self, with_members=True):
887 def get_api_data(self, with_members=True):
888 user_group = self
888 user_group = self
889
889
890 data = dict(
890 data = dict(
891 users_group_id=user_group.users_group_id,
891 users_group_id=user_group.users_group_id,
892 group_name=user_group.users_group_name,
892 group_name=user_group.users_group_name,
893 group_description=user_group.user_group_description,
893 group_description=user_group.user_group_description,
894 active=user_group.users_group_active,
894 active=user_group.users_group_active,
895 owner=user_group.user.username,
895 owner=user_group.user.username,
896 )
896 )
897 if with_members:
897 if with_members:
898 members = []
898 members = []
899 for user in user_group.members:
899 for user in user_group.members:
900 user = user.user
900 user = user.user
901 members.append(user.get_api_data())
901 members.append(user.get_api_data())
902 data['members'] = members
902 data['members'] = members
903
903
904 return data
904 return data
905
905
906
906
907 class UserGroupMember(Base, BaseModel):
907 class UserGroupMember(Base, BaseModel):
908 __tablename__ = 'users_groups_members'
908 __tablename__ = 'users_groups_members'
909 __table_args__ = (
909 __table_args__ = (
910 _table_args_default_dict,
910 _table_args_default_dict,
911 )
911 )
912
912
913 users_group_member_id = Column(Integer(), unique=True, primary_key=True)
913 users_group_member_id = Column(Integer(), unique=True, primary_key=True)
914 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
914 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
915 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
915 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
916
916
917 user = relationship('User')
917 user = relationship('User')
918 users_group = relationship('UserGroup')
918 users_group = relationship('UserGroup')
919
919
920 def __init__(self, gr_id='', u_id=''):
920 def __init__(self, gr_id='', u_id=''):
921 self.users_group_id = gr_id
921 self.users_group_id = gr_id
922 self.user_id = u_id
922 self.user_id = u_id
923
923
924
924
925 class RepositoryField(Base, BaseModel):
925 class RepositoryField(Base, BaseModel):
926 __tablename__ = 'repositories_fields'
926 __tablename__ = 'repositories_fields'
927 __table_args__ = (
927 __table_args__ = (
928 UniqueConstraint('repository_id', 'field_key'), # no-multi field
928 UniqueConstraint('repository_id', 'field_key'), # no-multi field
929 _table_args_default_dict,
929 _table_args_default_dict,
930 )
930 )
931
931
932 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
932 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
933
933
934 repo_field_id = Column(Integer(), unique=True, primary_key=True)
934 repo_field_id = Column(Integer(), unique=True, primary_key=True)
935 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
935 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
936 field_key = Column(String(250), nullable=False)
936 field_key = Column(String(250), nullable=False)
937 field_label = Column(String(1024), nullable=False)
937 field_label = Column(String(1024), nullable=False)
938 field_value = Column(String(10000), nullable=False)
938 field_value = Column(String(10000), nullable=False)
939 field_desc = Column(String(1024), nullable=False)
939 field_desc = Column(String(1024), nullable=False)
940 field_type = Column(String(255), nullable=False)
940 field_type = Column(String(255), nullable=False)
941 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
941 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
942
942
943 repository = relationship('Repository')
943 repository = relationship('Repository')
944
944
945 @property
945 @property
946 def field_key_prefixed(self):
946 def field_key_prefixed(self):
947 return 'ex_%s' % self.field_key
947 return 'ex_%s' % self.field_key
948
948
949 @classmethod
949 @classmethod
950 def un_prefix_key(cls, key):
950 def un_prefix_key(cls, key):
951 if key.startswith(cls.PREFIX):
951 if key.startswith(cls.PREFIX):
952 return key[len(cls.PREFIX):]
952 return key[len(cls.PREFIX):]
953 return key
953 return key
954
954
955 @classmethod
955 @classmethod
956 def get_by_key_name(cls, key, repo):
956 def get_by_key_name(cls, key, repo):
957 row = cls.query() \
957 row = cls.query() \
958 .filter(cls.repository == repo) \
958 .filter(cls.repository == repo) \
959 .filter(cls.field_key == key).scalar()
959 .filter(cls.field_key == key).scalar()
960 return row
960 return row
961
961
962
962
963 class Repository(Base, BaseModel):
963 class Repository(Base, BaseModel):
964 __tablename__ = 'repositories'
964 __tablename__ = 'repositories'
965 __table_args__ = (
965 __table_args__ = (
966 Index('r_repo_name_idx', 'repo_name'),
966 Index('r_repo_name_idx', 'repo_name'),
967 _table_args_default_dict,
967 _table_args_default_dict,
968 )
968 )
969
969
970 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
970 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
971 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
971 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
972
972
973 STATE_CREATED = 'repo_state_created'
973 STATE_CREATED = 'repo_state_created'
974 STATE_PENDING = 'repo_state_pending'
974 STATE_PENDING = 'repo_state_pending'
975 STATE_ERROR = 'repo_state_error'
975 STATE_ERROR = 'repo_state_error'
976
976
977 repo_id = Column(Integer(), unique=True, primary_key=True)
977 repo_id = Column(Integer(), unique=True, primary_key=True)
978 repo_name = Column(Unicode(255), nullable=False, unique=True)
978 repo_name = Column(Unicode(255), nullable=False, unique=True)
979 repo_state = Column(String(255), nullable=False)
979 repo_state = Column(String(255), nullable=False)
980
980
981 clone_uri = Column(String(255), nullable=True) # FIXME: not nullable?
981 clone_uri = Column(String(255), nullable=True) # FIXME: not nullable?
982 repo_type = Column(String(255), nullable=False)
982 repo_type = Column(String(255), nullable=False)
983 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
983 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
984 private = Column(Boolean(), nullable=False)
984 private = Column(Boolean(), nullable=False)
985 enable_statistics = Column("statistics", Boolean(), nullable=False, default=True)
985 enable_statistics = Column("statistics", Boolean(), nullable=False, default=True)
986 enable_downloads = Column("downloads", Boolean(), nullable=False, default=True)
986 enable_downloads = Column("downloads", Boolean(), nullable=False, default=True)
987 description = Column(Unicode(10000), nullable=False)
987 description = Column(Unicode(10000), nullable=False)
988 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
988 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
989 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
989 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
990 _landing_revision = Column("landing_revision", String(255), nullable=False)
990 _landing_revision = Column("landing_revision", String(255), nullable=False)
991 enable_locking = Column(Boolean(), nullable=False, default=False)
991 enable_locking = Column(Boolean(), nullable=False, default=False)
992 _locked = Column("locked", String(255), nullable=True) # FIXME: not nullable?
992 _locked = Column("locked", String(255), nullable=True) # FIXME: not nullable?
993 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
993 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
994
994
995 fork_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
995 fork_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
996 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True)
996 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True)
997
997
998 user = relationship('User')
998 user = relationship('User')
999 fork = relationship('Repository', remote_side=repo_id)
999 fork = relationship('Repository', remote_side=repo_id)
1000 group = relationship('RepoGroup')
1000 group = relationship('RepoGroup')
1001 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1001 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1002 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1002 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1003 stats = relationship('Statistics', cascade='all', uselist=False)
1003 stats = relationship('Statistics', cascade='all', uselist=False)
1004
1004
1005 followers = relationship('UserFollowing',
1005 followers = relationship('UserFollowing',
1006 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1006 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1007 cascade='all')
1007 cascade='all')
1008 extra_fields = relationship('RepositoryField',
1008 extra_fields = relationship('RepositoryField',
1009 cascade="all, delete-orphan")
1009 cascade="all, delete-orphan")
1010
1010
1011 logs = relationship('UserLog')
1011 logs = relationship('UserLog')
1012 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
1012 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
1013
1013
1014 pull_requests_org = relationship('PullRequest',
1014 pull_requests_org = relationship('PullRequest',
1015 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
1015 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
1016 cascade="all, delete-orphan")
1016 cascade="all, delete-orphan")
1017
1017
1018 pull_requests_other = relationship('PullRequest',
1018 pull_requests_other = relationship('PullRequest',
1019 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
1019 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
1020 cascade="all, delete-orphan")
1020 cascade="all, delete-orphan")
1021
1021
1022 def __unicode__(self):
1022 def __unicode__(self):
1023 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1023 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1024 safe_unicode(self.repo_name))
1024 safe_unicode(self.repo_name))
1025
1025
1026 @hybrid_property
1026 @hybrid_property
1027 def landing_rev(self):
1027 def landing_rev(self):
1028 # always should return [rev_type, rev]
1028 # always should return [rev_type, rev]
1029 if self._landing_revision:
1029 if self._landing_revision:
1030 _rev_info = self._landing_revision.split(':')
1030 _rev_info = self._landing_revision.split(':')
1031 if len(_rev_info) < 2:
1031 if len(_rev_info) < 2:
1032 _rev_info.insert(0, 'rev')
1032 _rev_info.insert(0, 'rev')
1033 return [_rev_info[0], _rev_info[1]]
1033 return [_rev_info[0], _rev_info[1]]
1034 return [None, None]
1034 return [None, None]
1035
1035
1036 @landing_rev.setter
1036 @landing_rev.setter
1037 def landing_rev(self, val):
1037 def landing_rev(self, val):
1038 if ':' not in val:
1038 if ':' not in val:
1039 raise ValueError('value must be delimited with `:` and consist '
1039 raise ValueError('value must be delimited with `:` and consist '
1040 'of <rev_type>:<rev>, got %s instead' % val)
1040 'of <rev_type>:<rev>, got %s instead' % val)
1041 self._landing_revision = val
1041 self._landing_revision = val
1042
1042
1043 @hybrid_property
1043 @hybrid_property
1044 def locked(self):
1044 def locked(self):
1045 # always should return [user_id, timelocked]
1045 # always should return [user_id, timelocked]
1046 if self._locked:
1046 if self._locked:
1047 _lock_info = self._locked.split(':')
1047 _lock_info = self._locked.split(':')
1048 return int(_lock_info[0]), _lock_info[1]
1048 return int(_lock_info[0]), _lock_info[1]
1049 return [None, None]
1049 return [None, None]
1050
1050
1051 @locked.setter
1051 @locked.setter
1052 def locked(self, val):
1052 def locked(self, val):
1053 if val and isinstance(val, (list, tuple)):
1053 if val and isinstance(val, (list, tuple)):
1054 self._locked = ':'.join(map(str, val))
1054 self._locked = ':'.join(map(str, val))
1055 else:
1055 else:
1056 self._locked = None
1056 self._locked = None
1057
1057
1058 @hybrid_property
1058 @hybrid_property
1059 def changeset_cache(self):
1059 def changeset_cache(self):
1060 try:
1060 try:
1061 cs_cache = json.loads(self._changeset_cache) # might raise on bad data
1061 cs_cache = json.loads(self._changeset_cache) # might raise on bad data
1062 cs_cache['raw_id'] # verify data, raise exception on error
1062 cs_cache['raw_id'] # verify data, raise exception on error
1063 return cs_cache
1063 return cs_cache
1064 except (TypeError, KeyError, ValueError):
1064 except (TypeError, KeyError, ValueError):
1065 return EmptyChangeset().__json__()
1065 return EmptyChangeset().__json__()
1066
1066
1067 @changeset_cache.setter
1067 @changeset_cache.setter
1068 def changeset_cache(self, val):
1068 def changeset_cache(self, val):
1069 try:
1069 try:
1070 self._changeset_cache = json.dumps(val)
1070 self._changeset_cache = json.dumps(val)
1071 except Exception:
1071 except Exception:
1072 log.error(traceback.format_exc())
1072 log.error(traceback.format_exc())
1073
1073
1074 @classmethod
1074 @classmethod
1075 def url_sep(cls):
1075 def url_sep(cls):
1076 return URL_SEP
1076 return URL_SEP
1077
1077
1078 @classmethod
1078 @classmethod
1079 def normalize_repo_name(cls, repo_name):
1079 def normalize_repo_name(cls, repo_name):
1080 """
1080 """
1081 Normalizes os specific repo_name to the format internally stored inside
1081 Normalizes os specific repo_name to the format internally stored inside
1082 database using URL_SEP
1082 database using URL_SEP
1083
1083
1084 :param cls:
1084 :param cls:
1085 :param repo_name:
1085 :param repo_name:
1086 """
1086 """
1087 return cls.url_sep().join(repo_name.split(os.sep))
1087 return cls.url_sep().join(repo_name.split(os.sep))
1088
1088
1089 @classmethod
1089 @classmethod
1090 def get_by_repo_name(cls, repo_name):
1090 def get_by_repo_name(cls, repo_name):
1091 q = Session().query(cls).filter(cls.repo_name == repo_name)
1091 q = Session().query(cls).filter(cls.repo_name == repo_name)
1092 q = q.options(joinedload(Repository.fork)) \
1092 q = q.options(joinedload(Repository.fork)) \
1093 .options(joinedload(Repository.user)) \
1093 .options(joinedload(Repository.user)) \
1094 .options(joinedload(Repository.group))
1094 .options(joinedload(Repository.group))
1095 return q.scalar()
1095 return q.scalar()
1096
1096
1097 @classmethod
1097 @classmethod
1098 def get_by_full_path(cls, repo_full_path):
1098 def get_by_full_path(cls, repo_full_path):
1099 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1099 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1100 repo_name = cls.normalize_repo_name(repo_name)
1100 repo_name = cls.normalize_repo_name(repo_name)
1101 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1101 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1102
1102
1103 @classmethod
1103 @classmethod
1104 def get_repo_forks(cls, repo_id):
1104 def get_repo_forks(cls, repo_id):
1105 return cls.query().filter(Repository.fork_id == repo_id)
1105 return cls.query().filter(Repository.fork_id == repo_id)
1106
1106
1107 @classmethod
1107 @classmethod
1108 def base_path(cls):
1108 def base_path(cls):
1109 """
1109 """
1110 Returns base path where all repos are stored
1110 Returns base path where all repos are stored
1111
1111
1112 :param cls:
1112 :param cls:
1113 """
1113 """
1114 q = Session().query(Ui) \
1114 q = Session().query(Ui) \
1115 .filter(Ui.ui_key == cls.url_sep())
1115 .filter(Ui.ui_key == cls.url_sep())
1116 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1116 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1117 return q.one().ui_value
1117 return q.one().ui_value
1118
1118
1119 @property
1119 @property
1120 def forks(self):
1120 def forks(self):
1121 """
1121 """
1122 Return forks of this repo
1122 Return forks of this repo
1123 """
1123 """
1124 return Repository.get_repo_forks(self.repo_id)
1124 return Repository.get_repo_forks(self.repo_id)
1125
1125
1126 @property
1126 @property
1127 def parent(self):
1127 def parent(self):
1128 """
1128 """
1129 Returns fork parent
1129 Returns fork parent
1130 """
1130 """
1131 return self.fork
1131 return self.fork
1132
1132
1133 @property
1133 @property
1134 def just_name(self):
1134 def just_name(self):
1135 return self.repo_name.split(Repository.url_sep())[-1]
1135 return self.repo_name.split(Repository.url_sep())[-1]
1136
1136
1137 @property
1137 @property
1138 def groups_with_parents(self):
1138 def groups_with_parents(self):
1139 groups = []
1139 groups = []
1140 if self.group is None:
1140 if self.group is None:
1141 return groups
1141 return groups
1142
1142
1143 cur_gr = self.group
1143 cur_gr = self.group
1144 groups.insert(0, cur_gr)
1144 groups.insert(0, cur_gr)
1145 while 1:
1145 while 1:
1146 gr = getattr(cur_gr, 'parent_group', None)
1146 gr = getattr(cur_gr, 'parent_group', None)
1147 cur_gr = cur_gr.parent_group
1147 cur_gr = cur_gr.parent_group
1148 if gr is None:
1148 if gr is None:
1149 break
1149 break
1150 groups.insert(0, gr)
1150 groups.insert(0, gr)
1151
1151
1152 return groups
1152 return groups
1153
1153
1154 @property
1154 @property
1155 def groups_and_repo(self):
1155 def groups_and_repo(self):
1156 return self.groups_with_parents, self.just_name, self.repo_name
1156 return self.groups_with_parents, self.just_name, self.repo_name
1157
1157
1158 @LazyProperty
1158 @LazyProperty
1159 def repo_path(self):
1159 def repo_path(self):
1160 """
1160 """
1161 Returns base full path for that repository means where it actually
1161 Returns base full path for that repository means where it actually
1162 exists on a filesystem
1162 exists on a filesystem
1163 """
1163 """
1164 q = Session().query(Ui).filter(Ui.ui_key ==
1164 q = Session().query(Ui).filter(Ui.ui_key ==
1165 Repository.url_sep())
1165 Repository.url_sep())
1166 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1166 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1167 return q.one().ui_value
1167 return q.one().ui_value
1168
1168
1169 @property
1169 @property
1170 def repo_full_path(self):
1170 def repo_full_path(self):
1171 p = [self.repo_path]
1171 p = [self.repo_path]
1172 # we need to split the name by / since this is how we store the
1172 # we need to split the name by / since this is how we store the
1173 # names in the database, but that eventually needs to be converted
1173 # names in the database, but that eventually needs to be converted
1174 # into a valid system path
1174 # into a valid system path
1175 p += self.repo_name.split(Repository.url_sep())
1175 p += self.repo_name.split(Repository.url_sep())
1176 return os.path.join(*map(safe_unicode, p))
1176 return os.path.join(*map(safe_unicode, p))
1177
1177
1178 @property
1178 @property
1179 def cache_keys(self):
1179 def cache_keys(self):
1180 """
1180 """
1181 Returns associated cache keys for that repo
1181 Returns associated cache keys for that repo
1182 """
1182 """
1183 return CacheInvalidation.query() \
1183 return CacheInvalidation.query() \
1184 .filter(CacheInvalidation.cache_args == self.repo_name) \
1184 .filter(CacheInvalidation.cache_args == self.repo_name) \
1185 .order_by(CacheInvalidation.cache_key) \
1185 .order_by(CacheInvalidation.cache_key) \
1186 .all()
1186 .all()
1187
1187
1188 def get_new_name(self, repo_name):
1188 def get_new_name(self, repo_name):
1189 """
1189 """
1190 returns new full repository name based on assigned group and new new
1190 returns new full repository name based on assigned group and new new
1191
1191
1192 :param group_name:
1192 :param group_name:
1193 """
1193 """
1194 path_prefix = self.group.full_path_splitted if self.group else []
1194 path_prefix = self.group.full_path_splitted if self.group else []
1195 return Repository.url_sep().join(path_prefix + [repo_name])
1195 return Repository.url_sep().join(path_prefix + [repo_name])
1196
1196
1197 @property
1197 @property
1198 def _ui(self):
1198 def _ui(self):
1199 """
1199 """
1200 Creates an db based ui object for this repository
1200 Creates an db based ui object for this repository
1201 """
1201 """
1202 from kallithea.lib.utils import make_ui
1202 from kallithea.lib.utils import make_ui
1203 return make_ui('db', clear_session=False)
1203 return make_ui('db', clear_session=False)
1204
1204
1205 @classmethod
1205 @classmethod
1206 def is_valid(cls, repo_name):
1206 def is_valid(cls, repo_name):
1207 """
1207 """
1208 returns True if given repo name is a valid filesystem repository
1208 returns True if given repo name is a valid filesystem repository
1209
1209
1210 :param cls:
1210 :param cls:
1211 :param repo_name:
1211 :param repo_name:
1212 """
1212 """
1213 from kallithea.lib.utils import is_valid_repo
1213 from kallithea.lib.utils import is_valid_repo
1214
1214
1215 return is_valid_repo(repo_name, cls.base_path())
1215 return is_valid_repo(repo_name, cls.base_path())
1216
1216
1217 def get_api_data(self):
1217 def get_api_data(self):
1218 """
1218 """
1219 Common function for generating repo api data
1219 Common function for generating repo api data
1220
1220
1221 """
1221 """
1222 repo = self
1222 repo = self
1223 data = dict(
1223 data = dict(
1224 repo_id=repo.repo_id,
1224 repo_id=repo.repo_id,
1225 repo_name=repo.repo_name,
1225 repo_name=repo.repo_name,
1226 repo_type=repo.repo_type,
1226 repo_type=repo.repo_type,
1227 clone_uri=repo.clone_uri,
1227 clone_uri=repo.clone_uri,
1228 private=repo.private,
1228 private=repo.private,
1229 created_on=repo.created_on,
1229 created_on=repo.created_on,
1230 description=repo.description,
1230 description=repo.description,
1231 landing_rev=repo.landing_rev,
1231 landing_rev=repo.landing_rev,
1232 owner=repo.user.username,
1232 owner=repo.user.username,
1233 fork_of=repo.fork.repo_name if repo.fork else None,
1233 fork_of=repo.fork.repo_name if repo.fork else None,
1234 enable_statistics=repo.enable_statistics,
1234 enable_statistics=repo.enable_statistics,
1235 enable_locking=repo.enable_locking,
1235 enable_locking=repo.enable_locking,
1236 enable_downloads=repo.enable_downloads,
1236 enable_downloads=repo.enable_downloads,
1237 last_changeset=repo.changeset_cache,
1237 last_changeset=repo.changeset_cache,
1238 locked_by=User.get(self.locked[0]).get_api_data() \
1238 locked_by=User.get(self.locked[0]).get_api_data() \
1239 if self.locked[0] else None,
1239 if self.locked[0] else None,
1240 locked_date=time_to_datetime(self.locked[1]) \
1240 locked_date=time_to_datetime(self.locked[1]) \
1241 if self.locked[1] else None
1241 if self.locked[1] else None
1242 )
1242 )
1243 rc_config = Setting.get_app_settings()
1243 rc_config = Setting.get_app_settings()
1244 repository_fields = str2bool(rc_config.get('repository_fields'))
1244 repository_fields = str2bool(rc_config.get('repository_fields'))
1245 if repository_fields:
1245 if repository_fields:
1246 for f in self.extra_fields:
1246 for f in self.extra_fields:
1247 data[f.field_key_prefixed] = f.field_value
1247 data[f.field_key_prefixed] = f.field_value
1248
1248
1249 return data
1249 return data
1250
1250
1251 @classmethod
1251 @classmethod
1252 def lock(cls, repo, user_id, lock_time=None):
1252 def lock(cls, repo, user_id, lock_time=None):
1253 if lock_time is not None:
1253 if lock_time is not None:
1254 lock_time = time.time()
1254 lock_time = time.time()
1255 repo.locked = [user_id, lock_time]
1255 repo.locked = [user_id, lock_time]
1256 Session().add(repo)
1256 Session().add(repo)
1257 Session().commit()
1257 Session().commit()
1258
1258
1259 @classmethod
1259 @classmethod
1260 def unlock(cls, repo):
1260 def unlock(cls, repo):
1261 repo.locked = None
1261 repo.locked = None
1262 Session().add(repo)
1262 Session().add(repo)
1263 Session().commit()
1263 Session().commit()
1264
1264
1265 @classmethod
1265 @classmethod
1266 def getlock(cls, repo):
1266 def getlock(cls, repo):
1267 return repo.locked
1267 return repo.locked
1268
1268
1269 @property
1269 @property
1270 def last_db_change(self):
1270 def last_db_change(self):
1271 return self.updated_on
1271 return self.updated_on
1272
1272
1273 @property
1273 @property
1274 def clone_uri_hidden(self):
1274 def clone_uri_hidden(self):
1275 clone_uri = self.clone_uri
1275 clone_uri = self.clone_uri
1276 if clone_uri:
1276 if clone_uri:
1277 import urlobject
1277 import urlobject
1278 url_obj = urlobject.URLObject(self.clone_uri)
1278 url_obj = urlobject.URLObject(self.clone_uri)
1279 if url_obj.password:
1279 if url_obj.password:
1280 clone_uri = url_obj.with_password('*****')
1280 clone_uri = url_obj.with_password('*****')
1281 return clone_uri
1281 return clone_uri
1282
1282
1283 def clone_url(self, **override):
1283 def clone_url(self, **override):
1284 import kallithea.lib.helpers as h
1284 import kallithea.lib.helpers as h
1285 qualified_home_url = h.canonical_url('home')
1285 qualified_home_url = h.canonical_url('home')
1286
1286
1287 uri_tmpl = None
1287 uri_tmpl = None
1288 if 'with_id' in override:
1288 if 'with_id' in override:
1289 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1289 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1290 del override['with_id']
1290 del override['with_id']
1291
1291
1292 if 'uri_tmpl' in override:
1292 if 'uri_tmpl' in override:
1293 uri_tmpl = override['uri_tmpl']
1293 uri_tmpl = override['uri_tmpl']
1294 del override['uri_tmpl']
1294 del override['uri_tmpl']
1295
1295
1296 # we didn't override our tmpl from **overrides
1296 # we didn't override our tmpl from **overrides
1297 if not uri_tmpl:
1297 if not uri_tmpl:
1298 uri_tmpl = self.DEFAULT_CLONE_URI
1298 uri_tmpl = self.DEFAULT_CLONE_URI
1299 try:
1299 try:
1300 from pylons import tmpl_context as c
1300 from pylons import tmpl_context as c
1301 uri_tmpl = c.clone_uri_tmpl
1301 uri_tmpl = c.clone_uri_tmpl
1302 except AttributeError:
1302 except AttributeError:
1303 # in any case if we call this outside of request context,
1303 # in any case if we call this outside of request context,
1304 # ie, not having tmpl_context set up
1304 # ie, not having tmpl_context set up
1305 pass
1305 pass
1306
1306
1307 return get_clone_url(uri_tmpl=uri_tmpl,
1307 return get_clone_url(uri_tmpl=uri_tmpl,
1308 qualified_home_url=qualified_home_url,
1308 qualified_home_url=qualified_home_url,
1309 repo_name=self.repo_name,
1309 repo_name=self.repo_name,
1310 repo_id=self.repo_id, **override)
1310 repo_id=self.repo_id, **override)
1311
1311
1312 def set_state(self, state):
1312 def set_state(self, state):
1313 self.repo_state = state
1313 self.repo_state = state
1314 Session().add(self)
1314 Session().add(self)
1315 #==========================================================================
1315 #==========================================================================
1316 # SCM PROPERTIES
1316 # SCM PROPERTIES
1317 #==========================================================================
1317 #==========================================================================
1318
1318
1319 def get_changeset(self, rev=None):
1319 def get_changeset(self, rev=None):
1320 return get_changeset_safe(self.scm_instance, rev)
1320 return get_changeset_safe(self.scm_instance, rev)
1321
1321
1322 def get_landing_changeset(self):
1322 def get_landing_changeset(self):
1323 """
1323 """
1324 Returns landing changeset, or if that doesn't exist returns the tip
1324 Returns landing changeset, or if that doesn't exist returns the tip
1325 """
1325 """
1326 _rev_type, _rev = self.landing_rev
1326 _rev_type, _rev = self.landing_rev
1327 cs = self.get_changeset(_rev)
1327 cs = self.get_changeset(_rev)
1328 if isinstance(cs, EmptyChangeset):
1328 if isinstance(cs, EmptyChangeset):
1329 return self.get_changeset()
1329 return self.get_changeset()
1330 return cs
1330 return cs
1331
1331
1332 def update_changeset_cache(self, cs_cache=None):
1332 def update_changeset_cache(self, cs_cache=None):
1333 """
1333 """
1334 Update cache of last changeset for repository, keys should be::
1334 Update cache of last changeset for repository, keys should be::
1335
1335
1336 short_id
1336 short_id
1337 raw_id
1337 raw_id
1338 revision
1338 revision
1339 message
1339 message
1340 date
1340 date
1341 author
1341 author
1342
1342
1343 :param cs_cache:
1343 :param cs_cache:
1344 """
1344 """
1345 from kallithea.lib.vcs.backends.base import BaseChangeset
1345 from kallithea.lib.vcs.backends.base import BaseChangeset
1346 if cs_cache is None:
1346 if cs_cache is None:
1347 cs_cache = EmptyChangeset()
1347 cs_cache = EmptyChangeset()
1348 # use no-cache version here
1348 # use no-cache version here
1349 scm_repo = self.scm_instance_no_cache()
1349 scm_repo = self.scm_instance_no_cache()
1350 if scm_repo:
1350 if scm_repo:
1351 cs_cache = scm_repo.get_changeset()
1351 cs_cache = scm_repo.get_changeset()
1352
1352
1353 if isinstance(cs_cache, BaseChangeset):
1353 if isinstance(cs_cache, BaseChangeset):
1354 cs_cache = cs_cache.__json__()
1354 cs_cache = cs_cache.__json__()
1355
1355
1356 if (not self.changeset_cache or cs_cache['raw_id'] != self.changeset_cache['raw_id']):
1356 if (not self.changeset_cache or cs_cache['raw_id'] != self.changeset_cache['raw_id']):
1357 _default = datetime.datetime.fromtimestamp(0)
1357 _default = datetime.datetime.fromtimestamp(0)
1358 last_change = cs_cache.get('date') or _default
1358 last_change = cs_cache.get('date') or _default
1359 log.debug('updated repo %s with new cs cache %s',
1359 log.debug('updated repo %s with new cs cache %s',
1360 self.repo_name, cs_cache)
1360 self.repo_name, cs_cache)
1361 self.updated_on = last_change
1361 self.updated_on = last_change
1362 self.changeset_cache = cs_cache
1362 self.changeset_cache = cs_cache
1363 Session().add(self)
1363 Session().add(self)
1364 Session().commit()
1364 Session().commit()
1365 else:
1365 else:
1366 log.debug('changeset_cache for %s already up to date with %s',
1366 log.debug('changeset_cache for %s already up to date with %s',
1367 self.repo_name, cs_cache['raw_id'])
1367 self.repo_name, cs_cache['raw_id'])
1368
1368
1369 @property
1369 @property
1370 def tip(self):
1370 def tip(self):
1371 return self.get_changeset('tip')
1371 return self.get_changeset('tip')
1372
1372
1373 @property
1373 @property
1374 def author(self):
1374 def author(self):
1375 return self.tip.author
1375 return self.tip.author
1376
1376
1377 @property
1377 @property
1378 def last_change(self):
1378 def last_change(self):
1379 return self.scm_instance.last_change
1379 return self.scm_instance.last_change
1380
1380
1381 def get_comments(self, revisions=None):
1381 def get_comments(self, revisions=None):
1382 """
1382 """
1383 Returns comments for this repository grouped by revisions
1383 Returns comments for this repository grouped by revisions
1384
1384
1385 :param revisions: filter query by revisions only
1385 :param revisions: filter query by revisions only
1386 """
1386 """
1387 cmts = ChangesetComment.query() \
1387 cmts = ChangesetComment.query() \
1388 .filter(ChangesetComment.repo == self)
1388 .filter(ChangesetComment.repo == self)
1389 if revisions is not None:
1389 if revisions is not None:
1390 if not revisions:
1390 if not revisions:
1391 return {} # don't use sql 'in' on empty set
1391 return {} # don't use sql 'in' on empty set
1392 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1392 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1393 grouped = collections.defaultdict(list)
1393 grouped = collections.defaultdict(list)
1394 for cmt in cmts.all():
1394 for cmt in cmts.all():
1395 grouped[cmt.revision].append(cmt)
1395 grouped[cmt.revision].append(cmt)
1396 return grouped
1396 return grouped
1397
1397
1398 def statuses(self, revisions):
1398 def statuses(self, revisions):
1399 """
1399 """
1400 Returns statuses for this repository.
1400 Returns statuses for this repository.
1401 PRs without any votes do _not_ show up as unreviewed.
1401 PRs without any votes do _not_ show up as unreviewed.
1402
1402
1403 :param revisions: list of revisions to get statuses for
1403 :param revisions: list of revisions to get statuses for
1404 """
1404 """
1405 if not revisions:
1405 if not revisions:
1406 return {}
1406 return {}
1407
1407
1408 statuses = ChangesetStatus.query() \
1408 statuses = ChangesetStatus.query() \
1409 .filter(ChangesetStatus.repo == self) \
1409 .filter(ChangesetStatus.repo == self) \
1410 .filter(ChangesetStatus.version == 0) \
1410 .filter(ChangesetStatus.version == 0) \
1411 .filter(ChangesetStatus.revision.in_(revisions))
1411 .filter(ChangesetStatus.revision.in_(revisions))
1412
1412
1413 grouped = {}
1413 grouped = {}
1414 for stat in statuses.all():
1414 for stat in statuses.all():
1415 pr_id = pr_nice_id = pr_repo = None
1415 pr_id = pr_nice_id = pr_repo = None
1416 if stat.pull_request:
1416 if stat.pull_request:
1417 pr_id = stat.pull_request.pull_request_id
1417 pr_id = stat.pull_request.pull_request_id
1418 pr_nice_id = PullRequest.make_nice_id(pr_id)
1418 pr_nice_id = PullRequest.make_nice_id(pr_id)
1419 pr_repo = stat.pull_request.other_repo.repo_name
1419 pr_repo = stat.pull_request.other_repo.repo_name
1420 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1420 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1421 pr_id, pr_repo, pr_nice_id,
1421 pr_id, pr_repo, pr_nice_id,
1422 stat.author]
1422 stat.author]
1423 return grouped
1423 return grouped
1424
1424
1425 def _repo_size(self):
1425 def _repo_size(self):
1426 from kallithea.lib import helpers as h
1426 from kallithea.lib import helpers as h
1427 log.debug('calculating repository size...')
1427 log.debug('calculating repository size...')
1428 return h.format_byte_size(self.scm_instance.size)
1428 return h.format_byte_size(self.scm_instance.size)
1429
1429
1430 #==========================================================================
1430 #==========================================================================
1431 # SCM CACHE INSTANCE
1431 # SCM CACHE INSTANCE
1432 #==========================================================================
1432 #==========================================================================
1433
1433
1434 def set_invalidate(self):
1434 def set_invalidate(self):
1435 """
1435 """
1436 Mark caches of this repo as invalid.
1436 Mark caches of this repo as invalid.
1437 """
1437 """
1438 CacheInvalidation.set_invalidate(self.repo_name)
1438 CacheInvalidation.set_invalidate(self.repo_name)
1439
1439
1440 @property
1440 @property
1441 def scm_instance(self):
1441 def scm_instance(self):
1442 import kallithea
1442 return self.scm_instance_cached()
1443 full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
1444 if full_cache:
1445 return self.scm_instance_cached()
1446 return self.__get_instance()
1447
1443
1448 def scm_instance_cached(self, valid_cache_keys=None):
1444 def scm_instance_cached(self, valid_cache_keys=None):
1449 @cache_region('long_term', 'scm_instance_cached')
1445 @cache_region('long_term', 'scm_instance_cached')
1450 def _c(repo_name): # repo_name is just for the cache key
1446 def _c(repo_name): # repo_name is just for the cache key
1451 log.debug('Creating new %s scm_instance and populating cache', repo_name)
1447 log.debug('Creating new %s scm_instance and populating cache', repo_name)
1452 return self.scm_instance_no_cache()
1448 return self.scm_instance_no_cache()
1453 rn = self.repo_name
1449 rn = self.repo_name
1454
1450
1455 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1451 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1456 if not valid:
1452 if not valid:
1457 log.debug('Cache for %s invalidated, getting new object', rn)
1453 log.debug('Cache for %s invalidated, getting new object', rn)
1458 region_invalidate(_c, None, 'scm_instance_cached', rn)
1454 region_invalidate(_c, None, 'scm_instance_cached', rn)
1459 else:
1455 else:
1460 log.debug('Trying to get scm_instance of %s from cache', rn)
1456 log.debug('Trying to get scm_instance of %s from cache', rn)
1461 return _c(rn)
1457 return _c(rn)
1462
1458
1463 def scm_instance_no_cache(self):
1459 def scm_instance_no_cache(self):
1464 repo_full_path = safe_str(self.repo_full_path)
1460 repo_full_path = safe_str(self.repo_full_path)
1465 alias = get_scm(repo_full_path)[0]
1461 alias = get_scm(repo_full_path)[0]
1466 log.debug('Creating instance of %s repository from %s',
1462 log.debug('Creating instance of %s repository from %s',
1467 alias, self.repo_full_path)
1463 alias, self.repo_full_path)
1468 backend = get_backend(alias)
1464 backend = get_backend(alias)
1469
1465
1470 if alias == 'hg':
1466 if alias == 'hg':
1471 repo = backend(repo_full_path, create=False,
1467 repo = backend(repo_full_path, create=False,
1472 baseui=self._ui)
1468 baseui=self._ui)
1473 else:
1469 else:
1474 repo = backend(repo_full_path, create=False)
1470 repo = backend(repo_full_path, create=False)
1475
1471
1476 return repo
1472 return repo
1477
1473
1478 def __json__(self):
1474 def __json__(self):
1479 return dict(landing_rev = self.landing_rev)
1475 return dict(landing_rev = self.landing_rev)
1480
1476
1481 class RepoGroup(Base, BaseModel):
1477 class RepoGroup(Base, BaseModel):
1482 __tablename__ = 'groups'
1478 __tablename__ = 'groups'
1483 __table_args__ = (
1479 __table_args__ = (
1484 UniqueConstraint('group_name', 'group_parent_id'),
1480 UniqueConstraint('group_name', 'group_parent_id'),
1485 CheckConstraint('group_id != group_parent_id'),
1481 CheckConstraint('group_id != group_parent_id'),
1486 _table_args_default_dict,
1482 _table_args_default_dict,
1487 )
1483 )
1488 __mapper_args__ = {'order_by': 'group_name'}
1484 __mapper_args__ = {'order_by': 'group_name'}
1489
1485
1490 SEP = ' &raquo; '
1486 SEP = ' &raquo; '
1491
1487
1492 group_id = Column(Integer(), unique=True, primary_key=True)
1488 group_id = Column(Integer(), unique=True, primary_key=True)
1493 group_name = Column(Unicode(255), nullable=False, unique=True)
1489 group_name = Column(Unicode(255), nullable=False, unique=True)
1494 group_parent_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True)
1490 group_parent_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True)
1495 group_description = Column(Unicode(10000), nullable=False)
1491 group_description = Column(Unicode(10000), nullable=False)
1496 enable_locking = Column(Boolean(), nullable=False, default=False)
1492 enable_locking = Column(Boolean(), nullable=False, default=False)
1497 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1493 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1498 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1494 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1499
1495
1500 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1496 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1501 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1497 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1502 parent_group = relationship('RepoGroup', remote_side=group_id)
1498 parent_group = relationship('RepoGroup', remote_side=group_id)
1503 user = relationship('User')
1499 user = relationship('User')
1504
1500
1505 def __init__(self, group_name='', parent_group=None):
1501 def __init__(self, group_name='', parent_group=None):
1506 self.group_name = group_name
1502 self.group_name = group_name
1507 self.parent_group = parent_group
1503 self.parent_group = parent_group
1508
1504
1509 def __unicode__(self):
1505 def __unicode__(self):
1510 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1506 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1511 self.group_name)
1507 self.group_name)
1512
1508
1513 @classmethod
1509 @classmethod
1514 def _generate_choice(cls, repo_group):
1510 def _generate_choice(cls, repo_group):
1515 """Return tuple with group_id and name as html literal"""
1511 """Return tuple with group_id and name as html literal"""
1516 from webhelpers.html import literal
1512 from webhelpers.html import literal
1517 if repo_group is None:
1513 if repo_group is None:
1518 return (-1, u'-- %s --' % _('top level'))
1514 return (-1, u'-- %s --' % _('top level'))
1519 return repo_group.group_id, literal(cls.SEP.join(repo_group.full_path_splitted))
1515 return repo_group.group_id, literal(cls.SEP.join(repo_group.full_path_splitted))
1520
1516
1521 @classmethod
1517 @classmethod
1522 def groups_choices(cls, groups):
1518 def groups_choices(cls, groups):
1523 """Return tuples with group_id and name as html literal."""
1519 """Return tuples with group_id and name as html literal."""
1524 return sorted((cls._generate_choice(g) for g in groups),
1520 return sorted((cls._generate_choice(g) for g in groups),
1525 key=lambda c: c[1].split(cls.SEP))
1521 key=lambda c: c[1].split(cls.SEP))
1526
1522
1527 @classmethod
1523 @classmethod
1528 def url_sep(cls):
1524 def url_sep(cls):
1529 return URL_SEP
1525 return URL_SEP
1530
1526
1531 @classmethod
1527 @classmethod
1532 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1528 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1533 if case_insensitive:
1529 if case_insensitive:
1534 gr = cls.query() \
1530 gr = cls.query() \
1535 .filter(func.lower(cls.group_name) == func.lower(group_name))
1531 .filter(func.lower(cls.group_name) == func.lower(group_name))
1536 else:
1532 else:
1537 gr = cls.query() \
1533 gr = cls.query() \
1538 .filter(cls.group_name == group_name)
1534 .filter(cls.group_name == group_name)
1539 if cache:
1535 if cache:
1540 gr = gr.options(FromCache(
1536 gr = gr.options(FromCache(
1541 "sql_cache_short",
1537 "sql_cache_short",
1542 "get_group_%s" % _hash_key(group_name)
1538 "get_group_%s" % _hash_key(group_name)
1543 )
1539 )
1544 )
1540 )
1545 return gr.scalar()
1541 return gr.scalar()
1546
1542
1547 @property
1543 @property
1548 def parents(self):
1544 def parents(self):
1549 parents_recursion_limit = 10
1545 parents_recursion_limit = 10
1550 groups = []
1546 groups = []
1551 if self.parent_group is None:
1547 if self.parent_group is None:
1552 return groups
1548 return groups
1553 cur_gr = self.parent_group
1549 cur_gr = self.parent_group
1554 groups.insert(0, cur_gr)
1550 groups.insert(0, cur_gr)
1555 cnt = 0
1551 cnt = 0
1556 while 1:
1552 while 1:
1557 cnt += 1
1553 cnt += 1
1558 gr = getattr(cur_gr, 'parent_group', None)
1554 gr = getattr(cur_gr, 'parent_group', None)
1559 cur_gr = cur_gr.parent_group
1555 cur_gr = cur_gr.parent_group
1560 if gr is None:
1556 if gr is None:
1561 break
1557 break
1562 if cnt == parents_recursion_limit:
1558 if cnt == parents_recursion_limit:
1563 # this will prevent accidental infinite loops
1559 # this will prevent accidental infinite loops
1564 log.error(('more than %s parents found for group %s, stopping '
1560 log.error(('more than %s parents found for group %s, stopping '
1565 'recursive parent fetching' % (parents_recursion_limit, self)))
1561 'recursive parent fetching' % (parents_recursion_limit, self)))
1566 break
1562 break
1567
1563
1568 groups.insert(0, gr)
1564 groups.insert(0, gr)
1569 return groups
1565 return groups
1570
1566
1571 @property
1567 @property
1572 def children(self):
1568 def children(self):
1573 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1569 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1574
1570
1575 @property
1571 @property
1576 def name(self):
1572 def name(self):
1577 return self.group_name.split(RepoGroup.url_sep())[-1]
1573 return self.group_name.split(RepoGroup.url_sep())[-1]
1578
1574
1579 @property
1575 @property
1580 def full_path(self):
1576 def full_path(self):
1581 return self.group_name
1577 return self.group_name
1582
1578
1583 @property
1579 @property
1584 def full_path_splitted(self):
1580 def full_path_splitted(self):
1585 return self.group_name.split(RepoGroup.url_sep())
1581 return self.group_name.split(RepoGroup.url_sep())
1586
1582
1587 @property
1583 @property
1588 def repositories(self):
1584 def repositories(self):
1589 return Repository.query() \
1585 return Repository.query() \
1590 .filter(Repository.group == self) \
1586 .filter(Repository.group == self) \
1591 .order_by(Repository.repo_name)
1587 .order_by(Repository.repo_name)
1592
1588
1593 @property
1589 @property
1594 def repositories_recursive_count(self):
1590 def repositories_recursive_count(self):
1595 cnt = self.repositories.count()
1591 cnt = self.repositories.count()
1596
1592
1597 def children_count(group):
1593 def children_count(group):
1598 cnt = 0
1594 cnt = 0
1599 for child in group.children:
1595 for child in group.children:
1600 cnt += child.repositories.count()
1596 cnt += child.repositories.count()
1601 cnt += children_count(child)
1597 cnt += children_count(child)
1602 return cnt
1598 return cnt
1603
1599
1604 return cnt + children_count(self)
1600 return cnt + children_count(self)
1605
1601
1606 def _recursive_objects(self, include_repos=True):
1602 def _recursive_objects(self, include_repos=True):
1607 all_ = []
1603 all_ = []
1608
1604
1609 def _get_members(root_gr):
1605 def _get_members(root_gr):
1610 if include_repos:
1606 if include_repos:
1611 for r in root_gr.repositories:
1607 for r in root_gr.repositories:
1612 all_.append(r)
1608 all_.append(r)
1613 childs = root_gr.children.all()
1609 childs = root_gr.children.all()
1614 if childs:
1610 if childs:
1615 for gr in childs:
1611 for gr in childs:
1616 all_.append(gr)
1612 all_.append(gr)
1617 _get_members(gr)
1613 _get_members(gr)
1618
1614
1619 _get_members(self)
1615 _get_members(self)
1620 return [self] + all_
1616 return [self] + all_
1621
1617
1622 def recursive_groups_and_repos(self):
1618 def recursive_groups_and_repos(self):
1623 """
1619 """
1624 Recursive return all groups, with repositories in those groups
1620 Recursive return all groups, with repositories in those groups
1625 """
1621 """
1626 return self._recursive_objects()
1622 return self._recursive_objects()
1627
1623
1628 def recursive_groups(self):
1624 def recursive_groups(self):
1629 """
1625 """
1630 Returns all children groups for this group including children of children
1626 Returns all children groups for this group including children of children
1631 """
1627 """
1632 return self._recursive_objects(include_repos=False)
1628 return self._recursive_objects(include_repos=False)
1633
1629
1634 def get_new_name(self, group_name):
1630 def get_new_name(self, group_name):
1635 """
1631 """
1636 returns new full group name based on parent and new name
1632 returns new full group name based on parent and new name
1637
1633
1638 :param group_name:
1634 :param group_name:
1639 """
1635 """
1640 path_prefix = (self.parent_group.full_path_splitted if
1636 path_prefix = (self.parent_group.full_path_splitted if
1641 self.parent_group else [])
1637 self.parent_group else [])
1642 return RepoGroup.url_sep().join(path_prefix + [group_name])
1638 return RepoGroup.url_sep().join(path_prefix + [group_name])
1643
1639
1644 def get_api_data(self):
1640 def get_api_data(self):
1645 """
1641 """
1646 Common function for generating api data
1642 Common function for generating api data
1647
1643
1648 """
1644 """
1649 group = self
1645 group = self
1650 data = dict(
1646 data = dict(
1651 group_id=group.group_id,
1647 group_id=group.group_id,
1652 group_name=group.group_name,
1648 group_name=group.group_name,
1653 group_description=group.group_description,
1649 group_description=group.group_description,
1654 parent_group=group.parent_group.group_name if group.parent_group else None,
1650 parent_group=group.parent_group.group_name if group.parent_group else None,
1655 repositories=[x.repo_name for x in group.repositories],
1651 repositories=[x.repo_name for x in group.repositories],
1656 owner=group.user.username
1652 owner=group.user.username
1657 )
1653 )
1658 return data
1654 return data
1659
1655
1660
1656
1661 class Permission(Base, BaseModel):
1657 class Permission(Base, BaseModel):
1662 __tablename__ = 'permissions'
1658 __tablename__ = 'permissions'
1663 __table_args__ = (
1659 __table_args__ = (
1664 Index('p_perm_name_idx', 'permission_name'),
1660 Index('p_perm_name_idx', 'permission_name'),
1665 _table_args_default_dict,
1661 _table_args_default_dict,
1666 )
1662 )
1667
1663
1668 PERMS = [
1664 PERMS = [
1669 ('hg.admin', _('Kallithea Administrator')),
1665 ('hg.admin', _('Kallithea Administrator')),
1670
1666
1671 ('repository.none', _('Default user has no access to new repositories')),
1667 ('repository.none', _('Default user has no access to new repositories')),
1672 ('repository.read', _('Default user has read access to new repositories')),
1668 ('repository.read', _('Default user has read access to new repositories')),
1673 ('repository.write', _('Default user has write access to new repositories')),
1669 ('repository.write', _('Default user has write access to new repositories')),
1674 ('repository.admin', _('Default user has admin access to new repositories')),
1670 ('repository.admin', _('Default user has admin access to new repositories')),
1675
1671
1676 ('group.none', _('Default user has no access to new repository groups')),
1672 ('group.none', _('Default user has no access to new repository groups')),
1677 ('group.read', _('Default user has read access to new repository groups')),
1673 ('group.read', _('Default user has read access to new repository groups')),
1678 ('group.write', _('Default user has write access to new repository groups')),
1674 ('group.write', _('Default user has write access to new repository groups')),
1679 ('group.admin', _('Default user has admin access to new repository groups')),
1675 ('group.admin', _('Default user has admin access to new repository groups')),
1680
1676
1681 ('usergroup.none', _('Default user has no access to new user groups')),
1677 ('usergroup.none', _('Default user has no access to new user groups')),
1682 ('usergroup.read', _('Default user has read access to new user groups')),
1678 ('usergroup.read', _('Default user has read access to new user groups')),
1683 ('usergroup.write', _('Default user has write access to new user groups')),
1679 ('usergroup.write', _('Default user has write access to new user groups')),
1684 ('usergroup.admin', _('Default user has admin access to new user groups')),
1680 ('usergroup.admin', _('Default user has admin access to new user groups')),
1685
1681
1686 ('hg.repogroup.create.false', _('Only admins can create repository groups')),
1682 ('hg.repogroup.create.false', _('Only admins can create repository groups')),
1687 ('hg.repogroup.create.true', _('Non-admins can create repository groups')),
1683 ('hg.repogroup.create.true', _('Non-admins can create repository groups')),
1688
1684
1689 ('hg.usergroup.create.false', _('Only admins can create user groups')),
1685 ('hg.usergroup.create.false', _('Only admins can create user groups')),
1690 ('hg.usergroup.create.true', _('Non-admins can create user groups')),
1686 ('hg.usergroup.create.true', _('Non-admins can create user groups')),
1691
1687
1692 ('hg.create.none', _('Only admins can create top level repositories')),
1688 ('hg.create.none', _('Only admins can create top level repositories')),
1693 ('hg.create.repository', _('Non-admins can create top level repositories')),
1689 ('hg.create.repository', _('Non-admins can create top level repositories')),
1694
1690
1695 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
1691 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
1696 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
1692 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
1697
1693
1698 ('hg.fork.none', _('Only admins can fork repositories')),
1694 ('hg.fork.none', _('Only admins can fork repositories')),
1699 ('hg.fork.repository', _('Non-admins can fork repositories')),
1695 ('hg.fork.repository', _('Non-admins can fork repositories')),
1700
1696
1701 ('hg.register.none', _('Registration disabled')),
1697 ('hg.register.none', _('Registration disabled')),
1702 ('hg.register.manual_activate', _('User registration with manual account activation')),
1698 ('hg.register.manual_activate', _('User registration with manual account activation')),
1703 ('hg.register.auto_activate', _('User registration with automatic account activation')),
1699 ('hg.register.auto_activate', _('User registration with automatic account activation')),
1704
1700
1705 ('hg.extern_activate.manual', _('Manual activation of external account')),
1701 ('hg.extern_activate.manual', _('Manual activation of external account')),
1706 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1702 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1707 ]
1703 ]
1708
1704
1709 #definition of system default permissions for DEFAULT user
1705 #definition of system default permissions for DEFAULT user
1710 DEFAULT_USER_PERMISSIONS = [
1706 DEFAULT_USER_PERMISSIONS = [
1711 'repository.read',
1707 'repository.read',
1712 'group.read',
1708 'group.read',
1713 'usergroup.read',
1709 'usergroup.read',
1714 'hg.create.repository',
1710 'hg.create.repository',
1715 'hg.create.write_on_repogroup.true',
1711 'hg.create.write_on_repogroup.true',
1716 'hg.fork.repository',
1712 'hg.fork.repository',
1717 'hg.register.manual_activate',
1713 'hg.register.manual_activate',
1718 'hg.extern_activate.auto',
1714 'hg.extern_activate.auto',
1719 ]
1715 ]
1720
1716
1721 # defines which permissions are more important higher the more important
1717 # defines which permissions are more important higher the more important
1722 # Weight defines which permissions are more important.
1718 # Weight defines which permissions are more important.
1723 # The higher number the more important.
1719 # The higher number the more important.
1724 PERM_WEIGHTS = {
1720 PERM_WEIGHTS = {
1725 'repository.none': 0,
1721 'repository.none': 0,
1726 'repository.read': 1,
1722 'repository.read': 1,
1727 'repository.write': 3,
1723 'repository.write': 3,
1728 'repository.admin': 4,
1724 'repository.admin': 4,
1729
1725
1730 'group.none': 0,
1726 'group.none': 0,
1731 'group.read': 1,
1727 'group.read': 1,
1732 'group.write': 3,
1728 'group.write': 3,
1733 'group.admin': 4,
1729 'group.admin': 4,
1734
1730
1735 'usergroup.none': 0,
1731 'usergroup.none': 0,
1736 'usergroup.read': 1,
1732 'usergroup.read': 1,
1737 'usergroup.write': 3,
1733 'usergroup.write': 3,
1738 'usergroup.admin': 4,
1734 'usergroup.admin': 4,
1739
1735
1740 'hg.repogroup.create.false': 0,
1736 'hg.repogroup.create.false': 0,
1741 'hg.repogroup.create.true': 1,
1737 'hg.repogroup.create.true': 1,
1742
1738
1743 'hg.usergroup.create.false': 0,
1739 'hg.usergroup.create.false': 0,
1744 'hg.usergroup.create.true': 1,
1740 'hg.usergroup.create.true': 1,
1745
1741
1746 'hg.fork.none': 0,
1742 'hg.fork.none': 0,
1747 'hg.fork.repository': 1,
1743 'hg.fork.repository': 1,
1748
1744
1749 'hg.create.none': 0,
1745 'hg.create.none': 0,
1750 'hg.create.repository': 1
1746 'hg.create.repository': 1
1751 }
1747 }
1752
1748
1753 permission_id = Column(Integer(), unique=True, primary_key=True)
1749 permission_id = Column(Integer(), unique=True, primary_key=True)
1754 permission_name = Column(String(255), nullable=False)
1750 permission_name = Column(String(255), nullable=False)
1755
1751
1756 def __unicode__(self):
1752 def __unicode__(self):
1757 return u"<%s('%s:%s')>" % (
1753 return u"<%s('%s:%s')>" % (
1758 self.__class__.__name__, self.permission_id, self.permission_name
1754 self.__class__.__name__, self.permission_id, self.permission_name
1759 )
1755 )
1760
1756
1761 @classmethod
1757 @classmethod
1762 def get_by_key(cls, key):
1758 def get_by_key(cls, key):
1763 return cls.query().filter(cls.permission_name == key).scalar()
1759 return cls.query().filter(cls.permission_name == key).scalar()
1764
1760
1765 @classmethod
1761 @classmethod
1766 def get_default_perms(cls, default_user_id):
1762 def get_default_perms(cls, default_user_id):
1767 q = Session().query(UserRepoToPerm, Repository, cls) \
1763 q = Session().query(UserRepoToPerm, Repository, cls) \
1768 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id)) \
1764 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id)) \
1769 .join((cls, UserRepoToPerm.permission_id == cls.permission_id)) \
1765 .join((cls, UserRepoToPerm.permission_id == cls.permission_id)) \
1770 .filter(UserRepoToPerm.user_id == default_user_id)
1766 .filter(UserRepoToPerm.user_id == default_user_id)
1771
1767
1772 return q.all()
1768 return q.all()
1773
1769
1774 @classmethod
1770 @classmethod
1775 def get_default_group_perms(cls, default_user_id):
1771 def get_default_group_perms(cls, default_user_id):
1776 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls) \
1772 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls) \
1777 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id)) \
1773 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id)) \
1778 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id)) \
1774 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id)) \
1779 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1775 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1780
1776
1781 return q.all()
1777 return q.all()
1782
1778
1783 @classmethod
1779 @classmethod
1784 def get_default_user_group_perms(cls, default_user_id):
1780 def get_default_user_group_perms(cls, default_user_id):
1785 q = Session().query(UserUserGroupToPerm, UserGroup, cls) \
1781 q = Session().query(UserUserGroupToPerm, UserGroup, cls) \
1786 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id)) \
1782 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id)) \
1787 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id)) \
1783 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id)) \
1788 .filter(UserUserGroupToPerm.user_id == default_user_id)
1784 .filter(UserUserGroupToPerm.user_id == default_user_id)
1789
1785
1790 return q.all()
1786 return q.all()
1791
1787
1792
1788
1793 class UserRepoToPerm(Base, BaseModel):
1789 class UserRepoToPerm(Base, BaseModel):
1794 __tablename__ = 'repo_to_perm'
1790 __tablename__ = 'repo_to_perm'
1795 __table_args__ = (
1791 __table_args__ = (
1796 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1792 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1797 _table_args_default_dict,
1793 _table_args_default_dict,
1798 )
1794 )
1799
1795
1800 repo_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1796 repo_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1801 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1797 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1802 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1798 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1803 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1799 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1804
1800
1805 user = relationship('User')
1801 user = relationship('User')
1806 repository = relationship('Repository')
1802 repository = relationship('Repository')
1807 permission = relationship('Permission')
1803 permission = relationship('Permission')
1808
1804
1809 @classmethod
1805 @classmethod
1810 def create(cls, user, repository, permission):
1806 def create(cls, user, repository, permission):
1811 n = cls()
1807 n = cls()
1812 n.user = user
1808 n.user = user
1813 n.repository = repository
1809 n.repository = repository
1814 n.permission = permission
1810 n.permission = permission
1815 Session().add(n)
1811 Session().add(n)
1816 return n
1812 return n
1817
1813
1818 def __unicode__(self):
1814 def __unicode__(self):
1819 return u'<%s => %s >' % (self.user, self.repository)
1815 return u'<%s => %s >' % (self.user, self.repository)
1820
1816
1821
1817
1822 class UserUserGroupToPerm(Base, BaseModel):
1818 class UserUserGroupToPerm(Base, BaseModel):
1823 __tablename__ = 'user_user_group_to_perm'
1819 __tablename__ = 'user_user_group_to_perm'
1824 __table_args__ = (
1820 __table_args__ = (
1825 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1821 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1826 _table_args_default_dict,
1822 _table_args_default_dict,
1827 )
1823 )
1828
1824
1829 user_user_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1825 user_user_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1830 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1826 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1831 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1827 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1832 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1828 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1833
1829
1834 user = relationship('User')
1830 user = relationship('User')
1835 user_group = relationship('UserGroup')
1831 user_group = relationship('UserGroup')
1836 permission = relationship('Permission')
1832 permission = relationship('Permission')
1837
1833
1838 @classmethod
1834 @classmethod
1839 def create(cls, user, user_group, permission):
1835 def create(cls, user, user_group, permission):
1840 n = cls()
1836 n = cls()
1841 n.user = user
1837 n.user = user
1842 n.user_group = user_group
1838 n.user_group = user_group
1843 n.permission = permission
1839 n.permission = permission
1844 Session().add(n)
1840 Session().add(n)
1845 return n
1841 return n
1846
1842
1847 def __unicode__(self):
1843 def __unicode__(self):
1848 return u'<%s => %s >' % (self.user, self.user_group)
1844 return u'<%s => %s >' % (self.user, self.user_group)
1849
1845
1850
1846
1851 class UserToPerm(Base, BaseModel):
1847 class UserToPerm(Base, BaseModel):
1852 __tablename__ = 'user_to_perm'
1848 __tablename__ = 'user_to_perm'
1853 __table_args__ = (
1849 __table_args__ = (
1854 UniqueConstraint('user_id', 'permission_id'),
1850 UniqueConstraint('user_id', 'permission_id'),
1855 _table_args_default_dict,
1851 _table_args_default_dict,
1856 )
1852 )
1857
1853
1858 user_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1854 user_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1859 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1855 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1860 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1856 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1861
1857
1862 user = relationship('User')
1858 user = relationship('User')
1863 permission = relationship('Permission')
1859 permission = relationship('Permission')
1864
1860
1865 def __unicode__(self):
1861 def __unicode__(self):
1866 return u'<%s => %s >' % (self.user, self.permission)
1862 return u'<%s => %s >' % (self.user, self.permission)
1867
1863
1868
1864
1869 class UserGroupRepoToPerm(Base, BaseModel):
1865 class UserGroupRepoToPerm(Base, BaseModel):
1870 __tablename__ = 'users_group_repo_to_perm'
1866 __tablename__ = 'users_group_repo_to_perm'
1871 __table_args__ = (
1867 __table_args__ = (
1872 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1868 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1873 _table_args_default_dict,
1869 _table_args_default_dict,
1874 )
1870 )
1875
1871
1876 users_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1872 users_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1877 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1873 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1878 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1874 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1879 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1875 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1880
1876
1881 users_group = relationship('UserGroup')
1877 users_group = relationship('UserGroup')
1882 permission = relationship('Permission')
1878 permission = relationship('Permission')
1883 repository = relationship('Repository')
1879 repository = relationship('Repository')
1884
1880
1885 @classmethod
1881 @classmethod
1886 def create(cls, users_group, repository, permission):
1882 def create(cls, users_group, repository, permission):
1887 n = cls()
1883 n = cls()
1888 n.users_group = users_group
1884 n.users_group = users_group
1889 n.repository = repository
1885 n.repository = repository
1890 n.permission = permission
1886 n.permission = permission
1891 Session().add(n)
1887 Session().add(n)
1892 return n
1888 return n
1893
1889
1894 def __unicode__(self):
1890 def __unicode__(self):
1895 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1891 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1896
1892
1897
1893
1898 class UserGroupUserGroupToPerm(Base, BaseModel):
1894 class UserGroupUserGroupToPerm(Base, BaseModel):
1899 __tablename__ = 'user_group_user_group_to_perm'
1895 __tablename__ = 'user_group_user_group_to_perm'
1900 __table_args__ = (
1896 __table_args__ = (
1901 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1897 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1902 CheckConstraint('target_user_group_id != user_group_id'),
1898 CheckConstraint('target_user_group_id != user_group_id'),
1903 _table_args_default_dict,
1899 _table_args_default_dict,
1904 )
1900 )
1905
1901
1906 user_group_user_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1902 user_group_user_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1907 target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1903 target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1908 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1904 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1909 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1905 user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1910
1906
1911 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1907 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1912 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1908 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1913 permission = relationship('Permission')
1909 permission = relationship('Permission')
1914
1910
1915 @classmethod
1911 @classmethod
1916 def create(cls, target_user_group, user_group, permission):
1912 def create(cls, target_user_group, user_group, permission):
1917 n = cls()
1913 n = cls()
1918 n.target_user_group = target_user_group
1914 n.target_user_group = target_user_group
1919 n.user_group = user_group
1915 n.user_group = user_group
1920 n.permission = permission
1916 n.permission = permission
1921 Session().add(n)
1917 Session().add(n)
1922 return n
1918 return n
1923
1919
1924 def __unicode__(self):
1920 def __unicode__(self):
1925 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1921 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1926
1922
1927
1923
1928 class UserGroupToPerm(Base, BaseModel):
1924 class UserGroupToPerm(Base, BaseModel):
1929 __tablename__ = 'users_group_to_perm'
1925 __tablename__ = 'users_group_to_perm'
1930 __table_args__ = (
1926 __table_args__ = (
1931 UniqueConstraint('users_group_id', 'permission_id',),
1927 UniqueConstraint('users_group_id', 'permission_id',),
1932 _table_args_default_dict,
1928 _table_args_default_dict,
1933 )
1929 )
1934
1930
1935 users_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1931 users_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1936 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1932 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1937 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1933 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1938
1934
1939 users_group = relationship('UserGroup')
1935 users_group = relationship('UserGroup')
1940 permission = relationship('Permission')
1936 permission = relationship('Permission')
1941
1937
1942
1938
1943 class UserRepoGroupToPerm(Base, BaseModel):
1939 class UserRepoGroupToPerm(Base, BaseModel):
1944 __tablename__ = 'user_repo_group_to_perm'
1940 __tablename__ = 'user_repo_group_to_perm'
1945 __table_args__ = (
1941 __table_args__ = (
1946 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1942 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1947 _table_args_default_dict,
1943 _table_args_default_dict,
1948 )
1944 )
1949
1945
1950 group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1946 group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1951 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1947 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
1952 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
1948 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
1953 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1949 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1954
1950
1955 user = relationship('User')
1951 user = relationship('User')
1956 group = relationship('RepoGroup')
1952 group = relationship('RepoGroup')
1957 permission = relationship('Permission')
1953 permission = relationship('Permission')
1958
1954
1959 @classmethod
1955 @classmethod
1960 def create(cls, user, repository_group, permission):
1956 def create(cls, user, repository_group, permission):
1961 n = cls()
1957 n = cls()
1962 n.user = user
1958 n.user = user
1963 n.group = repository_group
1959 n.group = repository_group
1964 n.permission = permission
1960 n.permission = permission
1965 Session().add(n)
1961 Session().add(n)
1966 return n
1962 return n
1967
1963
1968
1964
1969 class UserGroupRepoGroupToPerm(Base, BaseModel):
1965 class UserGroupRepoGroupToPerm(Base, BaseModel):
1970 __tablename__ = 'users_group_repo_group_to_perm'
1966 __tablename__ = 'users_group_repo_group_to_perm'
1971 __table_args__ = (
1967 __table_args__ = (
1972 UniqueConstraint('users_group_id', 'group_id'),
1968 UniqueConstraint('users_group_id', 'group_id'),
1973 _table_args_default_dict,
1969 _table_args_default_dict,
1974 )
1970 )
1975
1971
1976 users_group_repo_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1972 users_group_repo_group_to_perm_id = Column(Integer(), unique=True, primary_key=True)
1977 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1973 users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
1978 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
1974 group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
1979 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1975 permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
1980
1976
1981 users_group = relationship('UserGroup')
1977 users_group = relationship('UserGroup')
1982 permission = relationship('Permission')
1978 permission = relationship('Permission')
1983 group = relationship('RepoGroup')
1979 group = relationship('RepoGroup')
1984
1980
1985 @classmethod
1981 @classmethod
1986 def create(cls, user_group, repository_group, permission):
1982 def create(cls, user_group, repository_group, permission):
1987 n = cls()
1983 n = cls()
1988 n.users_group = user_group
1984 n.users_group = user_group
1989 n.group = repository_group
1985 n.group = repository_group
1990 n.permission = permission
1986 n.permission = permission
1991 Session().add(n)
1987 Session().add(n)
1992 return n
1988 return n
1993
1989
1994
1990
1995 class Statistics(Base, BaseModel):
1991 class Statistics(Base, BaseModel):
1996 __tablename__ = 'statistics'
1992 __tablename__ = 'statistics'
1997 __table_args__ = (
1993 __table_args__ = (
1998 _table_args_default_dict,
1994 _table_args_default_dict,
1999 )
1995 )
2000
1996
2001 stat_id = Column(Integer(), unique=True, primary_key=True)
1997 stat_id = Column(Integer(), unique=True, primary_key=True)
2002 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True)
1998 repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True)
2003 stat_on_revision = Column(Integer(), nullable=False)
1999 stat_on_revision = Column(Integer(), nullable=False)
2004 commit_activity = Column(LargeBinary(1000000), nullable=False)#JSON data
2000 commit_activity = Column(LargeBinary(1000000), nullable=False)#JSON data
2005 commit_activity_combined = Column(LargeBinary(), nullable=False)#JSON data
2001 commit_activity_combined = Column(LargeBinary(), nullable=False)#JSON data
2006 languages = Column(LargeBinary(1000000), nullable=False)#JSON data
2002 languages = Column(LargeBinary(1000000), nullable=False)#JSON data
2007
2003
2008 repository = relationship('Repository', single_parent=True)
2004 repository = relationship('Repository', single_parent=True)
2009
2005
2010
2006
2011 class UserFollowing(Base, BaseModel):
2007 class UserFollowing(Base, BaseModel):
2012 __tablename__ = 'user_followings'
2008 __tablename__ = 'user_followings'
2013 __table_args__ = (
2009 __table_args__ = (
2014 UniqueConstraint('user_id', 'follows_repository_id'),
2010 UniqueConstraint('user_id', 'follows_repository_id'),
2015 UniqueConstraint('user_id', 'follows_user_id'),
2011 UniqueConstraint('user_id', 'follows_user_id'),
2016 _table_args_default_dict,
2012 _table_args_default_dict,
2017 )
2013 )
2018
2014
2019 user_following_id = Column(Integer(), unique=True, primary_key=True)
2015 user_following_id = Column(Integer(), unique=True, primary_key=True)
2020 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2016 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2021 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
2017 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
2022 follows_user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
2018 follows_user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
2023 follows_from = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2019 follows_from = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2024
2020
2025 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2021 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2026
2022
2027 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2023 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2028 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2024 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2029
2025
2030 @classmethod
2026 @classmethod
2031 def get_repo_followers(cls, repo_id):
2027 def get_repo_followers(cls, repo_id):
2032 return cls.query().filter(cls.follows_repo_id == repo_id)
2028 return cls.query().filter(cls.follows_repo_id == repo_id)
2033
2029
2034
2030
2035 class CacheInvalidation(Base, BaseModel):
2031 class CacheInvalidation(Base, BaseModel):
2036 __tablename__ = 'cache_invalidation'
2032 __tablename__ = 'cache_invalidation'
2037 __table_args__ = (
2033 __table_args__ = (
2038 Index('key_idx', 'cache_key'),
2034 Index('key_idx', 'cache_key'),
2039 _table_args_default_dict,
2035 _table_args_default_dict,
2040 )
2036 )
2041
2037
2042 # cache_id, not used
2038 # cache_id, not used
2043 cache_id = Column(Integer(), unique=True, primary_key=True)
2039 cache_id = Column(Integer(), unique=True, primary_key=True)
2044 # cache_key as created by _get_cache_key
2040 # cache_key as created by _get_cache_key
2045 cache_key = Column(Unicode(255), nullable=False, unique=True)
2041 cache_key = Column(Unicode(255), nullable=False, unique=True)
2046 # cache_args is a repo_name
2042 # cache_args is a repo_name
2047 cache_args = Column(Unicode(255), nullable=False)
2043 cache_args = Column(Unicode(255), nullable=False)
2048 # instance sets cache_active True when it is caching, other instances set
2044 # instance sets cache_active True when it is caching, other instances set
2049 # cache_active to False to indicate that this cache is invalid
2045 # cache_active to False to indicate that this cache is invalid
2050 cache_active = Column(Boolean(), nullable=False, default=False)
2046 cache_active = Column(Boolean(), nullable=False, default=False)
2051
2047
2052 def __init__(self, cache_key, repo_name=''):
2048 def __init__(self, cache_key, repo_name=''):
2053 self.cache_key = cache_key
2049 self.cache_key = cache_key
2054 self.cache_args = repo_name
2050 self.cache_args = repo_name
2055 self.cache_active = False
2051 self.cache_active = False
2056
2052
2057 def __unicode__(self):
2053 def __unicode__(self):
2058 return u"<%s('%s:%s[%s]')>" % (
2054 return u"<%s('%s:%s[%s]')>" % (
2059 self.__class__.__name__,
2055 self.__class__.__name__,
2060 self.cache_id, self.cache_key, self.cache_active)
2056 self.cache_id, self.cache_key, self.cache_active)
2061
2057
2062 def _cache_key_partition(self):
2058 def _cache_key_partition(self):
2063 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2059 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2064 return prefix, repo_name, suffix
2060 return prefix, repo_name, suffix
2065
2061
2066 def get_prefix(self):
2062 def get_prefix(self):
2067 """
2063 """
2068 get prefix that might have been used in _get_cache_key to
2064 get prefix that might have been used in _get_cache_key to
2069 generate self.cache_key. Only used for informational purposes
2065 generate self.cache_key. Only used for informational purposes
2070 in repo_edit.html.
2066 in repo_edit.html.
2071 """
2067 """
2072 # prefix, repo_name, suffix
2068 # prefix, repo_name, suffix
2073 return self._cache_key_partition()[0]
2069 return self._cache_key_partition()[0]
2074
2070
2075 def get_suffix(self):
2071 def get_suffix(self):
2076 """
2072 """
2077 get suffix that might have been used in _get_cache_key to
2073 get suffix that might have been used in _get_cache_key to
2078 generate self.cache_key. Only used for informational purposes
2074 generate self.cache_key. Only used for informational purposes
2079 in repo_edit.html.
2075 in repo_edit.html.
2080 """
2076 """
2081 # prefix, repo_name, suffix
2077 # prefix, repo_name, suffix
2082 return self._cache_key_partition()[2]
2078 return self._cache_key_partition()[2]
2083
2079
2084 @classmethod
2080 @classmethod
2085 def clear_cache(cls):
2081 def clear_cache(cls):
2086 """
2082 """
2087 Delete all cache keys from database.
2083 Delete all cache keys from database.
2088 Should only be run when all instances are down and all entries thus stale.
2084 Should only be run when all instances are down and all entries thus stale.
2089 """
2085 """
2090 cls.query().delete()
2086 cls.query().delete()
2091 Session().commit()
2087 Session().commit()
2092
2088
2093 @classmethod
2089 @classmethod
2094 def _get_cache_key(cls, key):
2090 def _get_cache_key(cls, key):
2095 """
2091 """
2096 Wrapper for generating a unique cache key for this instance and "key".
2092 Wrapper for generating a unique cache key for this instance and "key".
2097 key must / will start with a repo_name which will be stored in .cache_args .
2093 key must / will start with a repo_name which will be stored in .cache_args .
2098 """
2094 """
2099 import kallithea
2095 import kallithea
2100 prefix = kallithea.CONFIG.get('instance_id', '')
2096 prefix = kallithea.CONFIG.get('instance_id', '')
2101 return "%s%s" % (prefix, key)
2097 return "%s%s" % (prefix, key)
2102
2098
2103 @classmethod
2099 @classmethod
2104 def set_invalidate(cls, repo_name):
2100 def set_invalidate(cls, repo_name):
2105 """
2101 """
2106 Mark all caches of a repo as invalid in the database.
2102 Mark all caches of a repo as invalid in the database.
2107 """
2103 """
2108 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
2104 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
2109 log.debug('for repo %s got %s invalidation objects',
2105 log.debug('for repo %s got %s invalidation objects',
2110 safe_str(repo_name), inv_objs)
2106 safe_str(repo_name), inv_objs)
2111
2107
2112 for inv_obj in inv_objs:
2108 for inv_obj in inv_objs:
2113 log.debug('marking %s key for invalidation based on repo_name=%s',
2109 log.debug('marking %s key for invalidation based on repo_name=%s',
2114 inv_obj, safe_str(repo_name))
2110 inv_obj, safe_str(repo_name))
2115 Session().delete(inv_obj)
2111 Session().delete(inv_obj)
2116 Session().commit()
2112 Session().commit()
2117
2113
2118 @classmethod
2114 @classmethod
2119 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
2115 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
2120 """
2116 """
2121 Mark this cache key as active and currently cached.
2117 Mark this cache key as active and currently cached.
2122 Return True if the existing cache registration still was valid.
2118 Return True if the existing cache registration still was valid.
2123 Return False to indicate that it had been invalidated and caches should be refreshed.
2119 Return False to indicate that it had been invalidated and caches should be refreshed.
2124 """
2120 """
2125
2121
2126 key = (repo_name + '_' + kind) if kind else repo_name
2122 key = (repo_name + '_' + kind) if kind else repo_name
2127 cache_key = cls._get_cache_key(key)
2123 cache_key = cls._get_cache_key(key)
2128
2124
2129 if valid_cache_keys and cache_key in valid_cache_keys:
2125 if valid_cache_keys and cache_key in valid_cache_keys:
2130 return True
2126 return True
2131
2127
2132 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2128 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2133 if inv_obj is None:
2129 if inv_obj is None:
2134 inv_obj = cls(cache_key, repo_name)
2130 inv_obj = cls(cache_key, repo_name)
2135 elif inv_obj.cache_active:
2131 elif inv_obj.cache_active:
2136 return True
2132 return True
2137 inv_obj.cache_active = True
2133 inv_obj.cache_active = True
2138 Session().add(inv_obj)
2134 Session().add(inv_obj)
2139 try:
2135 try:
2140 Session().commit()
2136 Session().commit()
2141 except sqlalchemy.exc.IntegrityError:
2137 except sqlalchemy.exc.IntegrityError:
2142 log.error('commit of CacheInvalidation failed - retrying')
2138 log.error('commit of CacheInvalidation failed - retrying')
2143 Session().rollback()
2139 Session().rollback()
2144 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2140 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2145 if inv_obj is None:
2141 if inv_obj is None:
2146 log.error('failed to create CacheInvalidation entry')
2142 log.error('failed to create CacheInvalidation entry')
2147 # TODO: fail badly?
2143 # TODO: fail badly?
2148 # else: TOCTOU - another thread added the key at the same time; no further action required
2144 # else: TOCTOU - another thread added the key at the same time; no further action required
2149 return False
2145 return False
2150
2146
2151 @classmethod
2147 @classmethod
2152 def get_valid_cache_keys(cls):
2148 def get_valid_cache_keys(cls):
2153 """
2149 """
2154 Return opaque object with information of which caches still are valid
2150 Return opaque object with information of which caches still are valid
2155 and can be used without checking for invalidation.
2151 and can be used without checking for invalidation.
2156 """
2152 """
2157 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
2153 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
2158
2154
2159
2155
2160 class ChangesetComment(Base, BaseModel):
2156 class ChangesetComment(Base, BaseModel):
2161 __tablename__ = 'changeset_comments'
2157 __tablename__ = 'changeset_comments'
2162 __table_args__ = (
2158 __table_args__ = (
2163 Index('cc_revision_idx', 'revision'),
2159 Index('cc_revision_idx', 'revision'),
2164 Index('cc_pull_request_id_idx', 'pull_request_id'),
2160 Index('cc_pull_request_id_idx', 'pull_request_id'),
2165 _table_args_default_dict,
2161 _table_args_default_dict,
2166 )
2162 )
2167
2163
2168 comment_id = Column(Integer(), unique=True, primary_key=True)
2164 comment_id = Column(Integer(), unique=True, primary_key=True)
2169 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2165 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2170 revision = Column(String(40), nullable=True)
2166 revision = Column(String(40), nullable=True)
2171 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2167 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2172 line_no = Column(Unicode(10), nullable=True)
2168 line_no = Column(Unicode(10), nullable=True)
2173 f_path = Column(Unicode(1000), nullable=True)
2169 f_path = Column(Unicode(1000), nullable=True)
2174 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2170 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2175 text = Column(UnicodeText(25000), nullable=False)
2171 text = Column(UnicodeText(25000), nullable=False)
2176 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2172 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2177 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2173 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2178
2174
2179 author = relationship('User')
2175 author = relationship('User')
2180 repo = relationship('Repository')
2176 repo = relationship('Repository')
2181 # status_change is frequently used directly in templates - make it a lazy
2177 # status_change is frequently used directly in templates - make it a lazy
2182 # join to avoid fetching each related ChangesetStatus on demand.
2178 # join to avoid fetching each related ChangesetStatus on demand.
2183 # There will only be one ChangesetStatus referencing each comment so the join will not explode.
2179 # There will only be one ChangesetStatus referencing each comment so the join will not explode.
2184 status_change = relationship('ChangesetStatus',
2180 status_change = relationship('ChangesetStatus',
2185 cascade="all, delete-orphan", lazy='joined')
2181 cascade="all, delete-orphan", lazy='joined')
2186 pull_request = relationship('PullRequest')
2182 pull_request = relationship('PullRequest')
2187
2183
2188 @classmethod
2184 @classmethod
2189 def get_users(cls, revision=None, pull_request_id=None):
2185 def get_users(cls, revision=None, pull_request_id=None):
2190 """
2186 """
2191 Returns user associated with this ChangesetComment. ie those
2187 Returns user associated with this ChangesetComment. ie those
2192 who actually commented
2188 who actually commented
2193
2189
2194 :param cls:
2190 :param cls:
2195 :param revision:
2191 :param revision:
2196 """
2192 """
2197 q = Session().query(User) \
2193 q = Session().query(User) \
2198 .join(ChangesetComment.author)
2194 .join(ChangesetComment.author)
2199 if revision is not None:
2195 if revision is not None:
2200 q = q.filter(cls.revision == revision)
2196 q = q.filter(cls.revision == revision)
2201 elif pull_request_id is not None:
2197 elif pull_request_id is not None:
2202 q = q.filter(cls.pull_request_id == pull_request_id)
2198 q = q.filter(cls.pull_request_id == pull_request_id)
2203 return q.all()
2199 return q.all()
2204
2200
2205 def url(self):
2201 def url(self):
2206 anchor = "comment-%s" % self.comment_id
2202 anchor = "comment-%s" % self.comment_id
2207 import kallithea.lib.helpers as h
2203 import kallithea.lib.helpers as h
2208 if self.revision:
2204 if self.revision:
2209 return h.url('changeset_home', repo_name=self.repo.repo_name, revision=self.revision, anchor=anchor)
2205 return h.url('changeset_home', repo_name=self.repo.repo_name, revision=self.revision, anchor=anchor)
2210 elif self.pull_request_id is not None:
2206 elif self.pull_request_id is not None:
2211 return self.pull_request.url(anchor=anchor)
2207 return self.pull_request.url(anchor=anchor)
2212
2208
2213 def deletable(self):
2209 def deletable(self):
2214 return self.created_on > datetime.datetime.now() - datetime.timedelta(minutes=5)
2210 return self.created_on > datetime.datetime.now() - datetime.timedelta(minutes=5)
2215
2211
2216
2212
2217 class ChangesetStatus(Base, BaseModel):
2213 class ChangesetStatus(Base, BaseModel):
2218 __tablename__ = 'changeset_statuses'
2214 __tablename__ = 'changeset_statuses'
2219 __table_args__ = (
2215 __table_args__ = (
2220 Index('cs_revision_idx', 'revision'),
2216 Index('cs_revision_idx', 'revision'),
2221 Index('cs_version_idx', 'version'),
2217 Index('cs_version_idx', 'version'),
2222 Index('cs_pull_request_id_idx', 'pull_request_id'),
2218 Index('cs_pull_request_id_idx', 'pull_request_id'),
2223 Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
2219 Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
2224 Index('cs_pull_request_id_user_id_version_idx', 'pull_request_id', 'user_id', 'version'),
2220 Index('cs_pull_request_id_user_id_version_idx', 'pull_request_id', 'user_id', 'version'),
2225 Index('cs_repo_id_pull_request_id_idx', 'repo_id', 'pull_request_id'),
2221 Index('cs_repo_id_pull_request_id_idx', 'repo_id', 'pull_request_id'),
2226 UniqueConstraint('repo_id', 'revision', 'version'),
2222 UniqueConstraint('repo_id', 'revision', 'version'),
2227 _table_args_default_dict,
2223 _table_args_default_dict,
2228 )
2224 )
2229
2225
2230 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2226 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2231 STATUS_APPROVED = 'approved'
2227 STATUS_APPROVED = 'approved'
2232 STATUS_REJECTED = 'rejected'
2228 STATUS_REJECTED = 'rejected'
2233 STATUS_UNDER_REVIEW = 'under_review'
2229 STATUS_UNDER_REVIEW = 'under_review'
2234
2230
2235 STATUSES = [
2231 STATUSES = [
2236 (STATUS_NOT_REVIEWED, _("Not reviewed")), # (no icon) and default
2232 (STATUS_NOT_REVIEWED, _("Not reviewed")), # (no icon) and default
2237 (STATUS_APPROVED, _("Approved")),
2233 (STATUS_APPROVED, _("Approved")),
2238 (STATUS_REJECTED, _("Rejected")),
2234 (STATUS_REJECTED, _("Rejected")),
2239 (STATUS_UNDER_REVIEW, _("Under review")),
2235 (STATUS_UNDER_REVIEW, _("Under review")),
2240 ]
2236 ]
2241
2237
2242 changeset_status_id = Column(Integer(), unique=True, primary_key=True)
2238 changeset_status_id = Column(Integer(), unique=True, primary_key=True)
2243 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2239 repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2244 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2240 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2245 revision = Column(String(40), nullable=True)
2241 revision = Column(String(40), nullable=True)
2246 status = Column(String(128), nullable=False, default=DEFAULT)
2242 status = Column(String(128), nullable=False, default=DEFAULT)
2247 changeset_comment_id = Column(Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
2243 changeset_comment_id = Column(Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
2248 modified_at = Column(DateTime(), nullable=False, default=datetime.datetime.now)
2244 modified_at = Column(DateTime(), nullable=False, default=datetime.datetime.now)
2249 version = Column(Integer(), nullable=False, default=0)
2245 version = Column(Integer(), nullable=False, default=0)
2250 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2246 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2251
2247
2252 author = relationship('User')
2248 author = relationship('User')
2253 repo = relationship('Repository')
2249 repo = relationship('Repository')
2254 comment = relationship('ChangesetComment')
2250 comment = relationship('ChangesetComment')
2255 pull_request = relationship('PullRequest')
2251 pull_request = relationship('PullRequest')
2256
2252
2257 def __unicode__(self):
2253 def __unicode__(self):
2258 return u"<%s('%s:%s')>" % (
2254 return u"<%s('%s:%s')>" % (
2259 self.__class__.__name__,
2255 self.__class__.__name__,
2260 self.status, self.author
2256 self.status, self.author
2261 )
2257 )
2262
2258
2263 @classmethod
2259 @classmethod
2264 def get_status_lbl(cls, value):
2260 def get_status_lbl(cls, value):
2265 return dict(cls.STATUSES).get(value)
2261 return dict(cls.STATUSES).get(value)
2266
2262
2267 @property
2263 @property
2268 def status_lbl(self):
2264 def status_lbl(self):
2269 return ChangesetStatus.get_status_lbl(self.status)
2265 return ChangesetStatus.get_status_lbl(self.status)
2270
2266
2271
2267
2272 class PullRequest(Base, BaseModel):
2268 class PullRequest(Base, BaseModel):
2273 __tablename__ = 'pull_requests'
2269 __tablename__ = 'pull_requests'
2274 __table_args__ = (
2270 __table_args__ = (
2275 Index('pr_org_repo_id_idx', 'org_repo_id'),
2271 Index('pr_org_repo_id_idx', 'org_repo_id'),
2276 Index('pr_other_repo_id_idx', 'other_repo_id'),
2272 Index('pr_other_repo_id_idx', 'other_repo_id'),
2277 _table_args_default_dict,
2273 _table_args_default_dict,
2278 )
2274 )
2279
2275
2280 # values for .status
2276 # values for .status
2281 STATUS_NEW = u'new'
2277 STATUS_NEW = u'new'
2282 STATUS_CLOSED = u'closed'
2278 STATUS_CLOSED = u'closed'
2283
2279
2284 pull_request_id = Column(Integer(), unique=True, primary_key=True)
2280 pull_request_id = Column(Integer(), unique=True, primary_key=True)
2285 title = Column(Unicode(255), nullable=False)
2281 title = Column(Unicode(255), nullable=False)
2286 description = Column(UnicodeText(10240), nullable=False)
2282 description = Column(UnicodeText(10240), nullable=False)
2287 status = Column(Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
2283 status = Column(Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
2288 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2284 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2289 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2285 updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2290 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2286 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2291 _revisions = Column('revisions', UnicodeText(20500), nullable=False) # 500 revisions max
2287 _revisions = Column('revisions', UnicodeText(20500), nullable=False) # 500 revisions max
2292 org_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2288 org_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2293 org_ref = Column(Unicode(255), nullable=False)
2289 org_ref = Column(Unicode(255), nullable=False)
2294 other_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2290 other_repo_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2295 other_ref = Column(Unicode(255), nullable=False)
2291 other_ref = Column(Unicode(255), nullable=False)
2296
2292
2297 @hybrid_property
2293 @hybrid_property
2298 def revisions(self):
2294 def revisions(self):
2299 return self._revisions.split(':')
2295 return self._revisions.split(':')
2300
2296
2301 @revisions.setter
2297 @revisions.setter
2302 def revisions(self, val):
2298 def revisions(self, val):
2303 self._revisions = safe_unicode(':'.join(val))
2299 self._revisions = safe_unicode(':'.join(val))
2304
2300
2305 @property
2301 @property
2306 def org_ref_parts(self):
2302 def org_ref_parts(self):
2307 return self.org_ref.split(':')
2303 return self.org_ref.split(':')
2308
2304
2309 @property
2305 @property
2310 def other_ref_parts(self):
2306 def other_ref_parts(self):
2311 return self.other_ref.split(':')
2307 return self.other_ref.split(':')
2312
2308
2313 owner = relationship('User')
2309 owner = relationship('User')
2314 reviewers = relationship('PullRequestReviewers',
2310 reviewers = relationship('PullRequestReviewers',
2315 cascade="all, delete-orphan")
2311 cascade="all, delete-orphan")
2316 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2312 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2317 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2313 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2318 statuses = relationship('ChangesetStatus', order_by='ChangesetStatus.changeset_status_id')
2314 statuses = relationship('ChangesetStatus', order_by='ChangesetStatus.changeset_status_id')
2319 comments = relationship('ChangesetComment', order_by='ChangesetComment.comment_id',
2315 comments = relationship('ChangesetComment', order_by='ChangesetComment.comment_id',
2320 cascade="all, delete-orphan")
2316 cascade="all, delete-orphan")
2321
2317
2322 def get_reviewer_users(self):
2318 def get_reviewer_users(self):
2323 """Like .reviewers, but actually returning the users"""
2319 """Like .reviewers, but actually returning the users"""
2324 return User.query() \
2320 return User.query() \
2325 .join(PullRequestReviewers) \
2321 .join(PullRequestReviewers) \
2326 .filter(PullRequestReviewers.pull_request == self) \
2322 .filter(PullRequestReviewers.pull_request == self) \
2327 .order_by(PullRequestReviewers.pull_requests_reviewers_id) \
2323 .order_by(PullRequestReviewers.pull_requests_reviewers_id) \
2328 .all()
2324 .all()
2329
2325
2330 def is_closed(self):
2326 def is_closed(self):
2331 return self.status == self.STATUS_CLOSED
2327 return self.status == self.STATUS_CLOSED
2332
2328
2333 def user_review_status(self, user_id):
2329 def user_review_status(self, user_id):
2334 """Return the user's latest status votes on PR"""
2330 """Return the user's latest status votes on PR"""
2335 # note: no filtering on repo - that would be redundant
2331 # note: no filtering on repo - that would be redundant
2336 status = ChangesetStatus.query() \
2332 status = ChangesetStatus.query() \
2337 .filter(ChangesetStatus.pull_request == self) \
2333 .filter(ChangesetStatus.pull_request == self) \
2338 .filter(ChangesetStatus.user_id == user_id) \
2334 .filter(ChangesetStatus.user_id == user_id) \
2339 .order_by(ChangesetStatus.version) \
2335 .order_by(ChangesetStatus.version) \
2340 .first()
2336 .first()
2341 return str(status.status) if status else ''
2337 return str(status.status) if status else ''
2342
2338
2343 @classmethod
2339 @classmethod
2344 def make_nice_id(cls, pull_request_id):
2340 def make_nice_id(cls, pull_request_id):
2345 '''Return pull request id nicely formatted for displaying'''
2341 '''Return pull request id nicely formatted for displaying'''
2346 return '#%s' % pull_request_id
2342 return '#%s' % pull_request_id
2347
2343
2348 def nice_id(self):
2344 def nice_id(self):
2349 '''Return the id of this pull request, nicely formatted for displaying'''
2345 '''Return the id of this pull request, nicely formatted for displaying'''
2350 return self.make_nice_id(self.pull_request_id)
2346 return self.make_nice_id(self.pull_request_id)
2351
2347
2352 def __json__(self):
2348 def __json__(self):
2353 return dict(
2349 return dict(
2354 revisions=self.revisions
2350 revisions=self.revisions
2355 )
2351 )
2356
2352
2357 def url(self, **kwargs):
2353 def url(self, **kwargs):
2358 canonical = kwargs.pop('canonical', None)
2354 canonical = kwargs.pop('canonical', None)
2359 import kallithea.lib.helpers as h
2355 import kallithea.lib.helpers as h
2360 b = self.org_ref_parts[1]
2356 b = self.org_ref_parts[1]
2361 if b != self.other_ref_parts[1]:
2357 if b != self.other_ref_parts[1]:
2362 s = '/_/' + b
2358 s = '/_/' + b
2363 else:
2359 else:
2364 s = '/_/' + self.title
2360 s = '/_/' + self.title
2365 kwargs['extra'] = urlreadable(s)
2361 kwargs['extra'] = urlreadable(s)
2366 if canonical:
2362 if canonical:
2367 return h.canonical_url('pullrequest_show', repo_name=self.other_repo.repo_name,
2363 return h.canonical_url('pullrequest_show', repo_name=self.other_repo.repo_name,
2368 pull_request_id=self.pull_request_id, **kwargs)
2364 pull_request_id=self.pull_request_id, **kwargs)
2369 return h.url('pullrequest_show', repo_name=self.other_repo.repo_name,
2365 return h.url('pullrequest_show', repo_name=self.other_repo.repo_name,
2370 pull_request_id=self.pull_request_id, **kwargs)
2366 pull_request_id=self.pull_request_id, **kwargs)
2371
2367
2372 class PullRequestReviewers(Base, BaseModel):
2368 class PullRequestReviewers(Base, BaseModel):
2373 __tablename__ = 'pull_request_reviewers'
2369 __tablename__ = 'pull_request_reviewers'
2374 __table_args__ = (
2370 __table_args__ = (
2375 Index('pull_request_reviewers_user_id_idx', 'user_id'),
2371 Index('pull_request_reviewers_user_id_idx', 'user_id'),
2376 _table_args_default_dict,
2372 _table_args_default_dict,
2377 )
2373 )
2378
2374
2379 def __init__(self, user=None, pull_request=None):
2375 def __init__(self, user=None, pull_request=None):
2380 self.user = user
2376 self.user = user
2381 self.pull_request = pull_request
2377 self.pull_request = pull_request
2382
2378
2383 pull_requests_reviewers_id = Column(Integer(), unique=True, primary_key=True)
2379 pull_requests_reviewers_id = Column(Integer(), unique=True, primary_key=True)
2384 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2380 pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2385 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2381 user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2386
2382
2387 user = relationship('User')
2383 user = relationship('User')
2388 pull_request = relationship('PullRequest')
2384 pull_request = relationship('PullRequest')
2389
2385
2390
2386
2391 class Notification(Base, BaseModel):
2387 class Notification(Base, BaseModel):
2392 __tablename__ = 'notifications'
2388 __tablename__ = 'notifications'
2393 __table_args__ = (
2389 __table_args__ = (
2394 Index('notification_type_idx', 'type'),
2390 Index('notification_type_idx', 'type'),
2395 _table_args_default_dict,
2391 _table_args_default_dict,
2396 )
2392 )
2397
2393
2398 TYPE_CHANGESET_COMMENT = u'cs_comment'
2394 TYPE_CHANGESET_COMMENT = u'cs_comment'
2399 TYPE_MESSAGE = u'message'
2395 TYPE_MESSAGE = u'message'
2400 TYPE_MENTION = u'mention'
2396 TYPE_MENTION = u'mention'
2401 TYPE_REGISTRATION = u'registration'
2397 TYPE_REGISTRATION = u'registration'
2402 TYPE_PULL_REQUEST = u'pull_request'
2398 TYPE_PULL_REQUEST = u'pull_request'
2403 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2399 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2404
2400
2405 notification_id = Column(Integer(), unique=True, primary_key=True)
2401 notification_id = Column(Integer(), unique=True, primary_key=True)
2406 subject = Column(Unicode(512), nullable=False)
2402 subject = Column(Unicode(512), nullable=False)
2407 body = Column(UnicodeText(50000), nullable=False)
2403 body = Column(UnicodeText(50000), nullable=False)
2408 created_by = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2404 created_by = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
2409 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2405 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2410 type_ = Column('type', Unicode(255), nullable=False)
2406 type_ = Column('type', Unicode(255), nullable=False)
2411
2407
2412 created_by_user = relationship('User')
2408 created_by_user = relationship('User')
2413 notifications_to_users = relationship('UserNotification', cascade="all, delete-orphan")
2409 notifications_to_users = relationship('UserNotification', cascade="all, delete-orphan")
2414
2410
2415 @property
2411 @property
2416 def recipients(self):
2412 def recipients(self):
2417 return [x.user for x in UserNotification.query()
2413 return [x.user for x in UserNotification.query()
2418 .filter(UserNotification.notification == self)
2414 .filter(UserNotification.notification == self)
2419 .order_by(UserNotification.user_id.asc()).all()]
2415 .order_by(UserNotification.user_id.asc()).all()]
2420
2416
2421 @classmethod
2417 @classmethod
2422 def create(cls, created_by, subject, body, recipients, type_=None):
2418 def create(cls, created_by, subject, body, recipients, type_=None):
2423 if type_ is None:
2419 if type_ is None:
2424 type_ = Notification.TYPE_MESSAGE
2420 type_ = Notification.TYPE_MESSAGE
2425
2421
2426 notification = cls()
2422 notification = cls()
2427 notification.created_by_user = created_by
2423 notification.created_by_user = created_by
2428 notification.subject = subject
2424 notification.subject = subject
2429 notification.body = body
2425 notification.body = body
2430 notification.type_ = type_
2426 notification.type_ = type_
2431 notification.created_on = datetime.datetime.now()
2427 notification.created_on = datetime.datetime.now()
2432
2428
2433 for recipient in recipients:
2429 for recipient in recipients:
2434 un = UserNotification()
2430 un = UserNotification()
2435 un.notification = notification
2431 un.notification = notification
2436 un.user_id = recipient.user_id
2432 un.user_id = recipient.user_id
2437 # Mark notifications to self "pre-read" - should perhaps just be skipped
2433 # Mark notifications to self "pre-read" - should perhaps just be skipped
2438 if recipient == created_by:
2434 if recipient == created_by:
2439 un.read = True
2435 un.read = True
2440 Session().add(un)
2436 Session().add(un)
2441
2437
2442 Session().add(notification)
2438 Session().add(notification)
2443 Session().flush() # assign notificaiton.notification_id
2439 Session().flush() # assign notificaiton.notification_id
2444 return notification
2440 return notification
2445
2441
2446 @property
2442 @property
2447 def description(self):
2443 def description(self):
2448 from kallithea.model.notification import NotificationModel
2444 from kallithea.model.notification import NotificationModel
2449 return NotificationModel().make_description(self)
2445 return NotificationModel().make_description(self)
2450
2446
2451
2447
2452 class UserNotification(Base, BaseModel):
2448 class UserNotification(Base, BaseModel):
2453 __tablename__ = 'user_to_notification'
2449 __tablename__ = 'user_to_notification'
2454 __table_args__ = (
2450 __table_args__ = (
2455 UniqueConstraint('user_id', 'notification_id'),
2451 UniqueConstraint('user_id', 'notification_id'),
2456 _table_args_default_dict,
2452 _table_args_default_dict,
2457 )
2453 )
2458
2454
2459 user_id = Column(Integer(), ForeignKey('users.user_id'), primary_key=True)
2455 user_id = Column(Integer(), ForeignKey('users.user_id'), primary_key=True)
2460 notification_id = Column(Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2456 notification_id = Column(Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2461 read = Column(Boolean, nullable=False, default=False)
2457 read = Column(Boolean, nullable=False, default=False)
2462 sent_on = Column(DateTime(timezone=False), nullable=True) # FIXME: not nullable?
2458 sent_on = Column(DateTime(timezone=False), nullable=True) # FIXME: not nullable?
2463
2459
2464 user = relationship('User')
2460 user = relationship('User')
2465 notification = relationship('Notification')
2461 notification = relationship('Notification')
2466
2462
2467 def mark_as_read(self):
2463 def mark_as_read(self):
2468 self.read = True
2464 self.read = True
2469 Session().add(self)
2465 Session().add(self)
2470
2466
2471
2467
2472 class Gist(Base, BaseModel):
2468 class Gist(Base, BaseModel):
2473 __tablename__ = 'gists'
2469 __tablename__ = 'gists'
2474 __table_args__ = (
2470 __table_args__ = (
2475 Index('g_gist_access_id_idx', 'gist_access_id'),
2471 Index('g_gist_access_id_idx', 'gist_access_id'),
2476 Index('g_created_on_idx', 'created_on'),
2472 Index('g_created_on_idx', 'created_on'),
2477 _table_args_default_dict,
2473 _table_args_default_dict,
2478 )
2474 )
2479
2475
2480 GIST_PUBLIC = u'public'
2476 GIST_PUBLIC = u'public'
2481 GIST_PRIVATE = u'private'
2477 GIST_PRIVATE = u'private'
2482 DEFAULT_FILENAME = u'gistfile1.txt'
2478 DEFAULT_FILENAME = u'gistfile1.txt'
2483
2479
2484 gist_id = Column(Integer(), unique=True, primary_key=True)
2480 gist_id = Column(Integer(), unique=True, primary_key=True)
2485 gist_access_id = Column(Unicode(250), nullable=False)
2481 gist_access_id = Column(Unicode(250), nullable=False)
2486 gist_description = Column(UnicodeText(1024), nullable=False)
2482 gist_description = Column(UnicodeText(1024), nullable=False)
2487 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2483 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2488 gist_expires = Column(Float(53), nullable=False)
2484 gist_expires = Column(Float(53), nullable=False)
2489 gist_type = Column(Unicode(128), nullable=False)
2485 gist_type = Column(Unicode(128), nullable=False)
2490 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2486 created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2491 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2487 modified_at = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2492
2488
2493 owner = relationship('User')
2489 owner = relationship('User')
2494
2490
2495 def __repr__(self):
2491 def __repr__(self):
2496 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
2492 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
2497
2493
2498 @classmethod
2494 @classmethod
2499 def get_or_404(cls, id_):
2495 def get_or_404(cls, id_):
2500 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2496 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2501 if res is None:
2497 if res is None:
2502 raise HTTPNotFound
2498 raise HTTPNotFound
2503 return res
2499 return res
2504
2500
2505 @classmethod
2501 @classmethod
2506 def get_by_access_id(cls, gist_access_id):
2502 def get_by_access_id(cls, gist_access_id):
2507 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2503 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2508
2504
2509 def gist_url(self):
2505 def gist_url(self):
2510 import kallithea
2506 import kallithea
2511 alias_url = kallithea.CONFIG.get('gist_alias_url')
2507 alias_url = kallithea.CONFIG.get('gist_alias_url')
2512 if alias_url:
2508 if alias_url:
2513 return alias_url.replace('{gistid}', self.gist_access_id)
2509 return alias_url.replace('{gistid}', self.gist_access_id)
2514
2510
2515 import kallithea.lib.helpers as h
2511 import kallithea.lib.helpers as h
2516 return h.canonical_url('gist', gist_id=self.gist_access_id)
2512 return h.canonical_url('gist', gist_id=self.gist_access_id)
2517
2513
2518 @classmethod
2514 @classmethod
2519 def base_path(cls):
2515 def base_path(cls):
2520 """
2516 """
2521 Returns base path where all gists are stored
2517 Returns base path where all gists are stored
2522
2518
2523 :param cls:
2519 :param cls:
2524 """
2520 """
2525 from kallithea.model.gist import GIST_STORE_LOC
2521 from kallithea.model.gist import GIST_STORE_LOC
2526 q = Session().query(Ui) \
2522 q = Session().query(Ui) \
2527 .filter(Ui.ui_key == URL_SEP)
2523 .filter(Ui.ui_key == URL_SEP)
2528 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2524 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2529 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2525 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2530
2526
2531 def get_api_data(self):
2527 def get_api_data(self):
2532 """
2528 """
2533 Common function for generating gist related data for API
2529 Common function for generating gist related data for API
2534 """
2530 """
2535 gist = self
2531 gist = self
2536 data = dict(
2532 data = dict(
2537 gist_id=gist.gist_id,
2533 gist_id=gist.gist_id,
2538 type=gist.gist_type,
2534 type=gist.gist_type,
2539 access_id=gist.gist_access_id,
2535 access_id=gist.gist_access_id,
2540 description=gist.gist_description,
2536 description=gist.gist_description,
2541 url=gist.gist_url(),
2537 url=gist.gist_url(),
2542 expires=gist.gist_expires,
2538 expires=gist.gist_expires,
2543 created_on=gist.created_on,
2539 created_on=gist.created_on,
2544 )
2540 )
2545 return data
2541 return data
2546
2542
2547 def __json__(self):
2543 def __json__(self):
2548 data = dict(
2544 data = dict(
2549 )
2545 )
2550 data.update(self.get_api_data())
2546 data.update(self.get_api_data())
2551 return data
2547 return data
2552 ## SCM functions
2548 ## SCM functions
2553
2549
2554 @property
2550 @property
2555 def scm_instance(self):
2551 def scm_instance(self):
2556 from kallithea.lib.vcs import get_repo
2552 from kallithea.lib.vcs import get_repo
2557 base_path = self.base_path()
2553 base_path = self.base_path()
2558 return get_repo(os.path.join(*map(safe_str,
2554 return get_repo(os.path.join(*map(safe_str,
2559 [base_path, self.gist_access_id])))
2555 [base_path, self.gist_access_id])))
2560
2556
2561
2557
2562 class DbMigrateVersion(Base, BaseModel):
2558 class DbMigrateVersion(Base, BaseModel):
2563 __tablename__ = 'db_migrate_version'
2559 __tablename__ = 'db_migrate_version'
2564 __table_args__ = (
2560 __table_args__ = (
2565 _table_args_default_dict,
2561 _table_args_default_dict,
2566 )
2562 )
2567
2563
2568 repository_id = Column(String(250), unique=True, primary_key=True)
2564 repository_id = Column(String(250), unique=True, primary_key=True)
2569 repository_path = Column(Text, nullable=False)
2565 repository_path = Column(Text, nullable=False)
2570 version = Column(Integer, nullable=False)
2566 version = Column(Integer, nullable=False)
@@ -1,599 +1,594 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # Kallithea - config for tests: #
3 # Kallithea - config for tests: #
4 # initial_repo_scan = true #
4 # initial_repo_scan = true #
5 # vcs_full_cache = false #
6 # sqlalchemy and kallithea_test.sqlite #
5 # sqlalchemy and kallithea_test.sqlite #
7 # custom logging #
6 # custom logging #
8 # #
7 # #
9 # The %(here)s variable will be replaced with the parent directory of this file#
8 # The %(here)s variable will be replaced with the parent directory of this file#
10 ################################################################################
9 ################################################################################
11 ################################################################################
10 ################################################################################
12
11
13 [DEFAULT]
12 [DEFAULT]
14 debug = true
13 debug = true
15 pdebug = false
14 pdebug = false
16
15
17 ################################################################################
16 ################################################################################
18 ## Email settings ##
17 ## Email settings ##
19 ## ##
18 ## ##
20 ## Refer to the documentation ("Email settings") for more details. ##
19 ## Refer to the documentation ("Email settings") for more details. ##
21 ## ##
20 ## ##
22 ## It is recommended to use a valid sender address that passes access ##
21 ## It is recommended to use a valid sender address that passes access ##
23 ## validation and spam filtering in mail servers. ##
22 ## validation and spam filtering in mail servers. ##
24 ################################################################################
23 ################################################################################
25
24
26 ## 'From' header for application emails. You can optionally add a name.
25 ## 'From' header for application emails. You can optionally add a name.
27 ## Default:
26 ## Default:
28 #app_email_from = Kallithea
27 #app_email_from = Kallithea
29 ## Examples:
28 ## Examples:
30 #app_email_from = Kallithea <kallithea-noreply@example.com>
29 #app_email_from = Kallithea <kallithea-noreply@example.com>
31 #app_email_from = kallithea-noreply@example.com
30 #app_email_from = kallithea-noreply@example.com
32
31
33 ## Subject prefix for application emails.
32 ## Subject prefix for application emails.
34 ## A space between this prefix and the real subject is automatically added.
33 ## A space between this prefix and the real subject is automatically added.
35 ## Default:
34 ## Default:
36 #email_prefix =
35 #email_prefix =
37 ## Example:
36 ## Example:
38 #email_prefix = [Kallithea]
37 #email_prefix = [Kallithea]
39
38
40 ## Recipients for error emails and fallback recipients of application mails.
39 ## Recipients for error emails and fallback recipients of application mails.
41 ## Multiple addresses can be specified, space-separated.
40 ## Multiple addresses can be specified, space-separated.
42 ## Only addresses are allowed, do not add any name part.
41 ## Only addresses are allowed, do not add any name part.
43 ## Default:
42 ## Default:
44 #email_to =
43 #email_to =
45 ## Examples:
44 ## Examples:
46 #email_to = admin@example.com
45 #email_to = admin@example.com
47 #email_to = admin@example.com another_admin@example.com
46 #email_to = admin@example.com another_admin@example.com
48
47
49 ## 'From' header for error emails. You can optionally add a name.
48 ## 'From' header for error emails. You can optionally add a name.
50 ## Default:
49 ## Default:
51 #error_email_from = pylons@yourapp.com
50 #error_email_from = pylons@yourapp.com
52 ## Examples:
51 ## Examples:
53 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
52 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
54 #error_email_from = paste_error@example.com
53 #error_email_from = paste_error@example.com
55
54
56 ## SMTP server settings
55 ## SMTP server settings
57 ## Only smtp_server is mandatory. All other settings take the specified default
56 ## Only smtp_server is mandatory. All other settings take the specified default
58 ## values.
57 ## values.
59 #smtp_server = smtp.example.com
58 #smtp_server = smtp.example.com
60 #smtp_username =
59 #smtp_username =
61 #smtp_password =
60 #smtp_password =
62 #smtp_port = 25
61 #smtp_port = 25
63 #smtp_use_tls = false
62 #smtp_use_tls = false
64 #smtp_use_ssl = false
63 #smtp_use_ssl = false
65 ## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
64 ## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
66 ## If empty, use any of the authentication parameters supported by the server.
65 ## If empty, use any of the authentication parameters supported by the server.
67 #smtp_auth =
66 #smtp_auth =
68
67
69 [server:main]
68 [server:main]
70 ## PASTE ##
69 ## PASTE ##
71 #use = egg:Paste#http
70 #use = egg:Paste#http
72 ## nr of worker threads to spawn
71 ## nr of worker threads to spawn
73 #threadpool_workers = 5
72 #threadpool_workers = 5
74 ## max request before thread respawn
73 ## max request before thread respawn
75 #threadpool_max_requests = 10
74 #threadpool_max_requests = 10
76 ## option to use threads of process
75 ## option to use threads of process
77 #use_threadpool = true
76 #use_threadpool = true
78
77
79 ## WAITRESS ##
78 ## WAITRESS ##
80 use = egg:waitress#main
79 use = egg:waitress#main
81 ## number of worker threads
80 ## number of worker threads
82 threads = 5
81 threads = 5
83 ## MAX BODY SIZE 100GB
82 ## MAX BODY SIZE 100GB
84 max_request_body_size = 107374182400
83 max_request_body_size = 107374182400
85 ## use poll instead of select, fixes fd limits, may not work on old
84 ## use poll instead of select, fixes fd limits, may not work on old
86 ## windows systems.
85 ## windows systems.
87 #asyncore_use_poll = True
86 #asyncore_use_poll = True
88
87
89 ## GUNICORN ##
88 ## GUNICORN ##
90 #use = egg:gunicorn#main
89 #use = egg:gunicorn#main
91 ## number of process workers. You must set `instance_id = *` when this option
90 ## number of process workers. You must set `instance_id = *` when this option
92 ## is set to more than one worker
91 ## is set to more than one worker
93 #workers = 1
92 #workers = 1
94 ## process name
93 ## process name
95 #proc_name = kallithea
94 #proc_name = kallithea
96 ## type of worker class, one of sync, eventlet, gevent, tornado
95 ## type of worker class, one of sync, eventlet, gevent, tornado
97 ## recommended for bigger setup is using of of other than sync one
96 ## recommended for bigger setup is using of of other than sync one
98 #worker_class = sync
97 #worker_class = sync
99 #max_requests = 1000
98 #max_requests = 1000
100 ## ammount of time a worker can handle request before it gets killed and
99 ## ammount of time a worker can handle request before it gets killed and
101 ## restarted
100 ## restarted
102 #timeout = 3600
101 #timeout = 3600
103
102
104 ## UWSGI ##
103 ## UWSGI ##
105 ## run with uwsgi --ini-paste-logged <inifile.ini>
104 ## run with uwsgi --ini-paste-logged <inifile.ini>
106 #[uwsgi]
105 #[uwsgi]
107 #socket = /tmp/uwsgi.sock
106 #socket = /tmp/uwsgi.sock
108 #master = true
107 #master = true
109 #http = 127.0.0.1:5000
108 #http = 127.0.0.1:5000
110
109
111 ## set as deamon and redirect all output to file
110 ## set as deamon and redirect all output to file
112 #daemonize = ./uwsgi_kallithea.log
111 #daemonize = ./uwsgi_kallithea.log
113
112
114 ## master process PID
113 ## master process PID
115 #pidfile = ./uwsgi_kallithea.pid
114 #pidfile = ./uwsgi_kallithea.pid
116
115
117 ## stats server with workers statistics, use uwsgitop
116 ## stats server with workers statistics, use uwsgitop
118 ## for monitoring, `uwsgitop 127.0.0.1:1717`
117 ## for monitoring, `uwsgitop 127.0.0.1:1717`
119 #stats = 127.0.0.1:1717
118 #stats = 127.0.0.1:1717
120 #memory-report = true
119 #memory-report = true
121
120
122 ## log 5XX errors
121 ## log 5XX errors
123 #log-5xx = true
122 #log-5xx = true
124
123
125 ## Set the socket listen queue size.
124 ## Set the socket listen queue size.
126 #listen = 256
125 #listen = 256
127
126
128 ## Gracefully Reload workers after the specified amount of managed requests
127 ## Gracefully Reload workers after the specified amount of managed requests
129 ## (avoid memory leaks).
128 ## (avoid memory leaks).
130 #max-requests = 1000
129 #max-requests = 1000
131
130
132 ## enable large buffers
131 ## enable large buffers
133 #buffer-size = 65535
132 #buffer-size = 65535
134
133
135 ## socket and http timeouts ##
134 ## socket and http timeouts ##
136 #http-timeout = 3600
135 #http-timeout = 3600
137 #socket-timeout = 3600
136 #socket-timeout = 3600
138
137
139 ## Log requests slower than the specified number of milliseconds.
138 ## Log requests slower than the specified number of milliseconds.
140 #log-slow = 10
139 #log-slow = 10
141
140
142 ## Exit if no app can be loaded.
141 ## Exit if no app can be loaded.
143 #need-app = true
142 #need-app = true
144
143
145 ## Set lazy mode (load apps in workers instead of master).
144 ## Set lazy mode (load apps in workers instead of master).
146 #lazy = true
145 #lazy = true
147
146
148 ## scaling ##
147 ## scaling ##
149 ## set cheaper algorithm to use, if not set default will be used
148 ## set cheaper algorithm to use, if not set default will be used
150 #cheaper-algo = spare
149 #cheaper-algo = spare
151
150
152 ## minimum number of workers to keep at all times
151 ## minimum number of workers to keep at all times
153 #cheaper = 1
152 #cheaper = 1
154
153
155 ## number of workers to spawn at startup
154 ## number of workers to spawn at startup
156 #cheaper-initial = 1
155 #cheaper-initial = 1
157
156
158 ## maximum number of workers that can be spawned
157 ## maximum number of workers that can be spawned
159 #workers = 4
158 #workers = 4
160
159
161 ## how many workers should be spawned at a time
160 ## how many workers should be spawned at a time
162 #cheaper-step = 1
161 #cheaper-step = 1
163
162
164 ## COMMON ##
163 ## COMMON ##
165 host = 127.0.0.1
164 host = 127.0.0.1
166 #port = 5000
165 #port = 5000
167 port = 4999
166 port = 4999
168
167
169 ## middleware for hosting the WSGI application under a URL prefix
168 ## middleware for hosting the WSGI application under a URL prefix
170 #[filter:proxy-prefix]
169 #[filter:proxy-prefix]
171 #use = egg:PasteDeploy#prefix
170 #use = egg:PasteDeploy#prefix
172 #prefix = /<your-prefix>
171 #prefix = /<your-prefix>
173
172
174 [app:main]
173 [app:main]
175 use = egg:kallithea
174 use = egg:kallithea
176 ## enable proxy prefix middleware
175 ## enable proxy prefix middleware
177 #filter-with = proxy-prefix
176 #filter-with = proxy-prefix
178
177
179 full_stack = true
178 full_stack = true
180 static_files = true
179 static_files = true
181 ## Available Languages:
180 ## Available Languages:
182 ## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
181 ## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
183 lang =
182 lang =
184 cache_dir = %(here)s/data
183 cache_dir = %(here)s/data
185 index_dir = %(here)s/data/index
184 index_dir = %(here)s/data/index
186
185
187 ## perform a full repository scan on each server start, this should be
186 ## perform a full repository scan on each server start, this should be
188 ## set to false after first startup, to allow faster server restarts.
187 ## set to false after first startup, to allow faster server restarts.
189 #initial_repo_scan = false
188 #initial_repo_scan = false
190 initial_repo_scan = true
189 initial_repo_scan = true
191
190
192 ## uncomment and set this path to use archive download cache
191 ## uncomment and set this path to use archive download cache
193 archive_cache_dir = %(here)s/tarballcache
192 archive_cache_dir = %(here)s/tarballcache
194
193
195 ## change this to unique ID for security
194 ## change this to unique ID for security
196 app_instance_uuid = test
195 app_instance_uuid = test
197
196
198 ## cut off limit for large diffs (size in bytes)
197 ## cut off limit for large diffs (size in bytes)
199 cut_off_limit = 256000
198 cut_off_limit = 256000
200
199
201 ## use cache version of scm repo everywhere
202 #vcs_full_cache = true
203 vcs_full_cache = false
204
205 ## force https in Kallithea, fixes https redirects, assumes it's always https
200 ## force https in Kallithea, fixes https redirects, assumes it's always https
206 force_https = false
201 force_https = false
207
202
208 ## use Strict-Transport-Security headers
203 ## use Strict-Transport-Security headers
209 use_htsts = false
204 use_htsts = false
210
205
211 ## number of commits stats will parse on each iteration
206 ## number of commits stats will parse on each iteration
212 commit_parse_limit = 25
207 commit_parse_limit = 25
213
208
214 ## path to git executable
209 ## path to git executable
215 git_path = git
210 git_path = git
216
211
217 ## git rev filter option, --all is the default filter, if you need to
212 ## git rev filter option, --all is the default filter, if you need to
218 ## hide all refs in changelog switch this to --branches --tags
213 ## hide all refs in changelog switch this to --branches --tags
219 #git_rev_filter = --branches --tags
214 #git_rev_filter = --branches --tags
220
215
221 ## RSS feed options
216 ## RSS feed options
222 rss_cut_off_limit = 256000
217 rss_cut_off_limit = 256000
223 rss_items_per_page = 10
218 rss_items_per_page = 10
224 rss_include_diff = false
219 rss_include_diff = false
225
220
226 ## options for showing and identifying changesets
221 ## options for showing and identifying changesets
227 show_sha_length = 12
222 show_sha_length = 12
228 #show_revision_number = false
223 #show_revision_number = false
229 show_revision_number = true
224 show_revision_number = true
230
225
231 ## gist URL alias, used to create nicer urls for gist. This should be an
226 ## gist URL alias, used to create nicer urls for gist. This should be an
232 ## url that does rewrites to _admin/gists/<gistid>.
227 ## url that does rewrites to _admin/gists/<gistid>.
233 ## example: http://gist.example.com/{gistid}. Empty means use the internal
228 ## example: http://gist.example.com/{gistid}. Empty means use the internal
234 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
229 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
235 gist_alias_url =
230 gist_alias_url =
236
231
237 ## white list of API enabled controllers. This allows to add list of
232 ## white list of API enabled controllers. This allows to add list of
238 ## controllers to which access will be enabled by api_key. eg: to enable
233 ## controllers to which access will be enabled by api_key. eg: to enable
239 ## api access to raw_files put `FilesController:raw`, to enable access to patches
234 ## api access to raw_files put `FilesController:raw`, to enable access to patches
240 ## add `ChangesetController:changeset_patch`. This list should be "," separated
235 ## add `ChangesetController:changeset_patch`. This list should be "," separated
241 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
236 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
242 ## Recommended settings below are commented out:
237 ## Recommended settings below are commented out:
243 api_access_controllers_whitelist =
238 api_access_controllers_whitelist =
244 # ChangesetController:changeset_patch,
239 # ChangesetController:changeset_patch,
245 # ChangesetController:changeset_raw,
240 # ChangesetController:changeset_raw,
246 # FilesController:raw,
241 # FilesController:raw,
247 # FilesController:archivefile
242 # FilesController:archivefile
248
243
249 ## default encoding used to convert from and to unicode
244 ## default encoding used to convert from and to unicode
250 ## can be also a comma seperated list of encoding in case of mixed encodings
245 ## can be also a comma seperated list of encoding in case of mixed encodings
251 default_encoding = utf8
246 default_encoding = utf8
252
247
253 ## issue tracker for Kallithea (leave blank to disable, absent for default)
248 ## issue tracker for Kallithea (leave blank to disable, absent for default)
254 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
249 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
255
250
256 ## issue tracking mapping for commits messages
251 ## issue tracking mapping for commits messages
257 ## comment out issue_pat, issue_server, issue_prefix to enable
252 ## comment out issue_pat, issue_server, issue_prefix to enable
258
253
259 ## pattern to get the issues from commit messages
254 ## pattern to get the issues from commit messages
260 ## default one used here is #<numbers> with a regex passive group for `#`
255 ## default one used here is #<numbers> with a regex passive group for `#`
261 ## {id} will be all groups matched from this pattern
256 ## {id} will be all groups matched from this pattern
262
257
263 issue_pat = (?:\s*#)(\d+)
258 issue_pat = (?:\s*#)(\d+)
264
259
265 ## server url to the issue, each {id} will be replaced with match
260 ## server url to the issue, each {id} will be replaced with match
266 ## fetched from the regex and {repo} is replaced with full repository name
261 ## fetched from the regex and {repo} is replaced with full repository name
267 ## including groups {repo_name} is replaced with just name of repo
262 ## including groups {repo_name} is replaced with just name of repo
268
263
269 issue_server_link = https://issues.example.com/{repo}/issue/{id}
264 issue_server_link = https://issues.example.com/{repo}/issue/{id}
270
265
271 ## prefix to add to link to indicate it's an url
266 ## prefix to add to link to indicate it's an url
272 ## #314 will be replaced by <issue_prefix><id>
267 ## #314 will be replaced by <issue_prefix><id>
273
268
274 issue_prefix = #
269 issue_prefix = #
275
270
276 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
271 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
277 ## multiple patterns, to other issues server, wiki or others
272 ## multiple patterns, to other issues server, wiki or others
278 ## below an example how to create a wiki pattern
273 ## below an example how to create a wiki pattern
279 # wiki-some-id -> https://wiki.example.com/some-id
274 # wiki-some-id -> https://wiki.example.com/some-id
280
275
281 #issue_pat_wiki = (?:wiki-)(.+)
276 #issue_pat_wiki = (?:wiki-)(.+)
282 #issue_server_link_wiki = https://wiki.example.com/{id}
277 #issue_server_link_wiki = https://wiki.example.com/{id}
283 #issue_prefix_wiki = WIKI-
278 #issue_prefix_wiki = WIKI-
284
279
285 ## alternative return HTTP header for failed authentication. Default HTTP
280 ## alternative return HTTP header for failed authentication. Default HTTP
286 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
281 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
287 ## handling that. Set this variable to 403 to return HTTPForbidden
282 ## handling that. Set this variable to 403 to return HTTPForbidden
288 auth_ret_code =
283 auth_ret_code =
289
284
290 ## locking return code. When repository is locked return this HTTP code. 2XX
285 ## locking return code. When repository is locked return this HTTP code. 2XX
291 ## codes don't break the transactions while 4XX codes do
286 ## codes don't break the transactions while 4XX codes do
292 lock_ret_code = 423
287 lock_ret_code = 423
293
288
294 ## allows to change the repository location in settings page
289 ## allows to change the repository location in settings page
295 allow_repo_location_change = True
290 allow_repo_location_change = True
296
291
297 ## allows to setup custom hooks in settings page
292 ## allows to setup custom hooks in settings page
298 allow_custom_hooks_settings = True
293 allow_custom_hooks_settings = True
299
294
300 ## extra extensions for indexing, space separated and without the leading '.'.
295 ## extra extensions for indexing, space separated and without the leading '.'.
301 # index.extensions =
296 # index.extensions =
302 # gemfile
297 # gemfile
303 # lock
298 # lock
304
299
305 ## extra filenames for indexing, space separated
300 ## extra filenames for indexing, space separated
306 # index.filenames =
301 # index.filenames =
307 # .dockerignore
302 # .dockerignore
308 # .editorconfig
303 # .editorconfig
309 # INSTALL
304 # INSTALL
310 # CHANGELOG
305 # CHANGELOG
311
306
312 ####################################
307 ####################################
313 ### CELERY CONFIG ####
308 ### CELERY CONFIG ####
314 ####################################
309 ####################################
315
310
316 use_celery = false
311 use_celery = false
317 broker.host = localhost
312 broker.host = localhost
318 broker.vhost = rabbitmqhost
313 broker.vhost = rabbitmqhost
319 broker.port = 5672
314 broker.port = 5672
320 broker.user = rabbitmq
315 broker.user = rabbitmq
321 broker.password = qweqwe
316 broker.password = qweqwe
322
317
323 celery.imports = kallithea.lib.celerylib.tasks
318 celery.imports = kallithea.lib.celerylib.tasks
324
319
325 celery.result.backend = amqp
320 celery.result.backend = amqp
326 celery.result.dburi = amqp://
321 celery.result.dburi = amqp://
327 celery.result.serialier = json
322 celery.result.serialier = json
328
323
329 #celery.send.task.error.emails = true
324 #celery.send.task.error.emails = true
330 #celery.amqp.task.result.expires = 18000
325 #celery.amqp.task.result.expires = 18000
331
326
332 celeryd.concurrency = 2
327 celeryd.concurrency = 2
333 #celeryd.log.file = celeryd.log
328 #celeryd.log.file = celeryd.log
334 celeryd.log.level = DEBUG
329 celeryd.log.level = DEBUG
335 celeryd.max.tasks.per.child = 1
330 celeryd.max.tasks.per.child = 1
336
331
337 ## tasks will never be sent to the queue, but executed locally instead.
332 ## tasks will never be sent to the queue, but executed locally instead.
338 celery.always.eager = false
333 celery.always.eager = false
339
334
340 ####################################
335 ####################################
341 ### BEAKER CACHE ####
336 ### BEAKER CACHE ####
342 ####################################
337 ####################################
343
338
344 beaker.cache.data_dir = %(here)s/data/cache/data
339 beaker.cache.data_dir = %(here)s/data/cache/data
345 beaker.cache.lock_dir = %(here)s/data/cache/lock
340 beaker.cache.lock_dir = %(here)s/data/cache/lock
346
341
347 beaker.cache.regions = short_term,long_term,sql_cache_short
342 beaker.cache.regions = short_term,long_term,sql_cache_short
348
343
349 beaker.cache.short_term.type = memory
344 beaker.cache.short_term.type = memory
350 beaker.cache.short_term.expire = 60
345 beaker.cache.short_term.expire = 60
351 beaker.cache.short_term.key_length = 256
346 beaker.cache.short_term.key_length = 256
352
347
353 beaker.cache.long_term.type = memory
348 beaker.cache.long_term.type = memory
354 beaker.cache.long_term.expire = 36000
349 beaker.cache.long_term.expire = 36000
355 beaker.cache.long_term.key_length = 256
350 beaker.cache.long_term.key_length = 256
356
351
357 beaker.cache.sql_cache_short.type = memory
352 beaker.cache.sql_cache_short.type = memory
358 #beaker.cache.sql_cache_short.expire = 10
353 #beaker.cache.sql_cache_short.expire = 10
359 beaker.cache.sql_cache_short.expire = 1
354 beaker.cache.sql_cache_short.expire = 1
360 beaker.cache.sql_cache_short.key_length = 256
355 beaker.cache.sql_cache_short.key_length = 256
361
356
362 ####################################
357 ####################################
363 ### BEAKER SESSION ####
358 ### BEAKER SESSION ####
364 ####################################
359 ####################################
365
360
366 ## Name of session cookie. Should be unique for a given host and path, even when running
361 ## Name of session cookie. Should be unique for a given host and path, even when running
367 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
362 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
368 beaker.session.key = kallithea
363 beaker.session.key = kallithea
369 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
364 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
370 beaker.session.httponly = true
365 beaker.session.httponly = true
371 ## Session lifetime. 2592000 seconds is 30 days.
366 ## Session lifetime. 2592000 seconds is 30 days.
372 beaker.session.timeout = 2592000
367 beaker.session.timeout = 2592000
373
368
374 ## Server secret used with HMAC to ensure integrity of cookies.
369 ## Server secret used with HMAC to ensure integrity of cookies.
375 beaker.session.secret = {74e0cd75-b339-478b-b129-07dd221def1f}
370 beaker.session.secret = {74e0cd75-b339-478b-b129-07dd221def1f}
376 ## Further, encrypt the data with AES.
371 ## Further, encrypt the data with AES.
377 #beaker.session.encrypt_key = <key_for_encryption>
372 #beaker.session.encrypt_key = <key_for_encryption>
378 #beaker.session.validate_key = <validation_key>
373 #beaker.session.validate_key = <validation_key>
379
374
380 ## Type of storage used for the session, current types are
375 ## Type of storage used for the session, current types are
381 ## dbm, file, memcached, database, and memory.
376 ## dbm, file, memcached, database, and memory.
382
377
383 ## File system storage of session data. (default)
378 ## File system storage of session data. (default)
384 #beaker.session.type = file
379 #beaker.session.type = file
385
380
386 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
381 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
387 #beaker.session.type = cookie
382 #beaker.session.type = cookie
388
383
389 ## Database storage of session data.
384 ## Database storage of session data.
390 #beaker.session.type = ext:database
385 #beaker.session.type = ext:database
391 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
386 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
392 #beaker.session.table_name = db_session
387 #beaker.session.table_name = db_session
393
388
394 ############################
389 ############################
395 ## ERROR HANDLING SYSTEMS ##
390 ## ERROR HANDLING SYSTEMS ##
396 ############################
391 ############################
397
392
398 ####################
393 ####################
399 ### [errormator] ###
394 ### [errormator] ###
400 ####################
395 ####################
401
396
402 ## Errormator is tailored to work with Kallithea, see
397 ## Errormator is tailored to work with Kallithea, see
403 ## http://errormator.com for details how to obtain an account
398 ## http://errormator.com for details how to obtain an account
404 ## you must install python package `errormator_client` to make it work
399 ## you must install python package `errormator_client` to make it work
405
400
406 ## errormator enabled
401 ## errormator enabled
407 errormator = false
402 errormator = false
408
403
409 errormator.server_url = https://api.errormator.com
404 errormator.server_url = https://api.errormator.com
410 errormator.api_key = YOUR_API_KEY
405 errormator.api_key = YOUR_API_KEY
411
406
412 ## TWEAK AMOUNT OF INFO SENT HERE
407 ## TWEAK AMOUNT OF INFO SENT HERE
413
408
414 ## enables 404 error logging (default False)
409 ## enables 404 error logging (default False)
415 errormator.report_404 = false
410 errormator.report_404 = false
416
411
417 ## time in seconds after request is considered being slow (default 1)
412 ## time in seconds after request is considered being slow (default 1)
418 errormator.slow_request_time = 1
413 errormator.slow_request_time = 1
419
414
420 ## record slow requests in application
415 ## record slow requests in application
421 ## (needs to be enabled for slow datastore recording and time tracking)
416 ## (needs to be enabled for slow datastore recording and time tracking)
422 errormator.slow_requests = true
417 errormator.slow_requests = true
423
418
424 ## enable hooking to application loggers
419 ## enable hooking to application loggers
425 #errormator.logging = true
420 #errormator.logging = true
426
421
427 ## minimum log level for log capture
422 ## minimum log level for log capture
428 #errormator.logging.level = WARNING
423 #errormator.logging.level = WARNING
429
424
430 ## send logs only from erroneous/slow requests
425 ## send logs only from erroneous/slow requests
431 ## (saves API quota for intensive logging)
426 ## (saves API quota for intensive logging)
432 errormator.logging_on_error = false
427 errormator.logging_on_error = false
433
428
434 ## list of additonal keywords that should be grabbed from environ object
429 ## list of additonal keywords that should be grabbed from environ object
435 ## can be string with comma separated list of words in lowercase
430 ## can be string with comma separated list of words in lowercase
436 ## (by default client will always send following info:
431 ## (by default client will always send following info:
437 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
432 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
438 ## start with HTTP* this list be extended with additional keywords here
433 ## start with HTTP* this list be extended with additional keywords here
439 errormator.environ_keys_whitelist =
434 errormator.environ_keys_whitelist =
440
435
441 ## list of keywords that should be blanked from request object
436 ## list of keywords that should be blanked from request object
442 ## can be string with comma separated list of words in lowercase
437 ## can be string with comma separated list of words in lowercase
443 ## (by default client will always blank keys that contain following words
438 ## (by default client will always blank keys that contain following words
444 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
439 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
445 ## this list be extended with additional keywords set here
440 ## this list be extended with additional keywords set here
446 errormator.request_keys_blacklist =
441 errormator.request_keys_blacklist =
447
442
448 ## list of namespaces that should be ignores when gathering log entries
443 ## list of namespaces that should be ignores when gathering log entries
449 ## can be string with comma separated list of namespaces
444 ## can be string with comma separated list of namespaces
450 ## (by default the client ignores own entries: errormator_client.client)
445 ## (by default the client ignores own entries: errormator_client.client)
451 errormator.log_namespace_blacklist =
446 errormator.log_namespace_blacklist =
452
447
453 ################
448 ################
454 ### [sentry] ###
449 ### [sentry] ###
455 ################
450 ################
456
451
457 ## sentry is a alternative open source error aggregator
452 ## sentry is a alternative open source error aggregator
458 ## you must install python packages `sentry` and `raven` to enable
453 ## you must install python packages `sentry` and `raven` to enable
459
454
460 sentry.dsn = YOUR_DNS
455 sentry.dsn = YOUR_DNS
461 sentry.servers =
456 sentry.servers =
462 sentry.name =
457 sentry.name =
463 sentry.key =
458 sentry.key =
464 sentry.public_key =
459 sentry.public_key =
465 sentry.secret_key =
460 sentry.secret_key =
466 sentry.project =
461 sentry.project =
467 sentry.site =
462 sentry.site =
468 sentry.include_paths =
463 sentry.include_paths =
469 sentry.exclude_paths =
464 sentry.exclude_paths =
470
465
471 ################################################################################
466 ################################################################################
472 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
467 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
473 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
468 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
474 ## execute malicious code after an exception is raised. ##
469 ## execute malicious code after an exception is raised. ##
475 ################################################################################
470 ################################################################################
476 set debug = false
471 set debug = false
477
472
478 ##################################
473 ##################################
479 ### LOGVIEW CONFIG ###
474 ### LOGVIEW CONFIG ###
480 ##################################
475 ##################################
481
476
482 logview.sqlalchemy = #faa
477 logview.sqlalchemy = #faa
483 logview.pylons.templating = #bfb
478 logview.pylons.templating = #bfb
484 logview.pylons.util = #eee
479 logview.pylons.util = #eee
485
480
486 #########################################################
481 #########################################################
487 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
482 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
488 #########################################################
483 #########################################################
489
484
490 # SQLITE [default]
485 # SQLITE [default]
491 #sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
486 #sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
492 sqlalchemy.db1.url = sqlite:///%(here)s/kallithea_test.sqlite
487 sqlalchemy.db1.url = sqlite:///%(here)s/kallithea_test.sqlite
493
488
494 # POSTGRESQL
489 # POSTGRESQL
495 #sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
490 #sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
496
491
497 # MySQL
492 # MySQL
498 #sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
493 #sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea?charset=utf8
499
494
500 # see sqlalchemy docs for others
495 # see sqlalchemy docs for others
501
496
502 sqlalchemy.db1.echo = false
497 sqlalchemy.db1.echo = false
503 sqlalchemy.db1.pool_recycle = 3600
498 sqlalchemy.db1.pool_recycle = 3600
504
499
505 ################################
500 ################################
506 ### LOGGING CONFIGURATION ####
501 ### LOGGING CONFIGURATION ####
507 ################################
502 ################################
508
503
509 [loggers]
504 [loggers]
510 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
505 keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
511
506
512 [handlers]
507 [handlers]
513 keys = console, console_sql
508 keys = console, console_sql
514
509
515 [formatters]
510 [formatters]
516 keys = generic, color_formatter, color_formatter_sql
511 keys = generic, color_formatter, color_formatter_sql
517
512
518 #############
513 #############
519 ## LOGGERS ##
514 ## LOGGERS ##
520 #############
515 #############
521
516
522 [logger_root]
517 [logger_root]
523 #level = NOTSET
518 #level = NOTSET
524 level = DEBUG
519 level = DEBUG
525 handlers = console
520 handlers = console
526
521
527 [logger_routes]
522 [logger_routes]
528 level = DEBUG
523 level = DEBUG
529 handlers =
524 handlers =
530 qualname = routes.middleware
525 qualname = routes.middleware
531 ## "level = DEBUG" logs the route matched and routing variables.
526 ## "level = DEBUG" logs the route matched and routing variables.
532 propagate = 1
527 propagate = 1
533
528
534 [logger_beaker]
529 [logger_beaker]
535 level = DEBUG
530 level = DEBUG
536 handlers =
531 handlers =
537 qualname = beaker.container
532 qualname = beaker.container
538 propagate = 1
533 propagate = 1
539
534
540 [logger_templates]
535 [logger_templates]
541 level = INFO
536 level = INFO
542 handlers =
537 handlers =
543 qualname = pylons.templating
538 qualname = pylons.templating
544 propagate = 1
539 propagate = 1
545
540
546 [logger_kallithea]
541 [logger_kallithea]
547 level = DEBUG
542 level = DEBUG
548 handlers =
543 handlers =
549 qualname = kallithea
544 qualname = kallithea
550 propagate = 1
545 propagate = 1
551
546
552 [logger_sqlalchemy]
547 [logger_sqlalchemy]
553 #level = INFO
548 #level = INFO
554 level = ERROR
549 level = ERROR
555 #handlers = console_sql
550 #handlers = console_sql
556 handlers = console
551 handlers = console
557 qualname = sqlalchemy.engine
552 qualname = sqlalchemy.engine
558 propagate = 0
553 propagate = 0
559
554
560 [logger_whoosh_indexer]
555 [logger_whoosh_indexer]
561 level = DEBUG
556 level = DEBUG
562 handlers =
557 handlers =
563 qualname = whoosh_indexer
558 qualname = whoosh_indexer
564 propagate = 1
559 propagate = 1
565
560
566 ##############
561 ##############
567 ## HANDLERS ##
562 ## HANDLERS ##
568 ##############
563 ##############
569
564
570 [handler_console]
565 [handler_console]
571 class = StreamHandler
566 class = StreamHandler
572 args = (sys.stderr,)
567 args = (sys.stderr,)
573 #level = INFO
568 #level = INFO
574 level = NOTSET
569 level = NOTSET
575 formatter = generic
570 formatter = generic
576
571
577 [handler_console_sql]
572 [handler_console_sql]
578 class = StreamHandler
573 class = StreamHandler
579 args = (sys.stderr,)
574 args = (sys.stderr,)
580 level = WARN
575 level = WARN
581 formatter = generic
576 formatter = generic
582
577
583 ################
578 ################
584 ## FORMATTERS ##
579 ## FORMATTERS ##
585 ################
580 ################
586
581
587 [formatter_generic]
582 [formatter_generic]
588 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
583 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
589 datefmt = %Y-%m-%d %H:%M:%S
584 datefmt = %Y-%m-%d %H:%M:%S
590
585
591 [formatter_color_formatter]
586 [formatter_color_formatter]
592 class = kallithea.lib.colored_formatter.ColorFormatter
587 class = kallithea.lib.colored_formatter.ColorFormatter
593 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
588 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
594 datefmt = %Y-%m-%d %H:%M:%S
589 datefmt = %Y-%m-%d %H:%M:%S
595
590
596 [formatter_color_formatter_sql]
591 [formatter_color_formatter_sql]
597 class = kallithea.lib.colored_formatter.ColorFormatterSql
592 class = kallithea.lib.colored_formatter.ColorFormatterSql
598 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
593 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
599 datefmt = %Y-%m-%d %H:%M:%S
594 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,173 +1,171 b''
1 #!/usr/bin/env python2
1 #!/usr/bin/env python2
2 """
2 """
3 Based on kallithea/bin/template.ini.mako, generate
3 Based on kallithea/bin/template.ini.mako, generate
4 kallithea/config/deployment.ini_tmpl
4 kallithea/config/deployment.ini_tmpl
5 development.ini
5 development.ini
6 kallithea/tests/test.ini
6 kallithea/tests/test.ini
7 """
7 """
8
8
9 import re
9 import re
10
10
11 makofile = 'kallithea/bin/template.ini.mako'
11 makofile = 'kallithea/bin/template.ini.mako'
12
12
13 # the mako conditionals used in all other ini files and templates
13 # the mako conditionals used in all other ini files and templates
14 selected_mako_conditionals = set([
14 selected_mako_conditionals = set([
15 "database_engine == 'sqlite'",
15 "database_engine == 'sqlite'",
16 "http_server == 'waitress'",
16 "http_server == 'waitress'",
17 "error_aggregation_service == 'errormator'",
17 "error_aggregation_service == 'errormator'",
18 "error_aggregation_service == 'sentry'",
18 "error_aggregation_service == 'sentry'",
19 ])
19 ])
20
20
21 # the mako variables used in all other ini files and templates
21 # the mako variables used in all other ini files and templates
22 mako_variable_values = {
22 mako_variable_values = {
23 'host': '127.0.0.1',
23 'host': '127.0.0.1',
24 'port': '5000',
24 'port': '5000',
25 'here': '%(here)s',
25 'here': '%(here)s',
26 'uuid()': '${app_instance_uuid}',
26 'uuid()': '${app_instance_uuid}',
27 }
27 }
28
28
29 # files to be generated from the mako template
29 # files to be generated from the mako template
30 ini_files = [
30 ini_files = [
31 ('kallithea/config/deployment.ini_tmpl',
31 ('kallithea/config/deployment.ini_tmpl',
32 '''
32 '''
33 Kallithea - Example config
33 Kallithea - Example config
34
34
35 The %(here)s variable will be replaced with the parent directory of this file
35 The %(here)s variable will be replaced with the parent directory of this file
36 ''',
36 ''',
37 {}, # exactly the same settings as template.ini.mako
37 {}, # exactly the same settings as template.ini.mako
38 ),
38 ),
39 ('kallithea/tests/test.ini',
39 ('kallithea/tests/test.ini',
40 '''
40 '''
41 Kallithea - config for tests:
41 Kallithea - config for tests:
42 initial_repo_scan = true
42 initial_repo_scan = true
43 vcs_full_cache = false
44 sqlalchemy and kallithea_test.sqlite
43 sqlalchemy and kallithea_test.sqlite
45 custom logging
44 custom logging
46
45
47 The %(here)s variable will be replaced with the parent directory of this file
46 The %(here)s variable will be replaced with the parent directory of this file
48 ''',
47 ''',
49 {
48 {
50 '[server:main]': {
49 '[server:main]': {
51 'port': '4999',
50 'port': '4999',
52 },
51 },
53 '[app:main]': {
52 '[app:main]': {
54 'initial_repo_scan': 'true',
53 'initial_repo_scan': 'true',
55 'app_instance_uuid': 'test',
54 'app_instance_uuid': 'test',
56 'vcs_full_cache': 'false',
57 'show_revision_number': 'true',
55 'show_revision_number': 'true',
58 'beaker.cache.sql_cache_short.expire': '1',
56 'beaker.cache.sql_cache_short.expire': '1',
59 'beaker.session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}',
57 'beaker.session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}',
60 'sqlalchemy.db1.url': 'sqlite:///%(here)s/kallithea_test.sqlite',
58 'sqlalchemy.db1.url': 'sqlite:///%(here)s/kallithea_test.sqlite',
61 },
59 },
62 '[logger_root]': {
60 '[logger_root]': {
63 'level': 'DEBUG',
61 'level': 'DEBUG',
64 },
62 },
65 '[logger_sqlalchemy]': {
63 '[logger_sqlalchemy]': {
66 'level': 'ERROR',
64 'level': 'ERROR',
67 'handlers': 'console',
65 'handlers': 'console',
68 },
66 },
69 '[handler_console]': {
67 '[handler_console]': {
70 'level': 'NOTSET',
68 'level': 'NOTSET',
71 },
69 },
72 },
70 },
73 ),
71 ),
74 ('development.ini',
72 ('development.ini',
75 '''
73 '''
76 Kallithea - Development config:
74 Kallithea - Development config:
77 listening on *:5000
75 listening on *:5000
78 sqlite and kallithea.db
76 sqlite and kallithea.db
79 initial_repo_scan = true
77 initial_repo_scan = true
80 set debug = true
78 set debug = true
81 verbose and colorful logging
79 verbose and colorful logging
82
80
83 The %(here)s variable will be replaced with the parent directory of this file
81 The %(here)s variable will be replaced with the parent directory of this file
84 ''',
82 ''',
85 {
83 {
86 '[server:main]': {
84 '[server:main]': {
87 'host': '0.0.0.0',
85 'host': '0.0.0.0',
88 },
86 },
89 '[app:main]': {
87 '[app:main]': {
90 'initial_repo_scan': 'true',
88 'initial_repo_scan': 'true',
91 'set debug': 'true',
89 'set debug': 'true',
92 'app_instance_uuid': 'development-not-secret',
90 'app_instance_uuid': 'development-not-secret',
93 'beaker.session.secret': 'development-not-secret',
91 'beaker.session.secret': 'development-not-secret',
94 },
92 },
95 '[handler_console]': {
93 '[handler_console]': {
96 'level': 'DEBUG',
94 'level': 'DEBUG',
97 'formatter': 'color_formatter',
95 'formatter': 'color_formatter',
98 },
96 },
99 '[handler_console_sql]': {
97 '[handler_console_sql]': {
100 'level': 'DEBUG',
98 'level': 'DEBUG',
101 'formatter': 'color_formatter_sql',
99 'formatter': 'color_formatter_sql',
102 },
100 },
103 },
101 },
104 ),
102 ),
105 ]
103 ]
106
104
107
105
108 def main():
106 def main():
109 # make sure all mako lines starting with '#' (the '##' comments) are marked up as <text>
107 # make sure all mako lines starting with '#' (the '##' comments) are marked up as <text>
110 print 'reading:', makofile
108 print 'reading:', makofile
111 mako_org = file(makofile).read()
109 mako_org = file(makofile).read()
112 mako_no_text_markup = re.sub(r'</?%text>', '', mako_org)
110 mako_no_text_markup = re.sub(r'</?%text>', '', mako_org)
113 mako_marked_up = re.sub(r'\n(##.*)', r'\n<%text>\1</%text>', mako_no_text_markup, flags=re.MULTILINE)
111 mako_marked_up = re.sub(r'\n(##.*)', r'\n<%text>\1</%text>', mako_no_text_markup, flags=re.MULTILINE)
114 if mako_marked_up != mako_org:
112 if mako_marked_up != mako_org:
115 print 'writing:', makofile
113 print 'writing:', makofile
116 file(makofile, 'w').write(mako_marked_up)
114 file(makofile, 'w').write(mako_marked_up)
117
115
118 # select the right mako conditionals for the other less sophisticated formats
116 # select the right mako conditionals for the other less sophisticated formats
119 def sub_conditionals(m):
117 def sub_conditionals(m):
120 """given a %if...%endif match, replace with just the selected
118 """given a %if...%endif match, replace with just the selected
121 conditional sections enabled and the rest as comments
119 conditional sections enabled and the rest as comments
122 """
120 """
123 conditional_lines = m.group(1)
121 conditional_lines = m.group(1)
124 def sub_conditional(m):
122 def sub_conditional(m):
125 """given a conditional and the corresponding lines, return them raw
123 """given a conditional and the corresponding lines, return them raw
126 or commented out, based on whether conditional is selected
124 or commented out, based on whether conditional is selected
127 """
125 """
128 criteria, lines = m.groups()
126 criteria, lines = m.groups()
129 if criteria not in selected_mako_conditionals:
127 if criteria not in selected_mako_conditionals:
130 lines = '\n'.join((l if not l or l.startswith('#') else '#' + l) for l in lines.split('\n'))
128 lines = '\n'.join((l if not l or l.startswith('#') else '#' + l) for l in lines.split('\n'))
131 return lines
129 return lines
132 conditional_lines = re.sub(r'^%(?:el)?if (.*):\n((?:^[^%\n].*\n|\n)*)',
130 conditional_lines = re.sub(r'^%(?:el)?if (.*):\n((?:^[^%\n].*\n|\n)*)',
133 sub_conditional, conditional_lines, flags=re.MULTILINE)
131 sub_conditional, conditional_lines, flags=re.MULTILINE)
134 return conditional_lines
132 return conditional_lines
135 mako_no_conditionals = re.sub(r'^(%if .*\n(?:[^%\n].*\n|%elif .*\n|\n)*)%endif\n',
133 mako_no_conditionals = re.sub(r'^(%if .*\n(?:[^%\n].*\n|%elif .*\n|\n)*)%endif\n',
136 sub_conditionals, mako_no_text_markup, flags=re.MULTILINE)
134 sub_conditionals, mako_no_text_markup, flags=re.MULTILINE)
137
135
138 # expand mako variables
136 # expand mako variables
139 def pyrepl(m):
137 def pyrepl(m):
140 return mako_variable_values.get(m.group(1), m.group(0))
138 return mako_variable_values.get(m.group(1), m.group(0))
141 mako_no_variables = re.sub(r'\${([^}]*)}', pyrepl, mako_no_conditionals)
139 mako_no_variables = re.sub(r'\${([^}]*)}', pyrepl, mako_no_conditionals)
142
140
143 # remove utf-8 coding header
141 # remove utf-8 coding header
144 base_ini = re.sub(r'^## -\*- coding: utf-8 -\*-\n', '', mako_no_variables)
142 base_ini = re.sub(r'^## -\*- coding: utf-8 -\*-\n', '', mako_no_variables)
145
143
146 # create ini files
144 # create ini files
147 for fn, desc, settings in ini_files:
145 for fn, desc, settings in ini_files:
148 print 'updating:', fn
146 print 'updating:', fn
149 ini_lines = re.sub(
147 ini_lines = re.sub(
150 '# Kallithea - config file generated with kallithea-config *#\n',
148 '# Kallithea - config file generated with kallithea-config *#\n',
151 ''.join('# %-77s#\n' % l.strip() for l in desc.strip().split('\n')),
149 ''.join('# %-77s#\n' % l.strip() for l in desc.strip().split('\n')),
152 base_ini)
150 base_ini)
153 def process_section(m):
151 def process_section(m):
154 """process a ini section, replacing values as necessary"""
152 """process a ini section, replacing values as necessary"""
155 sectionname, lines = m.groups()
153 sectionname, lines = m.groups()
156 if sectionname in settings:
154 if sectionname in settings:
157 section_settings = settings[sectionname]
155 section_settings = settings[sectionname]
158 def process_line(m):
156 def process_line(m):
159 """process a section line and update value if necessary"""
157 """process a section line and update value if necessary"""
160 setting, value = m.groups()
158 setting, value = m.groups()
161 line = m.group(0)
159 line = m.group(0)
162 if setting in section_settings:
160 if setting in section_settings:
163 line = '%s = %s' % (setting, section_settings[setting])
161 line = '%s = %s' % (setting, section_settings[setting])
164 if '$' not in value:
162 if '$' not in value:
165 line = '#%s = %s\n%s' % (setting, value, line)
163 line = '#%s = %s\n%s' % (setting, value, line)
166 return line.rstrip()
164 return line.rstrip()
167 lines = re.sub(r'^([^#\n].*) = ?(.*)', process_line, lines, flags=re.MULTILINE)
165 lines = re.sub(r'^([^#\n].*) = ?(.*)', process_line, lines, flags=re.MULTILINE)
168 return sectionname + '\n' + lines
166 return sectionname + '\n' + lines
169 ini_lines = re.sub(r'^(\[.*\])\n((?:(?:[^[\n].*)?\n)*)', process_section, ini_lines, flags=re.MULTILINE)
167 ini_lines = re.sub(r'^(\[.*\])\n((?:(?:[^[\n].*)?\n)*)', process_section, ini_lines, flags=re.MULTILINE)
170 file(fn, 'w').write(ini_lines)
168 file(fn, 'w').write(ini_lines)
171
169
172 if __name__ == '__main__':
170 if __name__ == '__main__':
173 main()
171 main()
General Comments 0
You need to be logged in to leave comments. Login now