##// END OF EJS Templates
merge stable
Thomas De Schampheleire -
r8316:a04d6926 merge default
parent child Browse files
Show More
@@ -1,519 +1,520 b''
1 1 ###################################################################################
2 2 ###################################################################################
3 3 ## Kallithea config file generated with kallithea-cli ##
4 4 ## ##
5 5 ## The %(here)s variable will generally be replaced with the parent directory of ##
6 6 ## this file. Other use of % must be escaped as %% . ##
7 7 ###################################################################################
8 8 ###################################################################################
9 9
10 10 [DEFAULT]
11 11
12 12 ################################################################################
13 13 ## Email settings ##
14 14 ## ##
15 15 ## Refer to the documentation ("Email settings") for more details. ##
16 16 ## ##
17 17 ## It is recommended to use a valid sender address that passes access ##
18 18 ## validation and spam filtering in mail servers. ##
19 19 ################################################################################
20 20
21 21 ## 'From' header for application emails. You can optionally add a name.
22 22 ## Default:
23 23 #app_email_from = Kallithea
24 24 ## Examples:
25 25 #app_email_from = Kallithea <kallithea-noreply@example.com>
26 26 #app_email_from = kallithea-noreply@example.com
27 27
28 28 ## Subject prefix for application emails.
29 29 ## A space between this prefix and the real subject is automatically added.
30 30 ## Default:
31 31 #email_prefix =
32 32 ## Example:
33 33 #email_prefix = [Kallithea]
34 34
35 35 ## Recipients for error emails and fallback recipients of application mails.
36 36 ## Multiple addresses can be specified, comma-separated.
37 37 ## Only addresses are allowed, do not add any name part.
38 38 ## Default:
39 39 #email_to =
40 40 ## Examples:
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 ## 'From' header for error emails. You can optionally add a name.
46 46 ## Default: (none)
47 47 ## Examples:
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 ## SMTP server settings
53 53 ## If specifying credentials, make sure to use secure connections.
54 54 ## Default: Send unencrypted unauthenticated mails to the specified smtp_server.
55 55 ## For "SSL", use smtp_use_ssl = true and smtp_port = 465.
56 56 ## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.
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 ## Entry point for 'gearbox serve'
65 65 [server:main]
66 66 #host = 127.0.0.1
67 67 host = 0.0.0.0
68 68 port = 5000
69 69
70 70 ## Gearbox serve uses the Waitress web server ##
71 71 use = egg:waitress#main
72 72 ## avoid multi threading
73 73 threads = 1
74 74 ## allow push of repos bigger than the default of 1 GB
75 75 max_request_body_size = 107374182400
76 76 ## use poll instead of select, fixes fd limits, may not work on old
77 77 ## windows systems.
78 78 #asyncore_use_poll = True
79 79
80 80 ## middleware for hosting the WSGI application under a URL prefix
81 81 #[filter:proxy-prefix]
82 82 #use = egg:PasteDeploy#prefix
83 83 #prefix = /<your-prefix>
84 84
85 85 [app:main]
86 86 use = egg:kallithea
87 87 ## enable proxy prefix middleware
88 88 #filter-with = proxy-prefix
89 89
90 90 full_stack = true
91 91 static_files = true
92 92
93 93 ## Internationalization (see setup documentation for details)
94 94 ## By default, the languages requested by the browser are used if available, with English as default.
95 95 ## Set i18n.enabled=false to disable automatic language choice.
96 96 #i18n.enabled = true
97 97 ## To Force a language, set i18n.enabled=false and specify the language in i18n.lang.
98 98 ## Valid values are the names of subdirectories in kallithea/i18n with a LC_MESSAGES/kallithea.mo
99 99 #i18n.lang = en
100 100
101 101 cache_dir = %(here)s/data
102 102 index_dir = %(here)s/data/index
103 103
104 104 ## uncomment and set this path to use archive download cache
105 105 archive_cache_dir = %(here)s/tarballcache
106 106
107 107 ## change this to unique ID for security
108 108 #app_instance_uuid = VERY-SECRET
109 109 app_instance_uuid = development-not-secret
110 110
111 111 ## cut off limit for large diffs (size in bytes)
112 112 cut_off_limit = 256000
113 113
114 114 ## force https in Kallithea, fixes https redirects, assumes it's always https
115 115 force_https = false
116 116
117 117 ## use Strict-Transport-Security headers
118 118 use_htsts = false
119 119
120 120 ## number of commits stats will parse on each iteration
121 121 commit_parse_limit = 25
122 122
123 123 ## Path to Python executable to be used for git hooks.
124 124 ## This value will be written inside the git hook scripts as the text
125 125 ## after '#!' (shebang). When empty or not defined, the value of
126 126 ## 'sys.executable' at the time of installation of the git hooks is
127 127 ## used, which is correct in many cases but for example not when using uwsgi.
128 128 ## If you change this setting, you should reinstall the Git hooks via
129 129 ## Admin > Settings > Remap and Rescan.
130 130 #git_hook_interpreter = /srv/kallithea/venv/bin/python3
131 131
132 132 ## path to git executable
133 133 git_path = git
134 134
135 135 ## git rev filter option, --all is the default filter, if you need to
136 136 ## hide all refs in changelog switch this to --branches --tags
137 137 #git_rev_filter = --branches --tags
138 138
139 139 ## RSS feed options
140 140 rss_cut_off_limit = 256000
141 141 rss_items_per_page = 10
142 142 rss_include_diff = false
143 143
144 144 ## options for showing and identifying changesets
145 145 show_sha_length = 12
146 146 show_revision_number = false
147 147
148 148 ## Canonical URL to use when creating full URLs in UI and texts.
149 149 ## Useful when the site is available under different names or protocols.
150 150 ## Defaults to what is provided in the WSGI environment.
151 151 #canonical_url = https://kallithea.example.com/repos
152 152
153 153 ## gist URL alias, used to create nicer urls for gist. This should be an
154 154 ## url that does rewrites to _admin/gists/<gistid>.
155 155 ## example: http://gist.example.com/{gistid}. Empty means use the internal
156 156 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
157 157 gist_alias_url =
158 158
159 159 ## default encoding used to convert from and to unicode
160 160 ## can be also a comma separated list of encoding in case of mixed encodings
161 161 default_encoding = utf-8
162 162
163 163 ## Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea
164 164 hgencoding = utf-8
165 165
166 166 ## issue tracker for Kallithea (leave blank to disable, absent for default)
167 167 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
168 168
169 169 ## issue tracking mapping for commit messages, comments, PR descriptions, ...
170 170 ## Refer to the documentation ("Integration with issue trackers") for more details.
171 171
172 172 ## regular expression to match issue references
173 173 ## This pattern may/should contain parenthesized groups, that can
174 174 ## be referred to in issue_server_link or issue_sub using Python backreferences
175 175 ## (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.
176 176 ## To require mandatory whitespace before the issue pattern, use:
177 177 ## (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace
178 178 ## behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.
179 179
180 180 issue_pat = #(\d+)
181 181
182 182 ## server url to the issue
183 183 ## This pattern may/should contain backreferences to parenthesized groups in issue_pat.
184 184 ## A backreference can be \1, \2, ... or \g<groupname> if you specified a named group
185 185 ## called 'groupname' in issue_pat.
186 186 ## The special token {repo} is replaced with the full repository name
187 187 ## including repository groups, while {repo_name} is replaced with just
188 188 ## the name of the repository.
189 189
190 190 issue_server_link = https://issues.example.com/{repo}/issue/\1
191 191
192 192 ## substitution pattern to use as the link text
193 193 ## If issue_sub is empty, the text matched by issue_pat is retained verbatim
194 194 ## for the link text. Otherwise, the link text is that of issue_sub, with any
195 195 ## backreferences to groups in issue_pat replaced.
196 196
197 197 issue_sub =
198 198
199 199 ## issue_pat, issue_server_link and issue_sub can have suffixes to specify
200 200 ## multiple patterns, to other issues server, wiki or others
201 201 ## below an example how to create a wiki pattern
202 202 ## wiki-some-id -> https://wiki.example.com/some-id
203 203
204 204 #issue_pat_wiki = wiki-(\S+)
205 205 #issue_server_link_wiki = https://wiki.example.com/\1
206 206 #issue_sub_wiki = WIKI-\1
207 207
208 208 ## alternative return HTTP header for failed authentication. Default HTTP
209 209 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
210 210 ## handling that. Set this variable to 403 to return HTTPForbidden
211 211 auth_ret_code =
212 212
213 213 ## allows to change the repository location in settings page
214 214 allow_repo_location_change = True
215 215
216 216 ## allows to setup custom hooks in settings page
217 217 allow_custom_hooks_settings = True
218 218
219 219 ## extra extensions for indexing, space separated and without the leading '.'.
220 220 #index.extensions =
221 221 # gemfile
222 222 # lock
223 223
224 224 ## extra filenames for indexing, space separated
225 225 #index.filenames =
226 226 # .dockerignore
227 227 # .editorconfig
228 228 # INSTALL
229 229 # CHANGELOG
230 230
231 231 ####################################
232 232 ## SSH CONFIG ##
233 233 ####################################
234 234
235 235 ## SSH is disabled by default, until an Administrator decides to enable it.
236 236 ssh_enabled = false
237 237
238 238 ## File where users' SSH keys will be stored *if* ssh_enabled is true.
239 239 #ssh_authorized_keys = /home/kallithea/.ssh/authorized_keys
240 240
241 241 ## Path to be used in ssh_authorized_keys file to invoke kallithea-cli with ssh-serve.
242 242 #kallithea_cli_path = /srv/kallithea/venv/bin/kallithea-cli
243 243
244 244 ## Locale to be used in the ssh-serve command.
245 245 ## This is needed because an SSH client may try to use its own locale
246 246 ## settings, which may not be available on the server.
247 247 ## See `locale -a` for valid values on this system.
248 248 #ssh_locale = C.UTF-8
249 249
250 250 ####################################
251 251 ## CELERY CONFIG ##
252 252 ####################################
253 253
254 254 ## Note: Celery doesn't support Windows.
255 255 use_celery = false
256 256
257 257 ## Celery config settings from https://docs.celeryproject.org/en/4.4.0/userguide/configuration.html prefixed with 'celery.'.
258 258
259 259 ## Example: use the message queue on the local virtual host 'kallitheavhost' as the RabbitMQ user 'kallithea':
260 260 celery.broker_url = amqp://kallithea:thepassword@localhost:5672/kallitheavhost
261 261
262 262 celery.result.backend = db+sqlite:///celery-results.db
263 263
264 264 #celery.amqp.task.result.expires = 18000
265 265
266 266 celery.worker_concurrency = 2
267 267 celery.worker_max_tasks_per_child = 1
268 268
269 269 ## If true, tasks will never be sent to the queue, but executed locally instead.
270 270 celery.task_always_eager = false
271 271
272 272 ####################################
273 273 ## BEAKER CACHE ##
274 274 ####################################
275 275
276 276 beaker.cache.data_dir = %(here)s/data/cache/data
277 277 beaker.cache.lock_dir = %(here)s/data/cache/lock
278 278
279 279 beaker.cache.regions = long_term,long_term_file
280 280
281 281 beaker.cache.long_term.type = memory
282 282 beaker.cache.long_term.expire = 36000
283 283 beaker.cache.long_term.key_length = 256
284 284
285 285 beaker.cache.long_term_file.type = file
286 286 beaker.cache.long_term_file.expire = 604800
287 287 beaker.cache.long_term_file.key_length = 256
288 288
289 289 ####################################
290 290 ## BEAKER SESSION ##
291 291 ####################################
292 292
293 293 ## Name of session cookie. Should be unique for a given host and path, even when running
294 294 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
295 295 session.key = kallithea
296 296 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
297 297 session.httponly = true
298 298 ## Session lifetime. 2592000 seconds is 30 days.
299 299 session.timeout = 2592000
300 300
301 301 ## Server secret used with HMAC to ensure integrity of cookies.
302 302 #session.secret = VERY-SECRET
303 303 session.secret = development-not-secret
304 304 ## Further, encrypt the data with AES.
305 305 #session.encrypt_key = <key_for_encryption>
306 306 #session.validate_key = <validation_key>
307 307
308 308 ## Type of storage used for the session, current types are
309 309 ## dbm, file, memcached, database, and memory.
310 310
311 311 ## File system storage of session data. (default)
312 312 #session.type = file
313 313
314 314 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
315 315 #session.type = cookie
316 316
317 317 ## Database storage of session data.
318 318 #session.type = ext:database
319 319 #session.sa.url = postgresql://postgres:qwe@localhost/kallithea
320 320 #session.table_name = db_session
321 321
322 322 ####################################
323 323 ## ERROR HANDLING ##
324 324 ####################################
325 325
326 326 ## Show a nice error page for application HTTP errors and exceptions (default true)
327 327 #errorpage.enabled = true
328 328
329 329 ## Enable Backlash client-side interactive debugger (default false)
330 330 ## WARNING: *THIS MUST BE false IN PRODUCTION ENVIRONMENTS!!!*
331 331 ## This debug mode will allow all visitors to execute malicious code.
332 332 #debug = false
333 333 debug = true
334 334
335 335 ## Enable Backlash server-side error reporting (unless debug mode handles it client-side) (default true)
336 336 #trace_errors.enable = true
337 337 ## Errors will be reported by mail if trace_errors.error_email is set.
338 338
339 339 ## Propagate email settings to ErrorReporter of TurboGears2
340 340 ## You do not normally need to change these lines
341 341 get trace_errors.smtp_server = smtp_server
342 342 get trace_errors.smtp_port = smtp_port
343 343 get trace_errors.from_address = error_email_from
344 344 get trace_errors.error_email = email_to
345 345 get trace_errors.smtp_username = smtp_username
346 346 get trace_errors.smtp_password = smtp_password
347 347 get trace_errors.smtp_use_tls = smtp_use_tls
348 348
349 349
350 350 ##################################
351 351 ## LOGVIEW CONFIG ##
352 352 ##################################
353 353
354 354 logview.sqlalchemy = #faa
355 355 logview.pylons.templating = #bfb
356 356 logview.pylons.util = #eee
357 357
358 358 #########################
359 359 ## DB CONFIG ##
360 360 #########################
361 361
362 362 sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
363 363 #sqlalchemy.url = postgresql://user:pass@localhost/kallithea
364 364 #sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8
365 ## Note: the mysql:// prefix should also be used for MariaDB
365 366
366 367 sqlalchemy.pool_recycle = 3600
367 368
368 369 ################################
369 370 ## ALEMBIC CONFIGURATION ##
370 371 ################################
371 372
372 373 [alembic]
373 374 script_location = kallithea:alembic
374 375
375 376 ################################
376 377 ## LOGGING CONFIGURATION ##
377 378 ################################
378 379
379 380 [loggers]
380 381 keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
381 382
382 383 [handlers]
383 384 keys = console, console_color, console_color_sql, null
384 385
385 386 [formatters]
386 387 keys = generic, color_formatter, color_formatter_sql
387 388
388 389 #############
389 390 ## LOGGERS ##
390 391 #############
391 392
392 393 [logger_root]
393 394 level = NOTSET
394 395 #handlers = console
395 396 ## For coloring based on log level:
396 397 handlers = console_color
397 398
398 399 [logger_routes]
399 400 #level = WARN
400 401 level = DEBUG
401 402 handlers =
402 403 qualname = routes.middleware
403 404 ## "level = DEBUG" logs the route matched and routing variables.
404 405
405 406 [logger_beaker]
406 407 #level = WARN
407 408 level = DEBUG
408 409 handlers =
409 410 qualname = beaker.container
410 411
411 412 [logger_templates]
412 413 #level = WARN
413 414 level = INFO
414 415 handlers =
415 416 qualname = pylons.templating
416 417
417 418 [logger_kallithea]
418 419 #level = WARN
419 420 level = DEBUG
420 421 handlers =
421 422 qualname = kallithea
422 423
423 424 [logger_tg]
424 425 #level = WARN
425 426 level = DEBUG
426 427 handlers =
427 428 qualname = tg
428 429
429 430 [logger_gearbox]
430 431 #level = WARN
431 432 level = DEBUG
432 433 handlers =
433 434 qualname = gearbox
434 435
435 436 [logger_sqlalchemy]
436 437 level = WARN
437 438 handlers =
438 439 qualname = sqlalchemy.engine
439 440 ## For coloring based on log level and pretty printing of SQL:
440 441 #level = INFO
441 442 #handlers = console_color_sql
442 443 #propagate = 0
443 444
444 445 [logger_whoosh_indexer]
445 446 #level = WARN
446 447 level = DEBUG
447 448 handlers =
448 449 qualname = whoosh_indexer
449 450
450 451 [logger_werkzeug]
451 452 level = WARN
452 453 handlers =
453 454 qualname = werkzeug
454 455
455 456 [logger_backlash]
456 457 level = WARN
457 458 handlers =
458 459 qualname = backlash
459 460
460 461 ##############
461 462 ## HANDLERS ##
462 463 ##############
463 464
464 465 [handler_console]
465 466 class = StreamHandler
466 467 args = (sys.stderr,)
467 468 formatter = generic
468 469
469 470 [handler_console_color]
470 471 ## ANSI color coding based on log level
471 472 class = StreamHandler
472 473 args = (sys.stderr,)
473 474 formatter = color_formatter
474 475
475 476 [handler_console_color_sql]
476 477 ## ANSI color coding and pretty printing of SQL statements
477 478 class = StreamHandler
478 479 args = (sys.stderr,)
479 480 formatter = color_formatter_sql
480 481
481 482 [handler_null]
482 483 class = NullHandler
483 484 args = ()
484 485
485 486 ################
486 487 ## FORMATTERS ##
487 488 ################
488 489
489 490 [formatter_generic]
490 491 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
491 492 datefmt = %Y-%m-%d %H:%M:%S
492 493
493 494 [formatter_color_formatter]
494 495 class = kallithea.lib.colored_formatter.ColorFormatter
495 496 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
496 497 datefmt = %Y-%m-%d %H:%M:%S
497 498
498 499 [formatter_color_formatter_sql]
499 500 class = kallithea.lib.colored_formatter.ColorFormatterSql
500 501 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
501 502 datefmt = %Y-%m-%d %H:%M:%S
502 503
503 504 #################
504 505 ## SSH LOGGING ##
505 506 #################
506 507
507 508 ## The default loggers use 'handler_console' that uses StreamHandler with
508 509 ## destination 'sys.stderr'. In the context of the SSH server process, these log
509 510 ## messages would be sent to the client, which is normally not what you want.
510 511 ## By default, when running ssh-serve, just use NullHandler and disable logging
511 512 ## completely. For other logging options, see:
512 513 ## https://docs.python.org/2/library/logging.handlers.html
513 514
514 515 [ssh_serve:logger_root]
515 516 level = CRITICAL
516 517 handlers = null
517 518
518 519 ## Note: If logging is configured with other handlers, they might need similar
519 520 ## muting for ssh-serve too.
@@ -1,216 +1,232 b''
1 1 .. _overview:
2 2
3 3 =====================
4 4 Installation overview
5 5 =====================
6 6
7 7 Some overview and some details that can help understanding the options when
8 8 installing Kallithea.
9 9
10 10 1. **Prepare environment and external dependencies.**
11 11 Kallithea needs:
12 12
13 13 * A filesystem where the Mercurial and Git repositories can be stored.
14 14 * A database where meta data can be stored.
15 15 * A Python environment where the Kallithea application and its dependencies
16 16 can be installed.
17 17 * A web server that can host the Kallithea web application using the WSGI
18 18 API.
19 19
20 20 2. **Install Kallithea software.**
21 21 This makes the ``kallithea-cli`` command line tool available.
22 22
23 23 3. **Create low level configuration file.**
24 24 Use ``kallithea-cli config-create`` to create a ``.ini`` file with database
25 connection info, mail server information, some web server configuration,
26 etc.
25 connection info, mail server information, configuration for the specified
26 web server, etc.
27 27
28 28 4. **Populate the database.**
29 29 Use ``kallithea-cli db-create`` with the ``.ini`` file to create the
30 30 database schema and insert the most basic information: the location of the
31 31 repository store and an initial local admin user.
32 32
33 33 5. **Configure the web server.**
34 34 The web server must invoke the WSGI entrypoint for the Kallithea software
35 35 using the ``.ini`` file (and thus the database). This makes the web
36 36 application available so the local admin user can log in and tweak the
37 37 configuration further.
38 38
39 39 6. **Configure users.**
40 40 The initial admin user can create additional local users, or configure how
41 41 users can be created and authenticated from other user directories.
42 42
43 43 See the subsequent sections, the separate OS-specific instructions, and
44 44 :ref:`setup` for details on these steps.
45 45
46 46
47 47 Python environment
48 48 ------------------
49 49
50 50 **Kallithea** is written entirely in Python_ and requires Python version
51 51 3.6 or higher.
52 52
53 53 Given a Python installation, there are different ways of providing the
54 54 environment for running Python applications. Each of them pretty much
55 55 corresponds to a ``site-packages`` directory somewhere where packages can be
56 56 installed.
57 57
58 58 Kallithea itself can be run from source or be installed, but even when running
59 59 from source, there are some dependencies that must be installed in the Python
60 60 environment used for running Kallithea.
61 61
62 62 - Packages *could* be installed in Python's ``site-packages`` directory ... but
63 63 that would require running pip_ as root and it would be hard to uninstall or
64 64 upgrade and is probably not a good idea unless using a package manager.
65 65
66 66 - Packages could also be installed in ``~/.local`` ... but that is probably
67 67 only a good idea if using a dedicated user per application or instance.
68 68
69 69 - Finally, it can be installed in a virtualenv. That is a very lightweight
70 70 "container" where each Kallithea instance can get its own dedicated and
71 71 self-contained virtual environment.
72 72
73 73 We recommend using virtualenv for installing Kallithea.
74 74
75 75
76 76 Locale environment
77 77 ------------------
78 78
79 79 In order to ensure a correct functioning of Kallithea with respect to non-ASCII
80 80 characters in user names, file paths, commit messages, etc., it is very
81 81 important that Kallithea is run with a correct `locale` configuration.
82 82
83 83 On Unix, environment variables like ``LANG`` or ``LC_ALL`` can specify a language (like
84 84 ``en_US``) and encoding (like ``UTF-8``) to use for code points outside the ASCII
85 85 range. The flexibility of supporting multiple encodings of Unicode has the flip
86 86 side of having to specify which encoding to use - especially for Mercurial.
87 87
88 88 It depends on the OS distribution and system configuration which locales are
89 89 available. For example, some Docker containers based on Debian default to only
90 90 supporting the ``C`` language, while other Linux environments have ``en_US`` but not
91 91 ``C``. The ``locale -a`` command will show which values are available on the
92 92 current system. Regardless of the actual language, you should normally choose a
93 93 locale that has the ``UTF-8`` encoding (note that spellings ``utf8``, ``utf-8``,
94 94 ``UTF8``, ``UTF-8`` are all referring to the same thing)
95 95
96 96 For technical reasons, the locale configuration **must** be provided in the
97 97 environment in which Kallithea runs - it cannot be specified in the ``.ini`` file.
98 98 How to practically do this depends on the web server that is used and the way it
99 99 is started. For example, gearbox is often started by a normal user, either
100 100 manually or via a script. In this case, the required locale environment
101 101 variables can be provided directly in that user's environment or in the script.
102 102 However, web servers like Apache are often started at boot via an init script or
103 103 service file. Modifying the environment for this case would thus require
104 104 root/administrator privileges. Moreover, that environment would dictate the
105 105 settings for all web services running under that web server, Kallithea being
106 106 just one of them. Specifically in the case of Apache with ``mod_wsgi``, the
107 107 locale can be set for a specific service in its ``WSGIDaemonProcess`` directive,
108 108 using the ``lang`` parameter.
109 109
110 110
111 111 Installation methods
112 112 --------------------
113 113
114 114 Kallithea must be installed on a server. Kallithea is installed in a Python
115 115 environment so it can use packages that are installed there and make itself
116 116 available for other packages.
117 117
118 118 Two different cases will pretty much cover the options for how it can be
119 119 installed.
120 120
121 121 - The Kallithea source repository can be cloned and used -- it is kept stable and
122 122 can be used in production. The Kallithea maintainers use the development
123 123 branch in production. The advantage of installation from source and regularly
124 124 updating it is that you take advantage of the most recent improvements. Using
125 125 it directly from a DVCS also means that it is easy to track local customizations.
126 126
127 127 Running ``pip install -e .`` in the source will use pip to install the
128 128 necessary dependencies in the Python environment and create a
129 129 ``.../site-packages/Kallithea.egg-link`` file there that points at the Kallithea
130 130 source.
131 131
132 132 - Kallithea can also be installed from ready-made packages using a package manager.
133 133 The official released versions are available on PyPI_ and can be downloaded and
134 134 installed with all dependencies using ``pip install kallithea``.
135 135
136 136 With this method, Kallithea is installed in the Python environment as any
137 137 other package, usually as a ``.../site-packages/Kallithea-X-py3.8.egg/``
138 138 directory with Python files and everything else that is needed.
139 139
140 140 (``pip install kallithea`` from a source tree will do pretty much the same
141 141 but build the Kallithea package itself locally instead of downloading it.)
142 142
143 143 .. note::
144 Kallithea includes front-end code that needs to be processed first.
145 The tool npm_ is used to download external dependencies and orchestrate the
146 processing. The ``npm`` binary must thus be available.
144 Kallithea includes front-end code that needs to be processed to prepare
145 static files that can be served at run time and used on the client side. The
146 tool npm_ is used to download external dependencies and orchestrate the
147 processing. The ``npm`` binary must thus be available at install time but is
148 not used at run time.
147 149
148 150
149 151 Web server
150 152 ----------
151 153
152 154 Kallithea is (primarily) a WSGI_ application that must be run from a web
153 155 server that serves WSGI applications over HTTP.
154 156
155 157 Kallithea itself is not serving HTTP (or HTTPS); that is the web server's
156 158 responsibility. Kallithea does however need to know its own user facing URL
157 159 (protocol, address, port and path) for each HTTP request. Kallithea will
158 160 usually use its own HTML/cookie based authentication but can also be configured
159 161 to use web server authentication.
160 162
161 163 There are several web server options:
162 164
163 165 - Kallithea uses the Gearbox_ tool as command line interface. Gearbox provides
164 166 ``gearbox serve`` as a convenient way to launch a Python WSGI / web server
165 167 from the command line. That is perfect for development and evaluation.
166 168 Actual use in production might have different requirements and need extra
167 169 work to make it manageable as a scalable system service.
168 170
169 Gearbox comes with its own built-in web server but Kallithea defaults to use
170 Waitress_. Gunicorn_ is also an option. These web servers have different
171 limited feature sets.
171 Gearbox comes with its own built-in web server for development but Kallithea
172 defaults to using Waitress_. Gunicorn_ and Gevent_ are also options. These
173 web servers have different limited feature sets.
172 174
173 The web server used by ``gearbox`` is configured in the ``.ini`` file passed
174 to it. The entry point for the WSGI application is configured
175 in ``setup.py`` as ``kallithea.config.application:make_app``.
175 The web server used by ``gearbox serve`` is configured in the ``.ini`` file.
176 Create it with ``config-create`` using for example ``http_server=waitress``
177 to get a configuration starting point for your choice of web server.
178
179 (Gearbox will do like ``paste`` and use the WSGI application entry point
180 ``kallithea.config.middleware:make_app`` as specified in ``setup.py``.)
176 181
177 182 - `Apache httpd`_ can serve WSGI applications directly using mod_wsgi_ and a
178 183 simple Python file with the necessary configuration. This is a good option if
179 184 Apache is an option.
180 185
181 - uWSGI_ is also a full web server with built-in WSGI module.
186 - uWSGI_ is also a full web server with built-in WSGI module. Use
187 ``config-create`` with ``http_server=uwsgi`` to get a ``.ini`` file with
188 uWSGI configuration.
182 189
183 190 - IIS_ can also server WSGI applications directly using isapi-wsgi_.
184 191
185 192 - A `reverse HTTP proxy <https://en.wikipedia.org/wiki/Reverse_proxy>`_
186 193 can be put in front of another web server which has WSGI support.
187 194 Such a layered setup can be complex but might in some cases be the right
188 195 option, for example to standardize on one internet-facing web server, to add
189 196 encryption or special authentication or for other security reasons, to
190 197 provide caching of static files, or to provide load balancing or fail-over.
191 198 Nginx_, Varnish_ and HAProxy_ are often used for this purpose, often in front
192 199 of a ``gearbox serve`` that somehow is wrapped as a service.
193 200
194 201 The best option depends on what you are familiar with and the requirements for
195 202 performance and stability. Also, keep in mind that Kallithea mainly is serving
196 203 dynamically generated pages from a relatively slow Python process. Kallithea is
197 204 also often used inside organizations with a limited amount of users and thus no
198 205 continuous hammering from the internet.
199 206
207 .. note::
208 Kallithea, the libraries it uses, and Python itself do in several places use
209 simple caching in memory. Caches and memory are not always released in a way
210 that is suitable for long-running processes. They might appear to be leaking
211 memory. The worker processes should thus regularly be restarted - for
212 example after 1000 requests and/or one hour. This can usually be done by the
213 web server or the tool used for running it as a system service.
214
200 215
201 216 .. _Python: http://www.python.org/
202 217 .. _Gunicorn: http://gunicorn.org/
218 .. _Gevent: http://www.gevent.org/
203 219 .. _Waitress: http://waitress.readthedocs.org/en/latest/
204 220 .. _Gearbox: http://turbogears.readthedocs.io/en/latest/turbogears/gearbox.html
205 221 .. _PyPI: https://pypi.python.org/pypi
206 222 .. _Apache httpd: http://httpd.apache.org/
207 223 .. _mod_wsgi: https://code.google.com/p/modwsgi/
208 224 .. _isapi-wsgi: https://github.com/hexdump42/isapi-wsgi
209 225 .. _uWSGI: https://uwsgi-docs.readthedocs.org/en/latest/
210 226 .. _nginx: http://nginx.org/en/
211 227 .. _iis: http://en.wikipedia.org/wiki/Internet_Information_Services
212 228 .. _pip: http://en.wikipedia.org/wiki/Pip_%28package_manager%29
213 229 .. _WSGI: http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
214 230 .. _HAProxy: http://www.haproxy.org/
215 231 .. _Varnish: https://www.varnish-cache.org/
216 232 .. _npm: https://www.npmjs.com/
@@ -1,646 +1,648 b''
1 1 .. _setup:
2 2
3 3 =====
4 4 Setup
5 5 =====
6 6
7 7
8 8 Setting up Kallithea
9 9 --------------------
10 10
11 11 First, you will need to create a Kallithea configuration file. Run the
12 12 following command to do so::
13 13
14 14 kallithea-cli config-create my.ini
15 15
16 16 This will create the file ``my.ini`` in the current directory. This
17 17 configuration file contains the various settings for Kallithea, e.g.
18 18 proxy port, email settings, usage of static files, cache, Celery
19 19 settings, and logging. Extra settings can be specified like::
20 20
21 21 kallithea-cli config-create my.ini host=8.8.8.8 "[handler_console]" formatter=color_formatter
22 22
23 23 Next, you need to create the databases used by Kallithea. It is recommended to
24 24 use PostgreSQL or SQLite (default). If you choose a database other than the
25 25 default, ensure you properly adjust the database URL in your ``my.ini``
26 26 configuration file to use this other database. Kallithea currently supports
27 PostgreSQL, SQLite and MySQL databases. Create the database by running
27 PostgreSQL, SQLite and MariaDB/MySQL databases. Create the database by running
28 28 the following command::
29 29
30 30 kallithea-cli db-create -c my.ini
31 31
32 32 This will prompt you for a "root" path. This "root" path is the location where
33 33 Kallithea will store all of its repositories on the current machine. After
34 34 entering this "root" path ``db-create`` will also prompt you for a username
35 35 and password for the initial admin account which ``db-create`` sets
36 36 up for you.
37 37
38 38 The ``db-create`` values can also be given on the command line.
39 39 Example::
40 40
41 41 kallithea-cli db-create -c my.ini --user=nn --password=secret --email=nn@example.com --repos=/srv/repos
42 42
43 43 The ``db-create`` command will create all needed tables and an
44 44 admin account. When choosing a root path you can either use a new
45 45 empty location, or a location which already contains existing
46 46 repositories. If you choose a location which contains existing
47 47 repositories Kallithea will add all of the repositories at the chosen
48 48 location to its database. (Note: make sure you specify the correct
49 49 path to the root).
50 50
51 51 .. note:: the given path for Mercurial_ repositories **must** be write
52 52 accessible for the application. It's very important since
53 53 the Kallithea web interface will work without write access,
54 54 but when trying to do a push it will fail with permission
55 55 denied errors unless it has write access.
56 56
57 Finally, prepare the front-end by running::
57 Finally, the front-end files must be prepared. This requires ``npm`` version 6
58 or later, which needs ``node.js`` (version 12 or later). Prepare the front-end
59 by running::
58 60
59 61 kallithea-cli front-end-build
60 62
61 63 You are now ready to use Kallithea. To run it simply execute::
62 64
63 65 gearbox serve -c my.ini
64 66
65 67 - This command runs the Kallithea server. The web app should be available at
66 68 http://127.0.0.1:5000. The IP address and port is configurable via the
67 69 configuration file created in the previous step.
68 70 - Log in to Kallithea using the admin account created when running ``db-create``.
69 71 - The default permissions on each repository is read, and the owner is admin.
70 72 Remember to update these if needed.
71 73 - In the admin panel you can toggle LDAP, anonymous, and permissions
72 74 settings, as well as edit more advanced options on users and
73 75 repositories.
74 76
75 77
76 78 Internationalization (i18n support)
77 79 -----------------------------------
78 80
79 81 The Kallithea web interface is automatically displayed in the user's preferred
80 82 language, as indicated by the browser. Thus, different users may see the
81 83 application in different languages. If the requested language is not available
82 84 (because the translation file for that language does not yet exist or is
83 85 incomplete), English is used.
84 86
85 87 If you want to disable automatic language detection and instead configure a
86 88 fixed language regardless of user preference, set ``i18n.enabled = false`` and
87 89 specify another language by setting ``i18n.lang`` in the Kallithea
88 90 configuration file.
89 91
90 92
91 93 Using Kallithea with SSH
92 94 ------------------------
93 95
94 96 Kallithea supports repository access via SSH key based authentication.
95 97 This means:
96 98
97 99 - repository URLs like ``ssh://kallithea@example.com/name/of/repository``
98 100
99 101 - all network traffic for both read and write happens over the SSH protocol on
100 102 port 22, without using HTTP/HTTPS nor the Kallithea WSGI application
101 103
102 104 - encryption and authentication protocols are managed by the system's ``sshd``
103 105 process, with all users using the same Kallithea system user (e.g.
104 106 ``kallithea``) when connecting to the SSH server, but with users' public keys
105 107 in the Kallithea system user's `.ssh/authorized_keys` file granting each user
106 108 sandboxed access to the repositories.
107 109
108 110 - users and admins can manage SSH public keys in the web UI
109 111
110 112 - in their SSH client configuration, users can configure how the client should
111 113 control access to their SSH key - without passphrase, with passphrase, and
112 114 optionally with passphrase caching in the local shell session (``ssh-agent``).
113 115 This is standard SSH functionality, not something Kallithea provides or
114 116 interferes with.
115 117
116 118 - network communication between client and server happens in a bidirectional
117 119 stateful stream, and will in some cases be faster than HTTP/HTTPS with several
118 120 stateless round-trips.
119 121
120 122 .. note:: At this moment, repository access via SSH has been tested on Unix
121 123 only. Windows users that care about SSH are invited to test it and report
122 124 problems, ideally contributing patches that solve these problems.
123 125
124 126 Users and admins can upload SSH public keys (e.g. ``.ssh/id_rsa.pub``) through
125 127 the web interface. The server's ``.ssh/authorized_keys`` file is automatically
126 128 maintained with an entry for each SSH key. Each entry will tell ``sshd`` to run
127 129 ``kallithea-cli`` with the ``ssh-serve`` sub-command and the right Kallithea user ID
128 130 when encountering the corresponding SSH key.
129 131
130 132 To enable SSH repository access, Kallithea must be configured with the path to
131 133 the ``.ssh/authorized_keys`` file for the Kallithea user, and the path to the
132 134 ``kallithea-cli`` command. Put something like this in the ``.ini`` file::
133 135
134 136 ssh_enabled = true
135 137 ssh_authorized_keys = /home/kallithea/.ssh/authorized_keys
136 138 kallithea_cli_path = /srv/kallithea/venv/bin/kallithea-cli
137 139
138 140 The SSH service must be running, and the Kallithea user account must be active
139 141 (not necessarily with password access, but public key access must be enabled),
140 142 all file permissions must be set as sshd wants it, and ``authorized_keys`` must
141 143 be writeable by the Kallithea user.
142 144
143 145 .. note:: The ``authorized_keys`` file will be rewritten from scratch on
144 146 each update. If it already exists with other data, Kallithea will not
145 147 overwrite the existing ``authorized_keys``, and the server process will
146 148 instead throw an exception. The system administrator thus cannot ssh
147 149 directly to the Kallithea user but must use su/sudo from another account.
148 150
149 151 If ``/home/kallithea/.ssh/`` (the directory of the path specified in the
150 152 ``ssh_authorized_keys`` setting of the ``.ini`` file) does not exist as a
151 153 directory, Kallithea will attempt to create it. If that path exists but is
152 154 *not* a directory, or is not readable-writable-executable by the server
153 155 process, the server process will raise an exception each time it attempts to
154 156 write the ``authorized_keys`` file.
155 157
156 158 .. note:: It is possible to configure the SSH server to look for authorized
157 159 keys in multiple files, for example reserving ``ssh/authorized_keys`` to be
158 160 used for normal SSH and with Kallithea using
159 161 ``.ssh/authorized_keys_kallithea``. In ``/etc/ssh/sshd_config`` set
160 162 ``AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys_kallithea``
161 163 and restart sshd, and in ``my.ini`` set ``ssh_authorized_keys =
162 164 /home/kallithea/.ssh/authorized_keys_kallithea``. Note that this new
163 165 location will apply to all system users, and that multiple entries for the
164 166 same SSH key will shadow each other.
165 167
166 168 .. warning:: The handling of SSH access is steered directly by the command
167 169 specified in the ``authorized_keys`` file. There is no interaction with the
168 170 web UI. Once SSH access is correctly configured and enabled, it will work
169 171 regardless of whether the Kallithea web process is actually running. Hence,
170 172 if you want to perform repository or server maintenance and want to fully
171 173 disable all access to the repositories, disable SSH access by setting
172 174 ``ssh_enabled = false`` in the correct ``.ini`` file (i.e. the ``.ini`` file
173 175 specified in the ``authorized_keys`` file.)
174 176
175 177 The ``authorized_keys`` file can be updated manually with ``kallithea-cli
176 178 ssh-update-authorized-keys -c my.ini``. This command is not needed in normal
177 179 operation but is for example useful after changing SSH-related settings in the
178 180 ``.ini`` file or renaming that file. (The path to the ``.ini`` file is used in
179 181 the generated ``authorized_keys`` file).
180 182
181 183
182 184 Setting up Whoosh full text search
183 185 ----------------------------------
184 186
185 187 Kallithea provides full text search of repositories using `Whoosh`__.
186 188
187 189 .. __: https://whoosh.readthedocs.io/en/latest/
188 190
189 191 For an incremental index build, run::
190 192
191 193 kallithea-cli index-create -c my.ini
192 194
193 195 For a full index rebuild, run::
194 196
195 197 kallithea-cli index-create -c my.ini --full
196 198
197 199 The ``--repo-location`` option allows the location of the repositories to be overridden;
198 200 usually, the location is retrieved from the Kallithea database.
199 201
200 202 The ``--index-only`` option can be used to limit the indexed repositories to a comma-separated list::
201 203
202 204 kallithea-cli index-create -c my.ini --index-only=vcs,kallithea
203 205
204 206 To keep your index up-to-date it is necessary to do periodic index builds;
205 207 for this, it is recommended to use a crontab entry. Example::
206 208
207 209 0 3 * * * /path/to/virtualenv/bin/kallithea-cli index-create -c /path/to/kallithea/my.ini
208 210
209 211 When using incremental mode (the default), Whoosh will check the last
210 212 modification date of each file and add it to be reindexed if a newer file is
211 213 available. The indexing daemon checks for any removed files and removes them
212 214 from index.
213 215
214 216 If you want to rebuild the index from scratch, you can use the ``-f`` flag as above,
215 217 or in the admin panel you can check the "build from scratch" checkbox.
216 218
217 219
218 220 Integration with issue trackers
219 221 -------------------------------
220 222
221 223 Kallithea provides a simple integration with issue trackers. It's possible
222 224 to define a regular expression that will match an issue ID in commit messages,
223 225 and have that replaced with a URL to the issue.
224 226
225 227 This is achieved with following three variables in the ini file::
226 228
227 229 issue_pat = #(\d+)
228 230 issue_server_link = https://issues.example.com/{repo}/issue/\1
229 231 issue_sub =
230 232
231 233 ``issue_pat`` is the regular expression describing which strings in
232 234 commit messages will be treated as issue references. The expression can/should
233 235 have one or more parenthesized groups that can later be referred to in
234 236 ``issue_server_link`` and ``issue_sub`` (see below). If you prefer, named groups
235 237 can be used instead of simple parenthesized groups.
236 238
237 239 If the pattern should only match if it is preceded by whitespace, add the
238 240 following string before the actual pattern: ``(?:^|(?<=\s))``.
239 241 If the pattern should only match if it is followed by whitespace, add the
240 242 following string after the actual pattern: ``(?:$|(?=\s))``.
241 243 These expressions use lookbehind and lookahead assertions of the Python regular
242 244 expression module to avoid the whitespace to be part of the actual pattern,
243 245 otherwise the link text will also contain that whitespace.
244 246
245 247 Matched issue references are replaced with the link specified in
246 248 ``issue_server_link``, in which any backreferences are resolved. Backreferences
247 249 can be ``\1``, ``\2``, ... or for named groups ``\g<groupname>``.
248 250 The special token ``{repo}`` is replaced with the full repository path
249 251 (including repository groups), while token ``{repo_name}`` is replaced with the
250 252 repository name (without repository groups).
251 253
252 254 The link text is determined by ``issue_sub``, which can be a string containing
253 255 backreferences to the groups specified in ``issue_pat``. If ``issue_sub`` is
254 256 empty, then the text matched by ``issue_pat`` is used verbatim.
255 257
256 258 The example settings shown above match issues in the format ``#<number>``.
257 259 This will cause the text ``#300`` to be transformed into a link:
258 260
259 261 .. code-block:: html
260 262
261 263 <a href="https://issues.example.com/example_repo/issue/300">#300</a>
262 264
263 265 The following example transforms a text starting with either of 'pullrequest',
264 266 'pull request' or 'PR', followed by an optional space, then a pound character
265 267 (#) and one or more digits, into a link with the text 'PR #' followed by the
266 268 digits::
267 269
268 270 issue_pat = (pullrequest|pull request|PR) ?#(\d+)
269 271 issue_server_link = https://issues.example.com/\2
270 272 issue_sub = PR #\2
271 273
272 274 The following example demonstrates how to require whitespace before the issue
273 275 reference in order for it to be recognized, such that the text ``issue#123`` will
274 276 not cause a match, but ``issue #123`` will::
275 277
276 278 issue_pat = (?:^|(?<=\s))#(\d+)
277 279 issue_server_link = https://issues.example.com/\1
278 280 issue_sub =
279 281
280 282 If needed, more than one pattern can be specified by appending a unique suffix to
281 283 the variables. For example, also demonstrating the use of named groups::
282 284
283 285 issue_pat_wiki = wiki-(?P<pagename>\S+)
284 286 issue_server_link_wiki = https://wiki.example.com/\g<pagename>
285 287 issue_sub_wiki = WIKI-\g<pagename>
286 288
287 289 With these settings, wiki pages can be referenced as wiki-some-id, and every
288 290 such reference will be transformed into:
289 291
290 292 .. code-block:: html
291 293
292 294 <a href="https://wiki.example.com/some-id">WIKI-some-id</a>
293 295
294 296 Refer to the `Python regular expression documentation`_ for more details about
295 297 the supported syntax in ``issue_pat``, ``issue_server_link`` and ``issue_sub``.
296 298
297 299
298 300 Hook management
299 301 ---------------
300 302
301 303 Hooks can be managed in similar way to that used in ``.hgrc`` files.
302 304 To manage hooks, choose *Admin > Settings > Hooks*.
303 305
304 306 The built-in hooks cannot be modified, though they can be enabled or disabled in the *VCS* section.
305 307
306 308 To add another custom hook simply fill in the first textbox with
307 309 ``<name>.<hook_type>`` and the second with the hook path. Example hooks
308 310 can be found in ``kallithea.lib.hooks``.
309 311
310 312
311 313 Changing default encoding
312 314 -------------------------
313 315
314 316 By default, Kallithea uses UTF-8 encoding.
315 317 This is configurable as ``default_encoding`` in the .ini file.
316 318 This affects many parts in Kallithea including user names, filenames, and
317 319 encoding of commit messages. In addition Kallithea can detect if the ``chardet``
318 320 library is installed. If ``chardet`` is detected Kallithea will fallback to it
319 321 when there are encode/decode errors.
320 322
321 323 The Mercurial encoding is configurable as ``hgencoding``. It is similar to
322 324 setting the ``HGENCODING`` environment variable, but will override it.
323 325
324 326
325 327 Celery configuration
326 328 --------------------
327 329
328 330 Kallithea can use the distributed task queue system Celery_ to run tasks like
329 331 cloning repositories or sending emails.
330 332
331 333 Kallithea will in most setups work perfectly fine out of the box (without
332 334 Celery), executing all tasks in the web server process. Some tasks can however
333 335 take some time to run and it can be better to run such tasks asynchronously in
334 336 a separate process so the web server can focus on serving web requests.
335 337
336 338 For installation and configuration of Celery, see the `Celery documentation`_.
337 339 Note that Celery requires a message broker service like RabbitMQ_ (recommended)
338 340 or Redis_.
339 341
340 342 The use of Celery is configured in the Kallithea ini configuration file.
341 343 To enable it, simply set::
342 344
343 345 use_celery = true
344 346
345 347 and add or change the ``celery.*`` configuration variables.
346 348
347 349 Configuration settings are prefixed with 'celery.', so for example setting
348 350 `broker_url` in Celery means setting `celery.broker_url` in the configuration
349 351 file.
350 352
351 353 To start the Celery process, run::
352 354
353 355 kallithea-cli celery-run -c my.ini
354 356
355 357 Extra options to the Celery worker can be passed after ``--`` - see ``-- -h``
356 358 for more info.
357 359
358 360 .. note::
359 361 Make sure you run this command from the same virtualenv, and with the same
360 362 user that Kallithea runs.
361 363
362 364
363 365 HTTPS support
364 366 -------------
365 367
366 368 Kallithea will by default generate URLs based on the WSGI environment.
367 369
368 370 Alternatively, you can use some special configuration settings to control
369 371 directly which scheme/protocol Kallithea will use when generating URLs:
370 372
371 373 - With ``https_fixup = true``, the scheme will be taken from the
372 374 ``X-Url-Scheme``, ``X-Forwarded-Scheme`` or ``X-Forwarded-Proto`` HTTP header
373 375 (default ``http``).
374 376 - With ``force_https = true`` the default will be ``https``.
375 377 - With ``use_htsts = true``, Kallithea will set ``Strict-Transport-Security`` when using https.
376 378
377 379 .. _nginx_virtual_host:
378 380
379 381
380 382 Nginx virtual host example
381 383 --------------------------
382 384
383 385 Sample config for Nginx using proxy:
384 386
385 387 .. code-block:: nginx
386 388
387 389 upstream kallithea {
388 390 server 127.0.0.1:5000;
389 391 # add more instances for load balancing
390 392 #server 127.0.0.1:5001;
391 393 #server 127.0.0.1:5002;
392 394 }
393 395
394 396 ## gist alias
395 397 server {
396 398 listen 443;
397 399 server_name gist.example.com;
398 400 access_log /var/log/nginx/gist.access.log;
399 401 error_log /var/log/nginx/gist.error.log;
400 402
401 403 ssl on;
402 404 ssl_certificate gist.your.kallithea.server.crt;
403 405 ssl_certificate_key gist.your.kallithea.server.key;
404 406
405 407 ssl_session_timeout 5m;
406 408
407 409 ssl_protocols SSLv3 TLSv1;
408 410 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
409 411 ssl_prefer_server_ciphers on;
410 412
411 413 rewrite ^/(.+)$ https://kallithea.example.com/_admin/gists/$1;
412 414 rewrite (.*) https://kallithea.example.com/_admin/gists;
413 415 }
414 416
415 417 server {
416 418 listen 443;
417 419 server_name kallithea.example.com
418 420 access_log /var/log/nginx/kallithea.access.log;
419 421 error_log /var/log/nginx/kallithea.error.log;
420 422
421 423 ssl on;
422 424 ssl_certificate your.kallithea.server.crt;
423 425 ssl_certificate_key your.kallithea.server.key;
424 426
425 427 ssl_session_timeout 5m;
426 428
427 429 ssl_protocols SSLv3 TLSv1;
428 430 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
429 431 ssl_prefer_server_ciphers on;
430 432
431 433 ## uncomment root directive if you want to serve static files by nginx
432 434 ## requires static_files = false in .ini file
433 435 #root /srv/kallithea/kallithea/kallithea/public;
434 436 include /etc/nginx/proxy.conf;
435 437 location / {
436 438 try_files $uri @kallithea;
437 439 }
438 440
439 441 location @kallithea {
440 442 proxy_pass http://127.0.0.1:5000;
441 443 }
442 444
443 445 }
444 446
445 447 Here's the proxy.conf. It's tuned so it will not timeout on long
446 448 pushes or large pushes::
447 449
448 450 proxy_redirect off;
449 451 proxy_set_header Host $host;
450 452 ## needed for container auth
451 453 #proxy_set_header REMOTE_USER $remote_user;
452 454 #proxy_set_header X-Forwarded-User $remote_user;
453 455 proxy_set_header X-Url-Scheme $scheme;
454 456 proxy_set_header X-Host $http_host;
455 457 proxy_set_header X-Real-IP $remote_addr;
456 458 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
457 459 proxy_set_header Proxy-host $proxy_host;
458 460 proxy_buffering off;
459 461 proxy_connect_timeout 7200;
460 462 proxy_send_timeout 7200;
461 463 proxy_read_timeout 7200;
462 464 proxy_buffers 8 32k;
463 465 client_max_body_size 1024m;
464 466 client_body_buffer_size 128k;
465 467 large_client_header_buffers 8 64k;
466 468
467 469 .. _apache_virtual_host_reverse_proxy:
468 470
469 471
470 472 Apache virtual host reverse proxy example
471 473 -----------------------------------------
472 474
473 475 Here is a sample configuration file for Apache using proxy:
474 476
475 477 .. code-block:: apache
476 478
477 479 <VirtualHost *:80>
478 480 ServerName kallithea.example.com
479 481
480 482 <Proxy *>
481 483 # For Apache 2.4 and later:
482 484 Require all granted
483 485
484 486 # For Apache 2.2 and earlier, instead use:
485 487 # Order allow,deny
486 488 # Allow from all
487 489 </Proxy>
488 490
489 491 #important !
490 492 #Directive to properly generate url (clone url) for Kallithea
491 493 ProxyPreserveHost On
492 494
493 495 #kallithea instance
494 496 ProxyPass / http://127.0.0.1:5000/
495 497 ProxyPassReverse / http://127.0.0.1:5000/
496 498
497 499 #to enable https use line below
498 500 #SetEnvIf X-Url-Scheme https HTTPS=1
499 501 </VirtualHost>
500 502
501 503 Additional tutorial
502 504 http://pylonsbook.com/en/1.1/deployment.html#using-apache-to-proxy-requests-to-pylons
503 505
504 506 .. _apache_subdirectory:
505 507
506 508
507 509 Apache as subdirectory
508 510 ----------------------
509 511
510 512 Apache subdirectory part:
511 513
512 514 .. code-block:: apache
513 515
514 516 <Location /PREFIX >
515 517 ProxyPass http://127.0.0.1:5000/PREFIX
516 518 ProxyPassReverse http://127.0.0.1:5000/PREFIX
517 519 SetEnvIf X-Url-Scheme https HTTPS=1
518 520 </Location>
519 521
520 522 Besides the regular apache setup you will need to add the following line
521 523 into ``[app:main]`` section of your .ini file::
522 524
523 525 filter-with = proxy-prefix
524 526
525 527 Add the following at the end of the .ini file::
526 528
527 529 [filter:proxy-prefix]
528 530 use = egg:PasteDeploy#prefix
529 531 prefix = /PREFIX
530 532
531 533 then change ``PREFIX`` into your chosen prefix
532 534
533 535 .. _apache_mod_wsgi:
534 536
535 537
536 538 Apache with mod_wsgi
537 539 --------------------
538 540
539 541 Alternatively, Kallithea can be set up with Apache under mod_wsgi. For
540 542 that, you'll need to:
541 543
542 544 - Install mod_wsgi. If using a Debian-based distro, you can install
543 545 the package libapache2-mod-wsgi::
544 546
545 547 aptitude install libapache2-mod-wsgi
546 548
547 549 - Enable mod_wsgi::
548 550
549 551 a2enmod wsgi
550 552
551 553 - Add global Apache configuration to tell mod_wsgi that Python only will be
552 554 used in the WSGI processes and shouldn't be initialized in the Apache
553 555 processes::
554 556
555 557 WSGIRestrictEmbedded On
556 558
557 559 - Create a WSGI dispatch script, like the one below. Make sure you
558 560 check that the paths correctly point to where you installed Kallithea
559 561 and its Python Virtual Environment.
560 562
561 563 .. code-block:: python
562 564
563 565 import os
564 566 os.environ['PYTHON_EGG_CACHE'] = '/srv/kallithea/.egg-cache'
565 567
566 568 # sometimes it's needed to set the current dir
567 569 os.chdir('/srv/kallithea/')
568 570
569 571 import site
570 572 site.addsitedir("/srv/kallithea/venv/lib/python3.7/site-packages")
571 573
572 574 ini = '/srv/kallithea/my.ini'
573 575 from logging.config import fileConfig
574 576 fileConfig(ini, {'__file__': ini, 'here': '/srv/kallithea'})
575 577 from paste.deploy import loadapp
576 578 application = loadapp('config:' + ini)
577 579
578 580 Or using proper virtualenv activation:
579 581
580 582 .. code-block:: python
581 583
582 584 activate_this = '/srv/kallithea/venv/bin/activate_this.py'
583 585 execfile(activate_this, dict(__file__=activate_this))
584 586
585 587 import os
586 588 os.environ['HOME'] = '/srv/kallithea'
587 589
588 590 ini = '/srv/kallithea/kallithea.ini'
589 591 from logging.config import fileConfig
590 592 fileConfig(ini, {'__file__': ini, 'here': '/srv/kallithea'})
591 593 from paste.deploy import loadapp
592 594 application = loadapp('config:' + ini)
593 595
594 596 - Add the necessary ``WSGI*`` directives to the Apache Virtual Host configuration
595 597 file, like in the example below. Notice that the WSGI dispatch script created
596 598 above is referred to with the ``WSGIScriptAlias`` directive.
597 599 The default locale settings Apache provides for web services are often not
598 600 adequate, with `C` as the default language and `ASCII` as the encoding.
599 601 Instead, use the ``lang`` parameter of ``WSGIDaemonProcess`` to specify a
600 602 suitable locale. See also the :ref:`overview` section and the
601 603 `WSGIDaemonProcess documentation`_.
602 604
603 605 Apache will by default run as a special Apache user, on Linux systems
604 606 usually ``www-data`` or ``apache``. If you need to have the repositories
605 607 directory owned by a different user, use the user and group options to
606 608 WSGIDaemonProcess to set the name of the user and group.
607 609
608 610 Once again, check that all paths are correctly specified.
609 611
610 612 .. code-block:: apache
611 613
612 614 WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100 \
613 615 python-home=/srv/kallithea/venv lang=C.UTF-8
614 616 WSGIProcessGroup kallithea
615 617 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
616 618 WSGIPassAuthorization On
617 619
618 620 Or if using a dispatcher WSGI script with proper virtualenv activation:
619 621
620 622 .. code-block:: apache
621 623
622 624 WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100 lang=en_US.utf8
623 625 WSGIProcessGroup kallithea
624 626 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
625 627 WSGIPassAuthorization On
626 628
627 629
628 630 Other configuration files
629 631 -------------------------
630 632
631 633 A number of `example init.d scripts`__ can be found in
632 634 the ``init.d`` directory of the Kallithea source.
633 635
634 636 .. __: https://kallithea-scm.org/repos/kallithea/files/tip/init.d/ .
635 637
636 638
637 639 .. _python: http://www.python.org/
638 640 .. _Python regular expression documentation: https://docs.python.org/2/library/re.html
639 641 .. _Mercurial: https://www.mercurial-scm.org/
640 642 .. _Celery: http://celeryproject.org/
641 643 .. _Celery documentation: http://docs.celeryproject.org/en/latest/getting-started/index.html
642 644 .. _RabbitMQ: http://www.rabbitmq.com/
643 645 .. _Redis: http://redis.io/
644 646 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
645 647 .. _PublishingRepositories: https://www.mercurial-scm.org/wiki/PublishingRepositories
646 648 .. _WSGIDaemonProcess documentation: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIDaemonProcess.html
@@ -1,243 +1,243 b''
1 1 .. _upgrade:
2 2
3 3 ===================
4 4 Upgrading Kallithea
5 5 ===================
6 6
7 7 This describes the process for upgrading Kallithea, independently of the
8 8 Kallithea installation method.
9 9
10 10 .. note::
11 11 If you are upgrading from a RhodeCode installation, you must first
12 12 install Kallithea 0.3.2 and follow the instructions in the 0.3.2
13 13 README to perform a one-time conversion of the database from
14 14 RhodeCode to Kallithea, before upgrading to the latest version
15 15 of Kallithea.
16 16
17 17
18 18 1. Stop the Kallithea web application
19 19 -------------------------------------
20 20
21 21 This step depends entirely on the web server software used to serve
22 22 Kallithea, but in any case, Kallithea should not be running during
23 23 the upgrade.
24 24
25 25 .. note::
26 26 If you're using Celery, make sure you stop all instances during the
27 27 upgrade.
28 28
29 29
30 30 2. Create a backup of both database and configuration
31 31 -----------------------------------------------------
32 32
33 33 You are of course strongly recommended to make backups regularly, but it
34 34 is *especially* important to make a full database and configuration
35 35 backup before performing a Kallithea upgrade.
36 36
37 37 Back up your configuration
38 38 ^^^^^^^^^^^^^^^^^^^^^^^^^^
39 39
40 40 Make a copy of your Kallithea configuration (``.ini``) file.
41 41
42 42 If you are using :ref:`rcextensions <customization>`, you should also
43 43 make a copy of the entire ``rcextensions`` directory.
44 44
45 45 Back up your database
46 46 ^^^^^^^^^^^^^^^^^^^^^
47 47
48 48 If using SQLite, simply make a copy of the Kallithea database (``.db``)
49 49 file.
50 50
51 51 If using PostgreSQL, please consult the documentation for the ``pg_dump``
52 52 utility.
53 53
54 If using MySQL, please consult the documentation for the ``mysqldump``
54 If using MariaDB/MySQL, please consult the documentation for the ``mysqldump``
55 55 utility.
56 56
57 57 Look for ``sqlalchemy.url`` in your configuration file to determine
58 58 database type, settings, location, etc. If you were running Kallithea 0.3.x or
59 59 older, this was ``sqlalchemy.db1.url``.
60 60
61 61
62 62 3. Activate or recreate the Kallithea virtual environment (if any)
63 63 ------------------------------------------------------------------
64 64
65 65 .. note::
66 66 If you did not install Kallithea in a virtual environment, skip this step.
67 67
68 68 For major upgrades, e.g. from 0.3.x to 0.4.x, it is recommended to create a new
69 69 virtual environment, rather than reusing the old. For minor upgrades, e.g.
70 70 within the 0.4.x range, this is not really necessary (but equally fine).
71 71
72 72 To create a new virtual environment, please refer to the appropriate
73 73 installation page for details. After creating and activating the new virtual
74 74 environment, proceed with the rest of the upgrade process starting from the next
75 75 section.
76 76
77 77 To reuse the same virtual environment, first activate it, then verify that you
78 78 are using the correct environment by running::
79 79
80 80 pip freeze
81 81
82 82 This will list all packages installed in the current environment. If
83 83 Kallithea isn't listed, deactivate the environment and then activate the correct
84 84 one, or recreate a new environment. See the appropriate installation page for
85 85 details.
86 86
87 87
88 88 4. Install new version of Kallithea
89 89 -----------------------------------
90 90
91 91 Please refer to the instructions for the installation method you
92 92 originally used to install Kallithea.
93 93
94 94 If you originally installed using pip, it is as simple as::
95 95
96 96 pip install --upgrade kallithea
97 97
98 98 If you originally installed from version control, assuming you did not make
99 99 private changes (in which case you should adapt the instructions accordingly)::
100 100
101 101 cd my-kallithea-clone
102 102 hg parent # make a note of the original revision
103 103 hg pull
104 104 hg update
105 105 hg parent # make a note of the new revision
106 106 pip install --upgrade -e .
107 107
108 108 .. _upgrade_config:
109 109
110 110
111 111 5. Upgrade your configuration
112 112 -----------------------------
113 113
114 114 Run the following command to create a new configuration (``.ini``) file::
115 115
116 116 kallithea-cli config-create new.ini
117 117
118 118 Then compare it with your old config file and copy over the required
119 119 configuration values from the old to the new file.
120 120
121 121 .. note::
122 122 Please always make sure your ``.ini`` files are up to date. Errors
123 123 can often be caused by missing parameters added in new versions.
124 124
125 125 .. _upgrade_db:
126 126
127 127
128 128 6. Upgrade your database
129 129 ------------------------
130 130
131 131 .. note::
132 132 If you are *downgrading* Kallithea, you should perform the database
133 133 migration step *before* installing the older version. (That is,
134 134 always perform migrations using the most recent of the two versions
135 135 you're migrating between.)
136 136
137 137 First, run the following command to see your current database version::
138 138
139 139 alembic -c new.ini current
140 140
141 141 Typical output will be something like "9358dc3d6828 (head)", which is
142 142 the current Alembic database "revision ID". Write down the entire output
143 143 for troubleshooting purposes.
144 144
145 145 The output will be empty if you're upgrading from Kallithea 0.3.x or
146 146 older. That's expected. If you get an error that the config file was not
147 147 found or has no ``[alembic]`` section, see the next section.
148 148
149 149 Next, if you are performing an *upgrade*: Run the following command to
150 150 upgrade your database to the current Kallithea version::
151 151
152 152 alembic -c new.ini upgrade head
153 153
154 154 If you are performing a *downgrade*: Run the following command to
155 155 downgrade your database to the given version::
156 156
157 157 alembic -c new.ini downgrade 0.4
158 158
159 159 Alembic will show the necessary migrations (if any) as it executes them.
160 160 If no "ERROR" is displayed, the command was successful.
161 161
162 162 Should an error occur, the database may be "stranded" half-way
163 163 through the migration, and you should restore it from backup.
164 164
165 165 Enabling old Kallithea config files for Alembic use
166 166 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
167 167
168 168 Kallithea configuration files created before the introduction of Alembic
169 169 (i.e. predating Kallithea 0.4) need to be updated for use with Alembic.
170 170 Without this, Alembic will fail with an error like this::
171 171
172 172 FAILED: No config file 'my.ini' found, or file has no '[alembic]' section
173 173
174 174 .. note::
175 175 If you followed this upgrade guide correctly, you will have created a
176 176 new configuration file in section :ref:`Upgrading your configuration
177 177 <upgrade_config>`. When calling Alembic, make
178 178 sure to use this new config file. In this case, you should not get any
179 179 errors and the below manual steps should not be needed.
180 180
181 181 If Alembic complains specifically about a missing ``alembic.ini``, it is
182 182 likely because you did not specify a config file using the ``-c`` option.
183 183 On the other hand, if the mentioned config file actually exists, you
184 184 need to append the following lines to it::
185 185
186 186 [alembic]
187 187 script_location = kallithea:alembic
188 188
189 189 Your config file should now work with Alembic.
190 190
191 191
192 192 7. Prepare the front-end
193 193 ------------------------
194 194
195 195 Starting with Kallithea 0.4, external front-end dependencies are no longer
196 196 shipped but need to be downloaded and/or generated at installation time. Run the
197 197 following command::
198 198
199 199 kallithea-cli front-end-build
200 200
201 201
202 202 8. Rebuild the Whoosh full-text index
203 203 -------------------------------------
204 204
205 205 It is recommended that you rebuild the Whoosh index after upgrading since
206 206 new Whoosh versions can introduce incompatible index changes.
207 207
208 208
209 209 9. Start the Kallithea web application
210 210 --------------------------------------
211 211
212 212 This step once again depends entirely on the web server software used to
213 213 serve Kallithea.
214 214
215 215 If you were running Kallithea 0.3.x or older and were using ``paster serve
216 216 my.ini`` before, then the corresponding command in Kallithea 0.4 and later is::
217 217
218 218 gearbox serve -c new.ini
219 219
220 220 Before starting the new version of Kallithea, you may find it helpful to
221 221 clear out your log file so that new errors are readily apparent.
222 222
223 223 .. note::
224 224 If you're using Celery, make sure you restart all instances of it after
225 225 upgrade.
226 226
227 227
228 228 10. Update Git repository hooks
229 229 -------------------------------
230 230
231 231 It is possible that an upgrade involves changes to the Git hooks installed by
232 232 Kallithea. As these hooks are created inside the repositories on the server
233 233 filesystem, they are not updated automatically when upgrading Kallithea itself.
234 234
235 235 To update the hooks of your Git repositories:
236 236
237 237 * Go to *Admin > Settings > Remap and Rescan*
238 238 * Select the checkbox *Install Git hooks*
239 239 * Click the button *Rescan repositories*
240 240
241 241 .. note::
242 242 Kallithea does not use hooks on Mercurial repositories. This step is thus
243 243 not necessary if you only have Mercurial repositories.
@@ -1,127 +1,127 b''
1 1 .. _performance:
2 2
3 3 ================================
4 4 Optimizing Kallithea performance
5 5 ================================
6 6
7 7 When serving a large amount of big repositories, Kallithea can start performing
8 8 slower than expected. Because of the demanding nature of handling large amounts
9 9 of data from version control systems, here are some tips on how to get the best
10 10 performance.
11 11
12 12
13 13 Fast storage
14 14 ------------
15 15
16 16 Kallithea is often I/O bound, and hence a fast disk (SSD/SAN) and plenty of RAM
17 17 is usually more important than a fast CPU.
18 18
19 19
20 20 Caching
21 21 -------
22 22
23 23 Tweak beaker cache settings in the ini file. The actual effect of that is
24 24 questionable.
25 25
26 26 .. note::
27 27
28 28 Beaker has no upper bound on cache size and will never drop any caches. For
29 29 memory cache, the only option is to regularly restart the worker process.
30 30 For file cache, it must be cleaned manually, as described in the `Beaker
31 31 documentation <https://beaker.readthedocs.io/en/latest/sessions.html#removing-expired-old-sessions>`_::
32 32
33 33 find data/cache -type f -mtime +30 -print -exec rm {} \;
34 34
35 35
36 36 Database
37 37 --------
38 38
39 39 SQLite is a good option when having a small load on the system. But due to
40 40 locking issues with SQLite, it is not recommended to use it for larger
41 41 deployments.
42 42
43 Switching to MySQL or PostgreSQL will result in an immediate performance
43 Switching to PostgreSQL or MariaDB/MySQL will result in an immediate performance
44 44 increase. A tool like SQLAlchemyGrate_ can be used for migrating to another
45 45 database platform.
46 46
47 47
48 48 Horizontal scaling
49 49 ------------------
50 50
51 51 Scaling horizontally means running several Kallithea instances and let them
52 52 share the load. That can give huge performance benefits when dealing with large
53 53 amounts of traffic (many users, CI servers, etc.). Kallithea can be scaled
54 54 horizontally on one (recommended) or multiple machines.
55 55
56 56 It is generally possible to run WSGI applications multithreaded, so that
57 57 several HTTP requests are served from the same Python process at once. That can
58 58 in principle give better utilization of internal caches and less process
59 59 overhead.
60 60
61 61 One danger of running multithreaded is that program execution becomes much more
62 62 complex; programs must be written to consider all combinations of events and
63 63 problems might depend on timing and be impossible to reproduce.
64 64
65 65 Kallithea can't promise to be thread-safe, just like the embedded Mercurial
66 66 backend doesn't make any strong promises when used as Kallithea uses it.
67 67 Instead, we recommend scaling by using multiple server processes.
68 68
69 69 Web servers with multiple worker processes (such as ``mod_wsgi`` with the
70 70 ``WSGIDaemonProcess`` ``processes`` parameter) will work out of the box.
71 71
72 72 In order to scale horizontally on multiple machines, you need to do the
73 73 following:
74 74
75 75 - Each instance's ``data`` storage needs to be configured to be stored on a
76 76 shared disk storage, preferably together with repositories. This ``data``
77 77 dir contains template caches, sessions, whoosh index and is used for
78 78 task locking (so it is safe across multiple instances). Set the
79 79 ``cache_dir``, ``index_dir``, ``beaker.cache.data_dir``, ``beaker.cache.lock_dir``
80 80 variables in each .ini file to a shared location across Kallithea instances
81 81 - If using several Celery instances,
82 82 the message broker should be common to all of them (e.g., one
83 83 shared RabbitMQ server)
84 84 - Load balance using round robin or IP hash, recommended is writing LB rules
85 85 that will separate regular user traffic from automated processes like CI
86 86 servers or build bots.
87 87
88 88
89 89 Serve static files directly from the web server
90 90 -----------------------------------------------
91 91
92 92 With the default ``static_files`` ini setting, the Kallithea WSGI application
93 93 will take care of serving the static files from ``kallithea/public/`` at the
94 94 root of the application URL.
95 95
96 96 The actual serving of the static files is very fast and unlikely to be a
97 97 problem in a Kallithea setup - the responses generated by Kallithea from
98 98 database and repository content will take significantly more time and
99 99 resources.
100 100
101 101 To serve static files from the web server, use something like this Apache config
102 102 snippet::
103 103
104 104 Alias /images/ /srv/kallithea/kallithea/kallithea/public/images/
105 105 Alias /css/ /srv/kallithea/kallithea/kallithea/public/css/
106 106 Alias /js/ /srv/kallithea/kallithea/kallithea/public/js/
107 107 Alias /codemirror/ /srv/kallithea/kallithea/kallithea/public/codemirror/
108 108 Alias /fontello/ /srv/kallithea/kallithea/kallithea/public/fontello/
109 109
110 110 Then disable serving of static files in the ``.ini`` ``app:main`` section::
111 111
112 112 static_files = false
113 113
114 114 If using Kallithea installed as a package, you should be able to find the files
115 115 under ``site-packages/kallithea``, either in your Python installation or in your
116 116 virtualenv. When upgrading, make sure to update the web server configuration
117 117 too if necessary.
118 118
119 119 It might also be possible to improve performance by configuring the web server
120 120 to compress responses (served from static files or generated by Kallithea) when
121 121 serving them. That might also imply buffering of responses - that is more
122 122 likely to be a problem; large responses (clones or pulls) will have to be fully
123 123 processed and spooled to disk or memory before the client will see any
124 124 response. See the documentation for your web server.
125 125
126 126
127 127 .. _SQLAlchemyGrate: https://github.com/shazow/sqlalchemygrate
@@ -1,617 +1,618 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%text>##</%text>#################################################################################
3 3 <%text>##</%text>#################################################################################
4 4 <%text>##</%text> Kallithea config file generated with kallithea-cli ${'%-27s' % version }##
5 5 <%text>##</%text> ##
6 6 <%text>##</%text> The %(here)s variable will generally be replaced with the parent directory of ##
7 7 <%text>##</%text> this file. Other use of % must be escaped as %% . ##
8 8 <%text>##</%text>#################################################################################
9 9 <%text>##</%text>#################################################################################
10 10
11 11 [DEFAULT]
12 12
13 13 <%text>##</%text>##############################################################################
14 14 <%text>##</%text> Email settings ##
15 15 <%text>##</%text> ##
16 16 <%text>##</%text> Refer to the documentation ("Email settings") for more details. ##
17 17 <%text>##</%text> ##
18 18 <%text>##</%text> It is recommended to use a valid sender address that passes access ##
19 19 <%text>##</%text> validation and spam filtering in mail servers. ##
20 20 <%text>##</%text>##############################################################################
21 21
22 22 <%text>##</%text> 'From' header for application emails. You can optionally add a name.
23 23 <%text>##</%text> Default:
24 24 #app_email_from = Kallithea
25 25 <%text>##</%text> Examples:
26 26 #app_email_from = Kallithea <kallithea-noreply@example.com>
27 27 #app_email_from = kallithea-noreply@example.com
28 28
29 29 <%text>##</%text> Subject prefix for application emails.
30 30 <%text>##</%text> A space between this prefix and the real subject is automatically added.
31 31 <%text>##</%text> Default:
32 32 #email_prefix =
33 33 <%text>##</%text> Example:
34 34 #email_prefix = [Kallithea]
35 35
36 36 <%text>##</%text> Recipients for error emails and fallback recipients of application mails.
37 37 <%text>##</%text> Multiple addresses can be specified, comma-separated.
38 38 <%text>##</%text> Only addresses are allowed, do not add any name part.
39 39 <%text>##</%text> Default:
40 40 #email_to =
41 41 <%text>##</%text> Examples:
42 42 #email_to = admin@example.com
43 43 #email_to = admin@example.com,another_admin@example.com
44 44 email_to =
45 45
46 46 <%text>##</%text> 'From' header for error emails. You can optionally add a name.
47 47 <%text>##</%text> Default: (none)
48 48 <%text>##</%text> Examples:
49 49 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
50 50 #error_email_from = kallithea_errors@example.com
51 51 error_email_from =
52 52
53 53 <%text>##</%text> SMTP server settings
54 54 <%text>##</%text> If specifying credentials, make sure to use secure connections.
55 55 <%text>##</%text> Default: Send unencrypted unauthenticated mails to the specified smtp_server.
56 56 <%text>##</%text> For "SSL", use smtp_use_ssl = true and smtp_port = 465.
57 57 <%text>##</%text> For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.
58 58 smtp_server =
59 59 smtp_username =
60 60 smtp_password =
61 61 smtp_port =
62 62 smtp_use_ssl = false
63 63 smtp_use_tls = false
64 64
65 65 %if http_server != 'uwsgi':
66 66 <%text>##</%text> Entry point for 'gearbox serve'
67 67 [server:main]
68 68 host = ${host}
69 69 port = ${port}
70 70
71 71 %if http_server == 'gearbox':
72 72 <%text>##</%text> Gearbox serve uses the built-in development web server ##
73 73 use = egg:gearbox#wsgiref
74 74 <%text>##</%text> nr of worker threads to spawn
75 75 threadpool_workers = 1
76 76 <%text>##</%text> max request before thread respawn
77 77 threadpool_max_requests = 100
78 78 <%text>##</%text> option to use threads of process
79 79 use_threadpool = true
80 80
81 81 %elif http_server == 'gevent':
82 82 <%text>##</%text> Gearbox serve uses the gevent web server ##
83 83 use = egg:gearbox#gevent
84 84
85 85 %elif http_server == 'waitress':
86 86 <%text>##</%text> Gearbox serve uses the Waitress web server ##
87 87 use = egg:waitress#main
88 88 <%text>##</%text> avoid multi threading
89 89 threads = 1
90 90 <%text>##</%text> allow push of repos bigger than the default of 1 GB
91 91 max_request_body_size = 107374182400
92 92 <%text>##</%text> use poll instead of select, fixes fd limits, may not work on old
93 93 <%text>##</%text> windows systems.
94 94 #asyncore_use_poll = True
95 95
96 96 %elif http_server == 'gunicorn':
97 97 <%text>##</%text> Gearbox serve uses the Gunicorn web server ##
98 98 use = egg:gunicorn#main
99 99 <%text>##</%text> number of process workers. You must set `instance_id = *` when this option
100 100 <%text>##</%text> is set to more than one worker
101 101 workers = 4
102 102 <%text>##</%text> process name
103 103 proc_name = kallithea
104 104 <%text>##</%text> type of worker class, one of sync, eventlet, gevent, tornado
105 105 <%text>##</%text> recommended for bigger setup is using of of other than sync one
106 106 worker_class = sync
107 107 max_requests = 1000
108 108 <%text>##</%text> amount of time a worker can handle request before it gets killed and
109 109 <%text>##</%text> restarted
110 110 timeout = 3600
111 111
112 112 %endif
113 113 %else:
114 114 <%text>##</%text> UWSGI ##
115 115 [uwsgi]
116 116 <%text>##</%text> Note: this section is parsed by the uWSGI .ini parser when run as:
117 117 <%text>##</%text> uwsgi --venv /srv/kallithea/venv --ini-paste-logged my.ini
118 118 <%text>##</%text> Note: in uWSGI 2.0.18 or older, pastescript needs to be installed to
119 119 <%text>##</%text> get correct application logging. In later versions this is not necessary.
120 120 <%text>##</%text> pip install pastescript
121 121
122 122 <%text>##</%text> HTTP Basics:
123 123 http-socket = ${host}:${port}
124 124 buffer-size = 65535 ; Mercurial will use huge GET headers for discovery
125 125
126 126 <%text>##</%text> Scaling:
127 127 master = true ; Use separate master and worker processes
128 128 auto-procname = true ; Name worker processes accordingly
129 129 lazy = true ; App *must* be loaded in workers - db connections can't be shared
130 130 workers = 4 ; On demand scaling up to this many worker processes
131 131 cheaper = 1 ; Initial and on demand scaling down to this many worker processes
132 132 max-requests = 1000 ; Graceful reload of worker processes to avoid leaks
133 133
134 134 <%text>##</%text> Tweak defaults:
135 135 strict = true ; Fail on unknown config directives
136 136 enable-threads = true ; Enable Python threads (not threaded workers)
137 137 vacuum = true ; Delete sockets during shutdown
138 138 single-interpreter = true
139 139 die-on-term = true ; Shutdown when receiving SIGTERM (default is respawn)
140 140 need-app = true ; Exit early if no app can be loaded.
141 141 reload-on-exception = true ; Don't assume that the application worker can process more requests after a severe error
142 142
143 143 %endif
144 144 <%text>##</%text> middleware for hosting the WSGI application under a URL prefix
145 145 #[filter:proxy-prefix]
146 146 #use = egg:PasteDeploy#prefix
147 147 #prefix = /<your-prefix>
148 148
149 149 [app:main]
150 150 use = egg:kallithea
151 151 <%text>##</%text> enable proxy prefix middleware
152 152 #filter-with = proxy-prefix
153 153
154 154 full_stack = true
155 155 static_files = true
156 156
157 157 <%text>##</%text> Internationalization (see setup documentation for details)
158 158 <%text>##</%text> By default, the languages requested by the browser are used if available, with English as default.
159 159 <%text>##</%text> Set i18n.enabled=false to disable automatic language choice.
160 160 #i18n.enabled = true
161 161 <%text>##</%text> To Force a language, set i18n.enabled=false and specify the language in i18n.lang.
162 162 <%text>##</%text> Valid values are the names of subdirectories in kallithea/i18n with a LC_MESSAGES/kallithea.mo
163 163 #i18n.lang = en
164 164
165 165 cache_dir = %(here)s/data
166 166 index_dir = %(here)s/data/index
167 167
168 168 <%text>##</%text> uncomment and set this path to use archive download cache
169 169 archive_cache_dir = %(here)s/tarballcache
170 170
171 171 <%text>##</%text> change this to unique ID for security
172 172 app_instance_uuid = ${uuid()}
173 173
174 174 <%text>##</%text> cut off limit for large diffs (size in bytes)
175 175 cut_off_limit = 256000
176 176
177 177 <%text>##</%text> force https in Kallithea, fixes https redirects, assumes it's always https
178 178 force_https = false
179 179
180 180 <%text>##</%text> use Strict-Transport-Security headers
181 181 use_htsts = false
182 182
183 183 <%text>##</%text> number of commits stats will parse on each iteration
184 184 commit_parse_limit = 25
185 185
186 186 <%text>##</%text> Path to Python executable to be used for git hooks.
187 187 <%text>##</%text> This value will be written inside the git hook scripts as the text
188 188 <%text>##</%text> after '#!' (shebang). When empty or not defined, the value of
189 189 <%text>##</%text> 'sys.executable' at the time of installation of the git hooks is
190 190 <%text>##</%text> used, which is correct in many cases but for example not when using uwsgi.
191 191 <%text>##</%text> If you change this setting, you should reinstall the Git hooks via
192 192 <%text>##</%text> Admin > Settings > Remap and Rescan.
193 193 #git_hook_interpreter = /srv/kallithea/venv/bin/python3
194 194 %if git_hook_interpreter:
195 195 git_hook_interpreter = ${git_hook_interpreter}
196 196 %endif
197 197
198 198 <%text>##</%text> path to git executable
199 199 git_path = git
200 200
201 201 <%text>##</%text> git rev filter option, --all is the default filter, if you need to
202 202 <%text>##</%text> hide all refs in changelog switch this to --branches --tags
203 203 #git_rev_filter = --branches --tags
204 204
205 205 <%text>##</%text> RSS feed options
206 206 rss_cut_off_limit = 256000
207 207 rss_items_per_page = 10
208 208 rss_include_diff = false
209 209
210 210 <%text>##</%text> options for showing and identifying changesets
211 211 show_sha_length = 12
212 212 show_revision_number = false
213 213
214 214 <%text>##</%text> Canonical URL to use when creating full URLs in UI and texts.
215 215 <%text>##</%text> Useful when the site is available under different names or protocols.
216 216 <%text>##</%text> Defaults to what is provided in the WSGI environment.
217 217 #canonical_url = https://kallithea.example.com/repos
218 218
219 219 <%text>##</%text> gist URL alias, used to create nicer urls for gist. This should be an
220 220 <%text>##</%text> url that does rewrites to _admin/gists/<gistid>.
221 221 <%text>##</%text> example: http://gist.example.com/{gistid}. Empty means use the internal
222 222 <%text>##</%text> Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
223 223 gist_alias_url =
224 224
225 225 <%text>##</%text> default encoding used to convert from and to unicode
226 226 <%text>##</%text> can be also a comma separated list of encoding in case of mixed encodings
227 227 default_encoding = utf-8
228 228
229 229 <%text>##</%text> Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea
230 230 hgencoding = utf-8
231 231
232 232 <%text>##</%text> issue tracker for Kallithea (leave blank to disable, absent for default)
233 233 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
234 234
235 235 <%text>##</%text> issue tracking mapping for commit messages, comments, PR descriptions, ...
236 236 <%text>##</%text> Refer to the documentation ("Integration with issue trackers") for more details.
237 237
238 238 <%text>##</%text> regular expression to match issue references
239 239 <%text>##</%text> This pattern may/should contain parenthesized groups, that can
240 240 <%text>##</%text> be referred to in issue_server_link or issue_sub using Python backreferences
241 241 <%text>##</%text> (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.
242 242 <%text>##</%text> To require mandatory whitespace before the issue pattern, use:
243 243 <%text>##</%text> (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace
244 244 <%text>##</%text> behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.
245 245
246 246 issue_pat = #(\d+)
247 247
248 248 <%text>##</%text> server url to the issue
249 249 <%text>##</%text> This pattern may/should contain backreferences to parenthesized groups in issue_pat.
250 250 <%text>##</%text> A backreference can be \1, \2, ... or \g<groupname> if you specified a named group
251 251 <%text>##</%text> called 'groupname' in issue_pat.
252 252 <%text>##</%text> The special token {repo} is replaced with the full repository name
253 253 <%text>##</%text> including repository groups, while {repo_name} is replaced with just
254 254 <%text>##</%text> the name of the repository.
255 255
256 256 issue_server_link = https://issues.example.com/{repo}/issue/\1
257 257
258 258 <%text>##</%text> substitution pattern to use as the link text
259 259 <%text>##</%text> If issue_sub is empty, the text matched by issue_pat is retained verbatim
260 260 <%text>##</%text> for the link text. Otherwise, the link text is that of issue_sub, with any
261 261 <%text>##</%text> backreferences to groups in issue_pat replaced.
262 262
263 263 issue_sub =
264 264
265 265 <%text>##</%text> issue_pat, issue_server_link and issue_sub can have suffixes to specify
266 266 <%text>##</%text> multiple patterns, to other issues server, wiki or others
267 267 <%text>##</%text> below an example how to create a wiki pattern
268 268 <%text>##</%text> wiki-some-id -> https://wiki.example.com/some-id
269 269
270 270 #issue_pat_wiki = wiki-(\S+)
271 271 #issue_server_link_wiki = https://wiki.example.com/\1
272 272 #issue_sub_wiki = WIKI-\1
273 273
274 274 <%text>##</%text> alternative return HTTP header for failed authentication. Default HTTP
275 275 <%text>##</%text> response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
276 276 <%text>##</%text> handling that. Set this variable to 403 to return HTTPForbidden
277 277 auth_ret_code =
278 278
279 279 <%text>##</%text> allows to change the repository location in settings page
280 280 allow_repo_location_change = True
281 281
282 282 <%text>##</%text> allows to setup custom hooks in settings page
283 283 allow_custom_hooks_settings = True
284 284
285 285 <%text>##</%text> extra extensions for indexing, space separated and without the leading '.'.
286 286 #index.extensions =
287 287 # gemfile
288 288 # lock
289 289
290 290 <%text>##</%text> extra filenames for indexing, space separated
291 291 #index.filenames =
292 292 # .dockerignore
293 293 # .editorconfig
294 294 # INSTALL
295 295 # CHANGELOG
296 296
297 297 <%text>##</%text>##################################
298 298 <%text>##</%text> SSH CONFIG ##
299 299 <%text>##</%text>##################################
300 300
301 301 <%text>##</%text> SSH is disabled by default, until an Administrator decides to enable it.
302 302 ssh_enabled = false
303 303
304 304 <%text>##</%text> File where users' SSH keys will be stored *if* ssh_enabled is true.
305 305 #ssh_authorized_keys = /home/kallithea/.ssh/authorized_keys
306 306 %if user_home_path:
307 307 ssh_authorized_keys = ${user_home_path}/.ssh/authorized_keys
308 308 %endif
309 309
310 310 <%text>##</%text> Path to be used in ssh_authorized_keys file to invoke kallithea-cli with ssh-serve.
311 311 #kallithea_cli_path = /srv/kallithea/venv/bin/kallithea-cli
312 312 %if kallithea_cli_path:
313 313 kallithea_cli_path = ${kallithea_cli_path}
314 314 %endif
315 315
316 316 <%text>##</%text> Locale to be used in the ssh-serve command.
317 317 <%text>##</%text> This is needed because an SSH client may try to use its own locale
318 318 <%text>##</%text> settings, which may not be available on the server.
319 319 <%text>##</%text> See `locale -a` for valid values on this system.
320 320 #ssh_locale = C.UTF-8
321 321 %if ssh_locale:
322 322 ssh_locale = ${ssh_locale}
323 323 %endif
324 324
325 325 <%text>##</%text>##################################
326 326 <%text>##</%text> CELERY CONFIG ##
327 327 <%text>##</%text>##################################
328 328
329 329 <%text>##</%text> Note: Celery doesn't support Windows.
330 330 use_celery = false
331 331
332 332 <%text>##</%text> Celery config settings from https://docs.celeryproject.org/en/4.4.0/userguide/configuration.html prefixed with 'celery.'.
333 333
334 334 <%text>##</%text> Example: use the message queue on the local virtual host 'kallitheavhost' as the RabbitMQ user 'kallithea':
335 335 celery.broker_url = amqp://kallithea:thepassword@localhost:5672/kallitheavhost
336 336
337 337 celery.result.backend = db+sqlite:///celery-results.db
338 338
339 339 #celery.amqp.task.result.expires = 18000
340 340
341 341 celery.worker_concurrency = 2
342 342 celery.worker_max_tasks_per_child = 1
343 343
344 344 <%text>##</%text> If true, tasks will never be sent to the queue, but executed locally instead.
345 345 celery.task_always_eager = false
346 346
347 347 <%text>##</%text>##################################
348 348 <%text>##</%text> BEAKER CACHE ##
349 349 <%text>##</%text>##################################
350 350
351 351 beaker.cache.data_dir = %(here)s/data/cache/data
352 352 beaker.cache.lock_dir = %(here)s/data/cache/lock
353 353
354 354 beaker.cache.regions = long_term,long_term_file
355 355
356 356 beaker.cache.long_term.type = memory
357 357 beaker.cache.long_term.expire = 36000
358 358 beaker.cache.long_term.key_length = 256
359 359
360 360 beaker.cache.long_term_file.type = file
361 361 beaker.cache.long_term_file.expire = 604800
362 362 beaker.cache.long_term_file.key_length = 256
363 363
364 364 <%text>##</%text>##################################
365 365 <%text>##</%text> BEAKER SESSION ##
366 366 <%text>##</%text>##################################
367 367
368 368 <%text>##</%text> Name of session cookie. Should be unique for a given host and path, even when running
369 369 <%text>##</%text> on different ports. Otherwise, cookie sessions will be shared and messed up.
370 370 session.key = kallithea
371 371 <%text>##</%text> Sessions should always only be accessible by the browser, not directly by JavaScript.
372 372 session.httponly = true
373 373 <%text>##</%text> Session lifetime. 2592000 seconds is 30 days.
374 374 session.timeout = 2592000
375 375
376 376 <%text>##</%text> Server secret used with HMAC to ensure integrity of cookies.
377 377 session.secret = ${uuid()}
378 378 <%text>##</%text> Further, encrypt the data with AES.
379 379 #session.encrypt_key = <key_for_encryption>
380 380 #session.validate_key = <validation_key>
381 381
382 382 <%text>##</%text> Type of storage used for the session, current types are
383 383 <%text>##</%text> dbm, file, memcached, database, and memory.
384 384
385 385 <%text>##</%text> File system storage of session data. (default)
386 386 #session.type = file
387 387
388 388 <%text>##</%text> Cookie only, store all session data inside the cookie. Requires secure secrets.
389 389 #session.type = cookie
390 390
391 391 <%text>##</%text> Database storage of session data.
392 392 #session.type = ext:database
393 393 #session.sa.url = postgresql://postgres:qwe@localhost/kallithea
394 394 #session.table_name = db_session
395 395
396 396 <%text>##</%text>##################################
397 397 <%text>##</%text> ERROR HANDLING ##
398 398 <%text>##</%text>##################################
399 399
400 400 <%text>##</%text> Show a nice error page for application HTTP errors and exceptions (default true)
401 401 #errorpage.enabled = true
402 402
403 403 <%text>##</%text> Enable Backlash client-side interactive debugger (default false)
404 404 <%text>##</%text> WARNING: *THIS MUST BE false IN PRODUCTION ENVIRONMENTS!!!*
405 405 <%text>##</%text> This debug mode will allow all visitors to execute malicious code.
406 406 #debug = false
407 407
408 408 <%text>##</%text> Enable Backlash server-side error reporting (unless debug mode handles it client-side) (default true)
409 409 #trace_errors.enable = true
410 410 <%text>##</%text> Errors will be reported by mail if trace_errors.error_email is set.
411 411
412 412 <%text>##</%text> Propagate email settings to ErrorReporter of TurboGears2
413 413 <%text>##</%text> You do not normally need to change these lines
414 414 get trace_errors.smtp_server = smtp_server
415 415 get trace_errors.smtp_port = smtp_port
416 416 get trace_errors.from_address = error_email_from
417 417 get trace_errors.error_email = email_to
418 418 get trace_errors.smtp_username = smtp_username
419 419 get trace_errors.smtp_password = smtp_password
420 420 get trace_errors.smtp_use_tls = smtp_use_tls
421 421
422 422 %if error_aggregation_service == 'sentry':
423 423 <%text>##</%text>##############
424 424 <%text>##</%text> [sentry] ##
425 425 <%text>##</%text>##############
426 426
427 427 <%text>##</%text> sentry is a alternative open source error aggregator
428 428 <%text>##</%text> you must install python packages `sentry` and `raven` to enable
429 429
430 430 sentry.dsn = YOUR_DNS
431 431 sentry.servers =
432 432 sentry.name =
433 433 sentry.key =
434 434 sentry.public_key =
435 435 sentry.secret_key =
436 436 sentry.project =
437 437 sentry.site =
438 438 sentry.include_paths =
439 439 sentry.exclude_paths =
440 440
441 441 %endif
442 442
443 443 <%text>##</%text>################################
444 444 <%text>##</%text> LOGVIEW CONFIG ##
445 445 <%text>##</%text>################################
446 446
447 447 logview.sqlalchemy = #faa
448 448 logview.pylons.templating = #bfb
449 449 logview.pylons.util = #eee
450 450
451 451 <%text>##</%text>#######################
452 452 <%text>##</%text> DB CONFIG ##
453 453 <%text>##</%text>#######################
454 454
455 455 %if database_engine == 'sqlite':
456 456 sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
457 457 %else:
458 458 #sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
459 459 %endif
460 460 %if database_engine == 'postgres':
461 461 sqlalchemy.url = postgresql://user:pass@localhost/kallithea
462 462 %else:
463 463 #sqlalchemy.url = postgresql://user:pass@localhost/kallithea
464 464 %endif
465 465 %if database_engine == 'mysql':
466 466 sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8
467 467 %else:
468 468 #sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8
469 469 %endif
470 <%text>##</%text> Note: the mysql:// prefix should also be used for MariaDB
470 471
471 472 sqlalchemy.pool_recycle = 3600
472 473
473 474 <%text>##</%text>##############################
474 475 <%text>##</%text> ALEMBIC CONFIGURATION ##
475 476 <%text>##</%text>##############################
476 477
477 478 [alembic]
478 479 script_location = kallithea:alembic
479 480
480 481 <%text>##</%text>##############################
481 482 <%text>##</%text> LOGGING CONFIGURATION ##
482 483 <%text>##</%text>##############################
483 484
484 485 [loggers]
485 486 keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
486 487
487 488 [handlers]
488 489 keys = console, console_color, console_color_sql, null
489 490
490 491 [formatters]
491 492 keys = generic, color_formatter, color_formatter_sql
492 493
493 494 <%text>##</%text>###########
494 495 <%text>##</%text> LOGGERS ##
495 496 <%text>##</%text>###########
496 497
497 498 [logger_root]
498 499 level = NOTSET
499 500 handlers = console
500 501 <%text>##</%text> For coloring based on log level:
501 502 #handlers = console_color
502 503
503 504 [logger_routes]
504 505 level = WARN
505 506 handlers =
506 507 qualname = routes.middleware
507 508 <%text>##</%text> "level = DEBUG" logs the route matched and routing variables.
508 509
509 510 [logger_beaker]
510 511 level = WARN
511 512 handlers =
512 513 qualname = beaker.container
513 514
514 515 [logger_templates]
515 516 level = WARN
516 517 handlers =
517 518 qualname = pylons.templating
518 519
519 520 [logger_kallithea]
520 521 level = WARN
521 522 handlers =
522 523 qualname = kallithea
523 524
524 525 [logger_tg]
525 526 level = WARN
526 527 handlers =
527 528 qualname = tg
528 529
529 530 [logger_gearbox]
530 531 level = WARN
531 532 handlers =
532 533 qualname = gearbox
533 534
534 535 [logger_sqlalchemy]
535 536 level = WARN
536 537 handlers =
537 538 qualname = sqlalchemy.engine
538 539 <%text>##</%text> For coloring based on log level and pretty printing of SQL:
539 540 #level = INFO
540 541 #handlers = console_color_sql
541 542 #propagate = 0
542 543
543 544 [logger_whoosh_indexer]
544 545 level = WARN
545 546 handlers =
546 547 qualname = whoosh_indexer
547 548
548 549 [logger_werkzeug]
549 550 level = WARN
550 551 handlers =
551 552 qualname = werkzeug
552 553
553 554 [logger_backlash]
554 555 level = WARN
555 556 handlers =
556 557 qualname = backlash
557 558
558 559 <%text>##</%text>############
559 560 <%text>##</%text> HANDLERS ##
560 561 <%text>##</%text>############
561 562
562 563 [handler_console]
563 564 class = StreamHandler
564 565 args = (sys.stderr,)
565 566 formatter = generic
566 567
567 568 [handler_console_color]
568 569 <%text>##</%text> ANSI color coding based on log level
569 570 class = StreamHandler
570 571 args = (sys.stderr,)
571 572 formatter = color_formatter
572 573
573 574 [handler_console_color_sql]
574 575 <%text>##</%text> ANSI color coding and pretty printing of SQL statements
575 576 class = StreamHandler
576 577 args = (sys.stderr,)
577 578 formatter = color_formatter_sql
578 579
579 580 [handler_null]
580 581 class = NullHandler
581 582 args = ()
582 583
583 584 <%text>##</%text>##############
584 585 <%text>##</%text> FORMATTERS ##
585 586 <%text>##</%text>##############
586 587
587 588 [formatter_generic]
588 589 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
589 590 datefmt = %Y-%m-%d %H:%M:%S
590 591
591 592 [formatter_color_formatter]
592 593 class = kallithea.lib.colored_formatter.ColorFormatter
593 594 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
594 595 datefmt = %Y-%m-%d %H:%M:%S
595 596
596 597 [formatter_color_formatter_sql]
597 598 class = kallithea.lib.colored_formatter.ColorFormatterSql
598 599 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
599 600 datefmt = %Y-%m-%d %H:%M:%S
600 601
601 602 <%text>##</%text>###############
602 603 <%text>##</%text> SSH LOGGING ##
603 604 <%text>##</%text>###############
604 605
605 606 <%text>##</%text> The default loggers use 'handler_console' that uses StreamHandler with
606 607 <%text>##</%text> destination 'sys.stderr'. In the context of the SSH server process, these log
607 608 <%text>##</%text> messages would be sent to the client, which is normally not what you want.
608 609 <%text>##</%text> By default, when running ssh-serve, just use NullHandler and disable logging
609 610 <%text>##</%text> completely. For other logging options, see:
610 611 <%text>##</%text> https://docs.python.org/2/library/logging.handlers.html
611 612
612 613 [ssh_serve:logger_root]
613 614 level = CRITICAL
614 615 handlers = null
615 616
616 617 <%text>##</%text> Note: If logging is configured with other handlers, they might need similar
617 618 <%text>##</%text> muting for ssh-serve too.
@@ -1,392 +1,392 b''
1 1 import os
2 2 import posixpath
3 3
4 4 import mercurial.archival
5 5 import mercurial.node
6 6 import mercurial.obsutil
7 7
8 8 from kallithea.lib.vcs.backends.base import BaseChangeset
9 9 from kallithea.lib.vcs.conf import settings
10 10 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
11 11 from kallithea.lib.vcs.nodes import (AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode,
12 12 SubModuleNode)
13 13 from kallithea.lib.vcs.utils import ascii_bytes, ascii_str, date_fromtimestamp, safe_bytes, safe_str
14 14 from kallithea.lib.vcs.utils.lazy import LazyProperty
15 15 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
16 16
17 17
18 18 class MercurialChangeset(BaseChangeset):
19 19 """
20 20 Represents state of the repository at a revision.
21 21 """
22 22
23 23 def __init__(self, repository, revision):
24 24 self.repository = repository
25 25 assert isinstance(revision, str), repr(revision)
26 26 self._ctx = repository._repo[ascii_bytes(revision)]
27 27 self.raw_id = ascii_str(self._ctx.hex())
28 28 self.revision = self._ctx._rev
29 29 self.nodes = {}
30 30
31 31 @LazyProperty
32 32 def tags(self):
33 33 return [safe_str(tag) for tag in self._ctx.tags()]
34 34
35 35 @LazyProperty
36 36 def branch(self):
37 37 return safe_str(self._ctx.branch())
38 38
39 39 @LazyProperty
40 40 def branches(self):
41 41 return [safe_str(self._ctx.branch())]
42 42
43 43 @LazyProperty
44 44 def closesbranch(self):
45 45 return self._ctx.closesbranch()
46 46
47 47 @LazyProperty
48 48 def obsolete(self):
49 49 return self._ctx.obsolete()
50 50
51 51 @LazyProperty
52 52 def bumped(self):
53 53 return self._ctx.phasedivergent()
54 54
55 55 @LazyProperty
56 56 def divergent(self):
57 57 return self._ctx.contentdivergent()
58 58
59 59 @LazyProperty
60 60 def extinct(self):
61 61 return self._ctx.extinct()
62 62
63 63 @LazyProperty
64 64 def unstable(self):
65 65 return self._ctx.orphan()
66 66
67 67 @LazyProperty
68 68 def phase(self):
69 69 if(self._ctx.phase() == 1):
70 70 return 'Draft'
71 71 elif(self._ctx.phase() == 2):
72 72 return 'Secret'
73 73 else:
74 74 return ''
75 75
76 76 @LazyProperty
77 77 def successors(self):
78 78 successors = mercurial.obsutil.successorssets(self._ctx._repo, self._ctx.node(), closest=True)
79 79 # flatten the list here handles both divergent (len > 1)
80 80 # and the usual case (len = 1)
81 81 return [safe_str(mercurial.node.hex(n)[:12]) for sub in successors for n in sub if n != self._ctx.node()]
82 82
83 83 @LazyProperty
84 84 def predecessors(self):
85 85 return [safe_str(mercurial.node.hex(n)[:12]) for n in mercurial.obsutil.closestpredecessors(self._ctx._repo, self._ctx.node())]
86 86
87 87 @LazyProperty
88 88 def bookmarks(self):
89 89 return [safe_str(bookmark) for bookmark in self._ctx.bookmarks()]
90 90
91 91 @LazyProperty
92 92 def message(self):
93 93 return safe_str(self._ctx.description())
94 94
95 95 @LazyProperty
96 96 def committer(self):
97 97 return safe_str(self.author)
98 98
99 99 @LazyProperty
100 100 def author(self):
101 101 return safe_str(self._ctx.user())
102 102
103 103 @LazyProperty
104 104 def date(self):
105 105 return date_fromtimestamp(*self._ctx.date())
106 106
107 107 @LazyProperty
108 108 def _timestamp(self):
109 109 return self._ctx.date()[0]
110 110
111 111 @LazyProperty
112 112 def status(self):
113 113 """
114 114 Returns modified, added, removed, deleted files for current changeset
115 115 """
116 116 return self.repository._repo.status(self._ctx.p1().node(),
117 117 self._ctx.node())
118 118
119 119 @LazyProperty
120 120 def _file_paths(self):
121 121 return list(safe_str(f) for f in self._ctx)
122 122
123 123 @LazyProperty
124 124 def _dir_paths(self):
125 125 p = list(set(get_dirs_for_path(*self._file_paths)))
126 126 p.insert(0, '')
127 127 return p
128 128
129 129 @LazyProperty
130 130 def _paths(self):
131 131 return self._dir_paths + self._file_paths
132 132
133 133 @LazyProperty
134 134 def short_id(self):
135 135 return self.raw_id[:12]
136 136
137 137 @LazyProperty
138 138 def parents(self):
139 139 """
140 140 Returns list of parents changesets.
141 141 """
142 142 return [self.repository.get_changeset(parent.rev())
143 143 for parent in self._ctx.parents() if parent.rev() >= 0]
144 144
145 145 @LazyProperty
146 146 def children(self):
147 147 """
148 148 Returns list of children changesets.
149 149 """
150 150 return [self.repository.get_changeset(child.rev())
151 151 for child in self._ctx.children() if child.rev() >= 0]
152 152
153 153 def next(self, branch=None):
154 154 if branch and self.branch != branch:
155 155 raise VCSError('Branch option used on changeset not belonging '
156 156 'to that branch')
157 157
158 158 cs = self
159 159 while True:
160 160 try:
161 161 next_ = cs.repository.revisions.index(cs.raw_id) + 1
162 162 next_rev = cs.repository.revisions[next_]
163 163 except IndexError:
164 164 raise ChangesetDoesNotExistError
165 165 cs = cs.repository.get_changeset(next_rev)
166 166
167 167 if not branch or branch == cs.branch:
168 168 return cs
169 169
170 170 def prev(self, branch=None):
171 171 if branch and self.branch != branch:
172 172 raise VCSError('Branch option used on changeset not belonging '
173 173 'to that branch')
174 174
175 175 cs = self
176 176 while True:
177 177 try:
178 178 prev_ = cs.repository.revisions.index(cs.raw_id) - 1
179 179 if prev_ < 0:
180 180 raise IndexError
181 181 prev_rev = cs.repository.revisions[prev_]
182 182 except IndexError:
183 183 raise ChangesetDoesNotExistError
184 184 cs = cs.repository.get_changeset(prev_rev)
185 185
186 186 if not branch or branch == cs.branch:
187 187 return cs
188 188
189 189 def diff(self):
190 190 # Only used to feed diffstat
191 191 return b''.join(self._ctx.diff())
192 192
193 193 def _get_kind(self, path):
194 194 path = path.rstrip('/')
195 195 if path in self._file_paths:
196 196 return NodeKind.FILE
197 197 elif path in self._dir_paths:
198 198 return NodeKind.DIR
199 199 else:
200 200 raise ChangesetError("Node does not exist at the given path '%s'"
201 201 % (path))
202 202
203 203 def _get_filectx(self, path):
204 204 path = path.rstrip('/')
205 205 if self._get_kind(path) != NodeKind.FILE:
206 206 raise ChangesetError("File does not exist for revision %s at "
207 207 " '%s'" % (self.raw_id, path))
208 208 return self._ctx.filectx(safe_bytes(path))
209 209
210 210 def _extract_submodules(self):
211 211 """
212 212 returns a dictionary with submodule information from substate file
213 213 of hg repository
214 214 """
215 215 return self._ctx.substate
216 216
217 217 def get_file_mode(self, path):
218 218 """
219 219 Returns stat mode of the file at the given ``path``.
220 220 """
221 221 fctx = self._get_filectx(path)
222 222 if b'x' in fctx.flags():
223 223 return 0o100755
224 224 else:
225 225 return 0o100644
226 226
227 227 def get_file_content(self, path):
228 228 """
229 229 Returns content of the file at given ``path``.
230 230 """
231 231 fctx = self._get_filectx(path)
232 232 return fctx.data()
233 233
234 234 def get_file_size(self, path):
235 235 """
236 236 Returns size of the file at given ``path``.
237 237 """
238 238 fctx = self._get_filectx(path)
239 239 return fctx.size()
240 240
241 241 def get_file_changeset(self, path):
242 242 """
243 243 Returns last commit of the file at the given ``path``.
244 244 """
245 245 return self.get_file_history(path, limit=1)[0]
246 246
247 247 def get_file_history(self, path, limit=None):
248 248 """
249 249 Returns history of file as reversed list of ``Changeset`` objects for
250 250 which file at given ``path`` has been modified.
251 251 """
252 252 fctx = self._get_filectx(path)
253 253 hist = []
254 254 cnt = 0
255 255 for cs in reversed([x for x in fctx.filelog()]):
256 256 cnt += 1
257 257 hist.append(mercurial.node.hex(fctx.filectx(cs).node()))
258 258 if limit is not None and cnt == limit:
259 259 break
260 260
261 261 return [self.repository.get_changeset(node) for node in hist]
262 262
263 263 def get_file_annotate(self, path):
264 264 """
265 265 Returns a generator of four element tuples with
266 266 lineno, sha, changeset lazy loader and line
267 267 """
268 268 annotations = self._get_filectx(path).annotate()
269 269 annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
270 270 for i, (fctx, line) in enumerate(annotation_lines):
271 271 sha = ascii_str(fctx.hex())
272 272 yield (i + 1, sha, lambda sha=sha: self.repository.get_changeset(sha), line)
273 273
274 274 def fill_archive(self, stream=None, kind='tgz', prefix=None,
275 275 subrepos=False):
276 276 """
277 277 Fills up given stream.
278 278
279 279 :param stream: file like object.
280 280 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
281 281 Default: ``tgz``.
282 282 :param prefix: name of root directory in archive.
283 283 Default is repository name and changeset's raw_id joined with dash
284 284 (``repo-tip.<KIND>``).
285 285 :param subrepos: include subrepos in this archive.
286 286
287 287 :raise ImproperArchiveTypeError: If given kind is wrong.
288 288 :raise VcsError: If given stream is None
289 289 """
290 290 allowed_kinds = settings.ARCHIVE_SPECS
291 291 if kind not in allowed_kinds:
292 292 raise ImproperArchiveTypeError('Archive kind not supported use one'
293 293 'of %s' % ' '.join(allowed_kinds))
294 294
295 295 if stream is None:
296 296 raise VCSError('You need to pass in a valid stream for filling'
297 297 ' with archival data')
298 298
299 299 if prefix is None:
300 300 prefix = '%s-%s' % (self.repository.name, self.short_id)
301 301 elif prefix.startswith('/'):
302 302 raise VCSError("Prefix cannot start with leading slash")
303 303 elif prefix.strip() == '':
304 304 raise VCSError("Prefix cannot be empty")
305 305
306 306 mercurial.archival.archive(self.repository._repo, stream, ascii_bytes(self.raw_id),
307 307 safe_bytes(kind), prefix=safe_bytes(prefix), subrepos=subrepos)
308 308
309 309 def get_nodes(self, path):
310 310 """
311 311 Returns combined ``DirNode`` and ``FileNode`` objects list representing
312 312 state of changeset at the given ``path``. If node at the given ``path``
313 313 is not instance of ``DirNode``, ChangesetError would be raised.
314 314 """
315 315
316 316 if self._get_kind(path) != NodeKind.DIR:
317 317 raise ChangesetError("Directory does not exist for revision %s at "
318 318 " '%s'" % (self.revision, path))
319 319 path = path.rstrip('/')
320 320 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
321 321 if os.path.dirname(f) == path]
322 322 dirs = path == '' and '' or [d for d in self._dir_paths
323 323 if d and posixpath.dirname(d) == path]
324 324 dirnodes = [DirNode(d, changeset=self) for d in dirs
325 325 if os.path.dirname(d) == path]
326 326
327 327 als = self.repository.alias
328 328 for k, vals in self._extract_submodules().items():
329 329 #vals = url,rev,type
330 330 loc = vals[0]
331 331 cs = vals[1]
332 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
332 dirnodes.append(SubModuleNode(safe_str(k), url=safe_str(loc), changeset=cs,
333 333 alias=als))
334 334 nodes = dirnodes + filenodes
335 335 for node in nodes:
336 336 self.nodes[node.path] = node
337 337 nodes.sort()
338 338 return nodes
339 339
340 340 def get_node(self, path):
341 341 """
342 342 Returns ``Node`` object from the given ``path``. If there is no node at
343 343 the given ``path``, ``ChangesetError`` would be raised.
344 344 """
345 345 path = path.rstrip('/')
346 346 if path not in self.nodes:
347 347 if path in self._file_paths:
348 348 node = FileNode(path, changeset=self)
349 349 elif path in self._dir_paths or path in self._dir_paths:
350 350 if path == '':
351 351 node = RootNode(changeset=self)
352 352 else:
353 353 node = DirNode(path, changeset=self)
354 354 else:
355 355 raise NodeDoesNotExistError("There is no file nor directory "
356 356 "at the given path: '%s' at revision %s"
357 357 % (path, self.short_id))
358 358 # cache node
359 359 self.nodes[path] = node
360 360 return self.nodes[path]
361 361
362 362 @LazyProperty
363 363 def affected_files(self):
364 364 """
365 365 Gets a fast accessible file changes for given changeset
366 366 """
367 367 return self._ctx.files()
368 368
369 369 @property
370 370 def added(self):
371 371 """
372 372 Returns list of added ``FileNode`` objects.
373 373 """
374 374 return AddedFileNodesGenerator([safe_str(n) for n in self.status.added], self)
375 375
376 376 @property
377 377 def changed(self):
378 378 """
379 379 Returns list of modified ``FileNode`` objects.
380 380 """
381 381 return ChangedFileNodesGenerator([safe_str(n) for n in self.status.modified], self)
382 382
383 383 @property
384 384 def removed(self):
385 385 """
386 386 Returns list of removed ``FileNode`` objects.
387 387 """
388 388 return RemovedFileNodesGenerator([safe_str(n) for n in self.status.removed], self)
389 389
390 390 @LazyProperty
391 391 def extra(self):
392 392 return self._ctx.extra()
@@ -1,618 +1,618 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.hg.repository
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Mercurial repository implementation.
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12 import datetime
13 13 import logging
14 14 import os
15 15 import time
16 16 import urllib.error
17 17 import urllib.parse
18 18 import urllib.request
19 19 from collections import OrderedDict
20 20
21 21 import mercurial.commands
22 22 import mercurial.error
23 23 import mercurial.exchange
24 24 import mercurial.hg
25 25 import mercurial.hgweb
26 26 import mercurial.httppeer
27 27 import mercurial.localrepo
28 28 import mercurial.match
29 29 import mercurial.mdiff
30 30 import mercurial.node
31 31 import mercurial.patch
32 32 import mercurial.scmutil
33 33 import mercurial.sshpeer
34 34 import mercurial.tags
35 35 import mercurial.ui
36 36 import mercurial.url
37 37 import mercurial.util
38 38
39 39 from kallithea.lib.vcs.backends.base import BaseRepository, CollectionGenerator
40 40 from kallithea.lib.vcs.exceptions import (BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
41 41 TagDoesNotExistError, VCSError)
42 42 from kallithea.lib.vcs.utils import ascii_str, author_email, author_name, date_fromtimestamp, makedate, safe_bytes, safe_str
43 43 from kallithea.lib.vcs.utils.lazy import LazyProperty
44 44 from kallithea.lib.vcs.utils.paths import abspath
45 45
46 46 from .changeset import MercurialChangeset
47 47 from .inmemory import MercurialInMemoryChangeset
48 48 from .workdir import MercurialWorkdir
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class MercurialRepository(BaseRepository):
55 55 """
56 56 Mercurial repository backend
57 57 """
58 58 DEFAULT_BRANCH_NAME = 'default'
59 59 scm = 'hg'
60 60
61 61 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
62 62 update_after_clone=False):
63 63 """
64 64 Raises RepositoryError if repository could not be find at the given
65 65 ``repo_path``.
66 66
67 67 :param repo_path: local path of the repository
68 68 :param create=False: if set to True, would try to create repository if
69 69 it does not exist rather than raising exception
70 70 :param baseui=None: user data
71 71 :param src_url=None: would try to clone repository from given location
72 72 :param update_after_clone=False: sets update of working copy after
73 73 making a clone
74 74 """
75 75
76 76 if not isinstance(repo_path, str):
77 77 raise VCSError('Mercurial backend requires repository path to '
78 78 'be instance of <str> got %s instead' %
79 79 type(repo_path))
80 80 self.path = abspath(repo_path)
81 81 self.baseui = baseui or mercurial.ui.ui()
82 82 # We've set path and ui, now we can set _repo itself
83 83 self._repo = self._get_repo(create, src_url, update_after_clone)
84 84
85 85 @property
86 86 def _empty(self):
87 87 """
88 88 Checks if repository is empty ie. without any changesets
89 89 """
90 90 # TODO: Following raises errors when using InMemoryChangeset...
91 91 # return len(self._repo.changelog) == 0
92 92 return len(self.revisions) == 0
93 93
94 94 @LazyProperty
95 95 def revisions(self):
96 96 """
97 97 Returns list of revisions' ids, in ascending order. Being lazy
98 98 attribute allows external tools to inject shas from cache.
99 99 """
100 100 return self._get_all_revisions()
101 101
102 102 @LazyProperty
103 103 def name(self):
104 104 return os.path.basename(self.path)
105 105
106 106 @LazyProperty
107 107 def branches(self):
108 108 return self._get_branches()
109 109
110 110 @LazyProperty
111 111 def closed_branches(self):
112 112 return self._get_branches(normal=False, closed=True)
113 113
114 114 @LazyProperty
115 115 def allbranches(self):
116 116 """
117 117 List all branches, including closed branches.
118 118 """
119 119 return self._get_branches(closed=True)
120 120
121 121 def _get_branches(self, normal=True, closed=False):
122 122 """
123 123 Gets branches for this repository
124 124 Returns only not closed branches by default
125 125
126 126 :param closed: return also closed branches for mercurial
127 127 :param normal: return also normal branches
128 128 """
129 129
130 130 if self._empty:
131 131 return {}
132 132
133 133 bt = OrderedDict()
134 134 for bn, _heads, node, isclosed in sorted(self._repo.branchmap().iterbranches()):
135 135 if isclosed:
136 136 if closed:
137 137 bt[safe_str(bn)] = ascii_str(mercurial.node.hex(node))
138 138 else:
139 139 if normal:
140 140 bt[safe_str(bn)] = ascii_str(mercurial.node.hex(node))
141 141 return bt
142 142
143 143 @LazyProperty
144 144 def tags(self):
145 145 """
146 146 Gets tags for this repository
147 147 """
148 148 return self._get_tags()
149 149
150 150 def _get_tags(self):
151 151 if self._empty:
152 152 return {}
153 153
154 154 return OrderedDict(sorted(
155 155 ((safe_str(n), ascii_str(mercurial.node.hex(h))) for n, h in self._repo.tags().items()),
156 156 reverse=True,
157 157 key=lambda x: x[0], # sort by name
158 158 ))
159 159
160 160 def tag(self, name, user, revision=None, message=None, date=None,
161 161 **kwargs):
162 162 """
163 163 Creates and returns a tag for the given ``revision``.
164 164
165 165 :param name: name for new tag
166 166 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
167 167 :param revision: changeset id for which new tag would be created
168 168 :param message: message of the tag's commit
169 169 :param date: date of tag's commit
170 170
171 171 :raises TagAlreadyExistError: if tag with same name already exists
172 172 """
173 173 if name in self.tags:
174 174 raise TagAlreadyExistError("Tag %s already exists" % name)
175 175 changeset = self.get_changeset(revision)
176 176 local = kwargs.setdefault('local', False)
177 177
178 178 if message is None:
179 179 message = "Added tag %s for changeset %s" % (name,
180 180 changeset.short_id)
181 181
182 182 if date is None:
183 183 date = safe_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S'))
184 184
185 185 try:
186 186 mercurial.tags.tag(self._repo, safe_bytes(name), changeset._ctx.node(), safe_bytes(message), local, safe_bytes(user), date)
187 187 except mercurial.error.Abort as e:
188 188 raise RepositoryError(e.args[0])
189 189
190 190 # Reinitialize tags
191 191 self.tags = self._get_tags()
192 192 tag_id = self.tags[name]
193 193
194 194 return self.get_changeset(revision=tag_id)
195 195
196 196 def remove_tag(self, name, user, message=None, date=None):
197 197 """
198 198 Removes tag with the given ``name``.
199 199
200 200 :param name: name of the tag to be removed
201 201 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
202 202 :param message: message of the tag's removal commit
203 203 :param date: date of tag's removal commit
204 204
205 205 :raises TagDoesNotExistError: if tag with given name does not exists
206 206 """
207 207 if name not in self.tags:
208 208 raise TagDoesNotExistError("Tag %s does not exist" % name)
209 209 if message is None:
210 210 message = "Removed tag %s" % name
211 211 if date is None:
212 212 date = safe_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S'))
213 213 local = False
214 214
215 215 try:
216 216 mercurial.tags.tag(self._repo, safe_bytes(name), mercurial.commands.nullid, safe_bytes(message), local, safe_bytes(user), date)
217 217 self.tags = self._get_tags()
218 218 except mercurial.error.Abort as e:
219 219 raise RepositoryError(e.args[0])
220 220
221 221 @LazyProperty
222 222 def bookmarks(self):
223 223 """
224 224 Gets bookmarks for this repository
225 225 """
226 226 return self._get_bookmarks()
227 227
228 228 def _get_bookmarks(self):
229 229 if self._empty:
230 230 return {}
231 231
232 232 return OrderedDict(sorted(
233 ((safe_str(n), ascii_str(h)) for n, h in self._repo._bookmarks.items()),
233 ((safe_str(n), ascii_str(mercurial.node.hex(h))) for n, h in self._repo._bookmarks.items()),
234 234 reverse=True,
235 235 key=lambda x: x[0], # sort by name
236 236 ))
237 237
238 238 def _get_all_revisions(self):
239 239 return [ascii_str(self._repo[x].hex()) for x in self._repo.filtered(b'visible').changelog.revs()]
240 240
241 241 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
242 242 context=3):
243 243 """
244 244 Returns (git like) *diff*, as plain text. Shows changes introduced by
245 245 ``rev2`` since ``rev1``.
246 246
247 247 :param rev1: Entry point from which diff is shown. Can be
248 248 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
249 249 the changes since empty state of the repository until ``rev2``
250 250 :param rev2: Until which revision changes should be shown.
251 251 :param ignore_whitespace: If set to ``True``, would not show whitespace
252 252 changes. Defaults to ``False``.
253 253 :param context: How many lines before/after changed lines should be
254 254 shown. Defaults to ``3``. If negative value is passed-in, it will be
255 255 set to ``0`` instead.
256 256 """
257 257
258 258 # Negative context values make no sense, and will result in
259 259 # errors. Ensure this does not happen.
260 260 if context < 0:
261 261 context = 0
262 262
263 263 if hasattr(rev1, 'raw_id'):
264 264 rev1 = getattr(rev1, 'raw_id')
265 265
266 266 if hasattr(rev2, 'raw_id'):
267 267 rev2 = getattr(rev2, 'raw_id')
268 268
269 269 # Check if given revisions are present at repository (may raise
270 270 # ChangesetDoesNotExistError)
271 271 if rev1 != self.EMPTY_CHANGESET:
272 272 self.get_changeset(rev1)
273 273 self.get_changeset(rev2)
274 274 if path:
275 275 file_filter = mercurial.match.exact([safe_bytes(path)])
276 276 else:
277 277 file_filter = None
278 278
279 279 return b''.join(mercurial.patch.diff(self._repo, rev1, rev2, match=file_filter,
280 280 opts=mercurial.mdiff.diffopts(git=True,
281 281 showfunc=True,
282 282 ignorews=ignore_whitespace,
283 283 context=context)))
284 284
285 285 @classmethod
286 286 def _check_url(cls, url, repoui=None):
287 287 """
288 288 Function will check given url and try to verify if it's a valid
289 289 link. Sometimes it may happened that mercurial will issue basic
290 290 auth request that can cause whole API to hang when used from python
291 291 or other external calls.
292 292
293 293 On failures it'll raise urllib2.HTTPError, exception is also thrown
294 294 when the return code is non 200
295 295 """
296 296 # check first if it's not an local url
297 297 url = safe_bytes(url)
298 298 if os.path.isdir(url) or url.startswith(b'file:'):
299 299 return True
300 300
301 301 if url.startswith(b'ssh:'):
302 302 # in case of invalid uri or authentication issues, sshpeer will
303 303 # throw an exception.
304 304 mercurial.sshpeer.instance(repoui or mercurial.ui.ui(), url, False).lookup(b'tip')
305 305 return True
306 306
307 307 url_prefix = None
308 308 if b'+' in url[:url.find(b'://')]:
309 309 url_prefix, url = url.split(b'+', 1)
310 310
311 311 handlers = []
312 312 url_obj = mercurial.util.url(url)
313 313 test_uri, authinfo = url_obj.authinfo()
314 314 url_obj.passwd = b'*****'
315 315 cleaned_uri = str(url_obj)
316 316
317 317 if authinfo:
318 318 # create a password manager
319 319 passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
320 320 passmgr.add_password(*authinfo)
321 321
322 322 handlers.extend((mercurial.url.httpbasicauthhandler(passmgr),
323 323 mercurial.url.httpdigestauthhandler(passmgr)))
324 324
325 325 o = urllib.request.build_opener(*handlers)
326 326 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
327 327 ('Accept', 'application/mercurial-0.1')]
328 328
329 329 req = urllib.request.Request(
330 330 "%s?%s" % (
331 331 test_uri,
332 332 urllib.parse.urlencode({
333 333 'cmd': 'between',
334 334 'pairs': "%s-%s" % ('0' * 40, '0' * 40),
335 335 })
336 336 ))
337 337
338 338 try:
339 339 resp = o.open(req)
340 340 if resp.code != 200:
341 341 raise Exception('Return Code is not 200')
342 342 except Exception as e:
343 343 # means it cannot be cloned
344 344 raise urllib.error.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
345 345
346 346 if not url_prefix: # skip svn+http://... (and git+... too)
347 347 # now check if it's a proper hg repo
348 348 try:
349 349 mercurial.httppeer.instance(repoui or mercurial.ui.ui(), url, False).lookup(b'tip')
350 350 except Exception as e:
351 351 raise urllib.error.URLError(
352 352 "url [%s] does not look like an hg repo org_exc: %s"
353 353 % (cleaned_uri, e))
354 354
355 355 return True
356 356
357 357 def _get_repo(self, create, src_url=None, update_after_clone=False):
358 358 """
359 359 Function will check for mercurial repository in given path and return
360 360 a localrepo object. If there is no repository in that path it will
361 361 raise an exception unless ``create`` parameter is set to True - in
362 362 that case repository would be created and returned.
363 363 If ``src_url`` is given, would try to clone repository from the
364 364 location at given clone_point. Additionally it'll make update to
365 365 working copy accordingly to ``update_after_clone`` flag
366 366 """
367 367 try:
368 368 if src_url:
369 369 url = safe_bytes(self._get_url(src_url))
370 370 opts = {}
371 371 if not update_after_clone:
372 372 opts.update({'noupdate': True})
373 373 MercurialRepository._check_url(url, self.baseui)
374 374 mercurial.commands.clone(self.baseui, url, safe_bytes(self.path), **opts)
375 375
376 376 # Don't try to create if we've already cloned repo
377 377 create = False
378 378 return mercurial.localrepo.instance(self.baseui, safe_bytes(self.path), create=create)
379 379 except (mercurial.error.Abort, mercurial.error.RepoError) as err:
380 380 if create:
381 381 msg = "Cannot create repository at %s. Original error was %s" \
382 382 % (self.name, err)
383 383 else:
384 384 msg = "Not valid repository at %s. Original error was %s" \
385 385 % (self.name, err)
386 386 raise RepositoryError(msg)
387 387
388 388 @LazyProperty
389 389 def in_memory_changeset(self):
390 390 return MercurialInMemoryChangeset(self)
391 391
392 392 @LazyProperty
393 393 def description(self):
394 394 _desc = self._repo.ui.config(b'web', b'description', None, untrusted=True)
395 395 return safe_str(_desc or b'unknown')
396 396
397 397 @LazyProperty
398 398 def contact(self):
399 399 return safe_str(mercurial.hgweb.common.get_contact(self._repo.ui.config)
400 400 or b'Unknown')
401 401
402 402 @LazyProperty
403 403 def last_change(self):
404 404 """
405 405 Returns last change made on this repository as datetime object
406 406 """
407 407 return date_fromtimestamp(self._get_mtime(), makedate()[1])
408 408
409 409 def _get_mtime(self):
410 410 try:
411 411 return time.mktime(self.get_changeset().date.timetuple())
412 412 except RepositoryError:
413 413 # fallback to filesystem
414 414 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
415 415 st_path = os.path.join(self.path, '.hg', "store")
416 416 if os.path.exists(cl_path):
417 417 return os.stat(cl_path).st_mtime
418 418 else:
419 419 return os.stat(st_path).st_mtime
420 420
421 421 def _get_revision(self, revision):
422 422 """
423 423 Given any revision identifier, returns a 40 char string with revision hash.
424 424
425 425 :param revision: str or int or None
426 426 """
427 427 if self._empty:
428 428 raise EmptyRepositoryError("There are no changesets yet")
429 429
430 430 if revision in [-1, None]:
431 431 revision = b'tip'
432 432 elif isinstance(revision, str):
433 433 revision = safe_bytes(revision)
434 434
435 435 try:
436 436 if isinstance(revision, int):
437 437 return ascii_str(self._repo[revision].hex())
438 438 return ascii_str(mercurial.scmutil.revsymbol(self._repo, revision).hex())
439 439 except (IndexError, ValueError, mercurial.error.RepoLookupError, TypeError):
440 440 msg = "Revision %r does not exist for %s" % (safe_str(revision), self.name)
441 441 raise ChangesetDoesNotExistError(msg)
442 442 except (LookupError, ):
443 443 msg = "Ambiguous identifier `%s` for %s" % (safe_str(revision), self.name)
444 444 raise ChangesetDoesNotExistError(msg)
445 445
446 446 def get_ref_revision(self, ref_type, ref_name):
447 447 """
448 448 Returns revision number for the given reference.
449 449 """
450 450 if ref_type == 'rev' and not ref_name.strip('0'):
451 451 return self.EMPTY_CHANGESET
452 452 # lookup up the exact node id
453 453 _revset_predicates = {
454 454 'branch': 'branch',
455 455 'book': 'bookmark',
456 456 'tag': 'tag',
457 457 'rev': 'id',
458 458 }
459 459 # avoid expensive branch(x) iteration over whole repo
460 460 rev_spec = "%%s & %s(%%s)" % _revset_predicates[ref_type]
461 461 try:
462 462 revs = self._repo.revs(rev_spec, ref_name, ref_name)
463 463 except LookupError:
464 464 msg = "Ambiguous identifier %s:%s for %s" % (ref_type, ref_name, self.name)
465 465 raise ChangesetDoesNotExistError(msg)
466 466 except mercurial.error.RepoLookupError:
467 467 msg = "Revision %s:%s does not exist for %s" % (ref_type, ref_name, self.name)
468 468 raise ChangesetDoesNotExistError(msg)
469 469 if revs:
470 470 revision = revs.last()
471 471 else:
472 472 # TODO: just report 'not found'?
473 473 revision = ref_name
474 474
475 475 return self._get_revision(revision)
476 476
477 477 def _get_archives(self, archive_name='tip'):
478 478 allowed = self.baseui.configlist(b"web", b"allow_archive",
479 479 untrusted=True)
480 480 for name, ext in [(b'zip', '.zip'), (b'gz', '.tar.gz'), (b'bz2', '.tar.bz2')]:
481 481 if name in allowed or self._repo.ui.configbool(b"web",
482 482 b"allow" + name,
483 483 untrusted=True):
484 484 yield {"type": safe_str(name), "extension": ext, "node": archive_name}
485 485
486 486 def _get_url(self, url):
487 487 """
488 488 Returns normalized url. If schema is not given, fall back to
489 489 filesystem (``file:///``) schema.
490 490 """
491 491 if url != 'default' and '://' not in url:
492 492 url = "file:" + urllib.request.pathname2url(url)
493 493 return url
494 494
495 495 def get_changeset(self, revision=None):
496 496 """
497 497 Returns ``MercurialChangeset`` object representing repository's
498 498 changeset at the given ``revision``.
499 499 """
500 500 return MercurialChangeset(repository=self, revision=self._get_revision(revision))
501 501
502 502 def get_changesets(self, start=None, end=None, start_date=None,
503 503 end_date=None, branch_name=None, reverse=False, max_revisions=None):
504 504 """
505 505 Returns iterator of ``MercurialChangeset`` objects from start to end
506 506 (both are inclusive)
507 507
508 508 :param start: None, str, int or mercurial lookup format
509 509 :param end: None, str, int or mercurial lookup format
510 510 :param start_date:
511 511 :param end_date:
512 512 :param branch_name:
513 513 :param reversed: return changesets in reversed order
514 514 """
515 515 start_raw_id = self._get_revision(start)
516 516 start_pos = None if start is None else self.revisions.index(start_raw_id)
517 517 end_raw_id = self._get_revision(end)
518 518 end_pos = None if end is None else self.revisions.index(end_raw_id)
519 519
520 520 if start_pos is not None and end_pos is not None and start_pos > end_pos:
521 521 raise RepositoryError("Start revision '%s' cannot be "
522 522 "after end revision '%s'" % (start, end))
523 523
524 524 if branch_name and branch_name not in self.allbranches:
525 525 msg = "Branch %r not found in %s" % (branch_name, self.name)
526 526 raise BranchDoesNotExistError(msg)
527 527 if end_pos is not None:
528 528 end_pos += 1
529 529 # filter branches
530 530 filter_ = []
531 531 if branch_name:
532 532 filter_.append(b'branch("%s")' % safe_bytes(branch_name))
533 533 if start_date:
534 534 filter_.append(b'date(">%s")' % safe_bytes(str(start_date)))
535 535 if end_date:
536 536 filter_.append(b'date("<%s")' % safe_bytes(str(end_date)))
537 537 if filter_ or max_revisions:
538 538 if filter_:
539 539 revspec = b' and '.join(filter_)
540 540 else:
541 541 revspec = b'all()'
542 542 if max_revisions:
543 543 revspec = b'limit(%s, %d)' % (revspec, max_revisions)
544 544 revisions = mercurial.scmutil.revrange(self._repo, [revspec])
545 545 else:
546 546 revisions = self.revisions
547 547
548 548 # this is very much a hack to turn this into a list; a better solution
549 549 # would be to get rid of this function entirely and use revsets
550 550 revs = list(revisions)[start_pos:end_pos]
551 551 if reverse:
552 552 revs.reverse()
553 553
554 554 return CollectionGenerator(self, revs)
555 555
556 556 def pull(self, url):
557 557 """
558 558 Tries to pull changes from external location.
559 559 """
560 560 other = mercurial.hg.peer(self._repo, {}, safe_bytes(self._get_url(url)))
561 561 try:
562 562 mercurial.exchange.pull(self._repo, other, heads=None, force=None)
563 563 except mercurial.error.Abort as err:
564 564 # Propagate error but with vcs's type
565 565 raise RepositoryError(str(err))
566 566
567 567 @LazyProperty
568 568 def workdir(self):
569 569 """
570 570 Returns ``Workdir`` instance for this repository.
571 571 """
572 572 return MercurialWorkdir(self)
573 573
574 574 def get_config_value(self, section, name=None, config_file=None):
575 575 """
576 576 Returns configuration value for a given [``section``] and ``name``.
577 577
578 578 :param section: Section we want to retrieve value from
579 579 :param name: Name of configuration we want to retrieve
580 580 :param config_file: A path to file which should be used to retrieve
581 581 configuration from (might also be a list of file paths)
582 582 """
583 583 if config_file is None:
584 584 config_file = []
585 585 elif isinstance(config_file, str):
586 586 config_file = [config_file]
587 587
588 588 config = self._repo.ui
589 589 if config_file:
590 590 config = mercurial.ui.ui()
591 591 for path in config_file:
592 592 config.readconfig(safe_bytes(path))
593 593 value = config.config(safe_bytes(section), safe_bytes(name))
594 594 return value if value is None else safe_str(value)
595 595
596 596 def get_user_name(self, config_file=None):
597 597 """
598 598 Returns user's name from global configuration file.
599 599
600 600 :param config_file: A path to file which should be used to retrieve
601 601 configuration from (might also be a list of file paths)
602 602 """
603 603 username = self.get_config_value('ui', 'username', config_file=config_file)
604 604 if username:
605 605 return author_name(username)
606 606 return None
607 607
608 608 def get_user_email(self, config_file=None):
609 609 """
610 610 Returns user's email from global configuration file.
611 611
612 612 :param config_file: A path to file which should be used to retrieve
613 613 configuration from (might also be a list of file paths)
614 614 """
615 615 username = self.get_config_value('ui', 'username', config_file=config_file)
616 616 if username:
617 617 return author_email(username)
618 618 return None
@@ -1,606 +1,606 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.nodes
4 4 ~~~~~~~~~
5 5
6 6 Module holding everything related to vcs nodes.
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12 import functools
13 13 import mimetypes
14 14 import posixpath
15 15 import stat
16 16
17 17 from kallithea.lib.vcs.backends.base import EmptyChangeset
18 18 from kallithea.lib.vcs.exceptions import NodeError, RemovedFileNodeError
19 19 from kallithea.lib.vcs.utils import safe_bytes, safe_str
20 20 from kallithea.lib.vcs.utils.lazy import LazyProperty
21 21
22 22
23 23 class NodeKind:
24 24 SUBMODULE = -1
25 25 DIR = 1
26 26 FILE = 2
27 27
28 28
29 29 class NodeState:
30 30 ADDED = 'added'
31 31 CHANGED = 'changed'
32 32 NOT_CHANGED = 'not changed'
33 33 REMOVED = 'removed'
34 34
35 35
36 36 class NodeGeneratorBase(object):
37 37 """
38 38 Base class for removed added and changed filenodes, it's a lazy generator
39 39 class that will create filenodes only on iteration or call
40 40
41 41 The len method doesn't need to create filenodes at all
42 42 """
43 43
44 44 def __init__(self, current_paths, cs):
45 45 self.cs = cs
46 46 self.current_paths = current_paths
47 47
48 48 def __getitem__(self, key):
49 49 assert isinstance(key, slice), key
50 50 for p in self.current_paths[key]:
51 51 yield self.cs.get_node(p)
52 52
53 53 def __len__(self):
54 54 return len(self.current_paths)
55 55
56 56 def __iter__(self):
57 57 for p in self.current_paths:
58 58 yield self.cs.get_node(p)
59 59
60 60
61 61 class AddedFileNodesGenerator(NodeGeneratorBase):
62 62 """
63 63 Class holding Added files for current changeset
64 64 """
65 65 pass
66 66
67 67
68 68 class ChangedFileNodesGenerator(NodeGeneratorBase):
69 69 """
70 70 Class holding Changed files for current changeset
71 71 """
72 72 pass
73 73
74 74
75 75 class RemovedFileNodesGenerator(NodeGeneratorBase):
76 76 """
77 77 Class holding removed files for current changeset
78 78 """
79 79 def __iter__(self):
80 80 for p in self.current_paths:
81 81 yield RemovedFileNode(path=p)
82 82
83 83 def __getitem__(self, key):
84 84 assert isinstance(key, slice), key
85 85 for p in self.current_paths[key]:
86 86 yield RemovedFileNode(path=p)
87 87
88 88
89 89 @functools.total_ordering
90 90 class Node(object):
91 91 """
92 92 Simplest class representing file or directory on repository. SCM backends
93 93 should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
94 94 directly.
95 95
96 96 Node's ``path`` cannot start with slash as we operate on *relative* paths
97 97 only. Moreover, every single node is identified by the ``path`` attribute,
98 98 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
99 99 """
100 100
101 101 def __init__(self, path, kind):
102 102 if path.startswith('/'):
103 103 raise NodeError("Cannot initialize Node objects with slash at "
104 104 "the beginning as only relative paths are supported")
105 105 self.path = path.rstrip('/')
106 106 if path == '' and kind != NodeKind.DIR:
107 107 raise NodeError("Only DirNode and its subclasses may be "
108 108 "initialized with empty path")
109 109 self.kind = kind
110 110 #self.dirs, self.files = [], []
111 111 if self.is_root() and not self.is_dir():
112 112 raise NodeError("Root node cannot be FILE kind")
113 113
114 114 @LazyProperty
115 115 def parent(self):
116 116 parent_path = self.get_parent_path()
117 117 if parent_path:
118 118 if self.changeset:
119 119 return self.changeset.get_node(parent_path)
120 120 return DirNode(parent_path)
121 121 return None
122 122
123 123 @LazyProperty
124 124 def name(self):
125 125 """
126 126 Returns name of the node so if its path
127 127 then only last part is returned.
128 128 """
129 129 return self.path.rstrip('/').split('/')[-1]
130 130
131 131 def __eq__(self, other):
132 132 if type(self) is not type(other):
133 133 return False
134 134 if self.kind != other.kind:
135 135 return False
136 136 if self.path != other.path:
137 137 return False
138 138
139 139 def __lt__(self, other):
140 140 if self.kind < other.kind:
141 141 return True
142 142 if self.kind > other.kind:
143 143 return False
144 144 if self.path < other.path:
145 145 return True
146 146 if self.path > other.path:
147 147 return False
148 148
149 149 def __repr__(self):
150 150 return '<%s %r>' % (self.__class__.__name__, self.path)
151 151
152 152 def get_parent_path(self):
153 153 """
154 154 Returns node's parent path or empty string if node is root.
155 155 """
156 156 if self.is_root():
157 157 return ''
158 158 return posixpath.dirname(self.path.rstrip('/')) + '/'
159 159
160 160 def is_file(self):
161 161 """
162 162 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
163 163 otherwise.
164 164 """
165 165 return self.kind == NodeKind.FILE
166 166
167 167 def is_dir(self):
168 168 """
169 169 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
170 170 otherwise.
171 171 """
172 172 return self.kind == NodeKind.DIR
173 173
174 174 def is_root(self):
175 175 """
176 176 Returns ``True`` if node is a root node and ``False`` otherwise.
177 177 """
178 178 return self.kind == NodeKind.DIR and self.path == ''
179 179
180 180 def is_submodule(self):
181 181 """
182 182 Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
183 183 otherwise.
184 184 """
185 185 return self.kind == NodeKind.SUBMODULE
186 186
187 187 @LazyProperty
188 188 def added(self):
189 189 return self.state is NodeState.ADDED
190 190
191 191 @LazyProperty
192 192 def changed(self):
193 193 return self.state is NodeState.CHANGED
194 194
195 195 @LazyProperty
196 196 def not_changed(self):
197 197 return self.state is NodeState.NOT_CHANGED
198 198
199 199 @LazyProperty
200 200 def removed(self):
201 201 return self.state is NodeState.REMOVED
202 202
203 203
204 204 class FileNode(Node):
205 205 """
206 206 Class representing file nodes.
207 207
208 208 :attribute: path: path to the node, relative to repository's root
209 209 :attribute: content: if given arbitrary sets content of the file
210 210 :attribute: changeset: if given, first time content is accessed, callback
211 211 :attribute: mode: octal stat mode for a node. Default is 0100644.
212 212 """
213 213
214 214 def __init__(self, path, content=None, changeset=None, mode=None):
215 215 """
216 216 Only one of ``content`` and ``changeset`` may be given. Passing both
217 217 would raise ``NodeError`` exception.
218 218
219 219 :param path: relative path to the node
220 220 :param content: content may be passed to constructor
221 221 :param changeset: if given, will use it to lazily fetch content
222 222 :param mode: octal representation of ST_MODE (i.e. 0100644)
223 223 """
224 224
225 225 if content and changeset:
226 226 raise NodeError("Cannot use both content and changeset")
227 227 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
228 228 self.changeset = changeset
229 229 if not isinstance(content, bytes) and content is not None:
230 230 # File content is one thing that inherently must be bytes ... but
231 231 # VCS module tries to be "user friendly" and support unicode ...
232 232 content = safe_bytes(content)
233 233 self._content = content
234 234 self._mode = mode or 0o100644
235 235
236 236 def __eq__(self, other):
237 237 eq = super(FileNode, self).__eq__(other)
238 238 if eq is not None:
239 239 return eq
240 240 return self.content == other.content
241 241
242 242 def __lt__(self, other):
243 243 lt = super(FileNode, self).__lt__(other)
244 244 if lt is not None:
245 245 return lt
246 246 return self.content < other.content
247 247
248 248 @LazyProperty
249 249 def mode(self):
250 250 """
251 251 Returns lazily mode of the FileNode. If ``changeset`` is not set, would
252 252 use value given at initialization or 0100644 (default).
253 253 """
254 254 if self.changeset:
255 255 mode = self.changeset.get_file_mode(self.path)
256 256 else:
257 257 mode = self._mode
258 258 return mode
259 259
260 260 @property
261 261 def content(self):
262 262 """
263 263 Returns lazily byte content of the FileNode.
264 264 """
265 265 if self.changeset:
266 266 content = self.changeset.get_file_content(self.path)
267 267 else:
268 268 content = self._content
269 269 return content
270 270
271 271 @LazyProperty
272 272 def size(self):
273 273 if self.changeset:
274 274 return self.changeset.get_file_size(self.path)
275 275 raise NodeError("Cannot retrieve size of the file without related "
276 276 "changeset attribute")
277 277
278 278 @LazyProperty
279 279 def message(self):
280 280 if self.changeset:
281 281 return self.last_changeset.message
282 282 raise NodeError("Cannot retrieve message of the file without related "
283 283 "changeset attribute")
284 284
285 285 @LazyProperty
286 286 def last_changeset(self):
287 287 if self.changeset:
288 288 return self.changeset.get_file_changeset(self.path)
289 289 raise NodeError("Cannot retrieve last changeset of the file without "
290 290 "related changeset attribute")
291 291
292 292 def get_mimetype(self):
293 293 """
294 294 Mimetype is calculated based on the file's content.
295 295 """
296 296
297 297 mtype, encoding = mimetypes.guess_type(self.name)
298 298
299 299 if mtype is None:
300 300 if self.is_binary:
301 301 mtype = 'application/octet-stream'
302 302 encoding = None
303 303 else:
304 304 mtype = 'text/plain'
305 305 encoding = None
306 306
307 307 # try with pygments
308 308 from pygments import lexers
309 309 try:
310 310 mt = lexers.get_lexer_for_filename(self.name).mimetypes
311 311 except lexers.ClassNotFound:
312 312 mt = None
313 313
314 314 if mt:
315 315 mtype = mt[0]
316 316
317 317 return mtype, encoding
318 318
319 319 @LazyProperty
320 320 def mimetype(self):
321 321 """
322 322 Wrapper around full mimetype info. It returns only type of fetched
323 323 mimetype without the encoding part. use get_mimetype function to fetch
324 324 full set of (type,encoding)
325 325 """
326 326 return self.get_mimetype()[0]
327 327
328 328 @LazyProperty
329 329 def mimetype_main(self):
330 330 return self.mimetype.split('/')[0]
331 331
332 332 @LazyProperty
333 333 def lexer(self):
334 334 """
335 335 Returns pygment's lexer class. Would try to guess lexer taking file's
336 336 content, name and mimetype.
337 337 """
338 338 from pygments import lexers
339 339 try:
340 340 lexer = lexers.guess_lexer_for_filename(self.name, safe_str(self.content), stripnl=False)
341 341 except lexers.ClassNotFound:
342 342 lexer = lexers.TextLexer(stripnl=False)
343 343 # returns first alias
344 344 return lexer
345 345
346 346 @LazyProperty
347 347 def lexer_alias(self):
348 348 """
349 349 Returns first alias of the lexer guessed for this file.
350 350 """
351 351 return self.lexer.aliases[0]
352 352
353 353 @LazyProperty
354 354 def history(self):
355 355 """
356 356 Returns a list of changeset for this file in which the file was changed
357 357 """
358 358 if self.changeset is None:
359 359 raise NodeError('Unable to get changeset for this FileNode')
360 360 return self.changeset.get_file_history(self.path)
361 361
362 362 @LazyProperty
363 363 def annotate(self):
364 364 """
365 365 Returns a list of three element tuples with lineno,changeset and line
366 366 """
367 367 if self.changeset is None:
368 368 raise NodeError('Unable to get changeset for this FileNode')
369 369 return self.changeset.get_file_annotate(self.path)
370 370
371 371 @LazyProperty
372 372 def state(self):
373 373 if not self.changeset:
374 374 raise NodeError("Cannot check state of the node if it's not "
375 375 "linked with changeset")
376 376 elif self.path in (node.path for node in self.changeset.added):
377 377 return NodeState.ADDED
378 378 elif self.path in (node.path for node in self.changeset.changed):
379 379 return NodeState.CHANGED
380 380 else:
381 381 return NodeState.NOT_CHANGED
382 382
383 383 @property
384 384 def is_binary(self):
385 385 """
386 386 Returns True if file has binary content.
387 387 """
388 388 return b'\0' in self.content
389 389
390 390 def is_browser_compatible_image(self):
391 391 return self.mimetype in [
392 392 "image/gif",
393 393 "image/jpeg",
394 394 "image/png",
395 395 "image/bmp"
396 396 ]
397 397
398 398 @LazyProperty
399 399 def extension(self):
400 400 """Returns filenode extension"""
401 401 return self.name.split('.')[-1]
402 402
403 403 @property
404 404 def is_executable(self):
405 405 """
406 406 Returns ``True`` if file has executable flag turned on.
407 407 """
408 408 return bool(self.mode & stat.S_IXUSR)
409 409
410 410 def __repr__(self):
411 411 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
412 412 getattr(self.changeset, 'short_id', ''))
413 413
414 414
415 415 class RemovedFileNode(FileNode):
416 416 """
417 417 Dummy FileNode class - trying to access any public attribute except path,
418 418 name, kind or state (or methods/attributes checking those two) would raise
419 419 RemovedFileNodeError.
420 420 """
421 421 ALLOWED_ATTRIBUTES = [
422 422 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
423 423 'added', 'changed', 'not_changed', 'removed'
424 424 ]
425 425
426 426 def __init__(self, path):
427 427 """
428 428 :param path: relative path to the node
429 429 """
430 430 super(RemovedFileNode, self).__init__(path=path)
431 431
432 432 def __getattribute__(self, attr):
433 433 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
434 434 return super(RemovedFileNode, self).__getattribute__(attr)
435 435 raise RemovedFileNodeError("Cannot access attribute %s on "
436 436 "RemovedFileNode" % attr)
437 437
438 438 @LazyProperty
439 439 def state(self):
440 440 return NodeState.REMOVED
441 441
442 442
443 443 class DirNode(Node):
444 444 """
445 445 DirNode stores list of files and directories within this node.
446 446 Nodes may be used standalone but within repository context they
447 447 lazily fetch data within same repository's changeset.
448 448 """
449 449
450 450 def __init__(self, path, nodes=(), changeset=None):
451 451 """
452 452 Only one of ``nodes`` and ``changeset`` may be given. Passing both
453 453 would raise ``NodeError`` exception.
454 454
455 455 :param path: relative path to the node
456 456 :param nodes: content may be passed to constructor
457 457 :param changeset: if given, will use it to lazily fetch content
458 458 :param size: always 0 for ``DirNode``
459 459 """
460 460 if nodes and changeset:
461 461 raise NodeError("Cannot use both nodes and changeset")
462 462 super(DirNode, self).__init__(path, NodeKind.DIR)
463 463 self.changeset = changeset
464 464 self._nodes = nodes
465 465
466 466 def __eq__(self, other):
467 467 eq = super(DirNode, self).__eq__(other)
468 468 if eq is not None:
469 469 return eq
470 470 # check without entering each dir
471 471 self_nodes_paths = list(sorted(n.path for n in self.nodes))
472 472 other_nodes_paths = list(sorted(n.path for n in self.nodes))
473 473 return self_nodes_paths == other_nodes_paths
474 474
475 475 def __lt__(self, other):
476 476 lt = super(DirNode, self).__lt__(other)
477 477 if lt is not None:
478 478 return lt
479 479 # check without entering each dir
480 480 self_nodes_paths = list(sorted(n.path for n in self.nodes))
481 481 other_nodes_paths = list(sorted(n.path for n in self.nodes))
482 482 return self_nodes_paths < other_nodes_paths
483 483
484 484 @LazyProperty
485 485 def nodes(self):
486 486 if self.changeset:
487 487 nodes = self.changeset.get_nodes(self.path)
488 488 else:
489 489 nodes = self._nodes
490 490 self._nodes_dict = dict((node.path, node) for node in nodes)
491 491 return sorted(nodes)
492 492
493 493 @LazyProperty
494 494 def files(self):
495 495 return sorted((node for node in self.nodes if node.is_file()))
496 496
497 497 @LazyProperty
498 498 def dirs(self):
499 499 return sorted((node for node in self.nodes if node.is_dir()))
500 500
501 501 def __iter__(self):
502 502 for node in self.nodes:
503 503 yield node
504 504
505 505 def get_node(self, path):
506 506 """
507 507 Returns node from within this particular ``DirNode``, so it is now
508 508 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
509 509 'docs'. In order to access deeper nodes one must fetch nodes between
510 510 them first - this would work::
511 511
512 512 docs = root.get_node('docs')
513 513 docs.get_node('api').get_node('index.rst')
514 514
515 515 :param: path - relative to the current node
516 516
517 517 .. note::
518 518 To access lazily (as in example above) node have to be initialized
519 519 with related changeset object - without it node is out of
520 520 context and may know nothing about anything else than nearest
521 521 (located at same level) nodes.
522 522 """
523 523 try:
524 524 path = path.rstrip('/')
525 525 if path == '':
526 526 raise NodeError("Cannot retrieve node without path")
527 527 self.nodes # access nodes first in order to set _nodes_dict
528 528 paths = path.split('/')
529 529 if len(paths) == 1:
530 530 if not self.is_root():
531 531 path = '/'.join((self.path, paths[0]))
532 532 else:
533 533 path = paths[0]
534 534 return self._nodes_dict[path]
535 535 elif len(paths) > 1:
536 536 if self.changeset is None:
537 537 raise NodeError("Cannot access deeper "
538 538 "nodes without changeset")
539 539 else:
540 540 path1, path2 = paths[0], '/'.join(paths[1:])
541 541 return self.get_node(path1).get_node(path2)
542 542 else:
543 543 raise KeyError
544 544 except KeyError:
545 545 raise NodeError("Node does not exist at %s" % path)
546 546
547 547 @LazyProperty
548 548 def state(self):
549 549 raise NodeError("Cannot access state of DirNode")
550 550
551 551 @LazyProperty
552 552 def size(self):
553 553 size = 0
554 554 for root, dirs, files in self.changeset.walk(self.path):
555 555 for f in files:
556 556 size += f.size
557 557
558 558 return size
559 559
560 560 def __repr__(self):
561 561 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
562 562 getattr(self.changeset, 'short_id', ''))
563 563
564 564
565 565 class RootNode(DirNode):
566 566 """
567 567 DirNode being the root node of the repository.
568 568 """
569 569
570 570 def __init__(self, nodes=(), changeset=None):
571 571 super(RootNode, self).__init__(path='', nodes=nodes,
572 572 changeset=changeset)
573 573
574 574 def __repr__(self):
575 575 return '<%s>' % self.__class__.__name__
576 576
577 577
578 578 class SubModuleNode(Node):
579 579 """
580 580 represents a SubModule of Git or SubRepo of Mercurial
581 581 """
582 582 is_binary = False
583 583 size = 0
584 584
585 585 def __init__(self, name, url, changeset=None, alias=None):
586 586 # Note: Doesn't call Node.__init__!
587 587 self.path = name.rstrip('/')
588 588 self.kind = NodeKind.SUBMODULE
589 589 self.alias = alias
590 590 # we have to use emptyChangeset here since this can point to svn/git/hg
591 591 # submodules we cannot get from repository
592 592 self.changeset = EmptyChangeset(changeset, alias=alias)
593 593 self.url = url
594 594
595 595 def __repr__(self):
596 596 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
597 597 getattr(self.changeset, 'short_id', ''))
598 598
599 599 @LazyProperty
600 600 def name(self):
601 601 """
602 602 Returns name of the node so if its path
603 603 then only last part is returned.
604 604 """
605 605 org = self.path.rstrip('/').rsplit('/', 1)[-1]
606 return '%s @ %s' % (org, self.changeset.short_id)
606 return '%s @ %s' % (org, safe_str(self.changeset.short_id))
General Comments 0
You need to be logged in to leave comments. Login now