##// END OF EJS Templates
hooks: make the Python interpreter for Git hooks configurable as 'git_hook_interpreter' (Issue #333)...
Thomas De Schampheleire -
r7538:1bafb2d0 stable
parent child Browse files
Show More
@@ -1,468 +1,477 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # Kallithea - config file generated with kallithea-config #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10
11 11 ################################################################################
12 12 ## Email settings ##
13 13 ## ##
14 14 ## Refer to the documentation ("Email settings") for more details. ##
15 15 ## ##
16 16 ## It is recommended to use a valid sender address that passes access ##
17 17 ## validation and spam filtering in mail servers. ##
18 18 ################################################################################
19 19
20 20 ## 'From' header for application emails. You can optionally add a name.
21 21 ## Default:
22 22 #app_email_from = Kallithea
23 23 ## Examples:
24 24 #app_email_from = Kallithea <kallithea-noreply@example.com>
25 25 #app_email_from = kallithea-noreply@example.com
26 26
27 27 ## Subject prefix for application emails.
28 28 ## A space between this prefix and the real subject is automatically added.
29 29 ## Default:
30 30 #email_prefix =
31 31 ## Example:
32 32 #email_prefix = [Kallithea]
33 33
34 34 ## Recipients for error emails and fallback recipients of application mails.
35 35 ## Multiple addresses can be specified, comma-separated.
36 36 ## Only addresses are allowed, do not add any name part.
37 37 ## Default:
38 38 #email_to =
39 39 ## Examples:
40 40 #email_to = admin@example.com
41 41 #email_to = admin@example.com,another_admin@example.com
42 42 email_to =
43 43
44 44 ## 'From' header for error emails. You can optionally add a name.
45 45 ## Default: (none)
46 46 ## Examples:
47 47 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
48 48 #error_email_from = kallithea_errors@example.com
49 49 error_email_from =
50 50
51 51 ## SMTP server settings
52 52 ## If specifying credentials, make sure to use secure connections.
53 53 ## Default: Send unencrypted unauthenticated mails to the specified smtp_server.
54 54 ## For "SSL", use smtp_use_ssl = true and smtp_port = 465.
55 55 ## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.
56 56 smtp_server =
57 57 #smtp_username =
58 58 #smtp_password =
59 59 smtp_port =
60 60 #smtp_use_ssl = false
61 61 #smtp_use_tls = false
62 62
63 63 ## Entry point for 'gearbox serve'
64 64 [server:main]
65 65 #host = 127.0.0.1
66 66 host = 0.0.0.0
67 67 port = 5000
68 68
69 69 ## WAITRESS ##
70 70 use = egg:waitress#main
71 71 ## number of worker threads
72 72 threads = 1
73 73 ## MAX BODY SIZE 100GB
74 74 max_request_body_size = 107374182400
75 75 ## use poll instead of select, fixes fd limits, may not work on old
76 76 ## windows systems.
77 77 #asyncore_use_poll = True
78 78
79 79 ## middleware for hosting the WSGI application under a URL prefix
80 80 #[filter:proxy-prefix]
81 81 #use = egg:PasteDeploy#prefix
82 82 #prefix = /<your-prefix>
83 83
84 84 [app:main]
85 85 use = egg:kallithea
86 86 ## enable proxy prefix middleware
87 87 #filter-with = proxy-prefix
88 88
89 89 full_stack = true
90 90 static_files = true
91 91
92 92 ## Internationalization (see setup documentation for details)
93 93 ## By default, the language requested by the browser is used if available.
94 94 #i18n.enabled = false
95 95 ## Fallback language, empty for English (valid values are the names of subdirectories in kallithea/i18n):
96 96 i18n.lang =
97 97
98 98 cache_dir = %(here)s/data
99 99 index_dir = %(here)s/data/index
100 100
101 101 ## uncomment and set this path to use archive download cache
102 102 archive_cache_dir = %(here)s/tarballcache
103 103
104 104 ## change this to unique ID for security
105 105 #app_instance_uuid = VERY-SECRET
106 106 app_instance_uuid = development-not-secret
107 107
108 108 ## cut off limit for large diffs (size in bytes)
109 109 cut_off_limit = 256000
110 110
111 111 ## force https in Kallithea, fixes https redirects, assumes it's always https
112 112 force_https = false
113 113
114 114 ## use Strict-Transport-Security headers
115 115 use_htsts = false
116 116
117 117 ## number of commits stats will parse on each iteration
118 118 commit_parse_limit = 25
119 119
120 ## Path to Python executable to be used for git hooks.
121 ## This value will be written inside the git hook scripts as the text
122 ## after '#!' (shebang). When empty or not defined, the value of
123 ## 'sys.executable' at the time of installation of the git hooks is
124 ## used, which is correct in many cases but for example not when using uwsgi.
125 ## If you change this setting, you should reinstall the Git hooks via
126 ## Admin > Settings > Remap and Rescan.
127 # git_hook_interpreter = /srv/kallithea/venv/bin/python2
128
120 129 ## path to git executable
121 130 git_path = git
122 131
123 132 ## git rev filter option, --all is the default filter, if you need to
124 133 ## hide all refs in changelog switch this to --branches --tags
125 134 #git_rev_filter = --branches --tags
126 135
127 136 ## RSS feed options
128 137 rss_cut_off_limit = 256000
129 138 rss_items_per_page = 10
130 139 rss_include_diff = false
131 140
132 141 ## options for showing and identifying changesets
133 142 show_sha_length = 12
134 143 show_revision_number = false
135 144
136 145 ## Canonical URL to use when creating full URLs in UI and texts.
137 146 ## Useful when the site is available under different names or protocols.
138 147 ## Defaults to what is provided in the WSGI environment.
139 148 #canonical_url = https://kallithea.example.com/repos
140 149
141 150 ## gist URL alias, used to create nicer urls for gist. This should be an
142 151 ## url that does rewrites to _admin/gists/<gistid>.
143 152 ## example: http://gist.example.com/{gistid}. Empty means use the internal
144 153 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
145 154 gist_alias_url =
146 155
147 156 ## white list of API enabled controllers. This allows to add list of
148 157 ## controllers to which access will be enabled by api_key. eg: to enable
149 158 ## api access to raw_files put `FilesController:raw`, to enable access to patches
150 159 ## add `ChangesetController:changeset_patch`. This list should be "," separated
151 160 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
152 161 ## Recommended settings below are commented out:
153 162 api_access_controllers_whitelist =
154 163 # ChangesetController:changeset_patch,
155 164 # ChangesetController:changeset_raw,
156 165 # FilesController:raw,
157 166 # FilesController:archivefile
158 167
159 168 ## default encoding used to convert from and to unicode
160 169 ## can be also a comma separated list of encoding in case of mixed encodings
161 170 default_encoding = utf-8
162 171
163 172 ## Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea
164 173 hgencoding = utf-8
165 174
166 175 ## issue tracker for Kallithea (leave blank to disable, absent for default)
167 176 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
168 177
169 178 ## issue tracking mapping for commit messages, comments, PR descriptions, ...
170 179 ## Refer to the documentation ("Integration with issue trackers") for more details.
171 180
172 181 ## regular expression to match issue references
173 182 ## This pattern may/should contain parenthesized groups, that can
174 183 ## be referred to in issue_server_link or issue_sub using Python backreferences
175 184 ## (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.
176 185 ## To require mandatory whitespace before the issue pattern, use:
177 186 ## (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace
178 187 ## behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.
179 188
180 189 issue_pat = #(\d+)
181 190
182 191 ## server url to the issue
183 192 ## This pattern may/should contain backreferences to parenthesized groups in issue_pat.
184 193 ## A backreference can be \1, \2, ... or \g<groupname> if you specified a named group
185 194 ## called 'groupname' in issue_pat.
186 195 ## The special token {repo} is replaced with the full repository name
187 196 ## including repository groups, while {repo_name} is replaced with just
188 197 ## the name of the repository.
189 198
190 199 issue_server_link = https://issues.example.com/{repo}/issue/\1
191 200
192 201 ## substitution pattern to use as the link text
193 202 ## If issue_sub is empty, the text matched by issue_pat is retained verbatim
194 203 ## for the link text. Otherwise, the link text is that of issue_sub, with any
195 204 ## backreferences to groups in issue_pat replaced.
196 205
197 206 issue_sub =
198 207
199 208 ## issue_pat, issue_server_link and issue_sub can have suffixes to specify
200 209 ## multiple patterns, to other issues server, wiki or others
201 210 ## below an example how to create a wiki pattern
202 211 # wiki-some-id -> https://wiki.example.com/some-id
203 212
204 213 #issue_pat_wiki = wiki-(\S+)
205 214 #issue_server_link_wiki = https://wiki.example.com/\1
206 215 #issue_sub_wiki = WIKI-\1
207 216
208 217 ## alternative return HTTP header for failed authentication. Default HTTP
209 218 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
210 219 ## handling that. Set this variable to 403 to return HTTPForbidden
211 220 auth_ret_code =
212 221
213 222 ## locking return code. When repository is locked return this HTTP code. 2XX
214 223 ## codes don't break the transactions while 4XX codes do
215 224 lock_ret_code = 423
216 225
217 226 ## allows to change the repository location in settings page
218 227 allow_repo_location_change = True
219 228
220 229 ## allows to setup custom hooks in settings page
221 230 allow_custom_hooks_settings = True
222 231
223 232 ## extra extensions for indexing, space separated and without the leading '.'.
224 233 # index.extensions =
225 234 # gemfile
226 235 # lock
227 236
228 237 ## extra filenames for indexing, space separated
229 238 # index.filenames =
230 239 # .dockerignore
231 240 # .editorconfig
232 241 # INSTALL
233 242 # CHANGELOG
234 243
235 244 ####################################
236 245 ### CELERY CONFIG ####
237 246 ####################################
238 247
239 248 use_celery = false
240 249
241 250 ## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq:
242 251 broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost
243 252
244 253 celery.imports = kallithea.lib.celerylib.tasks
245 254 celery.accept.content = pickle
246 255 celery.result.backend = amqp
247 256 celery.result.dburi = amqp://
248 257 celery.result.serialier = json
249 258
250 259 #celery.send.task.error.emails = true
251 260 #celery.amqp.task.result.expires = 18000
252 261
253 262 celeryd.concurrency = 2
254 263 celeryd.max.tasks.per.child = 1
255 264
256 265 ## If true, tasks will never be sent to the queue, but executed locally instead.
257 266 celery.always.eager = false
258 267
259 268 ####################################
260 269 ### BEAKER CACHE ####
261 270 ####################################
262 271
263 272 beaker.cache.data_dir = %(here)s/data/cache/data
264 273 beaker.cache.lock_dir = %(here)s/data/cache/lock
265 274
266 275 beaker.cache.regions = short_term,long_term,sql_cache_short
267 276
268 277 beaker.cache.short_term.type = memory
269 278 beaker.cache.short_term.expire = 60
270 279 beaker.cache.short_term.key_length = 256
271 280
272 281 beaker.cache.long_term.type = memory
273 282 beaker.cache.long_term.expire = 36000
274 283 beaker.cache.long_term.key_length = 256
275 284
276 285 beaker.cache.sql_cache_short.type = memory
277 286 beaker.cache.sql_cache_short.expire = 10
278 287 beaker.cache.sql_cache_short.key_length = 256
279 288
280 289 ####################################
281 290 ### BEAKER SESSION ####
282 291 ####################################
283 292
284 293 ## Name of session cookie. Should be unique for a given host and path, even when running
285 294 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
286 295 beaker.session.key = kallithea
287 296 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
288 297 beaker.session.httponly = true
289 298 ## Session lifetime. 2592000 seconds is 30 days.
290 299 beaker.session.timeout = 2592000
291 300
292 301 ## Server secret used with HMAC to ensure integrity of cookies.
293 302 #beaker.session.secret = VERY-SECRET
294 303 beaker.session.secret = development-not-secret
295 304 ## Further, encrypt the data with AES.
296 305 #beaker.session.encrypt_key = <key_for_encryption>
297 306 #beaker.session.validate_key = <validation_key>
298 307
299 308 ## Type of storage used for the session, current types are
300 309 ## dbm, file, memcached, database, and memory.
301 310
302 311 ## File system storage of session data. (default)
303 312 #beaker.session.type = file
304 313
305 314 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
306 315 #beaker.session.type = cookie
307 316
308 317 ## Database storage of session data.
309 318 #beaker.session.type = ext:database
310 319 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
311 320 #beaker.session.table_name = db_session
312 321
313 322 ################################################################################
314 323 ## WARNING: *DEBUG MODE MUST BE OFF IN A PRODUCTION ENVIRONMENT* ##
315 324 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
316 325 ## execute malicious code after an exception is raised. ##
317 326 ################################################################################
318 327 #debug = false
319 328 debug = true
320 329
321 330 ##################################
322 331 ### LOGVIEW CONFIG ###
323 332 ##################################
324 333
325 334 logview.sqlalchemy = #faa
326 335 logview.pylons.templating = #bfb
327 336 logview.pylons.util = #eee
328 337
329 338 #########################################################
330 339 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
331 340 #########################################################
332 341
333 342 # SQLITE [default]
334 343 sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
335 344
336 345 # see sqlalchemy docs for others
337 346
338 347 sqlalchemy.pool_recycle = 3600
339 348
340 349 ################################
341 350 ### ALEMBIC CONFIGURATION ####
342 351 ################################
343 352
344 353 [alembic]
345 354 script_location = kallithea:alembic
346 355
347 356 ################################
348 357 ### LOGGING CONFIGURATION ####
349 358 ################################
350 359
351 360 [loggers]
352 361 keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
353 362
354 363 [handlers]
355 364 keys = console, console_sql
356 365
357 366 [formatters]
358 367 keys = generic, color_formatter, color_formatter_sql
359 368
360 369 #############
361 370 ## LOGGERS ##
362 371 #############
363 372
364 373 [logger_root]
365 374 level = NOTSET
366 375 handlers = console
367 376
368 377 [logger_routes]
369 378 #level = WARN
370 379 level = DEBUG
371 380 handlers =
372 381 qualname = routes.middleware
373 382 ## "level = DEBUG" logs the route matched and routing variables.
374 383 propagate = 1
375 384
376 385 [logger_beaker]
377 386 #level = WARN
378 387 level = DEBUG
379 388 handlers =
380 389 qualname = beaker.container
381 390 propagate = 1
382 391
383 392 [logger_templates]
384 393 #level = WARN
385 394 level = INFO
386 395 handlers =
387 396 qualname = pylons.templating
388 397 propagate = 1
389 398
390 399 [logger_kallithea]
391 400 #level = WARN
392 401 level = DEBUG
393 402 handlers =
394 403 qualname = kallithea
395 404 propagate = 1
396 405
397 406 [logger_tg]
398 407 #level = WARN
399 408 level = DEBUG
400 409 handlers =
401 410 qualname = tg
402 411 propagate = 1
403 412
404 413 [logger_gearbox]
405 414 #level = WARN
406 415 level = DEBUG
407 416 handlers =
408 417 qualname = gearbox
409 418 propagate = 1
410 419
411 420 [logger_sqlalchemy]
412 421 level = WARN
413 422 handlers = console_sql
414 423 qualname = sqlalchemy.engine
415 424 propagate = 0
416 425
417 426 [logger_whoosh_indexer]
418 427 #level = WARN
419 428 level = DEBUG
420 429 handlers =
421 430 qualname = whoosh_indexer
422 431 propagate = 1
423 432
424 433 [logger_werkzeug]
425 434 level = WARN
426 435 handlers =
427 436 qualname = werkzeug
428 437 propagate = 1
429 438
430 439 [logger_backlash]
431 440 level = WARN
432 441 handlers =
433 442 qualname = backlash
434 443 propagate = 1
435 444
436 445 ##############
437 446 ## HANDLERS ##
438 447 ##############
439 448
440 449 [handler_console]
441 450 class = StreamHandler
442 451 args = (sys.stderr,)
443 452 #formatter = generic
444 453 formatter = color_formatter
445 454
446 455 [handler_console_sql]
447 456 class = StreamHandler
448 457 args = (sys.stderr,)
449 458 #formatter = generic
450 459 formatter = color_formatter_sql
451 460
452 461 ################
453 462 ## FORMATTERS ##
454 463 ################
455 464
456 465 [formatter_generic]
457 466 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
458 467 datefmt = %Y-%m-%d %H:%M:%S
459 468
460 469 [formatter_color_formatter]
461 470 class = kallithea.lib.colored_formatter.ColorFormatter
462 471 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
463 472 datefmt = %Y-%m-%d %H:%M:%S
464 473
465 474 [formatter_color_formatter_sql]
466 475 class = kallithea.lib.colored_formatter.ColorFormatterSql
467 476 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
468 477 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,648 +1,660 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%text>################################################################################</%text>
3 3 <%text>################################################################################</%text>
4 4 # Kallithea - config file generated with kallithea-config #
5 5 # #
6 6 # The %(here)s variable will be replaced with the parent directory of this file#
7 7 <%text>################################################################################</%text>
8 8 <%text>################################################################################</%text>
9 9
10 10 [DEFAULT]
11 11
12 12 <%text>################################################################################</%text>
13 13 <%text>## Email settings ##</%text>
14 14 <%text>## ##</%text>
15 15 <%text>## Refer to the documentation ("Email settings") for more details. ##</%text>
16 16 <%text>## ##</%text>
17 17 <%text>## It is recommended to use a valid sender address that passes access ##</%text>
18 18 <%text>## validation and spam filtering in mail servers. ##</%text>
19 19 <%text>################################################################################</%text>
20 20
21 21 <%text>## 'From' header for application emails. You can optionally add a name.</%text>
22 22 <%text>## Default:</%text>
23 23 #app_email_from = Kallithea
24 24 <%text>## Examples:</%text>
25 25 #app_email_from = Kallithea <kallithea-noreply@example.com>
26 26 #app_email_from = kallithea-noreply@example.com
27 27
28 28 <%text>## Subject prefix for application emails.</%text>
29 29 <%text>## A space between this prefix and the real subject is automatically added.</%text>
30 30 <%text>## Default:</%text>
31 31 #email_prefix =
32 32 <%text>## Example:</%text>
33 33 #email_prefix = [Kallithea]
34 34
35 35 <%text>## Recipients for error emails and fallback recipients of application mails.</%text>
36 36 <%text>## Multiple addresses can be specified, comma-separated.</%text>
37 37 <%text>## Only addresses are allowed, do not add any name part.</%text>
38 38 <%text>## Default:</%text>
39 39 #email_to =
40 40 <%text>## Examples:</%text>
41 41 #email_to = admin@example.com
42 42 #email_to = admin@example.com,another_admin@example.com
43 43 email_to =
44 44
45 45 <%text>## 'From' header for error emails. You can optionally add a name.</%text>
46 46 <%text>## Default: (none)</%text>
47 47 <%text>## Examples:</%text>
48 48 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
49 49 #error_email_from = kallithea_errors@example.com
50 50 error_email_from =
51 51
52 52 <%text>## SMTP server settings</%text>
53 53 <%text>## If specifying credentials, make sure to use secure connections.</%text>
54 54 <%text>## Default: Send unencrypted unauthenticated mails to the specified smtp_server.</%text>
55 55 <%text>## For "SSL", use smtp_use_ssl = true and smtp_port = 465.</%text>
56 56 <%text>## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.</%text>
57 57 smtp_server =
58 58 #smtp_username =
59 59 #smtp_password =
60 60 smtp_port =
61 61 #smtp_use_ssl = false
62 62 #smtp_use_tls = false
63 63
64 64 %if http_server != 'uwsgi':
65 65 <%text>## Entry point for 'gearbox serve'</%text>
66 66 [server:main]
67 67 host = ${host}
68 68 port = ${port}
69 69
70 70 %if http_server == 'gearbox':
71 71 <%text>## Gearbox default web server ##</%text>
72 72 use = egg:gearbox#wsgiref
73 73 <%text>## nr of worker threads to spawn</%text>
74 74 threadpool_workers = 1
75 75 <%text>## max request before thread respawn</%text>
76 76 threadpool_max_requests = 100
77 77 <%text>## option to use threads of process</%text>
78 78 use_threadpool = true
79 79
80 80 %elif http_server == 'gevent':
81 81 <%text>## Gearbox gevent web server ##</%text>
82 82 use = egg:gearbox#gevent
83 83
84 84 %elif http_server == 'waitress':
85 85 <%text>## WAITRESS ##</%text>
86 86 use = egg:waitress#main
87 87 <%text>## number of worker threads</%text>
88 88 threads = 1
89 89 <%text>## MAX BODY SIZE 100GB</%text>
90 90 max_request_body_size = 107374182400
91 91 <%text>## use poll instead of select, fixes fd limits, may not work on old</%text>
92 92 <%text>## windows systems.</%text>
93 93 #asyncore_use_poll = True
94 94
95 95 %elif http_server == 'gunicorn':
96 96 <%text>## GUNICORN ##</%text>
97 97 use = egg:gunicorn#main
98 98 <%text>## number of process workers. You must set `instance_id = *` when this option</%text>
99 99 <%text>## is set to more than one worker</%text>
100 100 workers = 4
101 101 <%text>## process name</%text>
102 102 proc_name = kallithea
103 103 <%text>## type of worker class, one of sync, eventlet, gevent, tornado</%text>
104 104 <%text>## recommended for bigger setup is using of of other than sync one</%text>
105 105 worker_class = sync
106 106 max_requests = 1000
107 107 <%text>## amount of time a worker can handle request before it gets killed and</%text>
108 108 <%text>## restarted</%text>
109 109 timeout = 3600
110 110
111 111 %endif
112 112 %else:
113 113 <%text>## UWSGI ##</%text>
114 114 <%text>## run with uwsgi --ini-paste-logged <inifile.ini></%text>
115 115 [uwsgi]
116 116 socket = /tmp/uwsgi.sock
117 117 master = true
118 118 http = ${host}:${port}
119 119
120 120 <%text>## set as daemon and redirect all output to file</%text>
121 121 #daemonize = ./uwsgi_kallithea.log
122 122
123 123 <%text>## master process PID</%text>
124 124 pidfile = ./uwsgi_kallithea.pid
125 125
126 126 <%text>## stats server with workers statistics, use uwsgitop</%text>
127 127 <%text>## for monitoring, `uwsgitop 127.0.0.1:1717`</%text>
128 128 stats = 127.0.0.1:1717
129 129 memory-report = true
130 130
131 131 <%text>## log 5XX errors</%text>
132 132 log-5xx = true
133 133
134 134 <%text>## Set the socket listen queue size.</%text>
135 135 listen = 128
136 136
137 137 <%text>## Gracefully Reload workers after the specified amount of managed requests</%text>
138 138 <%text>## (avoid memory leaks).</%text>
139 139 max-requests = 1000
140 140
141 141 <%text>## enable large buffers</%text>
142 142 buffer-size = 65535
143 143
144 144 <%text>## socket and http timeouts ##</%text>
145 145 http-timeout = 3600
146 146 socket-timeout = 3600
147 147
148 148 <%text>## Log requests slower than the specified number of milliseconds.</%text>
149 149 log-slow = 10
150 150
151 151 <%text>## Exit if no app can be loaded.</%text>
152 152 need-app = true
153 153
154 154 <%text>## Set lazy mode (load apps in workers instead of master).</%text>
155 155 lazy = true
156 156
157 157 <%text>## scaling ##</%text>
158 158 <%text>## set cheaper algorithm to use, if not set default will be used</%text>
159 159 cheaper-algo = spare
160 160
161 161 <%text>## minimum number of workers to keep at all times</%text>
162 162 cheaper = 1
163 163
164 164 <%text>## number of workers to spawn at startup</%text>
165 165 cheaper-initial = 1
166 166
167 167 <%text>## maximum number of workers that can be spawned</%text>
168 168 workers = 4
169 169
170 170 <%text>## how many workers should be spawned at a time</%text>
171 171 cheaper-step = 1
172 172
173 173 %endif
174 174 <%text>## middleware for hosting the WSGI application under a URL prefix</%text>
175 175 #[filter:proxy-prefix]
176 176 #use = egg:PasteDeploy#prefix
177 177 #prefix = /<your-prefix>
178 178
179 179 [app:main]
180 180 use = egg:kallithea
181 181 <%text>## enable proxy prefix middleware</%text>
182 182 #filter-with = proxy-prefix
183 183
184 184 full_stack = true
185 185 static_files = true
186 186
187 187 <%text>## Internationalization (see setup documentation for details)</%text>
188 188 <%text>## By default, the language requested by the browser is used if available.</%text>
189 189 #i18n.enabled = false
190 190 <%text>## Fallback language, empty for English (valid values are the names of subdirectories in kallithea/i18n):</%text>
191 191 i18n.lang =
192 192
193 193 cache_dir = %(here)s/data
194 194 index_dir = %(here)s/data/index
195 195
196 196 <%text>## uncomment and set this path to use archive download cache</%text>
197 197 archive_cache_dir = %(here)s/tarballcache
198 198
199 199 <%text>## change this to unique ID for security</%text>
200 200 app_instance_uuid = ${uuid()}
201 201
202 202 <%text>## cut off limit for large diffs (size in bytes)</%text>
203 203 cut_off_limit = 256000
204 204
205 205 <%text>## force https in Kallithea, fixes https redirects, assumes it's always https</%text>
206 206 force_https = false
207 207
208 208 <%text>## use Strict-Transport-Security headers</%text>
209 209 use_htsts = false
210 210
211 211 <%text>## number of commits stats will parse on each iteration</%text>
212 212 commit_parse_limit = 25
213 213
214 <%text>## Path to Python executable to be used for git hooks.</%text>
215 <%text>## This value will be written inside the git hook scripts as the text</%text>
216 <%text>## after '#!' (shebang). When empty or not defined, the value of</%text>
217 <%text>## 'sys.executable' at the time of installation of the git hooks is</%text>
218 <%text>## used, which is correct in many cases but for example not when using uwsgi.</%text>
219 <%text>## If you change this setting, you should reinstall the Git hooks via</%text>
220 <%text>## Admin > Settings > Remap and Rescan.</%text>
221 # git_hook_interpreter = /srv/kallithea/venv/bin/python2
222 %if git_hook_interpreter:
223 git_hook_interpreter = ${git_hook_interpreter}
224 %endif
225
214 226 <%text>## path to git executable</%text>
215 227 git_path = git
216 228
217 229 <%text>## git rev filter option, --all is the default filter, if you need to</%text>
218 230 <%text>## hide all refs in changelog switch this to --branches --tags</%text>
219 231 #git_rev_filter = --branches --tags
220 232
221 233 <%text>## RSS feed options</%text>
222 234 rss_cut_off_limit = 256000
223 235 rss_items_per_page = 10
224 236 rss_include_diff = false
225 237
226 238 <%text>## options for showing and identifying changesets</%text>
227 239 show_sha_length = 12
228 240 show_revision_number = false
229 241
230 242 <%text>## Canonical URL to use when creating full URLs in UI and texts.</%text>
231 243 <%text>## Useful when the site is available under different names or protocols.</%text>
232 244 <%text>## Defaults to what is provided in the WSGI environment.</%text>
233 245 #canonical_url = https://kallithea.example.com/repos
234 246
235 247 <%text>## gist URL alias, used to create nicer urls for gist. This should be an</%text>
236 248 <%text>## url that does rewrites to _admin/gists/<gistid>.</%text>
237 249 <%text>## example: http://gist.example.com/{gistid}. Empty means use the internal</%text>
238 250 <%text>## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid></%text>
239 251 gist_alias_url =
240 252
241 253 <%text>## white list of API enabled controllers. This allows to add list of</%text>
242 254 <%text>## controllers to which access will be enabled by api_key. eg: to enable</%text>
243 255 <%text>## api access to raw_files put `FilesController:raw`, to enable access to patches</%text>
244 256 <%text>## add `ChangesetController:changeset_patch`. This list should be "," separated</%text>
245 257 <%text>## Syntax is <ControllerClass>:<function>. Check debug logs for generated names</%text>
246 258 <%text>## Recommended settings below are commented out:</%text>
247 259 api_access_controllers_whitelist =
248 260 # ChangesetController:changeset_patch,
249 261 # ChangesetController:changeset_raw,
250 262 # FilesController:raw,
251 263 # FilesController:archivefile
252 264
253 265 <%text>## default encoding used to convert from and to unicode</%text>
254 266 <%text>## can be also a comma separated list of encoding in case of mixed encodings</%text>
255 267 default_encoding = utf-8
256 268
257 269 <%text>## Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea</%text>
258 270 hgencoding = utf-8
259 271
260 272 <%text>## issue tracker for Kallithea (leave blank to disable, absent for default)</%text>
261 273 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
262 274
263 275 <%text>## issue tracking mapping for commit messages, comments, PR descriptions, ...</%text>
264 276 <%text>## Refer to the documentation ("Integration with issue trackers") for more details.</%text>
265 277
266 278 <%text>## regular expression to match issue references</%text>
267 279 <%text>## This pattern may/should contain parenthesized groups, that can</%text>
268 280 <%text>## be referred to in issue_server_link or issue_sub using Python backreferences</%text>
269 281 <%text>## (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.</%text>
270 282 <%text>## To require mandatory whitespace before the issue pattern, use:</%text>
271 283 <%text>## (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace</%text>
272 284 <%text>## behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.</%text>
273 285
274 286 issue_pat = #(\d+)
275 287
276 288 <%text>## server url to the issue</%text>
277 289 <%text>## This pattern may/should contain backreferences to parenthesized groups in issue_pat.</%text>
278 290 <%text>## A backreference can be \1, \2, ... or \g<groupname> if you specified a named group</%text>
279 291 <%text>## called 'groupname' in issue_pat.</%text>
280 292 <%text>## The special token {repo} is replaced with the full repository name</%text>
281 293 <%text>## including repository groups, while {repo_name} is replaced with just</%text>
282 294 <%text>## the name of the repository.</%text>
283 295
284 296 issue_server_link = https://issues.example.com/{repo}/issue/\1
285 297
286 298 <%text>## substitution pattern to use as the link text</%text>
287 299 <%text>## If issue_sub is empty, the text matched by issue_pat is retained verbatim</%text>
288 300 <%text>## for the link text. Otherwise, the link text is that of issue_sub, with any</%text>
289 301 <%text>## backreferences to groups in issue_pat replaced.</%text>
290 302
291 303 issue_sub =
292 304
293 305 <%text>## issue_pat, issue_server_link and issue_sub can have suffixes to specify</%text>
294 306 <%text>## multiple patterns, to other issues server, wiki or others</%text>
295 307 <%text>## below an example how to create a wiki pattern</%text>
296 308 # wiki-some-id -> https://wiki.example.com/some-id
297 309
298 310 #issue_pat_wiki = wiki-(\S+)
299 311 #issue_server_link_wiki = https://wiki.example.com/\1
300 312 #issue_sub_wiki = WIKI-\1
301 313
302 314 <%text>## alternative return HTTP header for failed authentication. Default HTTP</%text>
303 315 <%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with</%text>
304 316 <%text>## handling that. Set this variable to 403 to return HTTPForbidden</%text>
305 317 auth_ret_code =
306 318
307 319 <%text>## locking return code. When repository is locked return this HTTP code. 2XX</%text>
308 320 <%text>## codes don't break the transactions while 4XX codes do</%text>
309 321 lock_ret_code = 423
310 322
311 323 <%text>## allows to change the repository location in settings page</%text>
312 324 allow_repo_location_change = True
313 325
314 326 <%text>## allows to setup custom hooks in settings page</%text>
315 327 allow_custom_hooks_settings = True
316 328
317 329 <%text>## extra extensions for indexing, space separated and without the leading '.'.</%text>
318 330 # index.extensions =
319 331 # gemfile
320 332 # lock
321 333
322 334 <%text>## extra filenames for indexing, space separated</%text>
323 335 # index.filenames =
324 336 # .dockerignore
325 337 # .editorconfig
326 338 # INSTALL
327 339 # CHANGELOG
328 340
329 341 <%text>####################################</%text>
330 342 <%text>### CELERY CONFIG ####</%text>
331 343 <%text>####################################</%text>
332 344
333 345 use_celery = false
334 346
335 347 <%text>## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq:</%text>
336 348 broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost
337 349
338 350 celery.imports = kallithea.lib.celerylib.tasks
339 351 celery.accept.content = pickle
340 352 celery.result.backend = amqp
341 353 celery.result.dburi = amqp://
342 354 celery.result.serialier = json
343 355
344 356 #celery.send.task.error.emails = true
345 357 #celery.amqp.task.result.expires = 18000
346 358
347 359 celeryd.concurrency = 2
348 360 celeryd.max.tasks.per.child = 1
349 361
350 362 <%text>## If true, tasks will never be sent to the queue, but executed locally instead.</%text>
351 363 celery.always.eager = false
352 364
353 365 <%text>####################################</%text>
354 366 <%text>### BEAKER CACHE ####</%text>
355 367 <%text>####################################</%text>
356 368
357 369 beaker.cache.data_dir = %(here)s/data/cache/data
358 370 beaker.cache.lock_dir = %(here)s/data/cache/lock
359 371
360 372 beaker.cache.regions = short_term,long_term,sql_cache_short
361 373
362 374 beaker.cache.short_term.type = memory
363 375 beaker.cache.short_term.expire = 60
364 376 beaker.cache.short_term.key_length = 256
365 377
366 378 beaker.cache.long_term.type = memory
367 379 beaker.cache.long_term.expire = 36000
368 380 beaker.cache.long_term.key_length = 256
369 381
370 382 beaker.cache.sql_cache_short.type = memory
371 383 beaker.cache.sql_cache_short.expire = 10
372 384 beaker.cache.sql_cache_short.key_length = 256
373 385
374 386 <%text>####################################</%text>
375 387 <%text>### BEAKER SESSION ####</%text>
376 388 <%text>####################################</%text>
377 389
378 390 <%text>## Name of session cookie. Should be unique for a given host and path, even when running</%text>
379 391 <%text>## on different ports. Otherwise, cookie sessions will be shared and messed up.</%text>
380 392 beaker.session.key = kallithea
381 393 <%text>## Sessions should always only be accessible by the browser, not directly by JavaScript.</%text>
382 394 beaker.session.httponly = true
383 395 <%text>## Session lifetime. 2592000 seconds is 30 days.</%text>
384 396 beaker.session.timeout = 2592000
385 397
386 398 <%text>## Server secret used with HMAC to ensure integrity of cookies.</%text>
387 399 beaker.session.secret = ${uuid()}
388 400 <%text>## Further, encrypt the data with AES.</%text>
389 401 #beaker.session.encrypt_key = <key_for_encryption>
390 402 #beaker.session.validate_key = <validation_key>
391 403
392 404 <%text>## Type of storage used for the session, current types are</%text>
393 405 <%text>## dbm, file, memcached, database, and memory.</%text>
394 406
395 407 <%text>## File system storage of session data. (default)</%text>
396 408 #beaker.session.type = file
397 409
398 410 <%text>## Cookie only, store all session data inside the cookie. Requires secure secrets.</%text>
399 411 #beaker.session.type = cookie
400 412
401 413 <%text>## Database storage of session data.</%text>
402 414 #beaker.session.type = ext:database
403 415 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
404 416 #beaker.session.table_name = db_session
405 417
406 418 %if error_aggregation_service == 'appenlight':
407 419 <%text>############################</%text>
408 420 <%text>## ERROR HANDLING SYSTEMS ##</%text>
409 421 <%text>############################</%text>
410 422
411 423 # Propagate email settings to ErrorReporter of TurboGears2
412 424 # You do not normally need to change these lines
413 425 get trace_errors.error_email = email_to
414 426 get trace_errors.smtp_server = smtp_server
415 427 get trace_errors.smtp_port = smtp_port
416 428 get trace_errors.from_address = error_email_from
417 429
418 430 <%text>####################</%text>
419 431 <%text>### [appenlight] ###</%text>
420 432 <%text>####################</%text>
421 433
422 434 <%text>## AppEnlight is tailored to work with Kallithea, see</%text>
423 435 <%text>## http://appenlight.com for details how to obtain an account</%text>
424 436 <%text>## you must install python package `appenlight_client` to make it work</%text>
425 437
426 438 <%text>## appenlight enabled</%text>
427 439 appenlight = false
428 440
429 441 appenlight.server_url = https://api.appenlight.com
430 442 appenlight.api_key = YOUR_API_KEY
431 443
432 444 <%text>## TWEAK AMOUNT OF INFO SENT HERE</%text>
433 445
434 446 <%text>## enables 404 error logging (default False)</%text>
435 447 appenlight.report_404 = false
436 448
437 449 <%text>## time in seconds after request is considered being slow (default 1)</%text>
438 450 appenlight.slow_request_time = 1
439 451
440 452 <%text>## record slow requests in application</%text>
441 453 <%text>## (needs to be enabled for slow datastore recording and time tracking)</%text>
442 454 appenlight.slow_requests = true
443 455
444 456 <%text>## enable hooking to application loggers</%text>
445 457 #appenlight.logging = true
446 458
447 459 <%text>## minimum log level for log capture</%text>
448 460 #appenlight.logging.level = WARNING
449 461
450 462 <%text>## send logs only from erroneous/slow requests</%text>
451 463 <%text>## (saves API quota for intensive logging)</%text>
452 464 appenlight.logging_on_error = false
453 465
454 466 <%text>## list of additional keywords that should be grabbed from environ object</%text>
455 467 <%text>## can be string with comma separated list of words in lowercase</%text>
456 468 <%text>## (by default client will always send following info:</%text>
457 469 <%text>## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that</%text>
458 470 <%text>## start with HTTP* this list be extended with additional keywords here</%text>
459 471 appenlight.environ_keys_whitelist =
460 472
461 473 <%text>## list of keywords that should be blanked from request object</%text>
462 474 <%text>## can be string with comma separated list of words in lowercase</%text>
463 475 <%text>## (by default client will always blank keys that contain following words</%text>
464 476 <%text>## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'</%text>
465 477 <%text>## this list be extended with additional keywords set here</%text>
466 478 appenlight.request_keys_blacklist =
467 479
468 480 <%text>## list of namespaces that should be ignores when gathering log entries</%text>
469 481 <%text>## can be string with comma separated list of namespaces</%text>
470 482 <%text>## (by default the client ignores own entries: appenlight_client.client)</%text>
471 483 appenlight.log_namespace_blacklist =
472 484
473 485 %elif error_aggregation_service == 'sentry':
474 486 <%text>################</%text>
475 487 <%text>### [sentry] ###</%text>
476 488 <%text>################</%text>
477 489
478 490 <%text>## sentry is a alternative open source error aggregator</%text>
479 491 <%text>## you must install python packages `sentry` and `raven` to enable</%text>
480 492
481 493 sentry.dsn = YOUR_DNS
482 494 sentry.servers =
483 495 sentry.name =
484 496 sentry.key =
485 497 sentry.public_key =
486 498 sentry.secret_key =
487 499 sentry.project =
488 500 sentry.site =
489 501 sentry.include_paths =
490 502 sentry.exclude_paths =
491 503
492 504 %endif
493 505 <%text>################################################################################</%text>
494 506 <%text>## WARNING: *DEBUG MODE MUST BE OFF IN A PRODUCTION ENVIRONMENT* ##</%text>
495 507 <%text>## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##</%text>
496 508 <%text>## execute malicious code after an exception is raised. ##</%text>
497 509 <%text>################################################################################</%text>
498 510 debug = false
499 511
500 512 <%text>##################################</%text>
501 513 <%text>### LOGVIEW CONFIG ###</%text>
502 514 <%text>##################################</%text>
503 515
504 516 logview.sqlalchemy = #faa
505 517 logview.pylons.templating = #bfb
506 518 logview.pylons.util = #eee
507 519
508 520 <%text>#########################################################</%text>
509 521 <%text>### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###</%text>
510 522 <%text>#########################################################</%text>
511 523
512 524 %if database_engine == 'sqlite':
513 525 # SQLITE [default]
514 526 sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
515 527
516 528 %elif database_engine == 'postgres':
517 529 # POSTGRESQL
518 530 sqlalchemy.url = postgresql://user:pass@localhost/kallithea
519 531
520 532 %elif database_engine == 'mysql':
521 533 # MySQL
522 534 sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8
523 535
524 536 %endif
525 537 # see sqlalchemy docs for others
526 538
527 539 sqlalchemy.pool_recycle = 3600
528 540
529 541 <%text>################################</%text>
530 542 <%text>### ALEMBIC CONFIGURATION ####</%text>
531 543 <%text>################################</%text>
532 544
533 545 [alembic]
534 546 script_location = kallithea:alembic
535 547
536 548 <%text>################################</%text>
537 549 <%text>### LOGGING CONFIGURATION ####</%text>
538 550 <%text>################################</%text>
539 551
540 552 [loggers]
541 553 keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
542 554
543 555 [handlers]
544 556 keys = console, console_sql
545 557
546 558 [formatters]
547 559 keys = generic, color_formatter, color_formatter_sql
548 560
549 561 <%text>#############</%text>
550 562 <%text>## LOGGERS ##</%text>
551 563 <%text>#############</%text>
552 564
553 565 [logger_root]
554 566 level = NOTSET
555 567 handlers = console
556 568
557 569 [logger_routes]
558 570 level = WARN
559 571 handlers =
560 572 qualname = routes.middleware
561 573 <%text>## "level = DEBUG" logs the route matched and routing variables.</%text>
562 574 propagate = 1
563 575
564 576 [logger_beaker]
565 577 level = WARN
566 578 handlers =
567 579 qualname = beaker.container
568 580 propagate = 1
569 581
570 582 [logger_templates]
571 583 level = WARN
572 584 handlers =
573 585 qualname = pylons.templating
574 586 propagate = 1
575 587
576 588 [logger_kallithea]
577 589 level = WARN
578 590 handlers =
579 591 qualname = kallithea
580 592 propagate = 1
581 593
582 594 [logger_tg]
583 595 level = WARN
584 596 handlers =
585 597 qualname = tg
586 598 propagate = 1
587 599
588 600 [logger_gearbox]
589 601 level = WARN
590 602 handlers =
591 603 qualname = gearbox
592 604 propagate = 1
593 605
594 606 [logger_sqlalchemy]
595 607 level = WARN
596 608 handlers = console_sql
597 609 qualname = sqlalchemy.engine
598 610 propagate = 0
599 611
600 612 [logger_whoosh_indexer]
601 613 level = WARN
602 614 handlers =
603 615 qualname = whoosh_indexer
604 616 propagate = 1
605 617
606 618 [logger_werkzeug]
607 619 level = WARN
608 620 handlers =
609 621 qualname = werkzeug
610 622 propagate = 1
611 623
612 624 [logger_backlash]
613 625 level = WARN
614 626 handlers =
615 627 qualname = backlash
616 628 propagate = 1
617 629
618 630 <%text>##############</%text>
619 631 <%text>## HANDLERS ##</%text>
620 632 <%text>##############</%text>
621 633
622 634 [handler_console]
623 635 class = StreamHandler
624 636 args = (sys.stderr,)
625 637 formatter = generic
626 638
627 639 [handler_console_sql]
628 640 class = StreamHandler
629 641 args = (sys.stderr,)
630 642 formatter = generic
631 643
632 644 <%text>################</%text>
633 645 <%text>## FORMATTERS ##</%text>
634 646 <%text>################</%text>
635 647
636 648 [formatter_generic]
637 649 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
638 650 datefmt = %Y-%m-%d %H:%M:%S
639 651
640 652 [formatter_color_formatter]
641 653 class = kallithea.lib.colored_formatter.ColorFormatter
642 654 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
643 655 datefmt = %Y-%m-%d %H:%M:%S
644 656
645 657 [formatter_color_formatter_sql]
646 658 class = kallithea.lib.colored_formatter.ColorFormatterSql
647 659 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
648 660 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,808 +1,811 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.model.scm
16 16 ~~~~~~~~~~~~~~~~~~~
17 17
18 18 Scm model for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 9, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import os
29 29 import sys
30 30 import posixpath
31 31 import re
32 32 import time
33 33 import traceback
34 34 import logging
35 35 import cStringIO
36 36 import pkg_resources
37 37
38 38 from sqlalchemy import func
39 39 from tg.i18n import ugettext as _
40 40
41 41 import kallithea
42 42 from kallithea.lib.vcs import get_backend
43 43 from kallithea.lib.vcs.exceptions import RepositoryError
44 44 from kallithea.lib.vcs.utils.lazy import LazyProperty
45 45 from kallithea.lib.vcs.nodes import FileNode
46 46 from kallithea.lib.vcs.backends.base import EmptyChangeset
47 47
48 48 from kallithea import BACKENDS
49 49 from kallithea.lib import helpers as h
50 50 from kallithea.lib.utils2 import safe_str, safe_unicode, get_server_url, \
51 51 _set_extras
52 52 from kallithea.lib.auth import HasRepoPermissionLevel, HasRepoGroupPermissionLevel, \
53 53 HasUserGroupPermissionLevel, HasPermissionAny, HasPermissionAny
54 54 from kallithea.lib.utils import get_filesystem_repos, make_ui, \
55 55 action_logger
56 56 from kallithea.model.db import Repository, Session, Ui, CacheInvalidation, \
57 57 UserFollowing, UserLog, User, RepoGroup, PullRequest
58 58 from kallithea.lib.hooks import log_push_action
59 59 from kallithea.lib.exceptions import NonRelativePathError, IMCCommitError
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class UserTemp(object):
65 65 def __init__(self, user_id):
66 66 self.user_id = user_id
67 67
68 68 def __repr__(self):
69 69 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
70 70
71 71
72 72 class RepoTemp(object):
73 73 def __init__(self, repo_id):
74 74 self.repo_id = repo_id
75 75
76 76 def __repr__(self):
77 77 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
78 78
79 79
80 80 class _PermCheckIterator(object):
81 81 def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None):
82 82 """
83 83 Creates iterator from given list of objects, additionally
84 84 checking permission for them from perm_set var
85 85
86 86 :param obj_list: list of db objects
87 87 :param obj_attr: attribute of object to pass into perm_checker
88 88 :param perm_set: list of permissions to check
89 89 :param perm_checker: callable to check permissions against
90 90 """
91 91 self.obj_list = obj_list
92 92 self.obj_attr = obj_attr
93 93 self.perm_set = perm_set
94 94 self.perm_checker = perm_checker
95 95 self.extra_kwargs = extra_kwargs or {}
96 96
97 97 def __len__(self):
98 98 return len(self.obj_list)
99 99
100 100 def __repr__(self):
101 101 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
102 102
103 103 def __iter__(self):
104 104 for db_obj in self.obj_list:
105 105 # check permission at this level
106 106 name = getattr(db_obj, self.obj_attr, None)
107 107 if not self.perm_checker(*self.perm_set)(
108 108 name, self.__class__.__name__, **self.extra_kwargs):
109 109 continue
110 110
111 111 yield db_obj
112 112
113 113
114 114 class RepoList(_PermCheckIterator):
115 115
116 116 def __init__(self, db_repo_list, perm_level, extra_kwargs=None):
117 117 super(RepoList, self).__init__(obj_list=db_repo_list,
118 118 obj_attr='repo_name', perm_set=[perm_level],
119 119 perm_checker=HasRepoPermissionLevel,
120 120 extra_kwargs=extra_kwargs)
121 121
122 122
123 123 class RepoGroupList(_PermCheckIterator):
124 124
125 125 def __init__(self, db_repo_group_list, perm_level, extra_kwargs=None):
126 126 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
127 127 obj_attr='group_name', perm_set=[perm_level],
128 128 perm_checker=HasRepoGroupPermissionLevel,
129 129 extra_kwargs=extra_kwargs)
130 130
131 131
132 132 class UserGroupList(_PermCheckIterator):
133 133
134 134 def __init__(self, db_user_group_list, perm_level, extra_kwargs=None):
135 135 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
136 136 obj_attr='users_group_name', perm_set=[perm_level],
137 137 perm_checker=HasUserGroupPermissionLevel,
138 138 extra_kwargs=extra_kwargs)
139 139
140 140
141 141 class ScmModel(object):
142 142 """
143 143 Generic Scm Model
144 144 """
145 145
146 146 def __get_repo(self, instance):
147 147 cls = Repository
148 148 if isinstance(instance, cls):
149 149 return instance
150 150 elif isinstance(instance, int) or safe_str(instance).isdigit():
151 151 return cls.get(instance)
152 152 elif isinstance(instance, basestring):
153 153 return cls.get_by_repo_name(instance)
154 154 elif instance is not None:
155 155 raise Exception('given object must be int, basestr or Instance'
156 156 ' of %s got %s' % (type(cls), type(instance)))
157 157
158 158 @LazyProperty
159 159 def repos_path(self):
160 160 """
161 161 Gets the repositories root path from database
162 162 """
163 163
164 164 q = Ui.query().filter(Ui.ui_key == '/').one()
165 165
166 166 return q.ui_value
167 167
168 168 def repo_scan(self, repos_path=None):
169 169 """
170 170 Listing of repositories in given path. This path should not be a
171 171 repository itself. Return a dictionary of repository objects
172 172
173 173 :param repos_path: path to directory containing repositories
174 174 """
175 175
176 176 if repos_path is None:
177 177 repos_path = self.repos_path
178 178
179 179 log.info('scanning for repositories in %s', repos_path)
180 180
181 181 baseui = make_ui('db')
182 182 repos = {}
183 183
184 184 for name, path in get_filesystem_repos(repos_path):
185 185 # name need to be decomposed and put back together using the /
186 186 # since this is internal storage separator for kallithea
187 187 name = Repository.normalize_repo_name(name)
188 188
189 189 try:
190 190 if name in repos:
191 191 raise RepositoryError('Duplicate repository name %s '
192 192 'found in %s' % (name, path))
193 193 else:
194 194
195 195 klass = get_backend(path[0])
196 196
197 197 if path[0] == 'hg' and path[0] in BACKENDS.keys():
198 198 repos[name] = klass(safe_str(path[1]), baseui=baseui)
199 199
200 200 if path[0] == 'git' and path[0] in BACKENDS.keys():
201 201 repos[name] = klass(path[1])
202 202 except OSError:
203 203 continue
204 204 log.debug('found %s paths with repositories', len(repos))
205 205 return repos
206 206
207 207 def get_repos(self, repos):
208 208 """Return the repos the user has access to"""
209 209 return RepoList(repos, perm_level='read')
210 210
211 211 def get_repo_groups(self, groups=None):
212 212 """Return the repo groups the user has access to
213 213 If no groups are specified, use top level groups.
214 214 """
215 215 if groups is None:
216 216 groups = RepoGroup.query() \
217 217 .filter(RepoGroup.parent_group_id == None).all()
218 218 return RepoGroupList(groups, perm_level='read')
219 219
220 220 def mark_for_invalidation(self, repo_name):
221 221 """
222 222 Mark caches of this repo invalid in the database.
223 223
224 224 :param repo_name: the repo for which caches should be marked invalid
225 225 """
226 226 CacheInvalidation.set_invalidate(repo_name)
227 227 repo = Repository.get_by_repo_name(repo_name)
228 228 if repo is not None:
229 229 repo.update_changeset_cache()
230 230
231 231 def toggle_following_repo(self, follow_repo_id, user_id):
232 232
233 233 f = UserFollowing.query() \
234 234 .filter(UserFollowing.follows_repository_id == follow_repo_id) \
235 235 .filter(UserFollowing.user_id == user_id).scalar()
236 236
237 237 if f is not None:
238 238 try:
239 239 Session().delete(f)
240 240 action_logger(UserTemp(user_id),
241 241 'stopped_following_repo',
242 242 RepoTemp(follow_repo_id))
243 243 return
244 244 except Exception:
245 245 log.error(traceback.format_exc())
246 246 raise
247 247
248 248 try:
249 249 f = UserFollowing()
250 250 f.user_id = user_id
251 251 f.follows_repository_id = follow_repo_id
252 252 Session().add(f)
253 253
254 254 action_logger(UserTemp(user_id),
255 255 'started_following_repo',
256 256 RepoTemp(follow_repo_id))
257 257 except Exception:
258 258 log.error(traceback.format_exc())
259 259 raise
260 260
261 261 def toggle_following_user(self, follow_user_id, user_id):
262 262 f = UserFollowing.query() \
263 263 .filter(UserFollowing.follows_user_id == follow_user_id) \
264 264 .filter(UserFollowing.user_id == user_id).scalar()
265 265
266 266 if f is not None:
267 267 try:
268 268 Session().delete(f)
269 269 return
270 270 except Exception:
271 271 log.error(traceback.format_exc())
272 272 raise
273 273
274 274 try:
275 275 f = UserFollowing()
276 276 f.user_id = user_id
277 277 f.follows_user_id = follow_user_id
278 278 Session().add(f)
279 279 except Exception:
280 280 log.error(traceback.format_exc())
281 281 raise
282 282
283 283 def is_following_repo(self, repo_name, user_id, cache=False):
284 284 r = Repository.query() \
285 285 .filter(Repository.repo_name == repo_name).scalar()
286 286
287 287 f = UserFollowing.query() \
288 288 .filter(UserFollowing.follows_repository == r) \
289 289 .filter(UserFollowing.user_id == user_id).scalar()
290 290
291 291 return f is not None
292 292
293 293 def is_following_user(self, username, user_id, cache=False):
294 294 u = User.get_by_username(username)
295 295
296 296 f = UserFollowing.query() \
297 297 .filter(UserFollowing.follows_user == u) \
298 298 .filter(UserFollowing.user_id == user_id).scalar()
299 299
300 300 return f is not None
301 301
302 302 def get_followers(self, repo):
303 303 repo = Repository.guess_instance(repo)
304 304
305 305 return UserFollowing.query() \
306 306 .filter(UserFollowing.follows_repository == repo).count()
307 307
308 308 def get_forks(self, repo):
309 309 repo = Repository.guess_instance(repo)
310 310 return Repository.query() \
311 311 .filter(Repository.fork == repo).count()
312 312
313 313 def get_pull_requests(self, repo):
314 314 repo = Repository.guess_instance(repo)
315 315 return PullRequest.query() \
316 316 .filter(PullRequest.other_repo == repo) \
317 317 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
318 318
319 319 def mark_as_fork(self, repo, fork, user):
320 320 repo = self.__get_repo(repo)
321 321 fork = self.__get_repo(fork)
322 322 if fork and repo.repo_id == fork.repo_id:
323 323 raise Exception("Cannot set repository as fork of itself")
324 324
325 325 if fork and repo.repo_type != fork.repo_type:
326 326 raise RepositoryError("Cannot set repository as fork of repository with other type")
327 327
328 328 repo.fork = fork
329 329 return repo
330 330
331 331 def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
332 332 action=None):
333 333 from kallithea import CONFIG
334 334 from kallithea.lib.base import _get_ip_addr
335 335 try:
336 336 from tg import request
337 337 environ = request.environ
338 338 except TypeError:
339 339 # we might use this outside of request context, let's fake the
340 340 # environ data
341 341 from webob import Request
342 342 environ = Request.blank('').environ
343 343 extras = {
344 344 'ip': _get_ip_addr(environ),
345 345 'username': username,
346 346 'action': action or 'push_local',
347 347 'repository': repo_name,
348 348 'scm': repo_alias,
349 349 'config': CONFIG['__file__'],
350 350 'server_url': get_server_url(environ),
351 351 'make_lock': None,
352 352 'locked_by': [None, None]
353 353 }
354 354 _set_extras(extras)
355 355
356 356 def _handle_push(self, repo, username, action, repo_name, revisions):
357 357 """
358 358 Triggers push action hooks
359 359
360 360 :param repo: SCM repo
361 361 :param username: username who pushes
362 362 :param action: push/push_local/push_remote
363 363 :param repo_name: name of repo
364 364 :param revisions: list of revisions that we pushed
365 365 """
366 366 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias, action=action)
367 367 _scm_repo = repo._repo
368 368 # trigger push hook
369 369 if repo.alias == 'hg':
370 370 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
371 371 elif repo.alias == 'git':
372 372 log_push_action(None, _scm_repo, _git_revs=revisions)
373 373
374 374 def _get_IMC_module(self, scm_type):
375 375 """
376 376 Returns InMemoryCommit class based on scm_type
377 377
378 378 :param scm_type:
379 379 """
380 380 if scm_type == 'hg':
381 381 from kallithea.lib.vcs.backends.hg import MercurialInMemoryChangeset
382 382 return MercurialInMemoryChangeset
383 383
384 384 if scm_type == 'git':
385 385 from kallithea.lib.vcs.backends.git import GitInMemoryChangeset
386 386 return GitInMemoryChangeset
387 387
388 388 raise Exception('Invalid scm_type, must be one of hg,git got %s'
389 389 % (scm_type,))
390 390
391 391 def pull_changes(self, repo, username, clone_uri=None):
392 392 """
393 393 Pull from "clone URL" or fork origin.
394 394 """
395 395 dbrepo = self.__get_repo(repo)
396 396 if clone_uri is None:
397 397 clone_uri = dbrepo.clone_uri or dbrepo.fork and dbrepo.fork.repo_full_path
398 398 if not clone_uri:
399 399 raise Exception("This repository doesn't have a clone uri")
400 400
401 401 repo = dbrepo.scm_instance
402 402 repo_name = dbrepo.repo_name
403 403 try:
404 404 if repo.alias == 'git':
405 405 repo.fetch(clone_uri)
406 406 # git doesn't really have something like post-fetch action
407 407 # we fake that now. #TODO: extract fetched revisions somehow
408 408 # here
409 409 self._handle_push(repo,
410 410 username=username,
411 411 action='push_remote',
412 412 repo_name=repo_name,
413 413 revisions=[])
414 414 else:
415 415 self._handle_rc_scm_extras(username, dbrepo.repo_name,
416 416 repo.alias, action='push_remote')
417 417 repo.pull(clone_uri)
418 418
419 419 self.mark_for_invalidation(repo_name)
420 420 except Exception:
421 421 log.error(traceback.format_exc())
422 422 raise
423 423
424 424 def commit_change(self, repo, repo_name, cs, user, author, message,
425 425 content, f_path):
426 426 """
427 427 Commit a change to a single file
428 428
429 429 :param repo: a db_repo.scm_instance
430 430 """
431 431 user = User.guess_instance(user)
432 432 IMC = self._get_IMC_module(repo.alias)
433 433
434 434 # decoding here will force that we have proper encoded values
435 435 # in any other case this will throw exceptions and deny commit
436 436 content = safe_str(content)
437 437 path = safe_str(f_path)
438 438 # message and author needs to be unicode
439 439 # proper backend should then translate that into required type
440 440 message = safe_unicode(message)
441 441 author = safe_unicode(author)
442 442 imc = IMC(repo)
443 443 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
444 444 try:
445 445 tip = imc.commit(message=message, author=author,
446 446 parents=[cs], branch=cs.branch)
447 447 except Exception as e:
448 448 log.error(traceback.format_exc())
449 449 raise IMCCommitError(str(e))
450 450 finally:
451 451 # always clear caches, if commit fails we want fresh object also
452 452 self.mark_for_invalidation(repo_name)
453 453 self._handle_push(repo,
454 454 username=user.username,
455 455 action='push_local',
456 456 repo_name=repo_name,
457 457 revisions=[tip.raw_id])
458 458 return tip
459 459
460 460 def _sanitize_path(self, f_path):
461 461 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
462 462 raise NonRelativePathError('%s is not an relative path' % f_path)
463 463 if f_path:
464 464 f_path = posixpath.normpath(f_path)
465 465 return f_path
466 466
467 467 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
468 468 """
469 469 Recursively walk root dir and return a set of all paths found.
470 470
471 471 :param repo_name: name of repository
472 472 :param revision: revision for which to list nodes
473 473 :param root_path: root path to list
474 474 :param flat: return as a list, if False returns a dict with description
475 475
476 476 """
477 477 _files = list()
478 478 _dirs = list()
479 479 try:
480 480 _repo = self.__get_repo(repo_name)
481 481 changeset = _repo.scm_instance.get_changeset(revision)
482 482 root_path = root_path.lstrip('/')
483 483 for topnode, dirs, files in changeset.walk(root_path):
484 484 for f in files:
485 485 _files.append(f.path if flat else {"name": f.path,
486 486 "type": "file"})
487 487 for d in dirs:
488 488 _dirs.append(d.path if flat else {"name": d.path,
489 489 "type": "dir"})
490 490 except RepositoryError:
491 491 log.debug(traceback.format_exc())
492 492 raise
493 493
494 494 return _dirs, _files
495 495
496 496 def create_nodes(self, user, repo, message, nodes, parent_cs=None,
497 497 author=None, trigger_push_hook=True):
498 498 """
499 499 Commits specified nodes to repo.
500 500
501 501 :param user: Kallithea User object or user_id, the committer
502 502 :param repo: Kallithea Repository object
503 503 :param message: commit message
504 504 :param nodes: mapping {filename:{'content':content},...}
505 505 :param parent_cs: parent changeset, can be empty than it's initial commit
506 506 :param author: author of commit, cna be different that committer only for git
507 507 :param trigger_push_hook: trigger push hooks
508 508
509 509 :returns: new committed changeset
510 510 """
511 511
512 512 user = User.guess_instance(user)
513 513 scm_instance = repo.scm_instance_no_cache()
514 514
515 515 processed_nodes = []
516 516 for f_path in nodes:
517 517 content = nodes[f_path]['content']
518 518 f_path = self._sanitize_path(f_path)
519 519 f_path = safe_str(f_path)
520 520 # decoding here will force that we have proper encoded values
521 521 # in any other case this will throw exceptions and deny commit
522 522 if isinstance(content, (basestring,)):
523 523 content = safe_str(content)
524 524 elif isinstance(content, (file, cStringIO.OutputType,)):
525 525 content = content.read()
526 526 else:
527 527 raise Exception('Content is of unrecognized type %s' % (
528 528 type(content)
529 529 ))
530 530 processed_nodes.append((f_path, content))
531 531
532 532 message = safe_unicode(message)
533 533 committer = user.full_contact
534 534 author = safe_unicode(author) if author else committer
535 535
536 536 IMC = self._get_IMC_module(scm_instance.alias)
537 537 imc = IMC(scm_instance)
538 538
539 539 if not parent_cs:
540 540 parent_cs = EmptyChangeset(alias=scm_instance.alias)
541 541
542 542 if isinstance(parent_cs, EmptyChangeset):
543 543 # EmptyChangeset means we we're editing empty repository
544 544 parents = None
545 545 else:
546 546 parents = [parent_cs]
547 547 # add multiple nodes
548 548 for path, content in processed_nodes:
549 549 imc.add(FileNode(path, content=content))
550 550
551 551 tip = imc.commit(message=message,
552 552 author=author,
553 553 parents=parents,
554 554 branch=parent_cs.branch)
555 555
556 556 self.mark_for_invalidation(repo.repo_name)
557 557 if trigger_push_hook:
558 558 self._handle_push(scm_instance,
559 559 username=user.username,
560 560 action='push_local',
561 561 repo_name=repo.repo_name,
562 562 revisions=[tip.raw_id])
563 563 return tip
564 564
565 565 def update_nodes(self, user, repo, message, nodes, parent_cs=None,
566 566 author=None, trigger_push_hook=True):
567 567 """
568 568 Commits specified nodes to repo. Again.
569 569 """
570 570 user = User.guess_instance(user)
571 571 scm_instance = repo.scm_instance_no_cache()
572 572
573 573 message = safe_unicode(message)
574 574 committer = user.full_contact
575 575 author = safe_unicode(author) if author else committer
576 576
577 577 imc_class = self._get_IMC_module(scm_instance.alias)
578 578 imc = imc_class(scm_instance)
579 579
580 580 if not parent_cs:
581 581 parent_cs = EmptyChangeset(alias=scm_instance.alias)
582 582
583 583 if isinstance(parent_cs, EmptyChangeset):
584 584 # EmptyChangeset means we we're editing empty repository
585 585 parents = None
586 586 else:
587 587 parents = [parent_cs]
588 588
589 589 # add multiple nodes
590 590 for _filename, data in nodes.items():
591 591 # new filename, can be renamed from the old one
592 592 filename = self._sanitize_path(data['filename'])
593 593 old_filename = self._sanitize_path(_filename)
594 594 content = data['content']
595 595
596 596 filenode = FileNode(old_filename, content=content)
597 597 op = data['op']
598 598 if op == 'add':
599 599 imc.add(filenode)
600 600 elif op == 'del':
601 601 imc.remove(filenode)
602 602 elif op == 'mod':
603 603 if filename != old_filename:
604 604 # TODO: handle renames, needs vcs lib changes
605 605 imc.remove(filenode)
606 606 imc.add(FileNode(filename, content=content))
607 607 else:
608 608 imc.change(filenode)
609 609
610 610 # commit changes
611 611 tip = imc.commit(message=message,
612 612 author=author,
613 613 parents=parents,
614 614 branch=parent_cs.branch)
615 615
616 616 self.mark_for_invalidation(repo.repo_name)
617 617 if trigger_push_hook:
618 618 self._handle_push(scm_instance,
619 619 username=user.username,
620 620 action='push_local',
621 621 repo_name=repo.repo_name,
622 622 revisions=[tip.raw_id])
623 623
624 624 def delete_nodes(self, user, repo, message, nodes, parent_cs=None,
625 625 author=None, trigger_push_hook=True):
626 626 """
627 627 Deletes specified nodes from repo.
628 628
629 629 :param user: Kallithea User object or user_id, the committer
630 630 :param repo: Kallithea Repository object
631 631 :param message: commit message
632 632 :param nodes: mapping {filename:{'content':content},...}
633 633 :param parent_cs: parent changeset, can be empty than it's initial commit
634 634 :param author: author of commit, cna be different that committer only for git
635 635 :param trigger_push_hook: trigger push hooks
636 636
637 637 :returns: new committed changeset after deletion
638 638 """
639 639
640 640 user = User.guess_instance(user)
641 641 scm_instance = repo.scm_instance_no_cache()
642 642
643 643 processed_nodes = []
644 644 for f_path in nodes:
645 645 f_path = self._sanitize_path(f_path)
646 646 # content can be empty but for compatibility it allows same dicts
647 647 # structure as add_nodes
648 648 content = nodes[f_path].get('content')
649 649 processed_nodes.append((f_path, content))
650 650
651 651 message = safe_unicode(message)
652 652 committer = user.full_contact
653 653 author = safe_unicode(author) if author else committer
654 654
655 655 IMC = self._get_IMC_module(scm_instance.alias)
656 656 imc = IMC(scm_instance)
657 657
658 658 if not parent_cs:
659 659 parent_cs = EmptyChangeset(alias=scm_instance.alias)
660 660
661 661 if isinstance(parent_cs, EmptyChangeset):
662 662 # EmptyChangeset means we we're editing empty repository
663 663 parents = None
664 664 else:
665 665 parents = [parent_cs]
666 666 # add multiple nodes
667 667 for path, content in processed_nodes:
668 668 imc.remove(FileNode(path, content=content))
669 669
670 670 tip = imc.commit(message=message,
671 671 author=author,
672 672 parents=parents,
673 673 branch=parent_cs.branch)
674 674
675 675 self.mark_for_invalidation(repo.repo_name)
676 676 if trigger_push_hook:
677 677 self._handle_push(scm_instance,
678 678 username=user.username,
679 679 action='push_local',
680 680 repo_name=repo.repo_name,
681 681 revisions=[tip.raw_id])
682 682 return tip
683 683
684 684 def get_unread_journal(self):
685 685 return UserLog.query().count()
686 686
687 687 def get_repo_landing_revs(self, repo=None):
688 688 """
689 689 Generates select option with tags branches and bookmarks (for hg only)
690 690 grouped by type
691 691
692 692 :param repo:
693 693 """
694 694
695 695 hist_l = []
696 696 choices = []
697 697 repo = self.__get_repo(repo)
698 698 hist_l.append(['rev:tip', _('latest tip')])
699 699 choices.append('rev:tip')
700 700 if repo is None:
701 701 return choices, hist_l
702 702
703 703 repo = repo.scm_instance
704 704
705 705 branches_group = ([(u'branch:%s' % k, k) for k, v in
706 706 repo.branches.iteritems()], _("Branches"))
707 707 hist_l.append(branches_group)
708 708 choices.extend([x[0] for x in branches_group[0]])
709 709
710 710 if repo.alias == 'hg':
711 711 bookmarks_group = ([(u'book:%s' % k, k) for k, v in
712 712 repo.bookmarks.iteritems()], _("Bookmarks"))
713 713 hist_l.append(bookmarks_group)
714 714 choices.extend([x[0] for x in bookmarks_group[0]])
715 715
716 716 tags_group = ([(u'tag:%s' % k, k) for k, v in
717 717 repo.tags.iteritems()], _("Tags"))
718 718 hist_l.append(tags_group)
719 719 choices.extend([x[0] for x in tags_group[0]])
720 720
721 721 return choices, hist_l
722 722
723 723 def _get_git_hook_interpreter(self):
724 724 """Return a suitable interpreter for Git hooks.
725 725
726 726 Return a suitable string to be written in the POSIX #! shebang line for
727 727 Git hook scripts so they invoke Kallithea code with the right Python
728 728 interpreter and in the right environment.
729 729 """
730 # Note: sys.executable might not point at a usable Python interpreter. For
731 # example, when using uwsgi, it will point at the uwsgi program itself.
730 732 # FIXME This may not work on Windows and may need a shell wrapper script.
731 return (sys.executable
733 return (kallithea.CONFIG.get('git_hook_interpreter')
734 or sys.executable
732 735 or '/usr/bin/env python2')
733 736
734 737 def install_git_hooks(self, repo, force_create=False):
735 738 """
736 739 Creates a kallithea hook inside a git repository
737 740
738 741 :param repo: Instance of VCS repo
739 742 :param force_create: Create even if same name hook exists
740 743 """
741 744
742 745 loc = os.path.join(repo.path, 'hooks')
743 746 if not repo.bare:
744 747 loc = os.path.join(repo.path, '.git', 'hooks')
745 748 if not os.path.isdir(loc):
746 749 os.makedirs(loc)
747 750
748 751 tmpl_post = "#!%s\n" % self._get_git_hook_interpreter()
749 752 tmpl_post += pkg_resources.resource_string(
750 753 'kallithea', os.path.join('config', 'post_receive_tmpl.py')
751 754 )
752 755 tmpl_pre = "#!%s\n" % self._get_git_hook_interpreter()
753 756 tmpl_pre += pkg_resources.resource_string(
754 757 'kallithea', os.path.join('config', 'pre_receive_tmpl.py')
755 758 )
756 759
757 760 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
758 761 _hook_file = os.path.join(loc, '%s-receive' % h_type)
759 762 has_hook = False
760 763 log.debug('Installing git hook in repo %s', repo)
761 764 if os.path.exists(_hook_file):
762 765 # let's take a look at this hook, maybe it's kallithea ?
763 766 log.debug('hook exists, checking if it is from kallithea')
764 767 with open(_hook_file, 'rb') as f:
765 768 data = f.read()
766 769 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
767 770 % 'KALLITHEA_HOOK_VER').search(data)
768 771 if matches:
769 772 try:
770 773 ver = matches.groups()[0]
771 774 log.debug('got %s it is kallithea', ver)
772 775 has_hook = True
773 776 except Exception:
774 777 log.error(traceback.format_exc())
775 778 else:
776 779 # there is no hook in this dir, so we want to create one
777 780 has_hook = True
778 781
779 782 if has_hook or force_create:
780 783 log.debug('writing %s hook file !', h_type)
781 784 try:
782 785 with open(_hook_file, 'wb') as f:
783 786 tmpl = tmpl.replace('_TMPL_', kallithea.__version__)
784 787 f.write(tmpl)
785 788 os.chmod(_hook_file, 0755)
786 789 except IOError as e:
787 790 log.error('error writing %s: %s', _hook_file, e)
788 791 else:
789 792 log.debug('skipping writing hook file')
790 793
791 794
792 795 def AvailableRepoGroupChoices(top_perms, repo_group_perm_level, extras=()):
793 796 """Return group_id,string tuples with choices for all the repo groups where
794 797 the user has the necessary permissions.
795 798
796 799 Top level is -1.
797 800 """
798 801 groups = RepoGroup.query().all()
799 802 if HasPermissionAny('hg.admin')('available repo groups'):
800 803 groups.append(None)
801 804 else:
802 805 groups = list(RepoGroupList(groups, perm_level=repo_group_perm_level))
803 806 if top_perms and HasPermissionAny(*top_perms)('available repo groups'):
804 807 groups.append(None)
805 808 for extra in extras:
806 809 if not any(rg == extra for rg in groups):
807 810 groups.append(extra)
808 811 return RepoGroup.groups_choices(groups=groups)
General Comments 0
You need to be logged in to leave comments. Login now