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