##// END OF EJS Templates
added alias configuration option for gists....
marcink -
r3850:7a4df261 beta
parent child Browse files
Show More
@@ -1,491 +1,497 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ## PASTE
32 ## PASTE
33 ## nr of threads to spawn
33 ## nr of threads to spawn
34 #threadpool_workers = 5
34 #threadpool_workers = 5
35
35
36 ## max request before thread respawn
36 ## max request before thread respawn
37 #threadpool_max_requests = 10
37 #threadpool_max_requests = 10
38
38
39 ## option to use threads of process
39 ## option to use threads of process
40 #use_threadpool = true
40 #use_threadpool = true
41
41
42 #use = egg:Paste#http
42 #use = egg:Paste#http
43
43
44 ## WAITRESS
44 ## WAITRESS
45 threads = 5
45 threads = 5
46 ## 100GB
46 ## 100GB
47 max_request_body_size = 107374182400
47 max_request_body_size = 107374182400
48 use = egg:waitress#main
48 use = egg:waitress#main
49
49
50 host = 0.0.0.0
50 host = 0.0.0.0
51 port = 5000
51 port = 5000
52
52
53 ## prefix middleware for rc
53 ## prefix middleware for rc
54 #[filter:proxy-prefix]
54 #[filter:proxy-prefix]
55 #use = egg:PasteDeploy#prefix
55 #use = egg:PasteDeploy#prefix
56 #prefix = /<your-prefix>
56 #prefix = /<your-prefix>
57
57
58 [app:main]
58 [app:main]
59 use = egg:rhodecode
59 use = egg:rhodecode
60 ## enable proxy prefix middleware
60 ## enable proxy prefix middleware
61 #filter-with = proxy-prefix
61 #filter-with = proxy-prefix
62
62
63 full_stack = true
63 full_stack = true
64 static_files = true
64 static_files = true
65 ## Optional Languages
65 ## Optional Languages
66 ## en, fr, ja, pt_BR, zh_CN, zh_TW, pl
66 ## en, fr, ja, pt_BR, zh_CN, zh_TW, pl
67 lang = en
67 lang = en
68 cache_dir = %(here)s/data
68 cache_dir = %(here)s/data
69 index_dir = %(here)s/data/index
69 index_dir = %(here)s/data/index
70
70
71 ## perform a full repository scan on each server start, this should be
71 ## perform a full repository scan on each server start, this should be
72 ## set to false after first startup, to allow faster server restarts.
72 ## set to false after first startup, to allow faster server restarts.
73 initial_repo_scan = true
73 initial_repo_scan = true
74
74
75 ## uncomment and set this path to use archive download cache
75 ## uncomment and set this path to use archive download cache
76 #archive_cache_dir = /tmp/tarballcache
76 #archive_cache_dir = /tmp/tarballcache
77
77
78 ## change this to unique ID for security
78 ## change this to unique ID for security
79 app_instance_uuid = rc-production
79 app_instance_uuid = rc-production
80
80
81 ## cut off limit for large diffs (size in bytes)
81 ## cut off limit for large diffs (size in bytes)
82 cut_off_limit = 256000
82 cut_off_limit = 256000
83
83
84 ## use cache version of scm repo everywhere
84 ## use cache version of scm repo everywhere
85 vcs_full_cache = true
85 vcs_full_cache = true
86
86
87 ## force https in RhodeCode, fixes https redirects, assumes it's always https
87 ## force https in RhodeCode, fixes https redirects, assumes it's always https
88 force_https = false
88 force_https = false
89
89
90 ## use Strict-Transport-Security headers
90 ## use Strict-Transport-Security headers
91 use_htsts = false
91 use_htsts = false
92
92
93 ## number of commits stats will parse on each iteration
93 ## number of commits stats will parse on each iteration
94 commit_parse_limit = 25
94 commit_parse_limit = 25
95
95
96 ## number of items displayed in lightweight dashboard before paginating is shown
96 ## number of items displayed in lightweight dashboard before paginating is shown
97 dashboard_items = 100
97 dashboard_items = 100
98
98
99 ## use gravatar service to display avatars
99 ## use gravatar service to display avatars
100 use_gravatar = true
100 use_gravatar = true
101
101
102 ## path to git executable
102 ## path to git executable
103 git_path = git
103 git_path = git
104
104
105 ## git rev filter option, --all is the default filter, if you need to
105 ## git rev filter option, --all is the default filter, if you need to
106 ## hide all refs in changelog switch this to --branches --tags
106 ## hide all refs in changelog switch this to --branches --tags
107 git_rev_filter=--all
107 git_rev_filter=--all
108
108
109 ## RSS feed options
109 ## RSS feed options
110 rss_cut_off_limit = 256000
110 rss_cut_off_limit = 256000
111 rss_items_per_page = 10
111 rss_items_per_page = 10
112 rss_include_diff = false
112 rss_include_diff = false
113
113
114 ## options for showing and identifying changesets
114 ## options for showing and identifying changesets
115 show_sha_length = 12
115 show_sha_length = 12
116 show_revision_number = true
116 show_revision_number = true
117
117
118 ## gist URL alias, used to create nicer urls for gist. This should be an
119 ## url that does rewrites to _admin/gists/<gistid>.
120 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
121 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
122 gist_alias_url =
123
118 ## white list of API enabled controllers. This allows to add list of
124 ## white list of API enabled controllers. This allows to add list of
119 ## controllers to which access will be enabled by api_key. eg: to enable
125 ## controllers to which access will be enabled by api_key. eg: to enable
120 ## api access to raw_files put `FilesController:raw`, to enable access to patches
126 ## api access to raw_files put `FilesController:raw`, to enable access to patches
121 ## add `ChangesetController:changeset_patch`. This list should be "," separated
127 ## add `ChangesetController:changeset_patch`. This list should be "," separated
122 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
128 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
123 api_access_controllers_whitelist =
129 api_access_controllers_whitelist =
124
130
125 ## alternative_gravatar_url allows you to use your own avatar server application
131 ## alternative_gravatar_url allows you to use your own avatar server application
126 ## the following parts of the URL will be replaced
132 ## the following parts of the URL will be replaced
127 ## {email} user email
133 ## {email} user email
128 ## {md5email} md5 hash of the user email (like at gravatar.com)
134 ## {md5email} md5 hash of the user email (like at gravatar.com)
129 ## {size} size of the image that is expected from the server application
135 ## {size} size of the image that is expected from the server application
130 ## {scheme} http/https from RhodeCode server
136 ## {scheme} http/https from RhodeCode server
131 ## {netloc} network location from RhodeCode server
137 ## {netloc} network location from RhodeCode server
132 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
138 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
133 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
139 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
134
140
135
141
136 ## container auth options
142 ## container auth options
137 container_auth_enabled = false
143 container_auth_enabled = false
138 proxypass_auth_enabled = false
144 proxypass_auth_enabled = false
139
145
140 ## default encoding used to convert from and to unicode
146 ## default encoding used to convert from and to unicode
141 ## can be also a comma seperated list of encoding in case of mixed encodings
147 ## can be also a comma seperated list of encoding in case of mixed encodings
142 default_encoding = utf8
148 default_encoding = utf8
143
149
144 ## overwrite schema of clone url
150 ## overwrite schema of clone url
145 ## available vars:
151 ## available vars:
146 ## scheme - http/https
152 ## scheme - http/https
147 ## user - current user
153 ## user - current user
148 ## pass - password
154 ## pass - password
149 ## netloc - network location
155 ## netloc - network location
150 ## path - usually repo_name
156 ## path - usually repo_name
151
157
152 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
158 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
153
159
154 ## issue tracking mapping for commits messages
160 ## issue tracking mapping for commits messages
155 ## comment out issue_pat, issue_server, issue_prefix to enable
161 ## comment out issue_pat, issue_server, issue_prefix to enable
156
162
157 ## pattern to get the issues from commit messages
163 ## pattern to get the issues from commit messages
158 ## default one used here is #<numbers> with a regex passive group for `#`
164 ## default one used here is #<numbers> with a regex passive group for `#`
159 ## {id} will be all groups matched from this pattern
165 ## {id} will be all groups matched from this pattern
160
166
161 issue_pat = (?:\s*#)(\d+)
167 issue_pat = (?:\s*#)(\d+)
162
168
163 ## server url to the issue, each {id} will be replaced with match
169 ## server url to the issue, each {id} will be replaced with match
164 ## fetched from the regex and {repo} is replaced with full repository name
170 ## fetched from the regex and {repo} is replaced with full repository name
165 ## including groups {repo_name} is replaced with just name of repo
171 ## including groups {repo_name} is replaced with just name of repo
166
172
167 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
173 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
168
174
169 ## prefix to add to link to indicate it's an url
175 ## prefix to add to link to indicate it's an url
170 ## #314 will be replaced by <issue_prefix><id>
176 ## #314 will be replaced by <issue_prefix><id>
171
177
172 issue_prefix = #
178 issue_prefix = #
173
179
174 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
180 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
175 ## multiple patterns, to other issues server, wiki or others
181 ## multiple patterns, to other issues server, wiki or others
176 ## below an example how to create a wiki pattern
182 ## below an example how to create a wiki pattern
177 # #wiki-some-id -> https://mywiki.com/some-id
183 # #wiki-some-id -> https://mywiki.com/some-id
178
184
179 #issue_pat_wiki = (?:wiki-)(.+)
185 #issue_pat_wiki = (?:wiki-)(.+)
180 #issue_server_link_wiki = https://mywiki.com/{id}
186 #issue_server_link_wiki = https://mywiki.com/{id}
181 #issue_prefix_wiki = WIKI-
187 #issue_prefix_wiki = WIKI-
182
188
183
189
184 ## instance-id prefix
190 ## instance-id prefix
185 ## a prefix key for this instance used for cache invalidation when running
191 ## a prefix key for this instance used for cache invalidation when running
186 ## multiple instances of rhodecode, make sure it's globally unique for
192 ## multiple instances of rhodecode, make sure it's globally unique for
187 ## all running rhodecode instances. Leave empty if you don't use it
193 ## all running rhodecode instances. Leave empty if you don't use it
188 instance_id =
194 instance_id =
189
195
190 ## alternative return HTTP header for failed authentication. Default HTTP
196 ## alternative return HTTP header for failed authentication. Default HTTP
191 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
197 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
192 ## handling that. Set this variable to 403 to return HTTPForbidden
198 ## handling that. Set this variable to 403 to return HTTPForbidden
193 auth_ret_code =
199 auth_ret_code =
194
200
195 ## locking return code. When repository is locked return this HTTP code. 2XX
201 ## locking return code. When repository is locked return this HTTP code. 2XX
196 ## codes don't break the transactions while 4XX codes do
202 ## codes don't break the transactions while 4XX codes do
197 lock_ret_code = 423
203 lock_ret_code = 423
198
204
199
205
200 ####################################
206 ####################################
201 ### CELERY CONFIG ####
207 ### CELERY CONFIG ####
202 ####################################
208 ####################################
203 use_celery = false
209 use_celery = false
204 broker.host = localhost
210 broker.host = localhost
205 broker.vhost = rabbitmqhost
211 broker.vhost = rabbitmqhost
206 broker.port = 5672
212 broker.port = 5672
207 broker.user = rabbitmq
213 broker.user = rabbitmq
208 broker.password = qweqwe
214 broker.password = qweqwe
209
215
210 celery.imports = rhodecode.lib.celerylib.tasks
216 celery.imports = rhodecode.lib.celerylib.tasks
211
217
212 celery.result.backend = amqp
218 celery.result.backend = amqp
213 celery.result.dburi = amqp://
219 celery.result.dburi = amqp://
214 celery.result.serialier = json
220 celery.result.serialier = json
215
221
216 #celery.send.task.error.emails = true
222 #celery.send.task.error.emails = true
217 #celery.amqp.task.result.expires = 18000
223 #celery.amqp.task.result.expires = 18000
218
224
219 celeryd.concurrency = 2
225 celeryd.concurrency = 2
220 #celeryd.log.file = celeryd.log
226 #celeryd.log.file = celeryd.log
221 celeryd.log.level = debug
227 celeryd.log.level = debug
222 celeryd.max.tasks.per.child = 1
228 celeryd.max.tasks.per.child = 1
223
229
224 ## tasks will never be sent to the queue, but executed locally instead.
230 ## tasks will never be sent to the queue, but executed locally instead.
225 celery.always.eager = false
231 celery.always.eager = false
226
232
227 ####################################
233 ####################################
228 ### BEAKER CACHE ####
234 ### BEAKER CACHE ####
229 ####################################
235 ####################################
230 beaker.cache.data_dir=%(here)s/data/cache/data
236 beaker.cache.data_dir=%(here)s/data/cache/data
231 beaker.cache.lock_dir=%(here)s/data/cache/lock
237 beaker.cache.lock_dir=%(here)s/data/cache/lock
232
238
233 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
239 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
234
240
235 beaker.cache.super_short_term.type=memory
241 beaker.cache.super_short_term.type=memory
236 beaker.cache.super_short_term.expire=10
242 beaker.cache.super_short_term.expire=10
237 beaker.cache.super_short_term.key_length = 256
243 beaker.cache.super_short_term.key_length = 256
238
244
239 beaker.cache.short_term.type=memory
245 beaker.cache.short_term.type=memory
240 beaker.cache.short_term.expire=60
246 beaker.cache.short_term.expire=60
241 beaker.cache.short_term.key_length = 256
247 beaker.cache.short_term.key_length = 256
242
248
243 beaker.cache.long_term.type=memory
249 beaker.cache.long_term.type=memory
244 beaker.cache.long_term.expire=36000
250 beaker.cache.long_term.expire=36000
245 beaker.cache.long_term.key_length = 256
251 beaker.cache.long_term.key_length = 256
246
252
247 beaker.cache.sql_cache_short.type=memory
253 beaker.cache.sql_cache_short.type=memory
248 beaker.cache.sql_cache_short.expire=10
254 beaker.cache.sql_cache_short.expire=10
249 beaker.cache.sql_cache_short.key_length = 256
255 beaker.cache.sql_cache_short.key_length = 256
250
256
251 beaker.cache.sql_cache_med.type=memory
257 beaker.cache.sql_cache_med.type=memory
252 beaker.cache.sql_cache_med.expire=360
258 beaker.cache.sql_cache_med.expire=360
253 beaker.cache.sql_cache_med.key_length = 256
259 beaker.cache.sql_cache_med.key_length = 256
254
260
255 beaker.cache.sql_cache_long.type=file
261 beaker.cache.sql_cache_long.type=file
256 beaker.cache.sql_cache_long.expire=3600
262 beaker.cache.sql_cache_long.expire=3600
257 beaker.cache.sql_cache_long.key_length = 256
263 beaker.cache.sql_cache_long.key_length = 256
258
264
259 ####################################
265 ####################################
260 ### BEAKER SESSION ####
266 ### BEAKER SESSION ####
261 ####################################
267 ####################################
262 ## Type of storage used for the session, current types are
268 ## Type of storage used for the session, current types are
263 ## dbm, file, memcached, database, and memory.
269 ## dbm, file, memcached, database, and memory.
264 ## The storage uses the Container API
270 ## The storage uses the Container API
265 ## that is also used by the cache system.
271 ## that is also used by the cache system.
266
272
267 ## db session ##
273 ## db session ##
268 #beaker.session.type = ext:database
274 #beaker.session.type = ext:database
269 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
275 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
270 #beaker.session.table_name = db_session
276 #beaker.session.table_name = db_session
271
277
272 ## encrypted cookie client side session, good for many instances ##
278 ## encrypted cookie client side session, good for many instances ##
273 #beaker.session.type = cookie
279 #beaker.session.type = cookie
274
280
275 ## file based cookies (default) ##
281 ## file based cookies (default) ##
276 #beaker.session.type = file
282 #beaker.session.type = file
277
283
278
284
279 beaker.session.key = rhodecode
285 beaker.session.key = rhodecode
280 ## secure cookie requires AES python libraries
286 ## secure cookie requires AES python libraries
281 #beaker.session.encrypt_key = <key_for_encryption>
287 #beaker.session.encrypt_key = <key_for_encryption>
282 #beaker.session.validate_key = <validation_key>
288 #beaker.session.validate_key = <validation_key>
283
289
284 ## sets session as invalid if it haven't been accessed for given amount of time
290 ## sets session as invalid if it haven't been accessed for given amount of time
285 beaker.session.timeout = 2592000
291 beaker.session.timeout = 2592000
286 beaker.session.httponly = true
292 beaker.session.httponly = true
287 #beaker.session.cookie_path = /<your-prefix>
293 #beaker.session.cookie_path = /<your-prefix>
288
294
289 ## uncomment for https secure cookie
295 ## uncomment for https secure cookie
290 beaker.session.secure = false
296 beaker.session.secure = false
291
297
292 ## auto save the session to not to use .save()
298 ## auto save the session to not to use .save()
293 beaker.session.auto = False
299 beaker.session.auto = False
294
300
295 ## default cookie expiration time in seconds `true` expire at browser close ##
301 ## default cookie expiration time in seconds `true` expire at browser close ##
296 #beaker.session.cookie_expires = 3600
302 #beaker.session.cookie_expires = 3600
297
303
298
304
299 ############################
305 ############################
300 ## ERROR HANDLING SYSTEMS ##
306 ## ERROR HANDLING SYSTEMS ##
301 ############################
307 ############################
302
308
303 ####################
309 ####################
304 ### [errormator] ###
310 ### [errormator] ###
305 ####################
311 ####################
306
312
307 ## Errormator is tailored to work with RhodeCode, see
313 ## Errormator is tailored to work with RhodeCode, see
308 ## http://errormator.com for details how to obtain an account
314 ## http://errormator.com for details how to obtain an account
309 ## you must install python package `errormator_client` to make it work
315 ## you must install python package `errormator_client` to make it work
310
316
311 ## errormator enabled
317 ## errormator enabled
312 errormator = false
318 errormator = false
313
319
314 errormator.server_url = https://api.errormator.com
320 errormator.server_url = https://api.errormator.com
315 errormator.api_key = YOUR_API_KEY
321 errormator.api_key = YOUR_API_KEY
316
322
317 ## TWEAK AMOUNT OF INFO SENT HERE
323 ## TWEAK AMOUNT OF INFO SENT HERE
318
324
319 ## enables 404 error logging (default False)
325 ## enables 404 error logging (default False)
320 errormator.report_404 = false
326 errormator.report_404 = false
321
327
322 ## time in seconds after request is considered being slow (default 1)
328 ## time in seconds after request is considered being slow (default 1)
323 errormator.slow_request_time = 1
329 errormator.slow_request_time = 1
324
330
325 ## record slow requests in application
331 ## record slow requests in application
326 ## (needs to be enabled for slow datastore recording and time tracking)
332 ## (needs to be enabled for slow datastore recording and time tracking)
327 errormator.slow_requests = true
333 errormator.slow_requests = true
328
334
329 ## enable hooking to application loggers
335 ## enable hooking to application loggers
330 # errormator.logging = true
336 # errormator.logging = true
331
337
332 ## minimum log level for log capture
338 ## minimum log level for log capture
333 # errormator.logging.level = WARNING
339 # errormator.logging.level = WARNING
334
340
335 ## send logs only from erroneous/slow requests
341 ## send logs only from erroneous/slow requests
336 ## (saves API quota for intensive logging)
342 ## (saves API quota for intensive logging)
337 errormator.logging_on_error = false
343 errormator.logging_on_error = false
338
344
339 ## list of additonal keywords that should be grabbed from environ object
345 ## list of additonal keywords that should be grabbed from environ object
340 ## can be string with comma separated list of words in lowercase
346 ## can be string with comma separated list of words in lowercase
341 ## (by default client will always send following info:
347 ## (by default client will always send following info:
342 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
348 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
343 ## start with HTTP* this list be extended with additional keywords here
349 ## start with HTTP* this list be extended with additional keywords here
344 errormator.environ_keys_whitelist =
350 errormator.environ_keys_whitelist =
345
351
346
352
347 ## list of keywords that should be blanked from request object
353 ## list of keywords that should be blanked from request object
348 ## can be string with comma separated list of words in lowercase
354 ## can be string with comma separated list of words in lowercase
349 ## (by default client will always blank keys that contain following words
355 ## (by default client will always blank keys that contain following words
350 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
356 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
351 ## this list be extended with additional keywords set here
357 ## this list be extended with additional keywords set here
352 errormator.request_keys_blacklist =
358 errormator.request_keys_blacklist =
353
359
354
360
355 ## list of namespaces that should be ignores when gathering log entries
361 ## list of namespaces that should be ignores when gathering log entries
356 ## can be string with comma separated list of namespaces
362 ## can be string with comma separated list of namespaces
357 ## (by default the client ignores own entries: errormator_client.client)
363 ## (by default the client ignores own entries: errormator_client.client)
358 errormator.log_namespace_blacklist =
364 errormator.log_namespace_blacklist =
359
365
360
366
361 ################
367 ################
362 ### [sentry] ###
368 ### [sentry] ###
363 ################
369 ################
364
370
365 ## sentry is a alternative open source error aggregator
371 ## sentry is a alternative open source error aggregator
366 ## you must install python packages `sentry` and `raven` to enable
372 ## you must install python packages `sentry` and `raven` to enable
367
373
368 sentry.dsn = YOUR_DNS
374 sentry.dsn = YOUR_DNS
369 sentry.servers =
375 sentry.servers =
370 sentry.name =
376 sentry.name =
371 sentry.key =
377 sentry.key =
372 sentry.public_key =
378 sentry.public_key =
373 sentry.secret_key =
379 sentry.secret_key =
374 sentry.project =
380 sentry.project =
375 sentry.site =
381 sentry.site =
376 sentry.include_paths =
382 sentry.include_paths =
377 sentry.exclude_paths =
383 sentry.exclude_paths =
378
384
379
385
380 ################################################################################
386 ################################################################################
381 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
387 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
382 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
388 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
383 ## execute malicious code after an exception is raised. ##
389 ## execute malicious code after an exception is raised. ##
384 ################################################################################
390 ################################################################################
385 #set debug = false
391 #set debug = false
386
392
387 ##################################
393 ##################################
388 ### LOGVIEW CONFIG ###
394 ### LOGVIEW CONFIG ###
389 ##################################
395 ##################################
390 logview.sqlalchemy = #faa
396 logview.sqlalchemy = #faa
391 logview.pylons.templating = #bfb
397 logview.pylons.templating = #bfb
392 logview.pylons.util = #eee
398 logview.pylons.util = #eee
393
399
394 #########################################################
400 #########################################################
395 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
401 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
396 #########################################################
402 #########################################################
397 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
403 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
398 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
404 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
399 sqlalchemy.db1.echo = false
405 sqlalchemy.db1.echo = false
400 sqlalchemy.db1.pool_recycle = 3600
406 sqlalchemy.db1.pool_recycle = 3600
401 sqlalchemy.db1.convert_unicode = true
407 sqlalchemy.db1.convert_unicode = true
402
408
403 ################################
409 ################################
404 ### LOGGING CONFIGURATION ####
410 ### LOGGING CONFIGURATION ####
405 ################################
411 ################################
406 [loggers]
412 [loggers]
407 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
413 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
408
414
409 [handlers]
415 [handlers]
410 keys = console, console_sql
416 keys = console, console_sql
411
417
412 [formatters]
418 [formatters]
413 keys = generic, color_formatter, color_formatter_sql
419 keys = generic, color_formatter, color_formatter_sql
414
420
415 #############
421 #############
416 ## LOGGERS ##
422 ## LOGGERS ##
417 #############
423 #############
418 [logger_root]
424 [logger_root]
419 level = NOTSET
425 level = NOTSET
420 handlers = console
426 handlers = console
421
427
422 [logger_routes]
428 [logger_routes]
423 level = DEBUG
429 level = DEBUG
424 handlers =
430 handlers =
425 qualname = routes.middleware
431 qualname = routes.middleware
426 ## "level = DEBUG" logs the route matched and routing variables.
432 ## "level = DEBUG" logs the route matched and routing variables.
427 propagate = 1
433 propagate = 1
428
434
429 [logger_beaker]
435 [logger_beaker]
430 level = DEBUG
436 level = DEBUG
431 handlers =
437 handlers =
432 qualname = beaker.container
438 qualname = beaker.container
433 propagate = 1
439 propagate = 1
434
440
435 [logger_templates]
441 [logger_templates]
436 level = INFO
442 level = INFO
437 handlers =
443 handlers =
438 qualname = pylons.templating
444 qualname = pylons.templating
439 propagate = 1
445 propagate = 1
440
446
441 [logger_rhodecode]
447 [logger_rhodecode]
442 level = DEBUG
448 level = DEBUG
443 handlers =
449 handlers =
444 qualname = rhodecode
450 qualname = rhodecode
445 propagate = 1
451 propagate = 1
446
452
447 [logger_sqlalchemy]
453 [logger_sqlalchemy]
448 level = INFO
454 level = INFO
449 handlers = console_sql
455 handlers = console_sql
450 qualname = sqlalchemy.engine
456 qualname = sqlalchemy.engine
451 propagate = 0
457 propagate = 0
452
458
453 [logger_whoosh_indexer]
459 [logger_whoosh_indexer]
454 level = DEBUG
460 level = DEBUG
455 handlers =
461 handlers =
456 qualname = whoosh_indexer
462 qualname = whoosh_indexer
457 propagate = 1
463 propagate = 1
458
464
459 ##############
465 ##############
460 ## HANDLERS ##
466 ## HANDLERS ##
461 ##############
467 ##############
462
468
463 [handler_console]
469 [handler_console]
464 class = StreamHandler
470 class = StreamHandler
465 args = (sys.stderr,)
471 args = (sys.stderr,)
466 level = DEBUG
472 level = DEBUG
467 formatter = color_formatter
473 formatter = color_formatter
468
474
469 [handler_console_sql]
475 [handler_console_sql]
470 class = StreamHandler
476 class = StreamHandler
471 args = (sys.stderr,)
477 args = (sys.stderr,)
472 level = DEBUG
478 level = DEBUG
473 formatter = color_formatter_sql
479 formatter = color_formatter_sql
474
480
475 ################
481 ################
476 ## FORMATTERS ##
482 ## FORMATTERS ##
477 ################
483 ################
478
484
479 [formatter_generic]
485 [formatter_generic]
480 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
486 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
481 datefmt = %Y-%m-%d %H:%M:%S
487 datefmt = %Y-%m-%d %H:%M:%S
482
488
483 [formatter_color_formatter]
489 [formatter_color_formatter]
484 class=rhodecode.lib.colored_formatter.ColorFormatter
490 class=rhodecode.lib.colored_formatter.ColorFormatter
485 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
486 datefmt = %Y-%m-%d %H:%M:%S
492 datefmt = %Y-%m-%d %H:%M:%S
487
493
488 [formatter_color_formatter_sql]
494 [formatter_color_formatter_sql]
489 class=rhodecode.lib.colored_formatter.ColorFormatterSql
495 class=rhodecode.lib.colored_formatter.ColorFormatterSql
490 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
491 datefmt = %Y-%m-%d %H:%M:%S
497 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,742 +1,744 b''
1 .. _setup:
1 .. _setup:
2
2
3 =====
3 =====
4 Setup
4 Setup
5 =====
5 =====
6
6
7
7
8 Setting up RhodeCode
8 Setting up RhodeCode
9 --------------------
9 --------------------
10
10
11 First, you will need to create a RhodeCode configuration file. Run the
11 First, you will need to create a RhodeCode configuration file. Run the
12 following command to do this::
12 following command to do this::
13
13
14 paster make-config RhodeCode production.ini
14 paster make-config RhodeCode production.ini
15
15
16 - This will create the file `production.ini` in the current directory. This
16 - This will create the file `production.ini` in the current directory. This
17 configuration file contains the various settings for RhodeCode, e.g proxy
17 configuration file contains the various settings for RhodeCode, e.g proxy
18 port, email settings, usage of static files, cache, celery settings and
18 port, email settings, usage of static files, cache, celery settings and
19 logging.
19 logging.
20
20
21
21
22 Next, you need to create the databases used by RhodeCode. I recommend that you
22 Next, you need to create the databases used by RhodeCode. I recommend that you
23 use postgresql or sqlite (default). If you choose a database other than the
23 use postgresql or sqlite (default). If you choose a database other than the
24 default ensure you properly adjust the db url in your production.ini
24 default ensure you properly adjust the db url in your production.ini
25 configuration file to use this other database. RhodeCode currently supports
25 configuration file to use this other database. RhodeCode currently supports
26 postgresql, sqlite and mysql databases. Create the database by running
26 postgresql, sqlite and mysql databases. Create the database by running
27 the following command::
27 the following command::
28
28
29 paster setup-rhodecode production.ini
29 paster setup-rhodecode production.ini
30
30
31 This will prompt you for a "root" path. This "root" path is the location where
31 This will prompt you for a "root" path. This "root" path is the location where
32 RhodeCode will store all of its repositories on the current machine. After
32 RhodeCode will store all of its repositories on the current machine. After
33 entering this "root" path ``setup-rhodecode`` will also prompt you for a username
33 entering this "root" path ``setup-rhodecode`` will also prompt you for a username
34 and password for the initial admin account which ``setup-rhodecode`` sets
34 and password for the initial admin account which ``setup-rhodecode`` sets
35 up for you.
35 up for you.
36
36
37 setup process can be fully automated, example for lazy::
37 setup process can be fully automated, example for lazy::
38
38
39 paster setup-rhodecode production.ini --user=marcink --password=secret --email=marcin@rhodecode.org --repos=/home/marcink/my_repos
39 paster setup-rhodecode production.ini --user=marcink --password=secret --email=marcin@rhodecode.org --repos=/home/marcink/my_repos
40
40
41
41
42 - The ``setup-rhodecode`` command will create all of the needed tables and an
42 - The ``setup-rhodecode`` command will create all of the needed tables and an
43 admin account. When choosing a root path you can either use a new empty
43 admin account. When choosing a root path you can either use a new empty
44 location, or a location which already contains existing repositories. If you
44 location, or a location which already contains existing repositories. If you
45 choose a location which contains existing repositories RhodeCode will simply
45 choose a location which contains existing repositories RhodeCode will simply
46 add all of the repositories at the chosen location to it's database.
46 add all of the repositories at the chosen location to it's database.
47 (Note: make sure you specify the correct path to the root).
47 (Note: make sure you specify the correct path to the root).
48 - Note: the given path for mercurial_ repositories **must** be write accessible
48 - Note: the given path for mercurial_ repositories **must** be write accessible
49 for the application. It's very important since the RhodeCode web interface
49 for the application. It's very important since the RhodeCode web interface
50 will work without write access, but when trying to do a push it will
50 will work without write access, but when trying to do a push it will
51 eventually fail with permission denied errors unless it has write access.
51 eventually fail with permission denied errors unless it has write access.
52
52
53 You are now ready to use RhodeCode, to run it simply execute::
53 You are now ready to use RhodeCode, to run it simply execute::
54
54
55 paster serve production.ini
55 paster serve production.ini
56
56
57 - This command runs the RhodeCode server. The web app should be available at the
57 - This command runs the RhodeCode server. The web app should be available at the
58 127.0.0.1:5000. This ip and port is configurable via the production.ini
58 127.0.0.1:5000. This ip and port is configurable via the production.ini
59 file created in previous step
59 file created in previous step
60 - Use the admin account you created above when running ``setup-rhodecode``
60 - Use the admin account you created above when running ``setup-rhodecode``
61 to login to the web app.
61 to login to the web app.
62 - The default permissions on each repository is read, and the owner is admin.
62 - The default permissions on each repository is read, and the owner is admin.
63 Remember to update these if needed.
63 Remember to update these if needed.
64 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
64 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
65 well as edit more advanced options on users and repositories
65 well as edit more advanced options on users and repositories
66
66
67 Optionally users can create `rcextensions` package that extends RhodeCode
67 Optionally users can create `rcextensions` package that extends RhodeCode
68 functionality. To do this simply execute::
68 functionality. To do this simply execute::
69
69
70 paster make-rcext production.ini
70 paster make-rcext production.ini
71
71
72 This will create `rcextensions` package in the same place that your `ini` file
72 This will create `rcextensions` package in the same place that your `ini` file
73 lives. With `rcextensions` it's possible to add additional mapping for whoosh,
73 lives. With `rcextensions` it's possible to add additional mapping for whoosh,
74 stats and add additional code into the push/pull/create/delete repo hooks.
74 stats and add additional code into the push/pull/create/delete repo hooks.
75 For example for sending signals to build-bots such as jenkins.
75 For example for sending signals to build-bots such as jenkins.
76 Please see the `__init__.py` file inside `rcextensions` package
76 Please see the `__init__.py` file inside `rcextensions` package
77 for more details.
77 for more details.
78
78
79
79
80 Using RhodeCode with SSH
80 Using RhodeCode with SSH
81 ------------------------
81 ------------------------
82
82
83 RhodeCode currently only hosts repositories using http and https. (The addition
83 RhodeCode currently only hosts repositories using http and https. (The addition
84 of ssh hosting is a planned future feature.) However you can easily use ssh in
84 of ssh hosting is a planned future feature.) However you can easily use ssh in
85 parallel with RhodeCode. (Repository access via ssh is a standard "out of
85 parallel with RhodeCode. (Repository access via ssh is a standard "out of
86 the box" feature of mercurial_ and you can use this to access any of the
86 the box" feature of mercurial_ and you can use this to access any of the
87 repositories that RhodeCode is hosting. See PublishingRepositories_)
87 repositories that RhodeCode is hosting. See PublishingRepositories_)
88
88
89 RhodeCode repository structures are kept in directories with the same name
89 RhodeCode repository structures are kept in directories with the same name
90 as the project. When using repository groups, each group is a subdirectory.
90 as the project. When using repository groups, each group is a subdirectory.
91 This allows you to easily use ssh for accessing repositories.
91 This allows you to easily use ssh for accessing repositories.
92
92
93 In order to use ssh you need to make sure that your web-server and the users
93 In order to use ssh you need to make sure that your web-server and the users
94 login accounts have the correct permissions set on the appropriate directories.
94 login accounts have the correct permissions set on the appropriate directories.
95 (Note that these permissions are independent of any permissions you have set up
95 (Note that these permissions are independent of any permissions you have set up
96 using the RhodeCode web interface.)
96 using the RhodeCode web interface.)
97
97
98 If your main directory (the same as set in RhodeCode settings) is for example
98 If your main directory (the same as set in RhodeCode settings) is for example
99 set to **/home/hg** and the repository you are using is named `rhodecode`, then
99 set to **/home/hg** and the repository you are using is named `rhodecode`, then
100 to clone via ssh you should run::
100 to clone via ssh you should run::
101
101
102 hg clone ssh://user@server.com/home/hg/rhodecode
102 hg clone ssh://user@server.com/home/hg/rhodecode
103
103
104 Using other external tools such as mercurial-server_ or using ssh key based
104 Using other external tools such as mercurial-server_ or using ssh key based
105 authentication is fully supported.
105 authentication is fully supported.
106
106
107 Note: In an advanced setup, in order for your ssh access to use the same
107 Note: In an advanced setup, in order for your ssh access to use the same
108 permissions as set up via the RhodeCode web interface, you can create an
108 permissions as set up via the RhodeCode web interface, you can create an
109 authentication hook to connect to the rhodecode db and runs check functions for
109 authentication hook to connect to the rhodecode db and runs check functions for
110 permissions against that.
110 permissions against that.
111
111
112 Setting up Whoosh full text search
112 Setting up Whoosh full text search
113 ----------------------------------
113 ----------------------------------
114
114
115 Starting from version 1.1 the whoosh index can be build by using the paster
115 Starting from version 1.1 the whoosh index can be build by using the paster
116 command ``make-index``. To use ``make-index`` you must specify the configuration
116 command ``make-index``. To use ``make-index`` you must specify the configuration
117 file that stores the location of the index. You may specify the location of the
117 file that stores the location of the index. You may specify the location of the
118 repositories (`--repo-location`). If not specified, this value is retrieved
118 repositories (`--repo-location`). If not specified, this value is retrieved
119 from the RhodeCode database. This was required prior to 1.2. Starting from
119 from the RhodeCode database. This was required prior to 1.2. Starting from
120 version 1.2 it is also possible to specify a comma separated list of
120 version 1.2 it is also possible to specify a comma separated list of
121 repositories (`--index-only`) to build index only on chooses repositories
121 repositories (`--index-only`) to build index only on chooses repositories
122 skipping any other found in repos location
122 skipping any other found in repos location
123
123
124 You may optionally pass the option `-f` to enable a full index rebuild. Without
124 You may optionally pass the option `-f` to enable a full index rebuild. Without
125 the `-f` option, indexing will run always in "incremental" mode.
125 the `-f` option, indexing will run always in "incremental" mode.
126
126
127 For an incremental index build use::
127 For an incremental index build use::
128
128
129 paster make-index production.ini
129 paster make-index production.ini
130
130
131 For a full index rebuild use::
131 For a full index rebuild use::
132
132
133 paster make-index production.ini -f
133 paster make-index production.ini -f
134
134
135
135
136 building index just for chosen repositories is possible with such command::
136 building index just for chosen repositories is possible with such command::
137
137
138 paster make-index production.ini --index-only=vcs,rhodecode
138 paster make-index production.ini --index-only=vcs,rhodecode
139
139
140
140
141 In order to do periodical index builds and keep your index always up to date.
141 In order to do periodical index builds and keep your index always up to date.
142 It's recommended to do a crontab entry for incremental indexing.
142 It's recommended to do a crontab entry for incremental indexing.
143 An example entry might look like this::
143 An example entry might look like this::
144
144
145 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
145 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
146
146
147 When using incremental mode (the default) whoosh will check the last
147 When using incremental mode (the default) whoosh will check the last
148 modification date of each file and add it to be reindexed if a newer file is
148 modification date of each file and add it to be reindexed if a newer file is
149 available. The indexing daemon checks for any removed files and removes them
149 available. The indexing daemon checks for any removed files and removes them
150 from index.
150 from index.
151
151
152 If you want to rebuild index from scratch, you can use the `-f` flag as above,
152 If you want to rebuild index from scratch, you can use the `-f` flag as above,
153 or in the admin panel you can check `build from scratch` flag.
153 or in the admin panel you can check `build from scratch` flag.
154
154
155
155
156 Setting up LDAP support
156 Setting up LDAP support
157 -----------------------
157 -----------------------
158
158
159 RhodeCode starting from version 1.1 supports ldap authentication. In order
159 RhodeCode starting from version 1.1 supports ldap authentication. In order
160 to use LDAP, you have to install the python-ldap_ package. This package is
160 to use LDAP, you have to install the python-ldap_ package. This package is
161 available via pypi, so you can install it by running
161 available via pypi, so you can install it by running
162
162
163 using easy_install::
163 using easy_install::
164
164
165 easy_install python-ldap
165 easy_install python-ldap
166
166
167 using pip::
167 using pip::
168
168
169 pip install python-ldap
169 pip install python-ldap
170
170
171 .. note::
171 .. note::
172 python-ldap requires some certain libs on your system, so before installing
172 python-ldap requires some certain libs on your system, so before installing
173 it check that you have at least `openldap`, and `sasl` libraries.
173 it check that you have at least `openldap`, and `sasl` libraries.
174
174
175 LDAP settings are located in admin->ldap section,
175 LDAP settings are located in admin->ldap section,
176
176
177 Here's a typical ldap setup::
177 Here's a typical ldap setup::
178
178
179 Connection settings
179 Connection settings
180 Enable LDAP = checked
180 Enable LDAP = checked
181 Host = host.example.org
181 Host = host.example.org
182 Port = 389
182 Port = 389
183 Account = <account>
183 Account = <account>
184 Password = <password>
184 Password = <password>
185 Connection Security = LDAPS connection
185 Connection Security = LDAPS connection
186 Certificate Checks = DEMAND
186 Certificate Checks = DEMAND
187
187
188 Search settings
188 Search settings
189 Base DN = CN=users,DC=host,DC=example,DC=org
189 Base DN = CN=users,DC=host,DC=example,DC=org
190 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
190 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
191 LDAP Search Scope = SUBTREE
191 LDAP Search Scope = SUBTREE
192
192
193 Attribute mappings
193 Attribute mappings
194 Login Attribute = uid
194 Login Attribute = uid
195 First Name Attribute = firstName
195 First Name Attribute = firstName
196 Last Name Attribute = lastName
196 Last Name Attribute = lastName
197 E-mail Attribute = mail
197 E-mail Attribute = mail
198
198
199 If your user groups are placed in a Organisation Unit (OU) structure the Search Settings configuration differs::
199 If your user groups are placed in a Organisation Unit (OU) structure the Search Settings configuration differs::
200
200
201 Search settings
201 Search settings
202 Base DN = DC=host,DC=example,DC=org
202 Base DN = DC=host,DC=example,DC=org
203 LDAP Filter = (&(memberOf=CN=your user group,OU=subunit,OU=unit,DC=host,DC=example,DC=org)(objectClass=user))
203 LDAP Filter = (&(memberOf=CN=your user group,OU=subunit,OU=unit,DC=host,DC=example,DC=org)(objectClass=user))
204 LDAP Search Scope = SUBTREE
204 LDAP Search Scope = SUBTREE
205
205
206 .. _enable_ldap:
206 .. _enable_ldap:
207
207
208 Enable LDAP : required
208 Enable LDAP : required
209 Whether to use LDAP for authenticating users.
209 Whether to use LDAP for authenticating users.
210
210
211 .. _ldap_host:
211 .. _ldap_host:
212
212
213 Host : required
213 Host : required
214 LDAP server hostname or IP address. Can be also a comma separated
214 LDAP server hostname or IP address. Can be also a comma separated
215 list of servers to support LDAP fail-over.
215 list of servers to support LDAP fail-over.
216
216
217 .. _Port:
217 .. _Port:
218
218
219 Port : required
219 Port : required
220 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
220 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
221
221
222 .. _ldap_account:
222 .. _ldap_account:
223
223
224 Account : optional
224 Account : optional
225 Only required if the LDAP server does not allow anonymous browsing of
225 Only required if the LDAP server does not allow anonymous browsing of
226 records. This should be a special account for record browsing. This
226 records. This should be a special account for record browsing. This
227 will require `LDAP Password`_ below.
227 will require `LDAP Password`_ below.
228
228
229 .. _LDAP Password:
229 .. _LDAP Password:
230
230
231 Password : optional
231 Password : optional
232 Only required if the LDAP server does not allow anonymous browsing of
232 Only required if the LDAP server does not allow anonymous browsing of
233 records.
233 records.
234
234
235 .. _Enable LDAPS:
235 .. _Enable LDAPS:
236
236
237 Connection Security : required
237 Connection Security : required
238 Defines the connection to LDAP server
238 Defines the connection to LDAP server
239
239
240 No encryption
240 No encryption
241 Plain non encrypted connection
241 Plain non encrypted connection
242
242
243 LDAPS connection
243 LDAPS connection
244 Enable ldaps connection. It will likely require `Port`_ to be set to
244 Enable ldaps connection. It will likely require `Port`_ to be set to
245 a different value (standard LDAPS port is 636). When LDAPS is enabled
245 a different value (standard LDAPS port is 636). When LDAPS is enabled
246 then `Certificate Checks`_ is required.
246 then `Certificate Checks`_ is required.
247
247
248 START_TLS on LDAP connection
248 START_TLS on LDAP connection
249 START TLS connection
249 START TLS connection
250
250
251 .. _Certificate Checks:
251 .. _Certificate Checks:
252
252
253 Certificate Checks : optional
253 Certificate Checks : optional
254 How SSL certificates verification is handled - this is only useful when
254 How SSL certificates verification is handled - this is only useful when
255 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
255 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
256 while the other options are susceptible to man-in-the-middle attacks. SSL
256 while the other options are susceptible to man-in-the-middle attacks. SSL
257 certificates can be installed to /etc/openldap/cacerts so that the
257 certificates can be installed to /etc/openldap/cacerts so that the
258 DEMAND or HARD options can be used with self-signed certificates or
258 DEMAND or HARD options can be used with self-signed certificates or
259 certificates that do not have traceable certificates of authority.
259 certificates that do not have traceable certificates of authority.
260
260
261 NEVER
261 NEVER
262 A serve certificate will never be requested or checked.
262 A serve certificate will never be requested or checked.
263
263
264 ALLOW
264 ALLOW
265 A server certificate is requested. Failure to provide a
265 A server certificate is requested. Failure to provide a
266 certificate or providing a bad certificate will not terminate the
266 certificate or providing a bad certificate will not terminate the
267 session.
267 session.
268
268
269 TRY
269 TRY
270 A server certificate is requested. Failure to provide a
270 A server certificate is requested. Failure to provide a
271 certificate does not halt the session; providing a bad certificate
271 certificate does not halt the session; providing a bad certificate
272 halts the session.
272 halts the session.
273
273
274 DEMAND
274 DEMAND
275 A server certificate is requested and must be provided and
275 A server certificate is requested and must be provided and
276 authenticated for the session to proceed.
276 authenticated for the session to proceed.
277
277
278 HARD
278 HARD
279 The same as DEMAND.
279 The same as DEMAND.
280
280
281 .. _Base DN:
281 .. _Base DN:
282
282
283 Base DN : required
283 Base DN : required
284 The Distinguished Name (DN) where searches for users will be performed.
284 The Distinguished Name (DN) where searches for users will be performed.
285 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
285 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
286
286
287 .. _LDAP Filter:
287 .. _LDAP Filter:
288
288
289 LDAP Filter : optional
289 LDAP Filter : optional
290 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
290 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
291 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
291 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
292 which LDAP objects are identified as representing Users for
292 which LDAP objects are identified as representing Users for
293 authentication. The filter is augmented by `Login Attribute`_ below.
293 authentication. The filter is augmented by `Login Attribute`_ below.
294 This can commonly be left blank.
294 This can commonly be left blank.
295
295
296 .. _LDAP Search Scope:
296 .. _LDAP Search Scope:
297
297
298 LDAP Search Scope : required
298 LDAP Search Scope : required
299 This limits how far LDAP will search for a matching object.
299 This limits how far LDAP will search for a matching object.
300
300
301 BASE
301 BASE
302 Only allows searching of `Base DN`_ and is usually not what you
302 Only allows searching of `Base DN`_ and is usually not what you
303 want.
303 want.
304
304
305 ONELEVEL
305 ONELEVEL
306 Searches all entries under `Base DN`_, but not Base DN itself.
306 Searches all entries under `Base DN`_, but not Base DN itself.
307
307
308 SUBTREE
308 SUBTREE
309 Searches all entries below `Base DN`_, but not Base DN itself.
309 Searches all entries below `Base DN`_, but not Base DN itself.
310 When using SUBTREE `LDAP Filter`_ is useful to limit object
310 When using SUBTREE `LDAP Filter`_ is useful to limit object
311 location.
311 location.
312
312
313 .. _Login Attribute:
313 .. _Login Attribute:
314
314
315 Login Attribute : required
315 Login Attribute : required
316 The LDAP record attribute that will be matched as the USERNAME or
316 The LDAP record attribute that will be matched as the USERNAME or
317 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
317 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
318 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
318 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
319 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
319 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
320 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
320 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
321 ::
321 ::
322
322
323 (&(LDAPFILTER)(uid=jsmith))
323 (&(LDAPFILTER)(uid=jsmith))
324
324
325 .. _ldap_attr_firstname:
325 .. _ldap_attr_firstname:
326
326
327 First Name Attribute : required
327 First Name Attribute : required
328 The LDAP record attribute which represents the user's first name.
328 The LDAP record attribute which represents the user's first name.
329
329
330 .. _ldap_attr_lastname:
330 .. _ldap_attr_lastname:
331
331
332 Last Name Attribute : required
332 Last Name Attribute : required
333 The LDAP record attribute which represents the user's last name.
333 The LDAP record attribute which represents the user's last name.
334
334
335 .. _ldap_attr_email:
335 .. _ldap_attr_email:
336
336
337 Email Attribute : required
337 Email Attribute : required
338 The LDAP record attribute which represents the user's email address.
338 The LDAP record attribute which represents the user's email address.
339
339
340 If all data are entered correctly, and python-ldap_ is properly installed
340 If all data are entered correctly, and python-ldap_ is properly installed
341 users should be granted access to RhodeCode with ldap accounts. At this
341 users should be granted access to RhodeCode with ldap accounts. At this
342 time user information is copied from LDAP into the RhodeCode user database.
342 time user information is copied from LDAP into the RhodeCode user database.
343 This means that updates of an LDAP user object may not be reflected as a
343 This means that updates of an LDAP user object may not be reflected as a
344 user update in RhodeCode.
344 user update in RhodeCode.
345
345
346 If You have problems with LDAP access and believe You entered correct
346 If You have problems with LDAP access and believe You entered correct
347 information check out the RhodeCode logs, any error messages sent from LDAP
347 information check out the RhodeCode logs, any error messages sent from LDAP
348 will be saved there.
348 will be saved there.
349
349
350 Active Directory
350 Active Directory
351 ''''''''''''''''
351 ''''''''''''''''
352
352
353 RhodeCode can use Microsoft Active Directory for user authentication. This
353 RhodeCode can use Microsoft Active Directory for user authentication. This
354 is done through an LDAP or LDAPS connection to Active Directory. The
354 is done through an LDAP or LDAPS connection to Active Directory. The
355 following LDAP configuration settings are typical for using Active
355 following LDAP configuration settings are typical for using Active
356 Directory ::
356 Directory ::
357
357
358 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
358 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
359 Login Attribute = sAMAccountName
359 Login Attribute = sAMAccountName
360 First Name Attribute = givenName
360 First Name Attribute = givenName
361 Last Name Attribute = sn
361 Last Name Attribute = sn
362 E-mail Attribute = mail
362 E-mail Attribute = mail
363
363
364 All other LDAP settings will likely be site-specific and should be
364 All other LDAP settings will likely be site-specific and should be
365 appropriately configured.
365 appropriately configured.
366
366
367
367
368 Authentication by container or reverse-proxy
368 Authentication by container or reverse-proxy
369 --------------------------------------------
369 --------------------------------------------
370
370
371 Starting with version 1.3, RhodeCode supports delegating the authentication
371 Starting with version 1.3, RhodeCode supports delegating the authentication
372 of users to its WSGI container, or to a reverse-proxy server through which all
372 of users to its WSGI container, or to a reverse-proxy server through which all
373 clients access the application.
373 clients access the application.
374
374
375 When these authentication methods are enabled in RhodeCode, it uses the
375 When these authentication methods are enabled in RhodeCode, it uses the
376 username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
376 username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
377 perform the authentication itself. The authorization, however, is still done by
377 perform the authentication itself. The authorization, however, is still done by
378 RhodeCode according to its settings.
378 RhodeCode according to its settings.
379
379
380 When a user logs in for the first time using these authentication methods,
380 When a user logs in for the first time using these authentication methods,
381 a matching user account is created in RhodeCode with default permissions. An
381 a matching user account is created in RhodeCode with default permissions. An
382 administrator can then modify it using RhodeCode's admin interface.
382 administrator can then modify it using RhodeCode's admin interface.
383 It's also possible for an administrator to create accounts and configure their
383 It's also possible for an administrator to create accounts and configure their
384 permissions before the user logs in for the first time.
384 permissions before the user logs in for the first time.
385
385
386 Container-based authentication
386 Container-based authentication
387 ''''''''''''''''''''''''''''''
387 ''''''''''''''''''''''''''''''
388
388
389 In a container-based authentication setup, RhodeCode reads the user name from
389 In a container-based authentication setup, RhodeCode reads the user name from
390 the ``REMOTE_USER`` server variable provided by the WSGI container.
390 the ``REMOTE_USER`` server variable provided by the WSGI container.
391
391
392 After setting up your container (see `Apache's WSGI config`_), you'd need
392 After setting up your container (see `Apache's WSGI config`_), you'd need
393 to configure it to require authentication on the location configured for
393 to configure it to require authentication on the location configured for
394 RhodeCode.
394 RhodeCode.
395
395
396 In order for RhodeCode to start using the provided username, you should set the
396 In order for RhodeCode to start using the provided username, you should set the
397 following in the [app:main] section of your .ini file::
397 following in the [app:main] section of your .ini file::
398
398
399 container_auth_enabled = true
399 container_auth_enabled = true
400
400
401
401
402 Proxy pass-through authentication
402 Proxy pass-through authentication
403 '''''''''''''''''''''''''''''''''
403 '''''''''''''''''''''''''''''''''
404
404
405 In a proxy pass-through authentication setup, RhodeCode reads the user name
405 In a proxy pass-through authentication setup, RhodeCode reads the user name
406 from the ``X-Forwarded-User`` request header, which should be configured to be
406 from the ``X-Forwarded-User`` request header, which should be configured to be
407 sent by the reverse-proxy server.
407 sent by the reverse-proxy server.
408
408
409 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
409 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
410 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
410 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
411 configure the authentication and add the username in a request header named
411 configure the authentication and add the username in a request header named
412 ``X-Forwarded-User``.
412 ``X-Forwarded-User``.
413
413
414 For example, the following config section for Apache sets a subdirectory in a
414 For example, the following config section for Apache sets a subdirectory in a
415 reverse-proxy setup with basic auth::
415 reverse-proxy setup with basic auth::
416
416
417 <Location /<someprefix> >
417 <Location /<someprefix> >
418 ProxyPass http://127.0.0.1:5000/<someprefix>
418 ProxyPass http://127.0.0.1:5000/<someprefix>
419 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
419 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
420 SetEnvIf X-Url-Scheme https HTTPS=1
420 SetEnvIf X-Url-Scheme https HTTPS=1
421
421
422 AuthType Basic
422 AuthType Basic
423 AuthName "RhodeCode authentication"
423 AuthName "RhodeCode authentication"
424 AuthUserFile /home/web/rhodecode/.htpasswd
424 AuthUserFile /home/web/rhodecode/.htpasswd
425 require valid-user
425 require valid-user
426
426
427 RequestHeader unset X-Forwarded-User
427 RequestHeader unset X-Forwarded-User
428
428
429 RewriteEngine On
429 RewriteEngine On
430 RewriteCond %{LA-U:REMOTE_USER} (.+)
430 RewriteCond %{LA-U:REMOTE_USER} (.+)
431 RewriteRule .* - [E=RU:%1]
431 RewriteRule .* - [E=RU:%1]
432 RequestHeader set X-Forwarded-User %{RU}e
432 RequestHeader set X-Forwarded-User %{RU}e
433 </Location>
433 </Location>
434
434
435 In order for RhodeCode to start using the forwarded username, you should set
435 In order for RhodeCode to start using the forwarded username, you should set
436 the following in the [app:main] section of your .ini file::
436 the following in the [app:main] section of your .ini file::
437
437
438 proxypass_auth_enabled = true
438 proxypass_auth_enabled = true
439
439
440 .. note::
440 .. note::
441 If you enable proxy pass-through authentication, make sure your server is
441 If you enable proxy pass-through authentication, make sure your server is
442 only accessible through the proxy. Otherwise, any client would be able to
442 only accessible through the proxy. Otherwise, any client would be able to
443 forge the authentication header and could effectively become authenticated
443 forge the authentication header and could effectively become authenticated
444 using any account of their liking.
444 using any account of their liking.
445
445
446 Integration with Issue trackers
446 Integration with Issue trackers
447 -------------------------------
447 -------------------------------
448
448
449 RhodeCode provides a simple integration with issue trackers. It's possible
449 RhodeCode provides a simple integration with issue trackers. It's possible
450 to define a regular expression that will fetch issue id stored in commit
450 to define a regular expression that will fetch issue id stored in commit
451 messages and replace that with an url to this issue. To enable this simply
451 messages and replace that with an url to this issue. To enable this simply
452 uncomment following variables in the ini file::
452 uncomment following variables in the ini file::
453
453
454 url_pat = (?:^#|\s#)(\w+)
454 url_pat = (?:^#|\s#)(\w+)
455 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
455 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
456 issue_prefix = #
456 issue_prefix = #
457
457
458 `url_pat` is the regular expression that will fetch issues from commit messages.
458 `url_pat` is the regular expression that will fetch issues from commit messages.
459 Default regex will match issues in format of #<number> eg. #300.
459 Default regex will match issues in format of #<number> eg. #300.
460
460
461 Matched issues will be replace with the link specified as `issue_server_link`
461 Matched issues will be replace with the link specified as `issue_server_link`
462 {id} will be replaced with issue id, and {repo} with repository name.
462 {id} will be replaced with issue id, and {repo} with repository name.
463 Since the # is striped `issue_prefix` is added as a prefix to url.
463 Since the # is striped `issue_prefix` is added as a prefix to url.
464 `issue_prefix` can be something different than # if you pass
464 `issue_prefix` can be something different than # if you pass
465 ISSUE- as issue prefix this will generate an url in format::
465 ISSUE- as issue prefix this will generate an url in format::
466
466
467 <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>
467 <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>
468
468
469 Hook management
469 Hook management
470 ---------------
470 ---------------
471
471
472 Hooks can be managed in similar way to this used in .hgrc files.
472 Hooks can be managed in similar way to this used in .hgrc files.
473 To access hooks setting click `advanced setup` on Hooks section of Mercurial
473 To access hooks setting click `advanced setup` on Hooks section of Mercurial
474 Settings in Admin.
474 Settings in Admin.
475
475
476 There are 4 built in hooks that cannot be changed (only enable/disable by
476 There are 4 built in hooks that cannot be changed (only enable/disable by
477 checkboxes on previos section).
477 checkboxes on previos section).
478 To add another custom hook simply fill in first section with
478 To add another custom hook simply fill in first section with
479 <name>.<hook_type> and the second one with hook path. Example hooks
479 <name>.<hook_type> and the second one with hook path. Example hooks
480 can be found at *rhodecode.lib.hooks*.
480 can be found at *rhodecode.lib.hooks*.
481
481
482
482
483 Changing default encoding
483 Changing default encoding
484 -------------------------
484 -------------------------
485
485
486 By default RhodeCode uses utf8 encoding, starting from 1.3 series this
486 By default RhodeCode uses utf8 encoding, starting from 1.3 series this
487 can be changed, simply edit default_encoding in .ini file to desired one.
487 can be changed, simply edit default_encoding in .ini file to desired one.
488 This affects many parts in rhodecode including committers names, filenames,
488 This affects many parts in rhodecode including committers names, filenames,
489 encoding of commit messages. In addition RhodeCode can detect if `chardet`
489 encoding of commit messages. In addition RhodeCode can detect if `chardet`
490 library is installed. If `chardet` is detected RhodeCode will fallback to it
490 library is installed. If `chardet` is detected RhodeCode will fallback to it
491 when there are encode/decode errors.
491 when there are encode/decode errors.
492
492
493
493
494 Setting Up Celery
494 Setting Up Celery
495 -----------------
495 -----------------
496
496
497 Since version 1.1 celery is configured by the rhodecode ini configuration files.
497 Since version 1.1 celery is configured by the rhodecode ini configuration files.
498 Simply set use_celery=true in the ini file then add / change the configuration
498 Simply set use_celery=true in the ini file then add / change the configuration
499 variables inside the ini file.
499 variables inside the ini file.
500
500
501 Remember that the ini files use the format with '.' not with '_' like celery.
501 Remember that the ini files use the format with '.' not with '_' like celery.
502 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
502 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
503 the config file.
503 the config file.
504
504
505 In order to start using celery run::
505 In order to start using celery run::
506
506
507 paster celeryd <configfile.ini>
507 paster celeryd <configfile.ini>
508
508
509
509
510 .. note::
510 .. note::
511 Make sure you run this command from the same virtualenv, and with the same
511 Make sure you run this command from the same virtualenv, and with the same
512 user that rhodecode runs.
512 user that rhodecode runs.
513
513
514 HTTPS support
514 HTTPS support
515 -------------
515 -------------
516
516
517 There are two ways to enable https:
517 There are two ways to enable https:
518
518
519 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
519 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
520 recognize this headers and make proper https redirections
520 recognize this headers and make proper https redirections
521 - Alternatively, change the `force_https = true` flag in the ini configuration
521 - Alternatively, change the `force_https = true` flag in the ini configuration
522 to force using https, no headers are needed than to enable https
522 to force using https, no headers are needed than to enable https
523
523
524
524
525 Nginx virtual host example
525 Nginx virtual host example
526 --------------------------
526 --------------------------
527
527
528 Sample config for nginx using proxy::
528 Sample config for nginx using proxy::
529
529
530 upstream rc {
530 upstream rc {
531 server 127.0.0.1:5000;
531 server 127.0.0.1:5000;
532 # add more instances for load balancing
532 # add more instances for load balancing
533 #server 127.0.0.1:5001;
533 #server 127.0.0.1:5001;
534 #server 127.0.0.1:5002;
534 #server 127.0.0.1:5002;
535 }
535 }
536
536
537 ## gist alias
538 server {
539 listen 443;
540 server_name gist.myserver.com;
541 access_log /var/log/nginx/gist.access.log;
542 error_log /var/log/nginx/gist.error.log;
543
544 ssl on;
545 ssl_certificate gist.rhodecode.myserver.com.crt;
546 ssl_certificate_key gist.rhodecode.myserver.com.key;
547
548 ssl_session_timeout 5m;
549
550 ssl_protocols SSLv3 TLSv1;
551 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;
552 ssl_prefer_server_ciphers on;
553
554 location / {
555 rewrite ^/(.*) https://rhodecode.myserver.com/_admin/gists/$1;
556 }
557 }
558
537 server {
559 server {
538 listen 443;
560 listen 443;
539 server_name rhodecode.myserver.com;
561 server_name rhodecode.myserver.com;
540 access_log /var/log/nginx/rhodecode.access.log;
562 access_log /var/log/nginx/rhodecode.access.log;
541 error_log /var/log/nginx/rhodecode.error.log;
563 error_log /var/log/nginx/rhodecode.error.log;
542
564
543 ssl on;
565 ssl on;
544 ssl_certificate rhodecode.myserver.com.crt;
566 ssl_certificate rhodecode.myserver.com.crt;
545 ssl_certificate_key rhodecode.myserver.com.key;
567 ssl_certificate_key rhodecode.myserver.com.key;
546
568
547 ssl_session_timeout 5m;
569 ssl_session_timeout 5m;
548
570
549 ssl_protocols SSLv3 TLSv1;
571 ssl_protocols SSLv3 TLSv1;
550 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;
572 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;
551 ssl_prefer_server_ciphers on;
573 ssl_prefer_server_ciphers on;
552
574
553 # uncomment if you have nginx with chunking module compiled
575 ## uncomment root directive if you want to serve static files by nginx
554 # fixes the issues of having to put postBuffer data for large git
576 ## requires static_files = false in .ini file
555 # pushes
556 #chunkin on;
557 #error_page 411 = @my_411_error;
558 #location @my_411_error {
559 # chunkin_resume;
560 #}
561
562 # uncomment if you want to serve static files by nginx
563 #root /path/to/installation/rhodecode/public;
577 #root /path/to/installation/rhodecode/public;
564
578
565 location / {
579 location / {
566 try_files $uri @rhode;
580 try_files $uri @rhode;
567 }
581 }
568
582
569 location @rhode {
583 location @rhode {
570 proxy_pass http://rc;
584 proxy_pass http://rc;
571 include /etc/nginx/proxy.conf;
585 include /etc/nginx/proxy.conf;
572 }
586 }
573
587
574 }
588 }
575
589
576 Here's the proxy.conf. It's tuned so it will not timeout on long
590 Here's the proxy.conf. It's tuned so it will not timeout on long
577 pushes or large pushes::
591 pushes or large pushes::
578
592
579 proxy_redirect off;
593 proxy_redirect off;
580 proxy_set_header Host $host;
594 proxy_set_header Host $host;
581 proxy_set_header X-Url-Scheme $scheme;
595 proxy_set_header X-Url-Scheme $scheme;
582 proxy_set_header X-Host $http_host;
596 proxy_set_header X-Host $http_host;
583 proxy_set_header X-Real-IP $remote_addr;
597 proxy_set_header X-Real-IP $remote_addr;
584 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
598 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
585 proxy_set_header Proxy-host $proxy_host;
599 proxy_set_header Proxy-host $proxy_host;
586 client_max_body_size 400m;
600 client_max_body_size 400m;
587 client_body_buffer_size 128k;
601 client_body_buffer_size 128k;
588 proxy_buffering off;
602 proxy_buffering off;
589 proxy_connect_timeout 7200;
603 proxy_connect_timeout 7200;
590 proxy_send_timeout 7200;
604 proxy_send_timeout 7200;
591 proxy_read_timeout 7200;
605 proxy_read_timeout 7200;
592 proxy_buffers 8 32k;
606 proxy_buffers 8 32k;
593
607
594 Also, when using root path with nginx you might set the static files to false
595 in the production.ini file::
596
597 [app:main]
598 use = egg:rhodecode
599 full_stack = true
600 static_files = false
601 lang=en
602 cache_dir = %(here)s/data
603
604 In order to not have the statics served by the application. This improves speed.
605
606
608
607 Apache virtual host reverse proxy example
609 Apache virtual host reverse proxy example
608 -----------------------------------------
610 -----------------------------------------
609
611
610 Here is a sample configuration file for apache using proxy::
612 Here is a sample configuration file for apache using proxy::
611
613
612 <VirtualHost *:80>
614 <VirtualHost *:80>
613 ServerName hg.myserver.com
615 ServerName hg.myserver.com
614 ServerAlias hg.myserver.com
616 ServerAlias hg.myserver.com
615
617
616 <Proxy *>
618 <Proxy *>
617 Order allow,deny
619 Order allow,deny
618 Allow from all
620 Allow from all
619 </Proxy>
621 </Proxy>
620
622
621 #important !
623 #important !
622 #Directive to properly generate url (clone url) for pylons
624 #Directive to properly generate url (clone url) for pylons
623 ProxyPreserveHost On
625 ProxyPreserveHost On
624
626
625 #rhodecode instance
627 #rhodecode instance
626 ProxyPass / http://127.0.0.1:5000/
628 ProxyPass / http://127.0.0.1:5000/
627 ProxyPassReverse / http://127.0.0.1:5000/
629 ProxyPassReverse / http://127.0.0.1:5000/
628
630
629 #to enable https use line below
631 #to enable https use line below
630 #SetEnvIf X-Url-Scheme https HTTPS=1
632 #SetEnvIf X-Url-Scheme https HTTPS=1
631
633
632 </VirtualHost>
634 </VirtualHost>
633
635
634
636
635 Additional tutorial
637 Additional tutorial
636 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
638 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
637
639
638
640
639 Apache as subdirectory
641 Apache as subdirectory
640 ----------------------
642 ----------------------
641
643
642 Apache subdirectory part::
644 Apache subdirectory part::
643
645
644 <Location /<someprefix> >
646 <Location /<someprefix> >
645 ProxyPass http://127.0.0.1:5000/<someprefix>
647 ProxyPass http://127.0.0.1:5000/<someprefix>
646 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
648 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
647 SetEnvIf X-Url-Scheme https HTTPS=1
649 SetEnvIf X-Url-Scheme https HTTPS=1
648 </Location>
650 </Location>
649
651
650 Besides the regular apache setup you will need to add the following line
652 Besides the regular apache setup you will need to add the following line
651 into [app:main] section of your .ini file::
653 into [app:main] section of your .ini file::
652
654
653 filter-with = proxy-prefix
655 filter-with = proxy-prefix
654
656
655 Add the following at the end of the .ini file::
657 Add the following at the end of the .ini file::
656
658
657 [filter:proxy-prefix]
659 [filter:proxy-prefix]
658 use = egg:PasteDeploy#prefix
660 use = egg:PasteDeploy#prefix
659 prefix = /<someprefix>
661 prefix = /<someprefix>
660
662
661
663
662 then change <someprefix> into your chosen prefix
664 then change <someprefix> into your chosen prefix
663
665
664 Apache's WSGI config
666 Apache's WSGI config
665 --------------------
667 --------------------
666
668
667 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
669 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
668 that, you'll need to:
670 that, you'll need to:
669
671
670 - Install mod_wsgi. If using a Debian-based distro, you can install
672 - Install mod_wsgi. If using a Debian-based distro, you can install
671 the package libapache2-mod-wsgi::
673 the package libapache2-mod-wsgi::
672
674
673 aptitude install libapache2-mod-wsgi
675 aptitude install libapache2-mod-wsgi
674
676
675 - Enable mod_wsgi::
677 - Enable mod_wsgi::
676
678
677 a2enmod wsgi
679 a2enmod wsgi
678
680
679 - Create a wsgi dispatch script, like the one below. Make sure you
681 - Create a wsgi dispatch script, like the one below. Make sure you
680 check the paths correctly point to where you installed RhodeCode
682 check the paths correctly point to where you installed RhodeCode
681 and its Python Virtual Environment.
683 and its Python Virtual Environment.
682 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
684 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
683 as in the following example. Once again, check the paths are
685 as in the following example. Once again, check the paths are
684 correctly specified.
686 correctly specified.
685
687
686 Here is a sample excerpt from an Apache Virtual Host configuration file::
688 Here is a sample excerpt from an Apache Virtual Host configuration file::
687
689
688 WSGIDaemonProcess pylons \
690 WSGIDaemonProcess pylons \
689 threads=4 \
691 threads=4 \
690 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
692 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
691 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
693 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
692 WSGIPassAuthorization On
694 WSGIPassAuthorization On
693
695
694 .. note::
696 .. note::
695 when running apache as root please add: `user=www-data group=www-data`
697 when running apache as root please add: `user=www-data group=www-data`
696 into above configuration
698 into above configuration
697
699
698 .. note::
700 .. note::
699 Running RhodeCode in multiprocess mode in apache is not supported,
701 Running RhodeCode in multiprocess mode in apache is not supported,
700 make sure you don't specify `processes=num` directive in the config
702 make sure you don't specify `processes=num` directive in the config
701
703
702
704
703 Example wsgi dispatch script::
705 Example wsgi dispatch script::
704
706
705 import os
707 import os
706 os.environ["HGENCODING"] = "UTF-8"
708 os.environ["HGENCODING"] = "UTF-8"
707 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
709 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
708
710
709 # sometimes it's needed to set the curent dir
711 # sometimes it's needed to set the curent dir
710 os.chdir('/home/web/rhodecode/')
712 os.chdir('/home/web/rhodecode/')
711
713
712 import site
714 import site
713 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
715 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
714
716
715 from paste.deploy import loadapp
717 from paste.deploy import loadapp
716 from paste.script.util.logging_config import fileConfig
718 from paste.script.util.logging_config import fileConfig
717
719
718 fileConfig('/home/web/rhodecode/production.ini')
720 fileConfig('/home/web/rhodecode/production.ini')
719 application = loadapp('config:/home/web/rhodecode/production.ini')
721 application = loadapp('config:/home/web/rhodecode/production.ini')
720
722
721 Note: when using mod_wsgi you'll need to install the same version of
723 Note: when using mod_wsgi you'll need to install the same version of
722 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
724 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
723 environment.
725 environment.
724
726
725
727
726 Other configuration files
728 Other configuration files
727 -------------------------
729 -------------------------
728
730
729 Some example init.d scripts can be found in init.d directory::
731 Some example init.d scripts can be found in init.d directory::
730
732
731 https://secure.rhodecode.org/rhodecode/files/beta/init.d
733 https://secure.rhodecode.org/rhodecode/files/beta/init.d
732
734
733 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
735 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
734 .. _python: http://www.python.org/
736 .. _python: http://www.python.org/
735 .. _mercurial: http://mercurial.selenic.com/
737 .. _mercurial: http://mercurial.selenic.com/
736 .. _celery: http://celeryproject.org/
738 .. _celery: http://celeryproject.org/
737 .. _rabbitmq: http://www.rabbitmq.com/
739 .. _rabbitmq: http://www.rabbitmq.com/
738 .. _python-ldap: http://www.python-ldap.org/
740 .. _python-ldap: http://www.python-ldap.org/
739 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
741 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
740 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
742 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
741 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
743 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
742 .. _google group rhodecode: http://groups.google.com/group/rhodecode
744 .. _google group rhodecode: http://groups.google.com/group/rhodecode
@@ -1,491 +1,497 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ## PASTE
32 ## PASTE
33 ## nr of threads to spawn
33 ## nr of threads to spawn
34 #threadpool_workers = 5
34 #threadpool_workers = 5
35
35
36 ## max request before thread respawn
36 ## max request before thread respawn
37 #threadpool_max_requests = 10
37 #threadpool_max_requests = 10
38
38
39 ## option to use threads of process
39 ## option to use threads of process
40 #use_threadpool = true
40 #use_threadpool = true
41
41
42 #use = egg:Paste#http
42 #use = egg:Paste#http
43
43
44 ## WAITRESS
44 ## WAITRESS
45 threads = 5
45 threads = 5
46 ## 100GB
46 ## 100GB
47 max_request_body_size = 107374182400
47 max_request_body_size = 107374182400
48 use = egg:waitress#main
48 use = egg:waitress#main
49
49
50 host = 127.0.0.1
50 host = 127.0.0.1
51 port = 8001
51 port = 8001
52
52
53 ## prefix middleware for rc
53 ## prefix middleware for rc
54 #[filter:proxy-prefix]
54 #[filter:proxy-prefix]
55 #use = egg:PasteDeploy#prefix
55 #use = egg:PasteDeploy#prefix
56 #prefix = /<your-prefix>
56 #prefix = /<your-prefix>
57
57
58 [app:main]
58 [app:main]
59 use = egg:rhodecode
59 use = egg:rhodecode
60 ## enable proxy prefix middleware
60 ## enable proxy prefix middleware
61 #filter-with = proxy-prefix
61 #filter-with = proxy-prefix
62
62
63 full_stack = true
63 full_stack = true
64 static_files = true
64 static_files = true
65 ## Optional Languages
65 ## Optional Languages
66 ## en, fr, ja, pt_BR, zh_CN, zh_TW, pl
66 ## en, fr, ja, pt_BR, zh_CN, zh_TW, pl
67 lang = en
67 lang = en
68 cache_dir = %(here)s/data
68 cache_dir = %(here)s/data
69 index_dir = %(here)s/data/index
69 index_dir = %(here)s/data/index
70
70
71 ## perform a full repository scan on each server start, this should be
71 ## perform a full repository scan on each server start, this should be
72 ## set to false after first startup, to allow faster server restarts.
72 ## set to false after first startup, to allow faster server restarts.
73 initial_repo_scan = true
73 initial_repo_scan = true
74
74
75 ## uncomment and set this path to use archive download cache
75 ## uncomment and set this path to use archive download cache
76 #archive_cache_dir = /tmp/tarballcache
76 #archive_cache_dir = /tmp/tarballcache
77
77
78 ## change this to unique ID for security
78 ## change this to unique ID for security
79 app_instance_uuid = rc-production
79 app_instance_uuid = rc-production
80
80
81 ## cut off limit for large diffs (size in bytes)
81 ## cut off limit for large diffs (size in bytes)
82 cut_off_limit = 256000
82 cut_off_limit = 256000
83
83
84 ## use cache version of scm repo everywhere
84 ## use cache version of scm repo everywhere
85 vcs_full_cache = true
85 vcs_full_cache = true
86
86
87 ## force https in RhodeCode, fixes https redirects, assumes it's always https
87 ## force https in RhodeCode, fixes https redirects, assumes it's always https
88 force_https = false
88 force_https = false
89
89
90 ## use Strict-Transport-Security headers
90 ## use Strict-Transport-Security headers
91 use_htsts = false
91 use_htsts = false
92
92
93 ## number of commits stats will parse on each iteration
93 ## number of commits stats will parse on each iteration
94 commit_parse_limit = 25
94 commit_parse_limit = 25
95
95
96 ## number of items displayed in lightweight dashboard before paginating is shown
96 ## number of items displayed in lightweight dashboard before paginating is shown
97 dashboard_items = 100
97 dashboard_items = 100
98
98
99 ## use gravatar service to display avatars
99 ## use gravatar service to display avatars
100 use_gravatar = true
100 use_gravatar = true
101
101
102 ## path to git executable
102 ## path to git executable
103 git_path = git
103 git_path = git
104
104
105 ## git rev filter option, --all is the default filter, if you need to
105 ## git rev filter option, --all is the default filter, if you need to
106 ## hide all refs in changelog switch this to --branches --tags
106 ## hide all refs in changelog switch this to --branches --tags
107 git_rev_filter=--all
107 git_rev_filter=--all
108
108
109 ## RSS feed options
109 ## RSS feed options
110 rss_cut_off_limit = 256000
110 rss_cut_off_limit = 256000
111 rss_items_per_page = 10
111 rss_items_per_page = 10
112 rss_include_diff = false
112 rss_include_diff = false
113
113
114 ## options for showing and identifying changesets
114 ## options for showing and identifying changesets
115 show_sha_length = 12
115 show_sha_length = 12
116 show_revision_number = true
116 show_revision_number = true
117
117
118 ## gist URL alias, used to create nicer urls for gist. This should be an
119 ## url that does rewrites to _admin/gists/<gistid>.
120 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
121 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
122 gist_alias_url =
123
118 ## white list of API enabled controllers. This allows to add list of
124 ## white list of API enabled controllers. This allows to add list of
119 ## controllers to which access will be enabled by api_key. eg: to enable
125 ## controllers to which access will be enabled by api_key. eg: to enable
120 ## api access to raw_files put `FilesController:raw`, to enable access to patches
126 ## api access to raw_files put `FilesController:raw`, to enable access to patches
121 ## add `ChangesetController:changeset_patch`. This list should be "," separated
127 ## add `ChangesetController:changeset_patch`. This list should be "," separated
122 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
128 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
123 api_access_controllers_whitelist =
129 api_access_controllers_whitelist =
124
130
125 ## alternative_gravatar_url allows you to use your own avatar server application
131 ## alternative_gravatar_url allows you to use your own avatar server application
126 ## the following parts of the URL will be replaced
132 ## the following parts of the URL will be replaced
127 ## {email} user email
133 ## {email} user email
128 ## {md5email} md5 hash of the user email (like at gravatar.com)
134 ## {md5email} md5 hash of the user email (like at gravatar.com)
129 ## {size} size of the image that is expected from the server application
135 ## {size} size of the image that is expected from the server application
130 ## {scheme} http/https from RhodeCode server
136 ## {scheme} http/https from RhodeCode server
131 ## {netloc} network location from RhodeCode server
137 ## {netloc} network location from RhodeCode server
132 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
138 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
133 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
139 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
134
140
135
141
136 ## container auth options
142 ## container auth options
137 container_auth_enabled = false
143 container_auth_enabled = false
138 proxypass_auth_enabled = false
144 proxypass_auth_enabled = false
139
145
140 ## default encoding used to convert from and to unicode
146 ## default encoding used to convert from and to unicode
141 ## can be also a comma seperated list of encoding in case of mixed encodings
147 ## can be also a comma seperated list of encoding in case of mixed encodings
142 default_encoding = utf8
148 default_encoding = utf8
143
149
144 ## overwrite schema of clone url
150 ## overwrite schema of clone url
145 ## available vars:
151 ## available vars:
146 ## scheme - http/https
152 ## scheme - http/https
147 ## user - current user
153 ## user - current user
148 ## pass - password
154 ## pass - password
149 ## netloc - network location
155 ## netloc - network location
150 ## path - usually repo_name
156 ## path - usually repo_name
151
157
152 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
158 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
153
159
154 ## issue tracking mapping for commits messages
160 ## issue tracking mapping for commits messages
155 ## comment out issue_pat, issue_server, issue_prefix to enable
161 ## comment out issue_pat, issue_server, issue_prefix to enable
156
162
157 ## pattern to get the issues from commit messages
163 ## pattern to get the issues from commit messages
158 ## default one used here is #<numbers> with a regex passive group for `#`
164 ## default one used here is #<numbers> with a regex passive group for `#`
159 ## {id} will be all groups matched from this pattern
165 ## {id} will be all groups matched from this pattern
160
166
161 issue_pat = (?:\s*#)(\d+)
167 issue_pat = (?:\s*#)(\d+)
162
168
163 ## server url to the issue, each {id} will be replaced with match
169 ## server url to the issue, each {id} will be replaced with match
164 ## fetched from the regex and {repo} is replaced with full repository name
170 ## fetched from the regex and {repo} is replaced with full repository name
165 ## including groups {repo_name} is replaced with just name of repo
171 ## including groups {repo_name} is replaced with just name of repo
166
172
167 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
173 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
168
174
169 ## prefix to add to link to indicate it's an url
175 ## prefix to add to link to indicate it's an url
170 ## #314 will be replaced by <issue_prefix><id>
176 ## #314 will be replaced by <issue_prefix><id>
171
177
172 issue_prefix = #
178 issue_prefix = #
173
179
174 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
180 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
175 ## multiple patterns, to other issues server, wiki or others
181 ## multiple patterns, to other issues server, wiki or others
176 ## below an example how to create a wiki pattern
182 ## below an example how to create a wiki pattern
177 # #wiki-some-id -> https://mywiki.com/some-id
183 # #wiki-some-id -> https://mywiki.com/some-id
178
184
179 #issue_pat_wiki = (?:wiki-)(.+)
185 #issue_pat_wiki = (?:wiki-)(.+)
180 #issue_server_link_wiki = https://mywiki.com/{id}
186 #issue_server_link_wiki = https://mywiki.com/{id}
181 #issue_prefix_wiki = WIKI-
187 #issue_prefix_wiki = WIKI-
182
188
183
189
184 ## instance-id prefix
190 ## instance-id prefix
185 ## a prefix key for this instance used for cache invalidation when running
191 ## a prefix key for this instance used for cache invalidation when running
186 ## multiple instances of rhodecode, make sure it's globally unique for
192 ## multiple instances of rhodecode, make sure it's globally unique for
187 ## all running rhodecode instances. Leave empty if you don't use it
193 ## all running rhodecode instances. Leave empty if you don't use it
188 instance_id =
194 instance_id =
189
195
190 ## alternative return HTTP header for failed authentication. Default HTTP
196 ## alternative return HTTP header for failed authentication. Default HTTP
191 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
197 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
192 ## handling that. Set this variable to 403 to return HTTPForbidden
198 ## handling that. Set this variable to 403 to return HTTPForbidden
193 auth_ret_code =
199 auth_ret_code =
194
200
195 ## locking return code. When repository is locked return this HTTP code. 2XX
201 ## locking return code. When repository is locked return this HTTP code. 2XX
196 ## codes don't break the transactions while 4XX codes do
202 ## codes don't break the transactions while 4XX codes do
197 lock_ret_code = 423
203 lock_ret_code = 423
198
204
199
205
200 ####################################
206 ####################################
201 ### CELERY CONFIG ####
207 ### CELERY CONFIG ####
202 ####################################
208 ####################################
203 use_celery = false
209 use_celery = false
204 broker.host = localhost
210 broker.host = localhost
205 broker.vhost = rabbitmqhost
211 broker.vhost = rabbitmqhost
206 broker.port = 5672
212 broker.port = 5672
207 broker.user = rabbitmq
213 broker.user = rabbitmq
208 broker.password = qweqwe
214 broker.password = qweqwe
209
215
210 celery.imports = rhodecode.lib.celerylib.tasks
216 celery.imports = rhodecode.lib.celerylib.tasks
211
217
212 celery.result.backend = amqp
218 celery.result.backend = amqp
213 celery.result.dburi = amqp://
219 celery.result.dburi = amqp://
214 celery.result.serialier = json
220 celery.result.serialier = json
215
221
216 #celery.send.task.error.emails = true
222 #celery.send.task.error.emails = true
217 #celery.amqp.task.result.expires = 18000
223 #celery.amqp.task.result.expires = 18000
218
224
219 celeryd.concurrency = 2
225 celeryd.concurrency = 2
220 #celeryd.log.file = celeryd.log
226 #celeryd.log.file = celeryd.log
221 celeryd.log.level = debug
227 celeryd.log.level = debug
222 celeryd.max.tasks.per.child = 1
228 celeryd.max.tasks.per.child = 1
223
229
224 ## tasks will never be sent to the queue, but executed locally instead.
230 ## tasks will never be sent to the queue, but executed locally instead.
225 celery.always.eager = false
231 celery.always.eager = false
226
232
227 ####################################
233 ####################################
228 ### BEAKER CACHE ####
234 ### BEAKER CACHE ####
229 ####################################
235 ####################################
230 beaker.cache.data_dir=%(here)s/data/cache/data
236 beaker.cache.data_dir=%(here)s/data/cache/data
231 beaker.cache.lock_dir=%(here)s/data/cache/lock
237 beaker.cache.lock_dir=%(here)s/data/cache/lock
232
238
233 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
239 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
234
240
235 beaker.cache.super_short_term.type=memory
241 beaker.cache.super_short_term.type=memory
236 beaker.cache.super_short_term.expire=10
242 beaker.cache.super_short_term.expire=10
237 beaker.cache.super_short_term.key_length = 256
243 beaker.cache.super_short_term.key_length = 256
238
244
239 beaker.cache.short_term.type=memory
245 beaker.cache.short_term.type=memory
240 beaker.cache.short_term.expire=60
246 beaker.cache.short_term.expire=60
241 beaker.cache.short_term.key_length = 256
247 beaker.cache.short_term.key_length = 256
242
248
243 beaker.cache.long_term.type=memory
249 beaker.cache.long_term.type=memory
244 beaker.cache.long_term.expire=36000
250 beaker.cache.long_term.expire=36000
245 beaker.cache.long_term.key_length = 256
251 beaker.cache.long_term.key_length = 256
246
252
247 beaker.cache.sql_cache_short.type=memory
253 beaker.cache.sql_cache_short.type=memory
248 beaker.cache.sql_cache_short.expire=10
254 beaker.cache.sql_cache_short.expire=10
249 beaker.cache.sql_cache_short.key_length = 256
255 beaker.cache.sql_cache_short.key_length = 256
250
256
251 beaker.cache.sql_cache_med.type=memory
257 beaker.cache.sql_cache_med.type=memory
252 beaker.cache.sql_cache_med.expire=360
258 beaker.cache.sql_cache_med.expire=360
253 beaker.cache.sql_cache_med.key_length = 256
259 beaker.cache.sql_cache_med.key_length = 256
254
260
255 beaker.cache.sql_cache_long.type=file
261 beaker.cache.sql_cache_long.type=file
256 beaker.cache.sql_cache_long.expire=3600
262 beaker.cache.sql_cache_long.expire=3600
257 beaker.cache.sql_cache_long.key_length = 256
263 beaker.cache.sql_cache_long.key_length = 256
258
264
259 ####################################
265 ####################################
260 ### BEAKER SESSION ####
266 ### BEAKER SESSION ####
261 ####################################
267 ####################################
262 ## Type of storage used for the session, current types are
268 ## Type of storage used for the session, current types are
263 ## dbm, file, memcached, database, and memory.
269 ## dbm, file, memcached, database, and memory.
264 ## The storage uses the Container API
270 ## The storage uses the Container API
265 ## that is also used by the cache system.
271 ## that is also used by the cache system.
266
272
267 ## db session ##
273 ## db session ##
268 #beaker.session.type = ext:database
274 #beaker.session.type = ext:database
269 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
275 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
270 #beaker.session.table_name = db_session
276 #beaker.session.table_name = db_session
271
277
272 ## encrypted cookie client side session, good for many instances ##
278 ## encrypted cookie client side session, good for many instances ##
273 #beaker.session.type = cookie
279 #beaker.session.type = cookie
274
280
275 ## file based cookies (default) ##
281 ## file based cookies (default) ##
276 #beaker.session.type = file
282 #beaker.session.type = file
277
283
278
284
279 beaker.session.key = rhodecode
285 beaker.session.key = rhodecode
280 ## secure cookie requires AES python libraries
286 ## secure cookie requires AES python libraries
281 #beaker.session.encrypt_key = <key_for_encryption>
287 #beaker.session.encrypt_key = <key_for_encryption>
282 #beaker.session.validate_key = <validation_key>
288 #beaker.session.validate_key = <validation_key>
283
289
284 ## sets session as invalid if it haven't been accessed for given amount of time
290 ## sets session as invalid if it haven't been accessed for given amount of time
285 beaker.session.timeout = 2592000
291 beaker.session.timeout = 2592000
286 beaker.session.httponly = true
292 beaker.session.httponly = true
287 #beaker.session.cookie_path = /<your-prefix>
293 #beaker.session.cookie_path = /<your-prefix>
288
294
289 ## uncomment for https secure cookie
295 ## uncomment for https secure cookie
290 beaker.session.secure = false
296 beaker.session.secure = false
291
297
292 ## auto save the session to not to use .save()
298 ## auto save the session to not to use .save()
293 beaker.session.auto = False
299 beaker.session.auto = False
294
300
295 ## default cookie expiration time in seconds `true` expire at browser close ##
301 ## default cookie expiration time in seconds `true` expire at browser close ##
296 #beaker.session.cookie_expires = 3600
302 #beaker.session.cookie_expires = 3600
297
303
298
304
299 ############################
305 ############################
300 ## ERROR HANDLING SYSTEMS ##
306 ## ERROR HANDLING SYSTEMS ##
301 ############################
307 ############################
302
308
303 ####################
309 ####################
304 ### [errormator] ###
310 ### [errormator] ###
305 ####################
311 ####################
306
312
307 ## Errormator is tailored to work with RhodeCode, see
313 ## Errormator is tailored to work with RhodeCode, see
308 ## http://errormator.com for details how to obtain an account
314 ## http://errormator.com for details how to obtain an account
309 ## you must install python package `errormator_client` to make it work
315 ## you must install python package `errormator_client` to make it work
310
316
311 ## errormator enabled
317 ## errormator enabled
312 errormator = false
318 errormator = false
313
319
314 errormator.server_url = https://api.errormator.com
320 errormator.server_url = https://api.errormator.com
315 errormator.api_key = YOUR_API_KEY
321 errormator.api_key = YOUR_API_KEY
316
322
317 ## TWEAK AMOUNT OF INFO SENT HERE
323 ## TWEAK AMOUNT OF INFO SENT HERE
318
324
319 ## enables 404 error logging (default False)
325 ## enables 404 error logging (default False)
320 errormator.report_404 = false
326 errormator.report_404 = false
321
327
322 ## time in seconds after request is considered being slow (default 1)
328 ## time in seconds after request is considered being slow (default 1)
323 errormator.slow_request_time = 1
329 errormator.slow_request_time = 1
324
330
325 ## record slow requests in application
331 ## record slow requests in application
326 ## (needs to be enabled for slow datastore recording and time tracking)
332 ## (needs to be enabled for slow datastore recording and time tracking)
327 errormator.slow_requests = true
333 errormator.slow_requests = true
328
334
329 ## enable hooking to application loggers
335 ## enable hooking to application loggers
330 # errormator.logging = true
336 # errormator.logging = true
331
337
332 ## minimum log level for log capture
338 ## minimum log level for log capture
333 # errormator.logging.level = WARNING
339 # errormator.logging.level = WARNING
334
340
335 ## send logs only from erroneous/slow requests
341 ## send logs only from erroneous/slow requests
336 ## (saves API quota for intensive logging)
342 ## (saves API quota for intensive logging)
337 errormator.logging_on_error = false
343 errormator.logging_on_error = false
338
344
339 ## list of additonal keywords that should be grabbed from environ object
345 ## list of additonal keywords that should be grabbed from environ object
340 ## can be string with comma separated list of words in lowercase
346 ## can be string with comma separated list of words in lowercase
341 ## (by default client will always send following info:
347 ## (by default client will always send following info:
342 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
348 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
343 ## start with HTTP* this list be extended with additional keywords here
349 ## start with HTTP* this list be extended with additional keywords here
344 errormator.environ_keys_whitelist =
350 errormator.environ_keys_whitelist =
345
351
346
352
347 ## list of keywords that should be blanked from request object
353 ## list of keywords that should be blanked from request object
348 ## can be string with comma separated list of words in lowercase
354 ## can be string with comma separated list of words in lowercase
349 ## (by default client will always blank keys that contain following words
355 ## (by default client will always blank keys that contain following words
350 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
356 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
351 ## this list be extended with additional keywords set here
357 ## this list be extended with additional keywords set here
352 errormator.request_keys_blacklist =
358 errormator.request_keys_blacklist =
353
359
354
360
355 ## list of namespaces that should be ignores when gathering log entries
361 ## list of namespaces that should be ignores when gathering log entries
356 ## can be string with comma separated list of namespaces
362 ## can be string with comma separated list of namespaces
357 ## (by default the client ignores own entries: errormator_client.client)
363 ## (by default the client ignores own entries: errormator_client.client)
358 errormator.log_namespace_blacklist =
364 errormator.log_namespace_blacklist =
359
365
360
366
361 ################
367 ################
362 ### [sentry] ###
368 ### [sentry] ###
363 ################
369 ################
364
370
365 ## sentry is a alternative open source error aggregator
371 ## sentry is a alternative open source error aggregator
366 ## you must install python packages `sentry` and `raven` to enable
372 ## you must install python packages `sentry` and `raven` to enable
367
373
368 sentry.dsn = YOUR_DNS
374 sentry.dsn = YOUR_DNS
369 sentry.servers =
375 sentry.servers =
370 sentry.name =
376 sentry.name =
371 sentry.key =
377 sentry.key =
372 sentry.public_key =
378 sentry.public_key =
373 sentry.secret_key =
379 sentry.secret_key =
374 sentry.project =
380 sentry.project =
375 sentry.site =
381 sentry.site =
376 sentry.include_paths =
382 sentry.include_paths =
377 sentry.exclude_paths =
383 sentry.exclude_paths =
378
384
379
385
380 ################################################################################
386 ################################################################################
381 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
387 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
382 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
388 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
383 ## execute malicious code after an exception is raised. ##
389 ## execute malicious code after an exception is raised. ##
384 ################################################################################
390 ################################################################################
385 set debug = false
391 set debug = false
386
392
387 ##################################
393 ##################################
388 ### LOGVIEW CONFIG ###
394 ### LOGVIEW CONFIG ###
389 ##################################
395 ##################################
390 logview.sqlalchemy = #faa
396 logview.sqlalchemy = #faa
391 logview.pylons.templating = #bfb
397 logview.pylons.templating = #bfb
392 logview.pylons.util = #eee
398 logview.pylons.util = #eee
393
399
394 #########################################################
400 #########################################################
395 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
401 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
396 #########################################################
402 #########################################################
397 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
403 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
398 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
404 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
399 sqlalchemy.db1.echo = false
405 sqlalchemy.db1.echo = false
400 sqlalchemy.db1.pool_recycle = 3600
406 sqlalchemy.db1.pool_recycle = 3600
401 sqlalchemy.db1.convert_unicode = true
407 sqlalchemy.db1.convert_unicode = true
402
408
403 ################################
409 ################################
404 ### LOGGING CONFIGURATION ####
410 ### LOGGING CONFIGURATION ####
405 ################################
411 ################################
406 [loggers]
412 [loggers]
407 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
413 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
408
414
409 [handlers]
415 [handlers]
410 keys = console, console_sql
416 keys = console, console_sql
411
417
412 [formatters]
418 [formatters]
413 keys = generic, color_formatter, color_formatter_sql
419 keys = generic, color_formatter, color_formatter_sql
414
420
415 #############
421 #############
416 ## LOGGERS ##
422 ## LOGGERS ##
417 #############
423 #############
418 [logger_root]
424 [logger_root]
419 level = NOTSET
425 level = NOTSET
420 handlers = console
426 handlers = console
421
427
422 [logger_routes]
428 [logger_routes]
423 level = DEBUG
429 level = DEBUG
424 handlers =
430 handlers =
425 qualname = routes.middleware
431 qualname = routes.middleware
426 ## "level = DEBUG" logs the route matched and routing variables.
432 ## "level = DEBUG" logs the route matched and routing variables.
427 propagate = 1
433 propagate = 1
428
434
429 [logger_beaker]
435 [logger_beaker]
430 level = DEBUG
436 level = DEBUG
431 handlers =
437 handlers =
432 qualname = beaker.container
438 qualname = beaker.container
433 propagate = 1
439 propagate = 1
434
440
435 [logger_templates]
441 [logger_templates]
436 level = INFO
442 level = INFO
437 handlers =
443 handlers =
438 qualname = pylons.templating
444 qualname = pylons.templating
439 propagate = 1
445 propagate = 1
440
446
441 [logger_rhodecode]
447 [logger_rhodecode]
442 level = DEBUG
448 level = DEBUG
443 handlers =
449 handlers =
444 qualname = rhodecode
450 qualname = rhodecode
445 propagate = 1
451 propagate = 1
446
452
447 [logger_sqlalchemy]
453 [logger_sqlalchemy]
448 level = INFO
454 level = INFO
449 handlers = console_sql
455 handlers = console_sql
450 qualname = sqlalchemy.engine
456 qualname = sqlalchemy.engine
451 propagate = 0
457 propagate = 0
452
458
453 [logger_whoosh_indexer]
459 [logger_whoosh_indexer]
454 level = DEBUG
460 level = DEBUG
455 handlers =
461 handlers =
456 qualname = whoosh_indexer
462 qualname = whoosh_indexer
457 propagate = 1
463 propagate = 1
458
464
459 ##############
465 ##############
460 ## HANDLERS ##
466 ## HANDLERS ##
461 ##############
467 ##############
462
468
463 [handler_console]
469 [handler_console]
464 class = StreamHandler
470 class = StreamHandler
465 args = (sys.stderr,)
471 args = (sys.stderr,)
466 level = INFO
472 level = INFO
467 formatter = generic
473 formatter = generic
468
474
469 [handler_console_sql]
475 [handler_console_sql]
470 class = StreamHandler
476 class = StreamHandler
471 args = (sys.stderr,)
477 args = (sys.stderr,)
472 level = WARN
478 level = WARN
473 formatter = generic
479 formatter = generic
474
480
475 ################
481 ################
476 ## FORMATTERS ##
482 ## FORMATTERS ##
477 ################
483 ################
478
484
479 [formatter_generic]
485 [formatter_generic]
480 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
486 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
481 datefmt = %Y-%m-%d %H:%M:%S
487 datefmt = %Y-%m-%d %H:%M:%S
482
488
483 [formatter_color_formatter]
489 [formatter_color_formatter]
484 class=rhodecode.lib.colored_formatter.ColorFormatter
490 class=rhodecode.lib.colored_formatter.ColorFormatter
485 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
486 datefmt = %Y-%m-%d %H:%M:%S
492 datefmt = %Y-%m-%d %H:%M:%S
487
493
488 [formatter_color_formatter_sql]
494 [formatter_color_formatter_sql]
489 class=rhodecode.lib.colored_formatter.ColorFormatterSql
495 class=rhodecode.lib.colored_formatter.ColorFormatterSql
490 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
491 datefmt = %Y-%m-%d %H:%M:%S
497 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,501 +1,507 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ## PASTE
32 ## PASTE
33 ## nr of threads to spawn
33 ## nr of threads to spawn
34 #threadpool_workers = 5
34 #threadpool_workers = 5
35
35
36 ## max request before thread respawn
36 ## max request before thread respawn
37 #threadpool_max_requests = 10
37 #threadpool_max_requests = 10
38
38
39 ## option to use threads of process
39 ## option to use threads of process
40 #use_threadpool = true
40 #use_threadpool = true
41
41
42 #use = egg:Paste#http
42 #use = egg:Paste#http
43
43
44 ## WAITRESS
44 ## WAITRESS
45 threads = 5
45 threads = 5
46 ## 100GB
46 ## 100GB
47 max_request_body_size = 107374182400
47 max_request_body_size = 107374182400
48 use = egg:waitress#main
48 use = egg:waitress#main
49
49
50 host = 127.0.0.1
50 host = 127.0.0.1
51 port = 5000
51 port = 5000
52
52
53 ## prefix middleware for rc
53 ## prefix middleware for rc
54 #[filter:proxy-prefix]
54 #[filter:proxy-prefix]
55 #use = egg:PasteDeploy#prefix
55 #use = egg:PasteDeploy#prefix
56 #prefix = /<your-prefix>
56 #prefix = /<your-prefix>
57
57
58 [app:main]
58 [app:main]
59 use = egg:rhodecode
59 use = egg:rhodecode
60 ## enable proxy prefix middleware
60 ## enable proxy prefix middleware
61 #filter-with = proxy-prefix
61 #filter-with = proxy-prefix
62
62
63 full_stack = true
63 full_stack = true
64 static_files = true
64 static_files = true
65 ## Optional Languages
65 ## Optional Languages
66 ## en, fr, ja, pt_BR, zh_CN, zh_TW, pl
66 ## en, fr, ja, pt_BR, zh_CN, zh_TW, pl
67 lang = en
67 lang = en
68 cache_dir = %(here)s/data
68 cache_dir = %(here)s/data
69 index_dir = %(here)s/data/index
69 index_dir = %(here)s/data/index
70
70
71 ## perform a full repository scan on each server start, this should be
71 ## perform a full repository scan on each server start, this should be
72 ## set to false after first startup, to allow faster server restarts.
72 ## set to false after first startup, to allow faster server restarts.
73 initial_repo_scan = true
73 initial_repo_scan = true
74
74
75 ## uncomment and set this path to use archive download cache
75 ## uncomment and set this path to use archive download cache
76 #archive_cache_dir = /tmp/tarballcache
76 #archive_cache_dir = /tmp/tarballcache
77
77
78 ## change this to unique ID for security
78 ## change this to unique ID for security
79 app_instance_uuid = ${app_instance_uuid}
79 app_instance_uuid = ${app_instance_uuid}
80
80
81 ## cut off limit for large diffs (size in bytes)
81 ## cut off limit for large diffs (size in bytes)
82 cut_off_limit = 256000
82 cut_off_limit = 256000
83
83
84 ## use cache version of scm repo everywhere
84 ## use cache version of scm repo everywhere
85 vcs_full_cache = true
85 vcs_full_cache = true
86
86
87 ## force https in RhodeCode, fixes https redirects, assumes it's always https
87 ## force https in RhodeCode, fixes https redirects, assumes it's always https
88 force_https = false
88 force_https = false
89
89
90 ## use Strict-Transport-Security headers
90 ## use Strict-Transport-Security headers
91 use_htsts = false
91 use_htsts = false
92
92
93 ## number of commits stats will parse on each iteration
93 ## number of commits stats will parse on each iteration
94 commit_parse_limit = 25
94 commit_parse_limit = 25
95
95
96 ## number of items displayed in lightweight dashboard before paginating is shown
96 ## number of items displayed in lightweight dashboard before paginating is shown
97 dashboard_items = 100
97 dashboard_items = 100
98
98
99 ## use gravatar service to display avatars
99 ## use gravatar service to display avatars
100 use_gravatar = true
100 use_gravatar = true
101
101
102 ## path to git executable
102 ## path to git executable
103 git_path = git
103 git_path = git
104
104
105 ## git rev filter option, --all is the default filter, if you need to
105 ## git rev filter option, --all is the default filter, if you need to
106 ## hide all refs in changelog switch this to --branches --tags
106 ## hide all refs in changelog switch this to --branches --tags
107 git_rev_filter=--all
107 git_rev_filter=--all
108
108
109 ## RSS feed options
109 ## RSS feed options
110 rss_cut_off_limit = 256000
110 rss_cut_off_limit = 256000
111 rss_items_per_page = 10
111 rss_items_per_page = 10
112 rss_include_diff = false
112 rss_include_diff = false
113
113
114 ## options for showing and identifying changesets
114 ## options for showing and identifying changesets
115 show_sha_length = 12
115 show_sha_length = 12
116 show_revision_number = true
116 show_revision_number = true
117
117
118 ## gist URL alias, used to create nicer urls for gist. This should be an
119 ## url that does rewrites to _admin/gists/<gistid>.
120 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
121 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
122 gist_alias_url =
123
118 ## white list of API enabled controllers. This allows to add list of
124 ## white list of API enabled controllers. This allows to add list of
119 ## controllers to which access will be enabled by api_key. eg: to enable
125 ## controllers to which access will be enabled by api_key. eg: to enable
120 ## api access to raw_files put `FilesController:raw`, to enable access to patches
126 ## api access to raw_files put `FilesController:raw`, to enable access to patches
121 ## add `ChangesetController:changeset_patch`. This list should be "," separated
127 ## add `ChangesetController:changeset_patch`. This list should be "," separated
122 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
128 ## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
123 api_access_controllers_whitelist =
129 api_access_controllers_whitelist =
124
130
125 ## alternative_gravatar_url allows you to use your own avatar server application
131 ## alternative_gravatar_url allows you to use your own avatar server application
126 ## the following parts of the URL will be replaced
132 ## the following parts of the URL will be replaced
127 ## {email} user email
133 ## {email} user email
128 ## {md5email} md5 hash of the user email (like at gravatar.com)
134 ## {md5email} md5 hash of the user email (like at gravatar.com)
129 ## {size} size of the image that is expected from the server application
135 ## {size} size of the image that is expected from the server application
130 ## {scheme} http/https from RhodeCode server
136 ## {scheme} http/https from RhodeCode server
131 ## {netloc} network location from RhodeCode server
137 ## {netloc} network location from RhodeCode server
132 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
138 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
133 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
139 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
134
140
135
141
136 ## container auth options
142 ## container auth options
137 container_auth_enabled = false
143 container_auth_enabled = false
138 proxypass_auth_enabled = false
144 proxypass_auth_enabled = false
139
145
140 ## default encoding used to convert from and to unicode
146 ## default encoding used to convert from and to unicode
141 ## can be also a comma seperated list of encoding in case of mixed encodings
147 ## can be also a comma seperated list of encoding in case of mixed encodings
142 default_encoding = utf8
148 default_encoding = utf8
143
149
144 ## overwrite schema of clone url
150 ## overwrite schema of clone url
145 ## available vars:
151 ## available vars:
146 ## scheme - http/https
152 ## scheme - http/https
147 ## user - current user
153 ## user - current user
148 ## pass - password
154 ## pass - password
149 ## netloc - network location
155 ## netloc - network location
150 ## path - usually repo_name
156 ## path - usually repo_name
151
157
152 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
158 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
153
159
154 ## issue tracking mapping for commits messages
160 ## issue tracking mapping for commits messages
155 ## comment out issue_pat, issue_server, issue_prefix to enable
161 ## comment out issue_pat, issue_server, issue_prefix to enable
156
162
157 ## pattern to get the issues from commit messages
163 ## pattern to get the issues from commit messages
158 ## default one used here is #<numbers> with a regex passive group for `#`
164 ## default one used here is #<numbers> with a regex passive group for `#`
159 ## {id} will be all groups matched from this pattern
165 ## {id} will be all groups matched from this pattern
160
166
161 issue_pat = (?:\s*#)(\d+)
167 issue_pat = (?:\s*#)(\d+)
162
168
163 ## server url to the issue, each {id} will be replaced with match
169 ## server url to the issue, each {id} will be replaced with match
164 ## fetched from the regex and {repo} is replaced with full repository name
170 ## fetched from the regex and {repo} is replaced with full repository name
165 ## including groups {repo_name} is replaced with just name of repo
171 ## including groups {repo_name} is replaced with just name of repo
166
172
167 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
173 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
168
174
169 ## prefix to add to link to indicate it's an url
175 ## prefix to add to link to indicate it's an url
170 ## #314 will be replaced by <issue_prefix><id>
176 ## #314 will be replaced by <issue_prefix><id>
171
177
172 issue_prefix = #
178 issue_prefix = #
173
179
174 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
180 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
175 ## multiple patterns, to other issues server, wiki or others
181 ## multiple patterns, to other issues server, wiki or others
176 ## below an example how to create a wiki pattern
182 ## below an example how to create a wiki pattern
177 # #wiki-some-id -> https://mywiki.com/some-id
183 # #wiki-some-id -> https://mywiki.com/some-id
178
184
179 #issue_pat_wiki = (?:wiki-)(.+)
185 #issue_pat_wiki = (?:wiki-)(.+)
180 #issue_server_link_wiki = https://mywiki.com/{id}
186 #issue_server_link_wiki = https://mywiki.com/{id}
181 #issue_prefix_wiki = WIKI-
187 #issue_prefix_wiki = WIKI-
182
188
183
189
184 ## instance-id prefix
190 ## instance-id prefix
185 ## a prefix key for this instance used for cache invalidation when running
191 ## a prefix key for this instance used for cache invalidation when running
186 ## multiple instances of rhodecode, make sure it's globally unique for
192 ## multiple instances of rhodecode, make sure it's globally unique for
187 ## all running rhodecode instances. Leave empty if you don't use it
193 ## all running rhodecode instances. Leave empty if you don't use it
188 instance_id =
194 instance_id =
189
195
190 ## alternative return HTTP header for failed authentication. Default HTTP
196 ## alternative return HTTP header for failed authentication. Default HTTP
191 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
197 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
192 ## handling that. Set this variable to 403 to return HTTPForbidden
198 ## handling that. Set this variable to 403 to return HTTPForbidden
193 auth_ret_code =
199 auth_ret_code =
194
200
195 ## locking return code. When repository is locked return this HTTP code. 2XX
201 ## locking return code. When repository is locked return this HTTP code. 2XX
196 ## codes don't break the transactions while 4XX codes do
202 ## codes don't break the transactions while 4XX codes do
197 lock_ret_code = 423
203 lock_ret_code = 423
198
204
199
205
200 ####################################
206 ####################################
201 ### CELERY CONFIG ####
207 ### CELERY CONFIG ####
202 ####################################
208 ####################################
203 use_celery = false
209 use_celery = false
204 broker.host = localhost
210 broker.host = localhost
205 broker.vhost = rabbitmqhost
211 broker.vhost = rabbitmqhost
206 broker.port = 5672
212 broker.port = 5672
207 broker.user = rabbitmq
213 broker.user = rabbitmq
208 broker.password = qweqwe
214 broker.password = qweqwe
209
215
210 celery.imports = rhodecode.lib.celerylib.tasks
216 celery.imports = rhodecode.lib.celerylib.tasks
211
217
212 celery.result.backend = amqp
218 celery.result.backend = amqp
213 celery.result.dburi = amqp://
219 celery.result.dburi = amqp://
214 celery.result.serialier = json
220 celery.result.serialier = json
215
221
216 #celery.send.task.error.emails = true
222 #celery.send.task.error.emails = true
217 #celery.amqp.task.result.expires = 18000
223 #celery.amqp.task.result.expires = 18000
218
224
219 celeryd.concurrency = 2
225 celeryd.concurrency = 2
220 #celeryd.log.file = celeryd.log
226 #celeryd.log.file = celeryd.log
221 celeryd.log.level = debug
227 celeryd.log.level = debug
222 celeryd.max.tasks.per.child = 1
228 celeryd.max.tasks.per.child = 1
223
229
224 ## tasks will never be sent to the queue, but executed locally instead.
230 ## tasks will never be sent to the queue, but executed locally instead.
225 celery.always.eager = false
231 celery.always.eager = false
226
232
227 ####################################
233 ####################################
228 ### BEAKER CACHE ####
234 ### BEAKER CACHE ####
229 ####################################
235 ####################################
230 beaker.cache.data_dir=%(here)s/data/cache/data
236 beaker.cache.data_dir=%(here)s/data/cache/data
231 beaker.cache.lock_dir=%(here)s/data/cache/lock
237 beaker.cache.lock_dir=%(here)s/data/cache/lock
232
238
233 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
239 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
234
240
235 beaker.cache.super_short_term.type=memory
241 beaker.cache.super_short_term.type=memory
236 beaker.cache.super_short_term.expire=10
242 beaker.cache.super_short_term.expire=10
237 beaker.cache.super_short_term.key_length = 256
243 beaker.cache.super_short_term.key_length = 256
238
244
239 beaker.cache.short_term.type=memory
245 beaker.cache.short_term.type=memory
240 beaker.cache.short_term.expire=60
246 beaker.cache.short_term.expire=60
241 beaker.cache.short_term.key_length = 256
247 beaker.cache.short_term.key_length = 256
242
248
243 beaker.cache.long_term.type=memory
249 beaker.cache.long_term.type=memory
244 beaker.cache.long_term.expire=36000
250 beaker.cache.long_term.expire=36000
245 beaker.cache.long_term.key_length = 256
251 beaker.cache.long_term.key_length = 256
246
252
247 beaker.cache.sql_cache_short.type=memory
253 beaker.cache.sql_cache_short.type=memory
248 beaker.cache.sql_cache_short.expire=10
254 beaker.cache.sql_cache_short.expire=10
249 beaker.cache.sql_cache_short.key_length = 256
255 beaker.cache.sql_cache_short.key_length = 256
250
256
251 beaker.cache.sql_cache_med.type=memory
257 beaker.cache.sql_cache_med.type=memory
252 beaker.cache.sql_cache_med.expire=360
258 beaker.cache.sql_cache_med.expire=360
253 beaker.cache.sql_cache_med.key_length = 256
259 beaker.cache.sql_cache_med.key_length = 256
254
260
255 beaker.cache.sql_cache_long.type=file
261 beaker.cache.sql_cache_long.type=file
256 beaker.cache.sql_cache_long.expire=3600
262 beaker.cache.sql_cache_long.expire=3600
257 beaker.cache.sql_cache_long.key_length = 256
263 beaker.cache.sql_cache_long.key_length = 256
258
264
259 ####################################
265 ####################################
260 ### BEAKER SESSION ####
266 ### BEAKER SESSION ####
261 ####################################
267 ####################################
262 ## Type of storage used for the session, current types are
268 ## Type of storage used for the session, current types are
263 ## dbm, file, memcached, database, and memory.
269 ## dbm, file, memcached, database, and memory.
264 ## The storage uses the Container API
270 ## The storage uses the Container API
265 ## that is also used by the cache system.
271 ## that is also used by the cache system.
266
272
267 ## db session ##
273 ## db session ##
268 #beaker.session.type = ext:database
274 #beaker.session.type = ext:database
269 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
275 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
270 #beaker.session.table_name = db_session
276 #beaker.session.table_name = db_session
271
277
272 ## encrypted cookie client side session, good for many instances ##
278 ## encrypted cookie client side session, good for many instances ##
273 #beaker.session.type = cookie
279 #beaker.session.type = cookie
274
280
275 ## file based cookies (default) ##
281 ## file based cookies (default) ##
276 #beaker.session.type = file
282 #beaker.session.type = file
277
283
278
284
279 beaker.session.key = rhodecode
285 beaker.session.key = rhodecode
280 ## secure cookie requires AES python libraries
286 ## secure cookie requires AES python libraries
281 #beaker.session.encrypt_key = <key_for_encryption>
287 #beaker.session.encrypt_key = <key_for_encryption>
282 #beaker.session.validate_key = <validation_key>
288 #beaker.session.validate_key = <validation_key>
283
289
284 ## sets session as invalid if it haven't been accessed for given amount of time
290 ## sets session as invalid if it haven't been accessed for given amount of time
285 beaker.session.timeout = 2592000
291 beaker.session.timeout = 2592000
286 beaker.session.httponly = true
292 beaker.session.httponly = true
287 #beaker.session.cookie_path = /<your-prefix>
293 #beaker.session.cookie_path = /<your-prefix>
288
294
289 ## uncomment for https secure cookie
295 ## uncomment for https secure cookie
290 beaker.session.secure = false
296 beaker.session.secure = false
291
297
292 ## auto save the session to not to use .save()
298 ## auto save the session to not to use .save()
293 beaker.session.auto = False
299 beaker.session.auto = False
294
300
295 ## default cookie expiration time in seconds `true` expire at browser close ##
301 ## default cookie expiration time in seconds `true` expire at browser close ##
296 #beaker.session.cookie_expires = 3600
302 #beaker.session.cookie_expires = 3600
297
303
298
304
299 ############################
305 ############################
300 ## ERROR HANDLING SYSTEMS ##
306 ## ERROR HANDLING SYSTEMS ##
301 ############################
307 ############################
302
308
303 ####################
309 ####################
304 ### [errormator] ###
310 ### [errormator] ###
305 ####################
311 ####################
306
312
307 ## Errormator is tailored to work with RhodeCode, see
313 ## Errormator is tailored to work with RhodeCode, see
308 ## http://errormator.com for details how to obtain an account
314 ## http://errormator.com for details how to obtain an account
309 ## you must install python package `errormator_client` to make it work
315 ## you must install python package `errormator_client` to make it work
310
316
311 ## errormator enabled
317 ## errormator enabled
312 errormator = false
318 errormator = false
313
319
314 errormator.server_url = https://api.errormator.com
320 errormator.server_url = https://api.errormator.com
315 errormator.api_key = YOUR_API_KEY
321 errormator.api_key = YOUR_API_KEY
316
322
317 ## TWEAK AMOUNT OF INFO SENT HERE
323 ## TWEAK AMOUNT OF INFO SENT HERE
318
324
319 ## enables 404 error logging (default False)
325 ## enables 404 error logging (default False)
320 errormator.report_404 = false
326 errormator.report_404 = false
321
327
322 ## time in seconds after request is considered being slow (default 1)
328 ## time in seconds after request is considered being slow (default 1)
323 errormator.slow_request_time = 1
329 errormator.slow_request_time = 1
324
330
325 ## record slow requests in application
331 ## record slow requests in application
326 ## (needs to be enabled for slow datastore recording and time tracking)
332 ## (needs to be enabled for slow datastore recording and time tracking)
327 errormator.slow_requests = true
333 errormator.slow_requests = true
328
334
329 ## enable hooking to application loggers
335 ## enable hooking to application loggers
330 # errormator.logging = true
336 # errormator.logging = true
331
337
332 ## minimum log level for log capture
338 ## minimum log level for log capture
333 # errormator.logging.level = WARNING
339 # errormator.logging.level = WARNING
334
340
335 ## send logs only from erroneous/slow requests
341 ## send logs only from erroneous/slow requests
336 ## (saves API quota for intensive logging)
342 ## (saves API quota for intensive logging)
337 errormator.logging_on_error = false
343 errormator.logging_on_error = false
338
344
339 ## list of additonal keywords that should be grabbed from environ object
345 ## list of additonal keywords that should be grabbed from environ object
340 ## can be string with comma separated list of words in lowercase
346 ## can be string with comma separated list of words in lowercase
341 ## (by default client will always send following info:
347 ## (by default client will always send following info:
342 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
348 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
343 ## start with HTTP* this list be extended with additional keywords here
349 ## start with HTTP* this list be extended with additional keywords here
344 errormator.environ_keys_whitelist =
350 errormator.environ_keys_whitelist =
345
351
346
352
347 ## list of keywords that should be blanked from request object
353 ## list of keywords that should be blanked from request object
348 ## can be string with comma separated list of words in lowercase
354 ## can be string with comma separated list of words in lowercase
349 ## (by default client will always blank keys that contain following words
355 ## (by default client will always blank keys that contain following words
350 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
356 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
351 ## this list be extended with additional keywords set here
357 ## this list be extended with additional keywords set here
352 errormator.request_keys_blacklist =
358 errormator.request_keys_blacklist =
353
359
354
360
355 ## list of namespaces that should be ignores when gathering log entries
361 ## list of namespaces that should be ignores when gathering log entries
356 ## can be string with comma separated list of namespaces
362 ## can be string with comma separated list of namespaces
357 ## (by default the client ignores own entries: errormator_client.client)
363 ## (by default the client ignores own entries: errormator_client.client)
358 errormator.log_namespace_blacklist =
364 errormator.log_namespace_blacklist =
359
365
360
366
361 ################
367 ################
362 ### [sentry] ###
368 ### [sentry] ###
363 ################
369 ################
364
370
365 ## sentry is a alternative open source error aggregator
371 ## sentry is a alternative open source error aggregator
366 ## you must install python packages `sentry` and `raven` to enable
372 ## you must install python packages `sentry` and `raven` to enable
367
373
368 sentry.dsn = YOUR_DNS
374 sentry.dsn = YOUR_DNS
369 sentry.servers =
375 sentry.servers =
370 sentry.name =
376 sentry.name =
371 sentry.key =
377 sentry.key =
372 sentry.public_key =
378 sentry.public_key =
373 sentry.secret_key =
379 sentry.secret_key =
374 sentry.project =
380 sentry.project =
375 sentry.site =
381 sentry.site =
376 sentry.include_paths =
382 sentry.include_paths =
377 sentry.exclude_paths =
383 sentry.exclude_paths =
378
384
379
385
380 ################################################################################
386 ################################################################################
381 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
387 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
382 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
388 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
383 ## execute malicious code after an exception is raised. ##
389 ## execute malicious code after an exception is raised. ##
384 ################################################################################
390 ################################################################################
385 set debug = false
391 set debug = false
386
392
387 ##################################
393 ##################################
388 ### LOGVIEW CONFIG ###
394 ### LOGVIEW CONFIG ###
389 ##################################
395 ##################################
390 logview.sqlalchemy = #faa
396 logview.sqlalchemy = #faa
391 logview.pylons.templating = #bfb
397 logview.pylons.templating = #bfb
392 logview.pylons.util = #eee
398 logview.pylons.util = #eee
393
399
394 #########################################################
400 #########################################################
395 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
401 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
396 #########################################################
402 #########################################################
397
403
398 # SQLITE [default]
404 # SQLITE [default]
399 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
405 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
400
406
401 # POSTGRESQL
407 # POSTGRESQL
402 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
408 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
403
409
404 # MySQL
410 # MySQL
405 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
411 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
406
412
407 # see sqlalchemy docs for others
413 # see sqlalchemy docs for others
408
414
409 sqlalchemy.db1.echo = false
415 sqlalchemy.db1.echo = false
410 sqlalchemy.db1.pool_recycle = 3600
416 sqlalchemy.db1.pool_recycle = 3600
411 sqlalchemy.db1.convert_unicode = true
417 sqlalchemy.db1.convert_unicode = true
412
418
413 ################################
419 ################################
414 ### LOGGING CONFIGURATION ####
420 ### LOGGING CONFIGURATION ####
415 ################################
421 ################################
416 [loggers]
422 [loggers]
417 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
423 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
418
424
419 [handlers]
425 [handlers]
420 keys = console, console_sql
426 keys = console, console_sql
421
427
422 [formatters]
428 [formatters]
423 keys = generic, color_formatter, color_formatter_sql
429 keys = generic, color_formatter, color_formatter_sql
424
430
425 #############
431 #############
426 ## LOGGERS ##
432 ## LOGGERS ##
427 #############
433 #############
428 [logger_root]
434 [logger_root]
429 level = NOTSET
435 level = NOTSET
430 handlers = console
436 handlers = console
431
437
432 [logger_routes]
438 [logger_routes]
433 level = DEBUG
439 level = DEBUG
434 handlers =
440 handlers =
435 qualname = routes.middleware
441 qualname = routes.middleware
436 ## "level = DEBUG" logs the route matched and routing variables.
442 ## "level = DEBUG" logs the route matched and routing variables.
437 propagate = 1
443 propagate = 1
438
444
439 [logger_beaker]
445 [logger_beaker]
440 level = DEBUG
446 level = DEBUG
441 handlers =
447 handlers =
442 qualname = beaker.container
448 qualname = beaker.container
443 propagate = 1
449 propagate = 1
444
450
445 [logger_templates]
451 [logger_templates]
446 level = INFO
452 level = INFO
447 handlers =
453 handlers =
448 qualname = pylons.templating
454 qualname = pylons.templating
449 propagate = 1
455 propagate = 1
450
456
451 [logger_rhodecode]
457 [logger_rhodecode]
452 level = DEBUG
458 level = DEBUG
453 handlers =
459 handlers =
454 qualname = rhodecode
460 qualname = rhodecode
455 propagate = 1
461 propagate = 1
456
462
457 [logger_sqlalchemy]
463 [logger_sqlalchemy]
458 level = INFO
464 level = INFO
459 handlers = console_sql
465 handlers = console_sql
460 qualname = sqlalchemy.engine
466 qualname = sqlalchemy.engine
461 propagate = 0
467 propagate = 0
462
468
463 [logger_whoosh_indexer]
469 [logger_whoosh_indexer]
464 level = DEBUG
470 level = DEBUG
465 handlers =
471 handlers =
466 qualname = whoosh_indexer
472 qualname = whoosh_indexer
467 propagate = 1
473 propagate = 1
468
474
469 ##############
475 ##############
470 ## HANDLERS ##
476 ## HANDLERS ##
471 ##############
477 ##############
472
478
473 [handler_console]
479 [handler_console]
474 class = StreamHandler
480 class = StreamHandler
475 args = (sys.stderr,)
481 args = (sys.stderr,)
476 level = INFO
482 level = INFO
477 formatter = generic
483 formatter = generic
478
484
479 [handler_console_sql]
485 [handler_console_sql]
480 class = StreamHandler
486 class = StreamHandler
481 args = (sys.stderr,)
487 args = (sys.stderr,)
482 level = WARN
488 level = WARN
483 formatter = generic
489 formatter = generic
484
490
485 ################
491 ################
486 ## FORMATTERS ##
492 ## FORMATTERS ##
487 ################
493 ################
488
494
489 [formatter_generic]
495 [formatter_generic]
490 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
491 datefmt = %Y-%m-%d %H:%M:%S
497 datefmt = %Y-%m-%d %H:%M:%S
492
498
493 [formatter_color_formatter]
499 [formatter_color_formatter]
494 class=rhodecode.lib.colored_formatter.ColorFormatter
500 class=rhodecode.lib.colored_formatter.ColorFormatter
495 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
496 datefmt = %Y-%m-%d %H:%M:%S
502 datefmt = %Y-%m-%d %H:%M:%S
497
503
498 [formatter_color_formatter_sql]
504 [formatter_color_formatter_sql]
499 class=rhodecode.lib.colored_formatter.ColorFormatterSql
505 class=rhodecode.lib.colored_formatter.ColorFormatterSql
500 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
506 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
501 datefmt = %Y-%m-%d %H:%M:%S
507 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,2213 +1,2218 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import time
27 import logging
28 import logging
28 import datetime
29 import datetime
29 import traceback
30 import traceback
30 import hashlib
31 import hashlib
31 import time
32 import collections
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48
48
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model.meta import Base, Session
54 from rhodecode.model.meta import Base, Session
55
55
56 URL_SEP = '/'
56 URL_SEP = '/'
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 #==============================================================================
59 #==============================================================================
60 # BASE CLASSES
60 # BASE CLASSES
61 #==============================================================================
61 #==============================================================================
62
62
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64
64
65
65
66 class BaseModel(object):
66 class BaseModel(object):
67 """
67 """
68 Base Model for all classess
68 Base Model for all classess
69 """
69 """
70
70
71 @classmethod
71 @classmethod
72 def _get_keys(cls):
72 def _get_keys(cls):
73 """return column names for this model """
73 """return column names for this model """
74 return class_mapper(cls).c.keys()
74 return class_mapper(cls).c.keys()
75
75
76 def get_dict(self):
76 def get_dict(self):
77 """
77 """
78 return dict with keys and values corresponding
78 return dict with keys and values corresponding
79 to this model data """
79 to this model data """
80
80
81 d = {}
81 d = {}
82 for k in self._get_keys():
82 for k in self._get_keys():
83 d[k] = getattr(self, k)
83 d[k] = getattr(self, k)
84
84
85 # also use __json__() if present to get additional fields
85 # also use __json__() if present to get additional fields
86 _json_attr = getattr(self, '__json__', None)
86 _json_attr = getattr(self, '__json__', None)
87 if _json_attr:
87 if _json_attr:
88 # update with attributes from __json__
88 # update with attributes from __json__
89 if callable(_json_attr):
89 if callable(_json_attr):
90 _json_attr = _json_attr()
90 _json_attr = _json_attr()
91 for k, val in _json_attr.iteritems():
91 for k, val in _json_attr.iteritems():
92 d[k] = val
92 d[k] = val
93 return d
93 return d
94
94
95 def get_appstruct(self):
95 def get_appstruct(self):
96 """return list with keys and values tupples corresponding
96 """return list with keys and values tupples corresponding
97 to this model data """
97 to this model data """
98
98
99 l = []
99 l = []
100 for k in self._get_keys():
100 for k in self._get_keys():
101 l.append((k, getattr(self, k),))
101 l.append((k, getattr(self, k),))
102 return l
102 return l
103
103
104 def populate_obj(self, populate_dict):
104 def populate_obj(self, populate_dict):
105 """populate model with data from given populate_dict"""
105 """populate model with data from given populate_dict"""
106
106
107 for k in self._get_keys():
107 for k in self._get_keys():
108 if k in populate_dict:
108 if k in populate_dict:
109 setattr(self, k, populate_dict[k])
109 setattr(self, k, populate_dict[k])
110
110
111 @classmethod
111 @classmethod
112 def query(cls):
112 def query(cls):
113 return Session().query(cls)
113 return Session().query(cls)
114
114
115 @classmethod
115 @classmethod
116 def get(cls, id_):
116 def get(cls, id_):
117 if id_:
117 if id_:
118 return cls.query().get(id_)
118 return cls.query().get(id_)
119
119
120 @classmethod
120 @classmethod
121 def get_or_404(cls, id_):
121 def get_or_404(cls, id_):
122 try:
122 try:
123 id_ = int(id_)
123 id_ = int(id_)
124 except (TypeError, ValueError):
124 except (TypeError, ValueError):
125 raise HTTPNotFound
125 raise HTTPNotFound
126
126
127 res = cls.query().get(id_)
127 res = cls.query().get(id_)
128 if not res:
128 if not res:
129 raise HTTPNotFound
129 raise HTTPNotFound
130 return res
130 return res
131
131
132 @classmethod
132 @classmethod
133 def getAll(cls):
133 def getAll(cls):
134 # deprecated and left for backward compatibility
134 # deprecated and left for backward compatibility
135 return cls.get_all()
135 return cls.get_all()
136
136
137 @classmethod
137 @classmethod
138 def get_all(cls):
138 def get_all(cls):
139 return cls.query().all()
139 return cls.query().all()
140
140
141 @classmethod
141 @classmethod
142 def delete(cls, id_):
142 def delete(cls, id_):
143 obj = cls.query().get(id_)
143 obj = cls.query().get(id_)
144 Session().delete(obj)
144 Session().delete(obj)
145
145
146 def __repr__(self):
146 def __repr__(self):
147 if hasattr(self, '__unicode__'):
147 if hasattr(self, '__unicode__'):
148 # python repr needs to return str
148 # python repr needs to return str
149 return safe_str(self.__unicode__())
149 return safe_str(self.__unicode__())
150 return '<DB:%s>' % (self.__class__.__name__)
150 return '<DB:%s>' % (self.__class__.__name__)
151
151
152
152
153 class RhodeCodeSetting(Base, BaseModel):
153 class RhodeCodeSetting(Base, BaseModel):
154 __tablename__ = 'rhodecode_settings'
154 __tablename__ = 'rhodecode_settings'
155 __table_args__ = (
155 __table_args__ = (
156 UniqueConstraint('app_settings_name'),
156 UniqueConstraint('app_settings_name'),
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
158 'mysql_charset': 'utf8'}
158 'mysql_charset': 'utf8'}
159 )
159 )
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163
163
164 def __init__(self, k='', v=''):
164 def __init__(self, k='', v=''):
165 self.app_settings_name = k
165 self.app_settings_name = k
166 self.app_settings_value = v
166 self.app_settings_value = v
167
167
168 @validates('_app_settings_value')
168 @validates('_app_settings_value')
169 def validate_settings_value(self, key, val):
169 def validate_settings_value(self, key, val):
170 assert type(val) == unicode
170 assert type(val) == unicode
171 return val
171 return val
172
172
173 @hybrid_property
173 @hybrid_property
174 def app_settings_value(self):
174 def app_settings_value(self):
175 v = self._app_settings_value
175 v = self._app_settings_value
176 if self.app_settings_name in ["ldap_active",
176 if self.app_settings_name in ["ldap_active",
177 "default_repo_enable_statistics",
177 "default_repo_enable_statistics",
178 "default_repo_enable_locking",
178 "default_repo_enable_locking",
179 "default_repo_private",
179 "default_repo_private",
180 "default_repo_enable_downloads"]:
180 "default_repo_enable_downloads"]:
181 v = str2bool(v)
181 v = str2bool(v)
182 return v
182 return v
183
183
184 @app_settings_value.setter
184 @app_settings_value.setter
185 def app_settings_value(self, val):
185 def app_settings_value(self, val):
186 """
186 """
187 Setter that will always make sure we use unicode in app_settings_value
187 Setter that will always make sure we use unicode in app_settings_value
188
188
189 :param val:
189 :param val:
190 """
190 """
191 self._app_settings_value = safe_unicode(val)
191 self._app_settings_value = safe_unicode(val)
192
192
193 def __unicode__(self):
193 def __unicode__(self):
194 return u"<%s('%s:%s')>" % (
194 return u"<%s('%s:%s')>" % (
195 self.__class__.__name__,
195 self.__class__.__name__,
196 self.app_settings_name, self.app_settings_value
196 self.app_settings_name, self.app_settings_value
197 )
197 )
198
198
199 @classmethod
199 @classmethod
200 def get_by_name(cls, key):
200 def get_by_name(cls, key):
201 return cls.query()\
201 return cls.query()\
202 .filter(cls.app_settings_name == key).scalar()
202 .filter(cls.app_settings_name == key).scalar()
203
203
204 @classmethod
204 @classmethod
205 def get_by_name_or_create(cls, key):
205 def get_by_name_or_create(cls, key):
206 res = cls.get_by_name(key)
206 res = cls.get_by_name(key)
207 if not res:
207 if not res:
208 res = cls(key)
208 res = cls(key)
209 return res
209 return res
210
210
211 @classmethod
211 @classmethod
212 def get_app_settings(cls, cache=False):
212 def get_app_settings(cls, cache=False):
213
213
214 ret = cls.query()
214 ret = cls.query()
215
215
216 if cache:
216 if cache:
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
218
218
219 if not ret:
219 if not ret:
220 raise Exception('Could not get application settings !')
220 raise Exception('Could not get application settings !')
221 settings = {}
221 settings = {}
222 for each in ret:
222 for each in ret:
223 settings['rhodecode_' + each.app_settings_name] = \
223 settings['rhodecode_' + each.app_settings_name] = \
224 each.app_settings_value
224 each.app_settings_value
225
225
226 return settings
226 return settings
227
227
228 @classmethod
228 @classmethod
229 def get_ldap_settings(cls, cache=False):
229 def get_ldap_settings(cls, cache=False):
230 ret = cls.query()\
230 ret = cls.query()\
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
232 fd = {}
232 fd = {}
233 for row in ret:
233 for row in ret:
234 fd.update({row.app_settings_name: row.app_settings_value})
234 fd.update({row.app_settings_name: row.app_settings_value})
235
235
236 return fd
236 return fd
237
237
238 @classmethod
238 @classmethod
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
240 ret = cls.query()\
240 ret = cls.query()\
241 .filter(cls.app_settings_name.startswith('default_')).all()
241 .filter(cls.app_settings_name.startswith('default_')).all()
242 fd = {}
242 fd = {}
243 for row in ret:
243 for row in ret:
244 key = row.app_settings_name
244 key = row.app_settings_name
245 if strip_prefix:
245 if strip_prefix:
246 key = remove_prefix(key, prefix='default_')
246 key = remove_prefix(key, prefix='default_')
247 fd.update({key: row.app_settings_value})
247 fd.update({key: row.app_settings_value})
248
248
249 return fd
249 return fd
250
250
251
251
252 class RhodeCodeUi(Base, BaseModel):
252 class RhodeCodeUi(Base, BaseModel):
253 __tablename__ = 'rhodecode_ui'
253 __tablename__ = 'rhodecode_ui'
254 __table_args__ = (
254 __table_args__ = (
255 UniqueConstraint('ui_key'),
255 UniqueConstraint('ui_key'),
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
257 'mysql_charset': 'utf8'}
257 'mysql_charset': 'utf8'}
258 )
258 )
259
259
260 HOOK_UPDATE = 'changegroup.update'
260 HOOK_UPDATE = 'changegroup.update'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
262 HOOK_PUSH = 'changegroup.push_logger'
262 HOOK_PUSH = 'changegroup.push_logger'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
264 HOOK_PULL = 'outgoing.pull_logger'
264 HOOK_PULL = 'outgoing.pull_logger'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
266
266
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
272
272
273 @classmethod
273 @classmethod
274 def get_by_key(cls, key):
274 def get_by_key(cls, key):
275 return cls.query().filter(cls.ui_key == key).scalar()
275 return cls.query().filter(cls.ui_key == key).scalar()
276
276
277 @classmethod
277 @classmethod
278 def get_builtin_hooks(cls):
278 def get_builtin_hooks(cls):
279 q = cls.query()
279 q = cls.query()
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
283 return q.all()
283 return q.all()
284
284
285 @classmethod
285 @classmethod
286 def get_custom_hooks(cls):
286 def get_custom_hooks(cls):
287 q = cls.query()
287 q = cls.query()
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
291 q = q.filter(cls.ui_section == 'hooks')
291 q = q.filter(cls.ui_section == 'hooks')
292 return q.all()
292 return q.all()
293
293
294 @classmethod
294 @classmethod
295 def get_repos_location(cls):
295 def get_repos_location(cls):
296 return cls.get_by_key('/').ui_value
296 return cls.get_by_key('/').ui_value
297
297
298 @classmethod
298 @classmethod
299 def create_or_update_hook(cls, key, val):
299 def create_or_update_hook(cls, key, val):
300 new_ui = cls.get_by_key(key) or cls()
300 new_ui = cls.get_by_key(key) or cls()
301 new_ui.ui_section = 'hooks'
301 new_ui.ui_section = 'hooks'
302 new_ui.ui_active = True
302 new_ui.ui_active = True
303 new_ui.ui_key = key
303 new_ui.ui_key = key
304 new_ui.ui_value = val
304 new_ui.ui_value = val
305
305
306 Session().add(new_ui)
306 Session().add(new_ui)
307
307
308 def __repr__(self):
308 def __repr__(self):
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
310 self.ui_value)
310 self.ui_value)
311
311
312
312
313 class User(Base, BaseModel):
313 class User(Base, BaseModel):
314 __tablename__ = 'users'
314 __tablename__ = 'users'
315 __table_args__ = (
315 __table_args__ = (
316 UniqueConstraint('username'), UniqueConstraint('email'),
316 UniqueConstraint('username'), UniqueConstraint('email'),
317 Index('u_username_idx', 'username'),
317 Index('u_username_idx', 'username'),
318 Index('u_email_idx', 'email'),
318 Index('u_email_idx', 'email'),
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
320 'mysql_charset': 'utf8'}
320 'mysql_charset': 'utf8'}
321 )
321 )
322 DEFAULT_USER = 'default'
322 DEFAULT_USER = 'default'
323
323
324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
336
336
337 user_log = relationship('UserLog')
337 user_log = relationship('UserLog')
338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
339
339
340 repositories = relationship('Repository')
340 repositories = relationship('Repository')
341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
343
343
344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
346
346
347 group_member = relationship('UserGroupMember', cascade='all')
347 group_member = relationship('UserGroupMember', cascade='all')
348
348
349 notifications = relationship('UserNotification', cascade='all')
349 notifications = relationship('UserNotification', cascade='all')
350 # notifications assigned to this user
350 # notifications assigned to this user
351 user_created_notifications = relationship('Notification', cascade='all')
351 user_created_notifications = relationship('Notification', cascade='all')
352 # comments created by this user
352 # comments created by this user
353 user_comments = relationship('ChangesetComment', cascade='all')
353 user_comments = relationship('ChangesetComment', cascade='all')
354 #extra emails for this user
354 #extra emails for this user
355 user_emails = relationship('UserEmailMap', cascade='all')
355 user_emails = relationship('UserEmailMap', cascade='all')
356
356
357 @hybrid_property
357 @hybrid_property
358 def email(self):
358 def email(self):
359 return self._email
359 return self._email
360
360
361 @email.setter
361 @email.setter
362 def email(self, val):
362 def email(self, val):
363 self._email = val.lower() if val else None
363 self._email = val.lower() if val else None
364
364
365 @property
365 @property
366 def firstname(self):
366 def firstname(self):
367 # alias for future
367 # alias for future
368 return self.name
368 return self.name
369
369
370 @property
370 @property
371 def emails(self):
371 def emails(self):
372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
373 return [self.email] + [x.email for x in other]
373 return [self.email] + [x.email for x in other]
374
374
375 @property
375 @property
376 def ip_addresses(self):
376 def ip_addresses(self):
377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
378 return [x.ip_addr for x in ret]
378 return [x.ip_addr for x in ret]
379
379
380 @property
380 @property
381 def username_and_name(self):
381 def username_and_name(self):
382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
383
383
384 @property
384 @property
385 def full_name(self):
385 def full_name(self):
386 return '%s %s' % (self.firstname, self.lastname)
386 return '%s %s' % (self.firstname, self.lastname)
387
387
388 @property
388 @property
389 def full_name_or_username(self):
389 def full_name_or_username(self):
390 return ('%s %s' % (self.firstname, self.lastname)
390 return ('%s %s' % (self.firstname, self.lastname)
391 if (self.firstname and self.lastname) else self.username)
391 if (self.firstname and self.lastname) else self.username)
392
392
393 @property
393 @property
394 def full_contact(self):
394 def full_contact(self):
395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
396
396
397 @property
397 @property
398 def short_contact(self):
398 def short_contact(self):
399 return '%s %s' % (self.firstname, self.lastname)
399 return '%s %s' % (self.firstname, self.lastname)
400
400
401 @property
401 @property
402 def is_admin(self):
402 def is_admin(self):
403 return self.admin
403 return self.admin
404
404
405 @property
405 @property
406 def AuthUser(self):
406 def AuthUser(self):
407 """
407 """
408 Returns instance of AuthUser for this user
408 Returns instance of AuthUser for this user
409 """
409 """
410 from rhodecode.lib.auth import AuthUser
410 from rhodecode.lib.auth import AuthUser
411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
412 username=self.username)
412 username=self.username)
413
413
414 def __unicode__(self):
414 def __unicode__(self):
415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
416 self.user_id, self.username)
416 self.user_id, self.username)
417
417
418 @classmethod
418 @classmethod
419 def get_by_username(cls, username, case_insensitive=False, cache=False):
419 def get_by_username(cls, username, case_insensitive=False, cache=False):
420 if case_insensitive:
420 if case_insensitive:
421 q = cls.query().filter(cls.username.ilike(username))
421 q = cls.query().filter(cls.username.ilike(username))
422 else:
422 else:
423 q = cls.query().filter(cls.username == username)
423 q = cls.query().filter(cls.username == username)
424
424
425 if cache:
425 if cache:
426 q = q.options(FromCache(
426 q = q.options(FromCache(
427 "sql_cache_short",
427 "sql_cache_short",
428 "get_user_%s" % _hash_key(username)
428 "get_user_%s" % _hash_key(username)
429 )
429 )
430 )
430 )
431 return q.scalar()
431 return q.scalar()
432
432
433 @classmethod
433 @classmethod
434 def get_by_api_key(cls, api_key, cache=False):
434 def get_by_api_key(cls, api_key, cache=False):
435 q = cls.query().filter(cls.api_key == api_key)
435 q = cls.query().filter(cls.api_key == api_key)
436
436
437 if cache:
437 if cache:
438 q = q.options(FromCache("sql_cache_short",
438 q = q.options(FromCache("sql_cache_short",
439 "get_api_key_%s" % api_key))
439 "get_api_key_%s" % api_key))
440 return q.scalar()
440 return q.scalar()
441
441
442 @classmethod
442 @classmethod
443 def get_by_email(cls, email, case_insensitive=False, cache=False):
443 def get_by_email(cls, email, case_insensitive=False, cache=False):
444 if case_insensitive:
444 if case_insensitive:
445 q = cls.query().filter(cls.email.ilike(email))
445 q = cls.query().filter(cls.email.ilike(email))
446 else:
446 else:
447 q = cls.query().filter(cls.email == email)
447 q = cls.query().filter(cls.email == email)
448
448
449 if cache:
449 if cache:
450 q = q.options(FromCache("sql_cache_short",
450 q = q.options(FromCache("sql_cache_short",
451 "get_email_key_%s" % email))
451 "get_email_key_%s" % email))
452
452
453 ret = q.scalar()
453 ret = q.scalar()
454 if ret is None:
454 if ret is None:
455 q = UserEmailMap.query()
455 q = UserEmailMap.query()
456 # try fetching in alternate email map
456 # try fetching in alternate email map
457 if case_insensitive:
457 if case_insensitive:
458 q = q.filter(UserEmailMap.email.ilike(email))
458 q = q.filter(UserEmailMap.email.ilike(email))
459 else:
459 else:
460 q = q.filter(UserEmailMap.email == email)
460 q = q.filter(UserEmailMap.email == email)
461 q = q.options(joinedload(UserEmailMap.user))
461 q = q.options(joinedload(UserEmailMap.user))
462 if cache:
462 if cache:
463 q = q.options(FromCache("sql_cache_short",
463 q = q.options(FromCache("sql_cache_short",
464 "get_email_map_key_%s" % email))
464 "get_email_map_key_%s" % email))
465 ret = getattr(q.scalar(), 'user', None)
465 ret = getattr(q.scalar(), 'user', None)
466
466
467 return ret
467 return ret
468
468
469 @classmethod
469 @classmethod
470 def get_from_cs_author(cls, author):
470 def get_from_cs_author(cls, author):
471 """
471 """
472 Tries to get User objects out of commit author string
472 Tries to get User objects out of commit author string
473
473
474 :param author:
474 :param author:
475 """
475 """
476 from rhodecode.lib.helpers import email, author_name
476 from rhodecode.lib.helpers import email, author_name
477 # Valid email in the attribute passed, see if they're in the system
477 # Valid email in the attribute passed, see if they're in the system
478 _email = email(author)
478 _email = email(author)
479 if _email:
479 if _email:
480 user = cls.get_by_email(_email, case_insensitive=True)
480 user = cls.get_by_email(_email, case_insensitive=True)
481 if user:
481 if user:
482 return user
482 return user
483 # Maybe we can match by username?
483 # Maybe we can match by username?
484 _author = author_name(author)
484 _author = author_name(author)
485 user = cls.get_by_username(_author, case_insensitive=True)
485 user = cls.get_by_username(_author, case_insensitive=True)
486 if user:
486 if user:
487 return user
487 return user
488
488
489 def update_lastlogin(self):
489 def update_lastlogin(self):
490 """Update user lastlogin"""
490 """Update user lastlogin"""
491 self.last_login = datetime.datetime.now()
491 self.last_login = datetime.datetime.now()
492 Session().add(self)
492 Session().add(self)
493 log.debug('updated user %s lastlogin' % self.username)
493 log.debug('updated user %s lastlogin' % self.username)
494
494
495 @classmethod
495 @classmethod
496 def get_first_admin(cls):
496 def get_first_admin(cls):
497 user = User.query().filter(User.admin == True).first()
497 user = User.query().filter(User.admin == True).first()
498 if user is None:
498 if user is None:
499 raise Exception('Missing administrative account!')
499 raise Exception('Missing administrative account!')
500 return user
500 return user
501
501
502 @classmethod
502 @classmethod
503 def get_default_user(cls, cache=False):
503 def get_default_user(cls, cache=False):
504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
505 if user is None:
505 if user is None:
506 raise Exception('Missing default account!')
506 raise Exception('Missing default account!')
507 return user
507 return user
508
508
509 def get_api_data(self):
509 def get_api_data(self):
510 """
510 """
511 Common function for generating user related data for API
511 Common function for generating user related data for API
512 """
512 """
513 user = self
513 user = self
514 data = dict(
514 data = dict(
515 user_id=user.user_id,
515 user_id=user.user_id,
516 username=user.username,
516 username=user.username,
517 firstname=user.name,
517 firstname=user.name,
518 lastname=user.lastname,
518 lastname=user.lastname,
519 email=user.email,
519 email=user.email,
520 emails=user.emails,
520 emails=user.emails,
521 api_key=user.api_key,
521 api_key=user.api_key,
522 active=user.active,
522 active=user.active,
523 admin=user.admin,
523 admin=user.admin,
524 ldap_dn=user.ldap_dn,
524 ldap_dn=user.ldap_dn,
525 last_login=user.last_login,
525 last_login=user.last_login,
526 ip_addresses=user.ip_addresses
526 ip_addresses=user.ip_addresses
527 )
527 )
528 return data
528 return data
529
529
530 def __json__(self):
530 def __json__(self):
531 data = dict(
531 data = dict(
532 full_name=self.full_name,
532 full_name=self.full_name,
533 full_name_or_username=self.full_name_or_username,
533 full_name_or_username=self.full_name_or_username,
534 short_contact=self.short_contact,
534 short_contact=self.short_contact,
535 full_contact=self.full_contact
535 full_contact=self.full_contact
536 )
536 )
537 data.update(self.get_api_data())
537 data.update(self.get_api_data())
538 return data
538 return data
539
539
540
540
541 class UserEmailMap(Base, BaseModel):
541 class UserEmailMap(Base, BaseModel):
542 __tablename__ = 'user_email_map'
542 __tablename__ = 'user_email_map'
543 __table_args__ = (
543 __table_args__ = (
544 Index('uem_email_idx', 'email'),
544 Index('uem_email_idx', 'email'),
545 UniqueConstraint('email'),
545 UniqueConstraint('email'),
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 'mysql_charset': 'utf8'}
547 'mysql_charset': 'utf8'}
548 )
548 )
549 __mapper_args__ = {}
549 __mapper_args__ = {}
550
550
551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 user = relationship('User', lazy='joined')
554 user = relationship('User', lazy='joined')
555
555
556 @validates('_email')
556 @validates('_email')
557 def validate_email(self, key, email):
557 def validate_email(self, key, email):
558 # check if this email is not main one
558 # check if this email is not main one
559 main_email = Session().query(User).filter(User.email == email).scalar()
559 main_email = Session().query(User).filter(User.email == email).scalar()
560 if main_email is not None:
560 if main_email is not None:
561 raise AttributeError('email %s is present is user table' % email)
561 raise AttributeError('email %s is present is user table' % email)
562 return email
562 return email
563
563
564 @hybrid_property
564 @hybrid_property
565 def email(self):
565 def email(self):
566 return self._email
566 return self._email
567
567
568 @email.setter
568 @email.setter
569 def email(self, val):
569 def email(self, val):
570 self._email = val.lower() if val else None
570 self._email = val.lower() if val else None
571
571
572
572
573 class UserIpMap(Base, BaseModel):
573 class UserIpMap(Base, BaseModel):
574 __tablename__ = 'user_ip_map'
574 __tablename__ = 'user_ip_map'
575 __table_args__ = (
575 __table_args__ = (
576 UniqueConstraint('user_id', 'ip_addr'),
576 UniqueConstraint('user_id', 'ip_addr'),
577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 'mysql_charset': 'utf8'}
578 'mysql_charset': 'utf8'}
579 )
579 )
580 __mapper_args__ = {}
580 __mapper_args__ = {}
581
581
582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
586 user = relationship('User', lazy='joined')
586 user = relationship('User', lazy='joined')
587
587
588 @classmethod
588 @classmethod
589 def _get_ip_range(cls, ip_addr):
589 def _get_ip_range(cls, ip_addr):
590 from rhodecode.lib import ipaddr
590 from rhodecode.lib import ipaddr
591 net = ipaddr.IPNetwork(address=ip_addr)
591 net = ipaddr.IPNetwork(address=ip_addr)
592 return [str(net.network), str(net.broadcast)]
592 return [str(net.network), str(net.broadcast)]
593
593
594 def __json__(self):
594 def __json__(self):
595 return dict(
595 return dict(
596 ip_addr=self.ip_addr,
596 ip_addr=self.ip_addr,
597 ip_range=self._get_ip_range(self.ip_addr)
597 ip_range=self._get_ip_range(self.ip_addr)
598 )
598 )
599
599
600
600
601 class UserLog(Base, BaseModel):
601 class UserLog(Base, BaseModel):
602 __tablename__ = 'user_logs'
602 __tablename__ = 'user_logs'
603 __table_args__ = (
603 __table_args__ = (
604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
605 'mysql_charset': 'utf8'},
605 'mysql_charset': 'utf8'},
606 )
606 )
607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
615
615
616 @property
616 @property
617 def action_as_day(self):
617 def action_as_day(self):
618 return datetime.date(*self.action_date.timetuple()[:3])
618 return datetime.date(*self.action_date.timetuple()[:3])
619
619
620 user = relationship('User')
620 user = relationship('User')
621 repository = relationship('Repository', cascade='')
621 repository = relationship('Repository', cascade='')
622
622
623
623
624 class UserGroup(Base, BaseModel):
624 class UserGroup(Base, BaseModel):
625 __tablename__ = 'users_groups'
625 __tablename__ = 'users_groups'
626 __table_args__ = (
626 __table_args__ = (
627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 'mysql_charset': 'utf8'},
628 'mysql_charset': 'utf8'},
629 )
629 )
630
630
631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
636
636
637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
642 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
642 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
643
643
644 user = relationship('User')
644 user = relationship('User')
645
645
646 def __unicode__(self):
646 def __unicode__(self):
647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
648 self.users_group_id,
648 self.users_group_id,
649 self.users_group_name)
649 self.users_group_name)
650
650
651 @classmethod
651 @classmethod
652 def get_by_group_name(cls, group_name, cache=False,
652 def get_by_group_name(cls, group_name, cache=False,
653 case_insensitive=False):
653 case_insensitive=False):
654 if case_insensitive:
654 if case_insensitive:
655 q = cls.query().filter(cls.users_group_name.ilike(group_name))
655 q = cls.query().filter(cls.users_group_name.ilike(group_name))
656 else:
656 else:
657 q = cls.query().filter(cls.users_group_name == group_name)
657 q = cls.query().filter(cls.users_group_name == group_name)
658 if cache:
658 if cache:
659 q = q.options(FromCache(
659 q = q.options(FromCache(
660 "sql_cache_short",
660 "sql_cache_short",
661 "get_user_%s" % _hash_key(group_name)
661 "get_user_%s" % _hash_key(group_name)
662 )
662 )
663 )
663 )
664 return q.scalar()
664 return q.scalar()
665
665
666 @classmethod
666 @classmethod
667 def get(cls, users_group_id, cache=False):
667 def get(cls, users_group_id, cache=False):
668 users_group = cls.query()
668 users_group = cls.query()
669 if cache:
669 if cache:
670 users_group = users_group.options(FromCache("sql_cache_short",
670 users_group = users_group.options(FromCache("sql_cache_short",
671 "get_users_group_%s" % users_group_id))
671 "get_users_group_%s" % users_group_id))
672 return users_group.get(users_group_id)
672 return users_group.get(users_group_id)
673
673
674 def get_api_data(self):
674 def get_api_data(self):
675 users_group = self
675 users_group = self
676
676
677 data = dict(
677 data = dict(
678 users_group_id=users_group.users_group_id,
678 users_group_id=users_group.users_group_id,
679 group_name=users_group.users_group_name,
679 group_name=users_group.users_group_name,
680 active=users_group.users_group_active,
680 active=users_group.users_group_active,
681 )
681 )
682
682
683 return data
683 return data
684
684
685
685
686 class UserGroupMember(Base, BaseModel):
686 class UserGroupMember(Base, BaseModel):
687 __tablename__ = 'users_groups_members'
687 __tablename__ = 'users_groups_members'
688 __table_args__ = (
688 __table_args__ = (
689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
690 'mysql_charset': 'utf8'},
690 'mysql_charset': 'utf8'},
691 )
691 )
692
692
693 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
693 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
696
696
697 user = relationship('User', lazy='joined')
697 user = relationship('User', lazy='joined')
698 users_group = relationship('UserGroup')
698 users_group = relationship('UserGroup')
699
699
700 def __init__(self, gr_id='', u_id=''):
700 def __init__(self, gr_id='', u_id=''):
701 self.users_group_id = gr_id
701 self.users_group_id = gr_id
702 self.user_id = u_id
702 self.user_id = u_id
703
703
704
704
705 class RepositoryField(Base, BaseModel):
705 class RepositoryField(Base, BaseModel):
706 __tablename__ = 'repositories_fields'
706 __tablename__ = 'repositories_fields'
707 __table_args__ = (
707 __table_args__ = (
708 UniqueConstraint('repository_id', 'field_key'), # no-multi field
708 UniqueConstraint('repository_id', 'field_key'), # no-multi field
709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
710 'mysql_charset': 'utf8'},
710 'mysql_charset': 'utf8'},
711 )
711 )
712 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
712 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
713
713
714 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
714 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
716 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
716 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
717 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
717 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
718 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
718 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
719 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
719 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
720 field_type = Column("field_type", String(256), nullable=False, unique=None)
720 field_type = Column("field_type", String(256), nullable=False, unique=None)
721 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
721 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
722
722
723 repository = relationship('Repository')
723 repository = relationship('Repository')
724
724
725 @property
725 @property
726 def field_key_prefixed(self):
726 def field_key_prefixed(self):
727 return 'ex_%s' % self.field_key
727 return 'ex_%s' % self.field_key
728
728
729 @classmethod
729 @classmethod
730 def un_prefix_key(cls, key):
730 def un_prefix_key(cls, key):
731 if key.startswith(cls.PREFIX):
731 if key.startswith(cls.PREFIX):
732 return key[len(cls.PREFIX):]
732 return key[len(cls.PREFIX):]
733 return key
733 return key
734
734
735 @classmethod
735 @classmethod
736 def get_by_key_name(cls, key, repo):
736 def get_by_key_name(cls, key, repo):
737 row = cls.query()\
737 row = cls.query()\
738 .filter(cls.repository == repo)\
738 .filter(cls.repository == repo)\
739 .filter(cls.field_key == key).scalar()
739 .filter(cls.field_key == key).scalar()
740 return row
740 return row
741
741
742
742
743 class Repository(Base, BaseModel):
743 class Repository(Base, BaseModel):
744 __tablename__ = 'repositories'
744 __tablename__ = 'repositories'
745 __table_args__ = (
745 __table_args__ = (
746 UniqueConstraint('repo_name'),
746 UniqueConstraint('repo_name'),
747 Index('r_repo_name_idx', 'repo_name'),
747 Index('r_repo_name_idx', 'repo_name'),
748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
749 'mysql_charset': 'utf8'},
749 'mysql_charset': 'utf8'},
750 )
750 )
751
751
752 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
752 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
753 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
753 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
754 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
754 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
755 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
755 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
757 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
757 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
758 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
758 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
759 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
759 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
760 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
760 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
761 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
761 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
762 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
762 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
763 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
763 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
764 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
764 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
765 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
765 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
766 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
766 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
767
767
768 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
768 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
769 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
769 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
770
770
771 user = relationship('User')
771 user = relationship('User')
772 fork = relationship('Repository', remote_side=repo_id)
772 fork = relationship('Repository', remote_side=repo_id)
773 group = relationship('RepoGroup')
773 group = relationship('RepoGroup')
774 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
774 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
775 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
775 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
776 stats = relationship('Statistics', cascade='all', uselist=False)
776 stats = relationship('Statistics', cascade='all', uselist=False)
777
777
778 followers = relationship('UserFollowing',
778 followers = relationship('UserFollowing',
779 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
779 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
780 cascade='all')
780 cascade='all')
781 extra_fields = relationship('RepositoryField',
781 extra_fields = relationship('RepositoryField',
782 cascade="all, delete, delete-orphan")
782 cascade="all, delete, delete-orphan")
783
783
784 logs = relationship('UserLog')
784 logs = relationship('UserLog')
785 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
785 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
786
786
787 pull_requests_org = relationship('PullRequest',
787 pull_requests_org = relationship('PullRequest',
788 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
788 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
789 cascade="all, delete, delete-orphan")
789 cascade="all, delete, delete-orphan")
790
790
791 pull_requests_other = relationship('PullRequest',
791 pull_requests_other = relationship('PullRequest',
792 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
792 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
793 cascade="all, delete, delete-orphan")
793 cascade="all, delete, delete-orphan")
794
794
795 def __unicode__(self):
795 def __unicode__(self):
796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
797 self.repo_name)
797 self.repo_name)
798
798
799 @hybrid_property
799 @hybrid_property
800 def locked(self):
800 def locked(self):
801 # always should return [user_id, timelocked]
801 # always should return [user_id, timelocked]
802 if self._locked:
802 if self._locked:
803 _lock_info = self._locked.split(':')
803 _lock_info = self._locked.split(':')
804 return int(_lock_info[0]), _lock_info[1]
804 return int(_lock_info[0]), _lock_info[1]
805 return [None, None]
805 return [None, None]
806
806
807 @locked.setter
807 @locked.setter
808 def locked(self, val):
808 def locked(self, val):
809 if val and isinstance(val, (list, tuple)):
809 if val and isinstance(val, (list, tuple)):
810 self._locked = ':'.join(map(str, val))
810 self._locked = ':'.join(map(str, val))
811 else:
811 else:
812 self._locked = None
812 self._locked = None
813
813
814 @hybrid_property
814 @hybrid_property
815 def changeset_cache(self):
815 def changeset_cache(self):
816 from rhodecode.lib.vcs.backends.base import EmptyChangeset
816 from rhodecode.lib.vcs.backends.base import EmptyChangeset
817 dummy = EmptyChangeset().__json__()
817 dummy = EmptyChangeset().__json__()
818 if not self._changeset_cache:
818 if not self._changeset_cache:
819 return dummy
819 return dummy
820 try:
820 try:
821 return json.loads(self._changeset_cache)
821 return json.loads(self._changeset_cache)
822 except TypeError:
822 except TypeError:
823 return dummy
823 return dummy
824
824
825 @changeset_cache.setter
825 @changeset_cache.setter
826 def changeset_cache(self, val):
826 def changeset_cache(self, val):
827 try:
827 try:
828 self._changeset_cache = json.dumps(val)
828 self._changeset_cache = json.dumps(val)
829 except Exception:
829 except Exception:
830 log.error(traceback.format_exc())
830 log.error(traceback.format_exc())
831
831
832 @classmethod
832 @classmethod
833 def url_sep(cls):
833 def url_sep(cls):
834 return URL_SEP
834 return URL_SEP
835
835
836 @classmethod
836 @classmethod
837 def normalize_repo_name(cls, repo_name):
837 def normalize_repo_name(cls, repo_name):
838 """
838 """
839 Normalizes os specific repo_name to the format internally stored inside
839 Normalizes os specific repo_name to the format internally stored inside
840 dabatabase using URL_SEP
840 dabatabase using URL_SEP
841
841
842 :param cls:
842 :param cls:
843 :param repo_name:
843 :param repo_name:
844 """
844 """
845 return cls.url_sep().join(repo_name.split(os.sep))
845 return cls.url_sep().join(repo_name.split(os.sep))
846
846
847 @classmethod
847 @classmethod
848 def get_by_repo_name(cls, repo_name):
848 def get_by_repo_name(cls, repo_name):
849 q = Session().query(cls).filter(cls.repo_name == repo_name)
849 q = Session().query(cls).filter(cls.repo_name == repo_name)
850 q = q.options(joinedload(Repository.fork))\
850 q = q.options(joinedload(Repository.fork))\
851 .options(joinedload(Repository.user))\
851 .options(joinedload(Repository.user))\
852 .options(joinedload(Repository.group))
852 .options(joinedload(Repository.group))
853 return q.scalar()
853 return q.scalar()
854
854
855 @classmethod
855 @classmethod
856 def get_by_full_path(cls, repo_full_path):
856 def get_by_full_path(cls, repo_full_path):
857 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
857 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
858 repo_name = cls.normalize_repo_name(repo_name)
858 repo_name = cls.normalize_repo_name(repo_name)
859 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
859 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
860
860
861 @classmethod
861 @classmethod
862 def get_repo_forks(cls, repo_id):
862 def get_repo_forks(cls, repo_id):
863 return cls.query().filter(Repository.fork_id == repo_id)
863 return cls.query().filter(Repository.fork_id == repo_id)
864
864
865 @classmethod
865 @classmethod
866 def base_path(cls):
866 def base_path(cls):
867 """
867 """
868 Returns base path when all repos are stored
868 Returns base path when all repos are stored
869
869
870 :param cls:
870 :param cls:
871 """
871 """
872 q = Session().query(RhodeCodeUi)\
872 q = Session().query(RhodeCodeUi)\
873 .filter(RhodeCodeUi.ui_key == cls.url_sep())
873 .filter(RhodeCodeUi.ui_key == cls.url_sep())
874 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
874 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
875 return q.one().ui_value
875 return q.one().ui_value
876
876
877 @property
877 @property
878 def forks(self):
878 def forks(self):
879 """
879 """
880 Return forks of this repo
880 Return forks of this repo
881 """
881 """
882 return Repository.get_repo_forks(self.repo_id)
882 return Repository.get_repo_forks(self.repo_id)
883
883
884 @property
884 @property
885 def parent(self):
885 def parent(self):
886 """
886 """
887 Returns fork parent
887 Returns fork parent
888 """
888 """
889 return self.fork
889 return self.fork
890
890
891 @property
891 @property
892 def just_name(self):
892 def just_name(self):
893 return self.repo_name.split(Repository.url_sep())[-1]
893 return self.repo_name.split(Repository.url_sep())[-1]
894
894
895 @property
895 @property
896 def groups_with_parents(self):
896 def groups_with_parents(self):
897 groups = []
897 groups = []
898 if self.group is None:
898 if self.group is None:
899 return groups
899 return groups
900
900
901 cur_gr = self.group
901 cur_gr = self.group
902 groups.insert(0, cur_gr)
902 groups.insert(0, cur_gr)
903 while 1:
903 while 1:
904 gr = getattr(cur_gr, 'parent_group', None)
904 gr = getattr(cur_gr, 'parent_group', None)
905 cur_gr = cur_gr.parent_group
905 cur_gr = cur_gr.parent_group
906 if gr is None:
906 if gr is None:
907 break
907 break
908 groups.insert(0, gr)
908 groups.insert(0, gr)
909
909
910 return groups
910 return groups
911
911
912 @property
912 @property
913 def groups_and_repo(self):
913 def groups_and_repo(self):
914 return self.groups_with_parents, self.just_name, self.repo_name
914 return self.groups_with_parents, self.just_name, self.repo_name
915
915
916 @LazyProperty
916 @LazyProperty
917 def repo_path(self):
917 def repo_path(self):
918 """
918 """
919 Returns base full path for that repository means where it actually
919 Returns base full path for that repository means where it actually
920 exists on a filesystem
920 exists on a filesystem
921 """
921 """
922 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
922 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
923 Repository.url_sep())
923 Repository.url_sep())
924 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
924 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
925 return q.one().ui_value
925 return q.one().ui_value
926
926
927 @property
927 @property
928 def repo_full_path(self):
928 def repo_full_path(self):
929 p = [self.repo_path]
929 p = [self.repo_path]
930 # we need to split the name by / since this is how we store the
930 # we need to split the name by / since this is how we store the
931 # names in the database, but that eventually needs to be converted
931 # names in the database, but that eventually needs to be converted
932 # into a valid system path
932 # into a valid system path
933 p += self.repo_name.split(Repository.url_sep())
933 p += self.repo_name.split(Repository.url_sep())
934 return os.path.join(*map(safe_unicode, p))
934 return os.path.join(*map(safe_unicode, p))
935
935
936 @property
936 @property
937 def cache_keys(self):
937 def cache_keys(self):
938 """
938 """
939 Returns associated cache keys for that repo
939 Returns associated cache keys for that repo
940 """
940 """
941 return CacheInvalidation.query()\
941 return CacheInvalidation.query()\
942 .filter(CacheInvalidation.cache_args == self.repo_name)\
942 .filter(CacheInvalidation.cache_args == self.repo_name)\
943 .order_by(CacheInvalidation.cache_key)\
943 .order_by(CacheInvalidation.cache_key)\
944 .all()
944 .all()
945
945
946 def get_new_name(self, repo_name):
946 def get_new_name(self, repo_name):
947 """
947 """
948 returns new full repository name based on assigned group and new new
948 returns new full repository name based on assigned group and new new
949
949
950 :param group_name:
950 :param group_name:
951 """
951 """
952 path_prefix = self.group.full_path_splitted if self.group else []
952 path_prefix = self.group.full_path_splitted if self.group else []
953 return Repository.url_sep().join(path_prefix + [repo_name])
953 return Repository.url_sep().join(path_prefix + [repo_name])
954
954
955 @property
955 @property
956 def _ui(self):
956 def _ui(self):
957 """
957 """
958 Creates an db based ui object for this repository
958 Creates an db based ui object for this repository
959 """
959 """
960 from rhodecode.lib.utils import make_ui
960 from rhodecode.lib.utils import make_ui
961 return make_ui('db', clear_session=False)
961 return make_ui('db', clear_session=False)
962
962
963 @classmethod
963 @classmethod
964 def is_valid(cls, repo_name):
964 def is_valid(cls, repo_name):
965 """
965 """
966 returns True if given repo name is a valid filesystem repository
966 returns True if given repo name is a valid filesystem repository
967
967
968 :param cls:
968 :param cls:
969 :param repo_name:
969 :param repo_name:
970 """
970 """
971 from rhodecode.lib.utils import is_valid_repo
971 from rhodecode.lib.utils import is_valid_repo
972
972
973 return is_valid_repo(repo_name, cls.base_path())
973 return is_valid_repo(repo_name, cls.base_path())
974
974
975 def get_api_data(self):
975 def get_api_data(self):
976 """
976 """
977 Common function for generating repo api data
977 Common function for generating repo api data
978
978
979 """
979 """
980 repo = self
980 repo = self
981 data = dict(
981 data = dict(
982 repo_id=repo.repo_id,
982 repo_id=repo.repo_id,
983 repo_name=repo.repo_name,
983 repo_name=repo.repo_name,
984 repo_type=repo.repo_type,
984 repo_type=repo.repo_type,
985 clone_uri=repo.clone_uri,
985 clone_uri=repo.clone_uri,
986 private=repo.private,
986 private=repo.private,
987 created_on=repo.created_on,
987 created_on=repo.created_on,
988 description=repo.description,
988 description=repo.description,
989 landing_rev=repo.landing_rev,
989 landing_rev=repo.landing_rev,
990 owner=repo.user.username,
990 owner=repo.user.username,
991 fork_of=repo.fork.repo_name if repo.fork else None,
991 fork_of=repo.fork.repo_name if repo.fork else None,
992 enable_statistics=repo.enable_statistics,
992 enable_statistics=repo.enable_statistics,
993 enable_locking=repo.enable_locking,
993 enable_locking=repo.enable_locking,
994 enable_downloads=repo.enable_downloads,
994 enable_downloads=repo.enable_downloads,
995 last_changeset=repo.changeset_cache,
995 last_changeset=repo.changeset_cache,
996 locked_by=User.get(self.locked[0]).get_api_data() \
996 locked_by=User.get(self.locked[0]).get_api_data() \
997 if self.locked[0] else None,
997 if self.locked[0] else None,
998 locked_date=time_to_datetime(self.locked[1]) \
998 locked_date=time_to_datetime(self.locked[1]) \
999 if self.locked[1] else None
999 if self.locked[1] else None
1000 )
1000 )
1001 rc_config = RhodeCodeSetting.get_app_settings()
1001 rc_config = RhodeCodeSetting.get_app_settings()
1002 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1002 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1003 if repository_fields:
1003 if repository_fields:
1004 for f in self.extra_fields:
1004 for f in self.extra_fields:
1005 data[f.field_key_prefixed] = f.field_value
1005 data[f.field_key_prefixed] = f.field_value
1006
1006
1007 return data
1007 return data
1008
1008
1009 @classmethod
1009 @classmethod
1010 def lock(cls, repo, user_id, lock_time=None):
1010 def lock(cls, repo, user_id, lock_time=None):
1011 if not lock_time:
1011 if not lock_time:
1012 lock_time = time.time()
1012 lock_time = time.time()
1013 repo.locked = [user_id, lock_time]
1013 repo.locked = [user_id, lock_time]
1014 Session().add(repo)
1014 Session().add(repo)
1015 Session().commit()
1015 Session().commit()
1016
1016
1017 @classmethod
1017 @classmethod
1018 def unlock(cls, repo):
1018 def unlock(cls, repo):
1019 repo.locked = None
1019 repo.locked = None
1020 Session().add(repo)
1020 Session().add(repo)
1021 Session().commit()
1021 Session().commit()
1022
1022
1023 @classmethod
1023 @classmethod
1024 def getlock(cls, repo):
1024 def getlock(cls, repo):
1025 return repo.locked
1025 return repo.locked
1026
1026
1027 @property
1027 @property
1028 def last_db_change(self):
1028 def last_db_change(self):
1029 return self.updated_on
1029 return self.updated_on
1030
1030
1031 def clone_url(self, **override):
1031 def clone_url(self, **override):
1032 from pylons import url
1032 from pylons import url
1033 from urlparse import urlparse
1033 from urlparse import urlparse
1034 import urllib
1034 import urllib
1035 parsed_url = urlparse(url('home', qualified=True))
1035 parsed_url = urlparse(url('home', qualified=True))
1036 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1036 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1037 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1037 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1038 args = {
1038 args = {
1039 'user': '',
1039 'user': '',
1040 'pass': '',
1040 'pass': '',
1041 'scheme': parsed_url.scheme,
1041 'scheme': parsed_url.scheme,
1042 'netloc': parsed_url.netloc,
1042 'netloc': parsed_url.netloc,
1043 'prefix': decoded_path,
1043 'prefix': decoded_path,
1044 'path': self.repo_name
1044 'path': self.repo_name
1045 }
1045 }
1046
1046
1047 args.update(override)
1047 args.update(override)
1048 return default_clone_uri % args
1048 return default_clone_uri % args
1049
1049
1050 #==========================================================================
1050 #==========================================================================
1051 # SCM PROPERTIES
1051 # SCM PROPERTIES
1052 #==========================================================================
1052 #==========================================================================
1053
1053
1054 def get_changeset(self, rev=None):
1054 def get_changeset(self, rev=None):
1055 return get_changeset_safe(self.scm_instance, rev)
1055 return get_changeset_safe(self.scm_instance, rev)
1056
1056
1057 def get_landing_changeset(self):
1057 def get_landing_changeset(self):
1058 """
1058 """
1059 Returns landing changeset, or if that doesn't exist returns the tip
1059 Returns landing changeset, or if that doesn't exist returns the tip
1060 """
1060 """
1061 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1061 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1062 return cs
1062 return cs
1063
1063
1064 def update_changeset_cache(self, cs_cache=None):
1064 def update_changeset_cache(self, cs_cache=None):
1065 """
1065 """
1066 Update cache of last changeset for repository, keys should be::
1066 Update cache of last changeset for repository, keys should be::
1067
1067
1068 short_id
1068 short_id
1069 raw_id
1069 raw_id
1070 revision
1070 revision
1071 message
1071 message
1072 date
1072 date
1073 author
1073 author
1074
1074
1075 :param cs_cache:
1075 :param cs_cache:
1076 """
1076 """
1077 from rhodecode.lib.vcs.backends.base import BaseChangeset
1077 from rhodecode.lib.vcs.backends.base import BaseChangeset
1078 if cs_cache is None:
1078 if cs_cache is None:
1079 cs_cache = EmptyChangeset()
1079 cs_cache = EmptyChangeset()
1080 # use no-cache version here
1080 # use no-cache version here
1081 scm_repo = self.scm_instance_no_cache()
1081 scm_repo = self.scm_instance_no_cache()
1082 if scm_repo:
1082 if scm_repo:
1083 cs_cache = scm_repo.get_changeset()
1083 cs_cache = scm_repo.get_changeset()
1084
1084
1085 if isinstance(cs_cache, BaseChangeset):
1085 if isinstance(cs_cache, BaseChangeset):
1086 cs_cache = cs_cache.__json__()
1086 cs_cache = cs_cache.__json__()
1087
1087
1088 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1088 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1089 _default = datetime.datetime.fromtimestamp(0)
1089 _default = datetime.datetime.fromtimestamp(0)
1090 last_change = cs_cache.get('date') or _default
1090 last_change = cs_cache.get('date') or _default
1091 log.debug('updated repo %s with new cs cache %s'
1091 log.debug('updated repo %s with new cs cache %s'
1092 % (self.repo_name, cs_cache))
1092 % (self.repo_name, cs_cache))
1093 self.updated_on = last_change
1093 self.updated_on = last_change
1094 self.changeset_cache = cs_cache
1094 self.changeset_cache = cs_cache
1095 Session().add(self)
1095 Session().add(self)
1096 Session().commit()
1096 Session().commit()
1097 else:
1097 else:
1098 log.debug('Skipping repo:%s already with latest changes'
1098 log.debug('Skipping repo:%s already with latest changes'
1099 % self.repo_name)
1099 % self.repo_name)
1100
1100
1101 @property
1101 @property
1102 def tip(self):
1102 def tip(self):
1103 return self.get_changeset('tip')
1103 return self.get_changeset('tip')
1104
1104
1105 @property
1105 @property
1106 def author(self):
1106 def author(self):
1107 return self.tip.author
1107 return self.tip.author
1108
1108
1109 @property
1109 @property
1110 def last_change(self):
1110 def last_change(self):
1111 return self.scm_instance.last_change
1111 return self.scm_instance.last_change
1112
1112
1113 def get_comments(self, revisions=None):
1113 def get_comments(self, revisions=None):
1114 """
1114 """
1115 Returns comments for this repository grouped by revisions
1115 Returns comments for this repository grouped by revisions
1116
1116
1117 :param revisions: filter query by revisions only
1117 :param revisions: filter query by revisions only
1118 """
1118 """
1119 cmts = ChangesetComment.query()\
1119 cmts = ChangesetComment.query()\
1120 .filter(ChangesetComment.repo == self)
1120 .filter(ChangesetComment.repo == self)
1121 if revisions:
1121 if revisions:
1122 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1122 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1123 grouped = defaultdict(list)
1123 grouped = collections.defaultdict(list)
1124 for cmt in cmts.all():
1124 for cmt in cmts.all():
1125 grouped[cmt.revision].append(cmt)
1125 grouped[cmt.revision].append(cmt)
1126 return grouped
1126 return grouped
1127
1127
1128 def statuses(self, revisions=None):
1128 def statuses(self, revisions=None):
1129 """
1129 """
1130 Returns statuses for this repository
1130 Returns statuses for this repository
1131
1131
1132 :param revisions: list of revisions to get statuses for
1132 :param revisions: list of revisions to get statuses for
1133 """
1133 """
1134
1134
1135 statuses = ChangesetStatus.query()\
1135 statuses = ChangesetStatus.query()\
1136 .filter(ChangesetStatus.repo == self)\
1136 .filter(ChangesetStatus.repo == self)\
1137 .filter(ChangesetStatus.version == 0)
1137 .filter(ChangesetStatus.version == 0)
1138 if revisions:
1138 if revisions:
1139 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1139 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1140 grouped = {}
1140 grouped = {}
1141
1141
1142 #maybe we have open new pullrequest without a status ?
1142 #maybe we have open new pullrequest without a status ?
1143 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1143 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1144 status_lbl = ChangesetStatus.get_status_lbl(stat)
1144 status_lbl = ChangesetStatus.get_status_lbl(stat)
1145 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1145 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1146 for rev in pr.revisions:
1146 for rev in pr.revisions:
1147 pr_id = pr.pull_request_id
1147 pr_id = pr.pull_request_id
1148 pr_repo = pr.other_repo.repo_name
1148 pr_repo = pr.other_repo.repo_name
1149 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1149 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1150
1150
1151 for stat in statuses.all():
1151 for stat in statuses.all():
1152 pr_id = pr_repo = None
1152 pr_id = pr_repo = None
1153 if stat.pull_request:
1153 if stat.pull_request:
1154 pr_id = stat.pull_request.pull_request_id
1154 pr_id = stat.pull_request.pull_request_id
1155 pr_repo = stat.pull_request.other_repo.repo_name
1155 pr_repo = stat.pull_request.other_repo.repo_name
1156 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1156 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1157 pr_id, pr_repo]
1157 pr_id, pr_repo]
1158 return grouped
1158 return grouped
1159
1159
1160 def _repo_size(self):
1160 def _repo_size(self):
1161 from rhodecode.lib import helpers as h
1161 from rhodecode.lib import helpers as h
1162 log.debug('calculating repository size...')
1162 log.debug('calculating repository size...')
1163 return h.format_byte_size(self.scm_instance.size)
1163 return h.format_byte_size(self.scm_instance.size)
1164
1164
1165 #==========================================================================
1165 #==========================================================================
1166 # SCM CACHE INSTANCE
1166 # SCM CACHE INSTANCE
1167 #==========================================================================
1167 #==========================================================================
1168
1168
1169 def set_invalidate(self):
1169 def set_invalidate(self):
1170 """
1170 """
1171 Mark caches of this repo as invalid.
1171 Mark caches of this repo as invalid.
1172 """
1172 """
1173 CacheInvalidation.set_invalidate(self.repo_name)
1173 CacheInvalidation.set_invalidate(self.repo_name)
1174
1174
1175 def scm_instance_no_cache(self):
1175 def scm_instance_no_cache(self):
1176 return self.__get_instance()
1176 return self.__get_instance()
1177
1177
1178 @property
1178 @property
1179 def scm_instance(self):
1179 def scm_instance(self):
1180 import rhodecode
1180 import rhodecode
1181 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1181 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1182 if full_cache:
1182 if full_cache:
1183 return self.scm_instance_cached()
1183 return self.scm_instance_cached()
1184 return self.__get_instance()
1184 return self.__get_instance()
1185
1185
1186 def scm_instance_cached(self, valid_cache_keys=None):
1186 def scm_instance_cached(self, valid_cache_keys=None):
1187 @cache_region('long_term')
1187 @cache_region('long_term')
1188 def _c(repo_name):
1188 def _c(repo_name):
1189 return self.__get_instance()
1189 return self.__get_instance()
1190 rn = self.repo_name
1190 rn = self.repo_name
1191
1191
1192 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1192 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1193 if not valid:
1193 if not valid:
1194 log.debug('Cache for %s invalidated, getting new object' % (rn))
1194 log.debug('Cache for %s invalidated, getting new object' % (rn))
1195 region_invalidate(_c, None, rn)
1195 region_invalidate(_c, None, rn)
1196 else:
1196 else:
1197 log.debug('Getting obj for %s from cache' % (rn))
1197 log.debug('Getting obj for %s from cache' % (rn))
1198 return _c(rn)
1198 return _c(rn)
1199
1199
1200 def __get_instance(self):
1200 def __get_instance(self):
1201 repo_full_path = self.repo_full_path
1201 repo_full_path = self.repo_full_path
1202 try:
1202 try:
1203 alias = get_scm(repo_full_path)[0]
1203 alias = get_scm(repo_full_path)[0]
1204 log.debug('Creating instance of %s repository from %s'
1204 log.debug('Creating instance of %s repository from %s'
1205 % (alias, repo_full_path))
1205 % (alias, repo_full_path))
1206 backend = get_backend(alias)
1206 backend = get_backend(alias)
1207 except VCSError:
1207 except VCSError:
1208 log.error(traceback.format_exc())
1208 log.error(traceback.format_exc())
1209 log.error('Perhaps this repository is in db and not in '
1209 log.error('Perhaps this repository is in db and not in '
1210 'filesystem run rescan repositories with '
1210 'filesystem run rescan repositories with '
1211 '"destroy old data " option from admin panel')
1211 '"destroy old data " option from admin panel')
1212 return
1212 return
1213
1213
1214 if alias == 'hg':
1214 if alias == 'hg':
1215
1215
1216 repo = backend(safe_str(repo_full_path), create=False,
1216 repo = backend(safe_str(repo_full_path), create=False,
1217 baseui=self._ui)
1217 baseui=self._ui)
1218 # skip hidden web repository
1218 # skip hidden web repository
1219 if repo._get_hidden():
1219 if repo._get_hidden():
1220 return
1220 return
1221 else:
1221 else:
1222 repo = backend(repo_full_path, create=False)
1222 repo = backend(repo_full_path, create=False)
1223
1223
1224 return repo
1224 return repo
1225
1225
1226
1226
1227 class RepoGroup(Base, BaseModel):
1227 class RepoGroup(Base, BaseModel):
1228 __tablename__ = 'groups'
1228 __tablename__ = 'groups'
1229 __table_args__ = (
1229 __table_args__ = (
1230 UniqueConstraint('group_name', 'group_parent_id'),
1230 UniqueConstraint('group_name', 'group_parent_id'),
1231 CheckConstraint('group_id != group_parent_id'),
1231 CheckConstraint('group_id != group_parent_id'),
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 'mysql_charset': 'utf8'},
1233 'mysql_charset': 'utf8'},
1234 )
1234 )
1235 __mapper_args__ = {'order_by': 'group_name'}
1235 __mapper_args__ = {'order_by': 'group_name'}
1236
1236
1237 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1238 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1239 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1239 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1240 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1240 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1241 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1241 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1243
1243
1244 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1244 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1245 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1245 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1246 parent_group = relationship('RepoGroup', remote_side=group_id)
1246 parent_group = relationship('RepoGroup', remote_side=group_id)
1247 user = relationship('User')
1247 user = relationship('User')
1248
1248
1249 def __init__(self, group_name='', parent_group=None):
1249 def __init__(self, group_name='', parent_group=None):
1250 self.group_name = group_name
1250 self.group_name = group_name
1251 self.parent_group = parent_group
1251 self.parent_group = parent_group
1252
1252
1253 def __unicode__(self):
1253 def __unicode__(self):
1254 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1254 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1255 self.group_name)
1255 self.group_name)
1256
1256
1257 @classmethod
1257 @classmethod
1258 def groups_choices(cls, groups=None, show_empty_group=True):
1258 def groups_choices(cls, groups=None, show_empty_group=True):
1259 from webhelpers.html import literal as _literal
1259 from webhelpers.html import literal as _literal
1260 if not groups:
1260 if not groups:
1261 groups = cls.query().all()
1261 groups = cls.query().all()
1262
1262
1263 repo_groups = []
1263 repo_groups = []
1264 if show_empty_group:
1264 if show_empty_group:
1265 repo_groups = [('-1', '-- %s --' % _('top level'))]
1265 repo_groups = [('-1', '-- %s --' % _('top level'))]
1266 sep = ' &raquo; '
1266 sep = ' &raquo; '
1267 _name = lambda k: _literal(sep.join(k))
1267 _name = lambda k: _literal(sep.join(k))
1268
1268
1269 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1269 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1270 for x in groups])
1270 for x in groups])
1271
1271
1272 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1272 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1273 return repo_groups
1273 return repo_groups
1274
1274
1275 @classmethod
1275 @classmethod
1276 def url_sep(cls):
1276 def url_sep(cls):
1277 return URL_SEP
1277 return URL_SEP
1278
1278
1279 @classmethod
1279 @classmethod
1280 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1280 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1281 if case_insensitive:
1281 if case_insensitive:
1282 gr = cls.query()\
1282 gr = cls.query()\
1283 .filter(cls.group_name.ilike(group_name))
1283 .filter(cls.group_name.ilike(group_name))
1284 else:
1284 else:
1285 gr = cls.query()\
1285 gr = cls.query()\
1286 .filter(cls.group_name == group_name)
1286 .filter(cls.group_name == group_name)
1287 if cache:
1287 if cache:
1288 gr = gr.options(FromCache(
1288 gr = gr.options(FromCache(
1289 "sql_cache_short",
1289 "sql_cache_short",
1290 "get_group_%s" % _hash_key(group_name)
1290 "get_group_%s" % _hash_key(group_name)
1291 )
1291 )
1292 )
1292 )
1293 return gr.scalar()
1293 return gr.scalar()
1294
1294
1295 @property
1295 @property
1296 def parents(self):
1296 def parents(self):
1297 parents_recursion_limit = 5
1297 parents_recursion_limit = 5
1298 groups = []
1298 groups = []
1299 if self.parent_group is None:
1299 if self.parent_group is None:
1300 return groups
1300 return groups
1301 cur_gr = self.parent_group
1301 cur_gr = self.parent_group
1302 groups.insert(0, cur_gr)
1302 groups.insert(0, cur_gr)
1303 cnt = 0
1303 cnt = 0
1304 while 1:
1304 while 1:
1305 cnt += 1
1305 cnt += 1
1306 gr = getattr(cur_gr, 'parent_group', None)
1306 gr = getattr(cur_gr, 'parent_group', None)
1307 cur_gr = cur_gr.parent_group
1307 cur_gr = cur_gr.parent_group
1308 if gr is None:
1308 if gr is None:
1309 break
1309 break
1310 if cnt == parents_recursion_limit:
1310 if cnt == parents_recursion_limit:
1311 # this will prevent accidental infinit loops
1311 # this will prevent accidental infinit loops
1312 log.error('group nested more than %s' %
1312 log.error('group nested more than %s' %
1313 parents_recursion_limit)
1313 parents_recursion_limit)
1314 break
1314 break
1315
1315
1316 groups.insert(0, gr)
1316 groups.insert(0, gr)
1317 return groups
1317 return groups
1318
1318
1319 @property
1319 @property
1320 def children(self):
1320 def children(self):
1321 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1321 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1322
1322
1323 @property
1323 @property
1324 def name(self):
1324 def name(self):
1325 return self.group_name.split(RepoGroup.url_sep())[-1]
1325 return self.group_name.split(RepoGroup.url_sep())[-1]
1326
1326
1327 @property
1327 @property
1328 def full_path(self):
1328 def full_path(self):
1329 return self.group_name
1329 return self.group_name
1330
1330
1331 @property
1331 @property
1332 def full_path_splitted(self):
1332 def full_path_splitted(self):
1333 return self.group_name.split(RepoGroup.url_sep())
1333 return self.group_name.split(RepoGroup.url_sep())
1334
1334
1335 @property
1335 @property
1336 def repositories(self):
1336 def repositories(self):
1337 return Repository.query()\
1337 return Repository.query()\
1338 .filter(Repository.group == self)\
1338 .filter(Repository.group == self)\
1339 .order_by(Repository.repo_name)
1339 .order_by(Repository.repo_name)
1340
1340
1341 @property
1341 @property
1342 def repositories_recursive_count(self):
1342 def repositories_recursive_count(self):
1343 cnt = self.repositories.count()
1343 cnt = self.repositories.count()
1344
1344
1345 def children_count(group):
1345 def children_count(group):
1346 cnt = 0
1346 cnt = 0
1347 for child in group.children:
1347 for child in group.children:
1348 cnt += child.repositories.count()
1348 cnt += child.repositories.count()
1349 cnt += children_count(child)
1349 cnt += children_count(child)
1350 return cnt
1350 return cnt
1351
1351
1352 return cnt + children_count(self)
1352 return cnt + children_count(self)
1353
1353
1354 def _recursive_objects(self, include_repos=True):
1354 def _recursive_objects(self, include_repos=True):
1355 all_ = []
1355 all_ = []
1356
1356
1357 def _get_members(root_gr):
1357 def _get_members(root_gr):
1358 if include_repos:
1358 if include_repos:
1359 for r in root_gr.repositories:
1359 for r in root_gr.repositories:
1360 all_.append(r)
1360 all_.append(r)
1361 childs = root_gr.children.all()
1361 childs = root_gr.children.all()
1362 if childs:
1362 if childs:
1363 for gr in childs:
1363 for gr in childs:
1364 all_.append(gr)
1364 all_.append(gr)
1365 _get_members(gr)
1365 _get_members(gr)
1366
1366
1367 _get_members(self)
1367 _get_members(self)
1368 return [self] + all_
1368 return [self] + all_
1369
1369
1370 def recursive_groups_and_repos(self):
1370 def recursive_groups_and_repos(self):
1371 """
1371 """
1372 Recursive return all groups, with repositories in those groups
1372 Recursive return all groups, with repositories in those groups
1373 """
1373 """
1374 return self._recursive_objects()
1374 return self._recursive_objects()
1375
1375
1376 def recursive_groups(self):
1376 def recursive_groups(self):
1377 """
1377 """
1378 Returns all children groups for this group including children of children
1378 Returns all children groups for this group including children of children
1379 """
1379 """
1380 return self._recursive_objects(include_repos=False)
1380 return self._recursive_objects(include_repos=False)
1381
1381
1382 def get_new_name(self, group_name):
1382 def get_new_name(self, group_name):
1383 """
1383 """
1384 returns new full group name based on parent and new name
1384 returns new full group name based on parent and new name
1385
1385
1386 :param group_name:
1386 :param group_name:
1387 """
1387 """
1388 path_prefix = (self.parent_group.full_path_splitted if
1388 path_prefix = (self.parent_group.full_path_splitted if
1389 self.parent_group else [])
1389 self.parent_group else [])
1390 return RepoGroup.url_sep().join(path_prefix + [group_name])
1390 return RepoGroup.url_sep().join(path_prefix + [group_name])
1391
1391
1392
1392
1393 class Permission(Base, BaseModel):
1393 class Permission(Base, BaseModel):
1394 __tablename__ = 'permissions'
1394 __tablename__ = 'permissions'
1395 __table_args__ = (
1395 __table_args__ = (
1396 Index('p_perm_name_idx', 'permission_name'),
1396 Index('p_perm_name_idx', 'permission_name'),
1397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1398 'mysql_charset': 'utf8'},
1398 'mysql_charset': 'utf8'},
1399 )
1399 )
1400 PERMS = [
1400 PERMS = [
1401 ('hg.admin', _('RhodeCode Administrator')),
1401 ('hg.admin', _('RhodeCode Administrator')),
1402
1402
1403 ('repository.none', _('Repository no access')),
1403 ('repository.none', _('Repository no access')),
1404 ('repository.read', _('Repository read access')),
1404 ('repository.read', _('Repository read access')),
1405 ('repository.write', _('Repository write access')),
1405 ('repository.write', _('Repository write access')),
1406 ('repository.admin', _('Repository admin access')),
1406 ('repository.admin', _('Repository admin access')),
1407
1407
1408 ('group.none', _('Repository group no access')),
1408 ('group.none', _('Repository group no access')),
1409 ('group.read', _('Repository group read access')),
1409 ('group.read', _('Repository group read access')),
1410 ('group.write', _('Repository group write access')),
1410 ('group.write', _('Repository group write access')),
1411 ('group.admin', _('Repository group admin access')),
1411 ('group.admin', _('Repository group admin access')),
1412
1412
1413 ('usergroup.none', _('User group no access')),
1413 ('usergroup.none', _('User group no access')),
1414 ('usergroup.read', _('User group read access')),
1414 ('usergroup.read', _('User group read access')),
1415 ('usergroup.write', _('User group write access')),
1415 ('usergroup.write', _('User group write access')),
1416 ('usergroup.admin', _('User group admin access')),
1416 ('usergroup.admin', _('User group admin access')),
1417
1417
1418 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1418 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1419 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1419 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1420
1420
1421 ('hg.usergroup.create.false', _('User Group creation disabled')),
1421 ('hg.usergroup.create.false', _('User Group creation disabled')),
1422 ('hg.usergroup.create.true', _('User Group creation enabled')),
1422 ('hg.usergroup.create.true', _('User Group creation enabled')),
1423
1423
1424 ('hg.create.none', _('Repository creation disabled')),
1424 ('hg.create.none', _('Repository creation disabled')),
1425 ('hg.create.repository', _('Repository creation enabled')),
1425 ('hg.create.repository', _('Repository creation enabled')),
1426
1426
1427 ('hg.fork.none', _('Repository forking disabled')),
1427 ('hg.fork.none', _('Repository forking disabled')),
1428 ('hg.fork.repository', _('Repository forking enabled')),
1428 ('hg.fork.repository', _('Repository forking enabled')),
1429
1429
1430 ('hg.register.none', _('Registration disabled')),
1430 ('hg.register.none', _('Registration disabled')),
1431 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1431 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1432 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1432 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1433
1433
1434 ('hg.extern_activate.manual', _('Manual activation of external account')),
1434 ('hg.extern_activate.manual', _('Manual activation of external account')),
1435 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1435 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1436
1436
1437 ]
1437 ]
1438
1438
1439 #definition of system default permissions for DEFAULT user
1439 #definition of system default permissions for DEFAULT user
1440 DEFAULT_USER_PERMISSIONS = [
1440 DEFAULT_USER_PERMISSIONS = [
1441 'repository.read',
1441 'repository.read',
1442 'group.read',
1442 'group.read',
1443 'usergroup.read',
1443 'usergroup.read',
1444 'hg.create.repository',
1444 'hg.create.repository',
1445 'hg.fork.repository',
1445 'hg.fork.repository',
1446 'hg.register.manual_activate',
1446 'hg.register.manual_activate',
1447 'hg.extern_activate.auto',
1447 'hg.extern_activate.auto',
1448 ]
1448 ]
1449
1449
1450 # defines which permissions are more important higher the more important
1450 # defines which permissions are more important higher the more important
1451 # Weight defines which permissions are more important.
1451 # Weight defines which permissions are more important.
1452 # The higher number the more important.
1452 # The higher number the more important.
1453 PERM_WEIGHTS = {
1453 PERM_WEIGHTS = {
1454 'repository.none': 0,
1454 'repository.none': 0,
1455 'repository.read': 1,
1455 'repository.read': 1,
1456 'repository.write': 3,
1456 'repository.write': 3,
1457 'repository.admin': 4,
1457 'repository.admin': 4,
1458
1458
1459 'group.none': 0,
1459 'group.none': 0,
1460 'group.read': 1,
1460 'group.read': 1,
1461 'group.write': 3,
1461 'group.write': 3,
1462 'group.admin': 4,
1462 'group.admin': 4,
1463
1463
1464 'usergroup.none': 0,
1464 'usergroup.none': 0,
1465 'usergroup.read': 1,
1465 'usergroup.read': 1,
1466 'usergroup.write': 3,
1466 'usergroup.write': 3,
1467 'usergroup.admin': 4,
1467 'usergroup.admin': 4,
1468 'hg.repogroup.create.false': 0,
1468 'hg.repogroup.create.false': 0,
1469 'hg.repogroup.create.true': 1,
1469 'hg.repogroup.create.true': 1,
1470
1470
1471 'hg.usergroup.create.false': 0,
1471 'hg.usergroup.create.false': 0,
1472 'hg.usergroup.create.true': 1,
1472 'hg.usergroup.create.true': 1,
1473
1473
1474 'hg.fork.none': 0,
1474 'hg.fork.none': 0,
1475 'hg.fork.repository': 1,
1475 'hg.fork.repository': 1,
1476 'hg.create.none': 0,
1476 'hg.create.none': 0,
1477 'hg.create.repository': 1
1477 'hg.create.repository': 1
1478 }
1478 }
1479
1479
1480 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1480 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1481 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1481 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1482 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1482 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1483
1483
1484 def __unicode__(self):
1484 def __unicode__(self):
1485 return u"<%s('%s:%s')>" % (
1485 return u"<%s('%s:%s')>" % (
1486 self.__class__.__name__, self.permission_id, self.permission_name
1486 self.__class__.__name__, self.permission_id, self.permission_name
1487 )
1487 )
1488
1488
1489 @classmethod
1489 @classmethod
1490 def get_by_key(cls, key):
1490 def get_by_key(cls, key):
1491 return cls.query().filter(cls.permission_name == key).scalar()
1491 return cls.query().filter(cls.permission_name == key).scalar()
1492
1492
1493 @classmethod
1493 @classmethod
1494 def get_default_perms(cls, default_user_id):
1494 def get_default_perms(cls, default_user_id):
1495 q = Session().query(UserRepoToPerm, Repository, cls)\
1495 q = Session().query(UserRepoToPerm, Repository, cls)\
1496 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1496 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1497 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1497 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1498 .filter(UserRepoToPerm.user_id == default_user_id)
1498 .filter(UserRepoToPerm.user_id == default_user_id)
1499
1499
1500 return q.all()
1500 return q.all()
1501
1501
1502 @classmethod
1502 @classmethod
1503 def get_default_group_perms(cls, default_user_id):
1503 def get_default_group_perms(cls, default_user_id):
1504 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1504 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1506 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1506 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1507 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1507 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1508
1508
1509 return q.all()
1509 return q.all()
1510
1510
1511 @classmethod
1511 @classmethod
1512 def get_default_user_group_perms(cls, default_user_id):
1512 def get_default_user_group_perms(cls, default_user_id):
1513 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1513 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1514 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1514 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1515 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1515 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1516 .filter(UserUserGroupToPerm.user_id == default_user_id)
1516 .filter(UserUserGroupToPerm.user_id == default_user_id)
1517
1517
1518 return q.all()
1518 return q.all()
1519
1519
1520
1520
1521 class UserRepoToPerm(Base, BaseModel):
1521 class UserRepoToPerm(Base, BaseModel):
1522 __tablename__ = 'repo_to_perm'
1522 __tablename__ = 'repo_to_perm'
1523 __table_args__ = (
1523 __table_args__ = (
1524 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1524 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1525 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1525 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1526 'mysql_charset': 'utf8'}
1526 'mysql_charset': 'utf8'}
1527 )
1527 )
1528 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1528 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1529 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1530 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1530 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1531 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1531 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1532
1532
1533 user = relationship('User')
1533 user = relationship('User')
1534 repository = relationship('Repository')
1534 repository = relationship('Repository')
1535 permission = relationship('Permission')
1535 permission = relationship('Permission')
1536
1536
1537 @classmethod
1537 @classmethod
1538 def create(cls, user, repository, permission):
1538 def create(cls, user, repository, permission):
1539 n = cls()
1539 n = cls()
1540 n.user = user
1540 n.user = user
1541 n.repository = repository
1541 n.repository = repository
1542 n.permission = permission
1542 n.permission = permission
1543 Session().add(n)
1543 Session().add(n)
1544 return n
1544 return n
1545
1545
1546 def __unicode__(self):
1546 def __unicode__(self):
1547 return u'<%s => %s >' % (self.user, self.repository)
1547 return u'<%s => %s >' % (self.user, self.repository)
1548
1548
1549
1549
1550 class UserUserGroupToPerm(Base, BaseModel):
1550 class UserUserGroupToPerm(Base, BaseModel):
1551 __tablename__ = 'user_user_group_to_perm'
1551 __tablename__ = 'user_user_group_to_perm'
1552 __table_args__ = (
1552 __table_args__ = (
1553 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1553 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1555 'mysql_charset': 'utf8'}
1555 'mysql_charset': 'utf8'}
1556 )
1556 )
1557 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1557 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1560 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1560 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1561
1561
1562 user = relationship('User')
1562 user = relationship('User')
1563 user_group = relationship('UserGroup')
1563 user_group = relationship('UserGroup')
1564 permission = relationship('Permission')
1564 permission = relationship('Permission')
1565
1565
1566 @classmethod
1566 @classmethod
1567 def create(cls, user, user_group, permission):
1567 def create(cls, user, user_group, permission):
1568 n = cls()
1568 n = cls()
1569 n.user = user
1569 n.user = user
1570 n.user_group = user_group
1570 n.user_group = user_group
1571 n.permission = permission
1571 n.permission = permission
1572 Session().add(n)
1572 Session().add(n)
1573 return n
1573 return n
1574
1574
1575 def __unicode__(self):
1575 def __unicode__(self):
1576 return u'<%s => %s >' % (self.user, self.user_group)
1576 return u'<%s => %s >' % (self.user, self.user_group)
1577
1577
1578
1578
1579 class UserToPerm(Base, BaseModel):
1579 class UserToPerm(Base, BaseModel):
1580 __tablename__ = 'user_to_perm'
1580 __tablename__ = 'user_to_perm'
1581 __table_args__ = (
1581 __table_args__ = (
1582 UniqueConstraint('user_id', 'permission_id'),
1582 UniqueConstraint('user_id', 'permission_id'),
1583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1584 'mysql_charset': 'utf8'}
1584 'mysql_charset': 'utf8'}
1585 )
1585 )
1586 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1586 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1589
1589
1590 user = relationship('User')
1590 user = relationship('User')
1591 permission = relationship('Permission', lazy='joined')
1591 permission = relationship('Permission', lazy='joined')
1592
1592
1593 def __unicode__(self):
1593 def __unicode__(self):
1594 return u'<%s => %s >' % (self.user, self.permission)
1594 return u'<%s => %s >' % (self.user, self.permission)
1595
1595
1596
1596
1597 class UserGroupRepoToPerm(Base, BaseModel):
1597 class UserGroupRepoToPerm(Base, BaseModel):
1598 __tablename__ = 'users_group_repo_to_perm'
1598 __tablename__ = 'users_group_repo_to_perm'
1599 __table_args__ = (
1599 __table_args__ = (
1600 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1600 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1602 'mysql_charset': 'utf8'}
1602 'mysql_charset': 'utf8'}
1603 )
1603 )
1604 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1604 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1605 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1605 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1606 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1606 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1607 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1607 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1608
1608
1609 users_group = relationship('UserGroup')
1609 users_group = relationship('UserGroup')
1610 permission = relationship('Permission')
1610 permission = relationship('Permission')
1611 repository = relationship('Repository')
1611 repository = relationship('Repository')
1612
1612
1613 @classmethod
1613 @classmethod
1614 def create(cls, users_group, repository, permission):
1614 def create(cls, users_group, repository, permission):
1615 n = cls()
1615 n = cls()
1616 n.users_group = users_group
1616 n.users_group = users_group
1617 n.repository = repository
1617 n.repository = repository
1618 n.permission = permission
1618 n.permission = permission
1619 Session().add(n)
1619 Session().add(n)
1620 return n
1620 return n
1621
1621
1622 def __unicode__(self):
1622 def __unicode__(self):
1623 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1623 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1624
1624
1625
1625
1626 class UserGroupUserGroupToPerm(Base, BaseModel):
1626 class UserGroupUserGroupToPerm(Base, BaseModel):
1627 __tablename__ = 'user_group_user_group_to_perm'
1627 __tablename__ = 'user_group_user_group_to_perm'
1628 __table_args__ = (
1628 __table_args__ = (
1629 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1629 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1630 CheckConstraint('target_user_group_id != user_group_id'),
1630 CheckConstraint('target_user_group_id != user_group_id'),
1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1632 'mysql_charset': 'utf8'}
1632 'mysql_charset': 'utf8'}
1633 )
1633 )
1634 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1635 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1636 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1636 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1637 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1637 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1638
1638
1639 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1639 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1640 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1640 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1641 permission = relationship('Permission')
1641 permission = relationship('Permission')
1642
1642
1643 @classmethod
1643 @classmethod
1644 def create(cls, target_user_group, user_group, permission):
1644 def create(cls, target_user_group, user_group, permission):
1645 n = cls()
1645 n = cls()
1646 n.target_user_group = target_user_group
1646 n.target_user_group = target_user_group
1647 n.user_group = user_group
1647 n.user_group = user_group
1648 n.permission = permission
1648 n.permission = permission
1649 Session().add(n)
1649 Session().add(n)
1650 return n
1650 return n
1651
1651
1652 def __unicode__(self):
1652 def __unicode__(self):
1653 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1653 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1654
1654
1655
1655
1656 class UserGroupToPerm(Base, BaseModel):
1656 class UserGroupToPerm(Base, BaseModel):
1657 __tablename__ = 'users_group_to_perm'
1657 __tablename__ = 'users_group_to_perm'
1658 __table_args__ = (
1658 __table_args__ = (
1659 UniqueConstraint('users_group_id', 'permission_id',),
1659 UniqueConstraint('users_group_id', 'permission_id',),
1660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1661 'mysql_charset': 'utf8'}
1661 'mysql_charset': 'utf8'}
1662 )
1662 )
1663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1666
1666
1667 users_group = relationship('UserGroup')
1667 users_group = relationship('UserGroup')
1668 permission = relationship('Permission')
1668 permission = relationship('Permission')
1669
1669
1670
1670
1671 class UserRepoGroupToPerm(Base, BaseModel):
1671 class UserRepoGroupToPerm(Base, BaseModel):
1672 __tablename__ = 'user_repo_group_to_perm'
1672 __tablename__ = 'user_repo_group_to_perm'
1673 __table_args__ = (
1673 __table_args__ = (
1674 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1674 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1676 'mysql_charset': 'utf8'}
1676 'mysql_charset': 'utf8'}
1677 )
1677 )
1678
1678
1679 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1679 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1681 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1681 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1683
1683
1684 user = relationship('User')
1684 user = relationship('User')
1685 group = relationship('RepoGroup')
1685 group = relationship('RepoGroup')
1686 permission = relationship('Permission')
1686 permission = relationship('Permission')
1687
1687
1688
1688
1689 class UserGroupRepoGroupToPerm(Base, BaseModel):
1689 class UserGroupRepoGroupToPerm(Base, BaseModel):
1690 __tablename__ = 'users_group_repo_group_to_perm'
1690 __tablename__ = 'users_group_repo_group_to_perm'
1691 __table_args__ = (
1691 __table_args__ = (
1692 UniqueConstraint('users_group_id', 'group_id'),
1692 UniqueConstraint('users_group_id', 'group_id'),
1693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1694 'mysql_charset': 'utf8'}
1694 'mysql_charset': 'utf8'}
1695 )
1695 )
1696
1696
1697 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1697 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1699 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1699 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1700 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1700 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1701
1701
1702 users_group = relationship('UserGroup')
1702 users_group = relationship('UserGroup')
1703 permission = relationship('Permission')
1703 permission = relationship('Permission')
1704 group = relationship('RepoGroup')
1704 group = relationship('RepoGroup')
1705
1705
1706
1706
1707 class Statistics(Base, BaseModel):
1707 class Statistics(Base, BaseModel):
1708 __tablename__ = 'statistics'
1708 __tablename__ = 'statistics'
1709 __table_args__ = (
1709 __table_args__ = (
1710 UniqueConstraint('repository_id'),
1710 UniqueConstraint('repository_id'),
1711 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1711 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1712 'mysql_charset': 'utf8'}
1712 'mysql_charset': 'utf8'}
1713 )
1713 )
1714 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1714 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1716 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1716 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1717 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1717 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1718 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1718 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1719 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1719 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1720
1720
1721 repository = relationship('Repository', single_parent=True)
1721 repository = relationship('Repository', single_parent=True)
1722
1722
1723
1723
1724 class UserFollowing(Base, BaseModel):
1724 class UserFollowing(Base, BaseModel):
1725 __tablename__ = 'user_followings'
1725 __tablename__ = 'user_followings'
1726 __table_args__ = (
1726 __table_args__ = (
1727 UniqueConstraint('user_id', 'follows_repository_id'),
1727 UniqueConstraint('user_id', 'follows_repository_id'),
1728 UniqueConstraint('user_id', 'follows_user_id'),
1728 UniqueConstraint('user_id', 'follows_user_id'),
1729 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1729 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1730 'mysql_charset': 'utf8'}
1730 'mysql_charset': 'utf8'}
1731 )
1731 )
1732
1732
1733 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1733 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1735 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1735 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1736 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1736 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1737 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1737 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1738
1738
1739 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1739 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1740
1740
1741 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1741 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1742 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1742 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1743
1743
1744 @classmethod
1744 @classmethod
1745 def get_repo_followers(cls, repo_id):
1745 def get_repo_followers(cls, repo_id):
1746 return cls.query().filter(cls.follows_repo_id == repo_id)
1746 return cls.query().filter(cls.follows_repo_id == repo_id)
1747
1747
1748
1748
1749 class CacheInvalidation(Base, BaseModel):
1749 class CacheInvalidation(Base, BaseModel):
1750 __tablename__ = 'cache_invalidation'
1750 __tablename__ = 'cache_invalidation'
1751 __table_args__ = (
1751 __table_args__ = (
1752 UniqueConstraint('cache_key'),
1752 UniqueConstraint('cache_key'),
1753 Index('key_idx', 'cache_key'),
1753 Index('key_idx', 'cache_key'),
1754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1755 'mysql_charset': 'utf8'},
1755 'mysql_charset': 'utf8'},
1756 )
1756 )
1757 # cache_id, not used
1757 # cache_id, not used
1758 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1758 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1759 # cache_key as created by _get_cache_key
1759 # cache_key as created by _get_cache_key
1760 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1760 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1761 # cache_args is a repo_name
1761 # cache_args is a repo_name
1762 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1762 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1763 # instance sets cache_active True when it is caching,
1763 # instance sets cache_active True when it is caching,
1764 # other instances set cache_active to False to indicate that this cache is invalid
1764 # other instances set cache_active to False to indicate that this cache is invalid
1765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1766
1766
1767 def __init__(self, cache_key, repo_name=''):
1767 def __init__(self, cache_key, repo_name=''):
1768 self.cache_key = cache_key
1768 self.cache_key = cache_key
1769 self.cache_args = repo_name
1769 self.cache_args = repo_name
1770 self.cache_active = False
1770 self.cache_active = False
1771
1771
1772 def __unicode__(self):
1772 def __unicode__(self):
1773 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1773 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1774 self.cache_id, self.cache_key, self.cache_active)
1774 self.cache_id, self.cache_key, self.cache_active)
1775
1775
1776 def _cache_key_partition(self):
1776 def _cache_key_partition(self):
1777 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1777 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1778 return prefix, repo_name, suffix
1778 return prefix, repo_name, suffix
1779
1779
1780 def get_prefix(self):
1780 def get_prefix(self):
1781 """
1781 """
1782 get prefix that might have been used in _get_cache_key to
1782 get prefix that might have been used in _get_cache_key to
1783 generate self.cache_key. Only used for informational purposes
1783 generate self.cache_key. Only used for informational purposes
1784 in repo_edit.html.
1784 in repo_edit.html.
1785 """
1785 """
1786 # prefix, repo_name, suffix
1786 # prefix, repo_name, suffix
1787 return self._cache_key_partition()[0]
1787 return self._cache_key_partition()[0]
1788
1788
1789 def get_suffix(self):
1789 def get_suffix(self):
1790 """
1790 """
1791 get suffix that might have been used in _get_cache_key to
1791 get suffix that might have been used in _get_cache_key to
1792 generate self.cache_key. Only used for informational purposes
1792 generate self.cache_key. Only used for informational purposes
1793 in repo_edit.html.
1793 in repo_edit.html.
1794 """
1794 """
1795 # prefix, repo_name, suffix
1795 # prefix, repo_name, suffix
1796 return self._cache_key_partition()[2]
1796 return self._cache_key_partition()[2]
1797
1797
1798 @classmethod
1798 @classmethod
1799 def clear_cache(cls):
1799 def clear_cache(cls):
1800 """
1800 """
1801 Delete all cache keys from database.
1801 Delete all cache keys from database.
1802 Should only be run when all instances are down and all entries thus stale.
1802 Should only be run when all instances are down and all entries thus stale.
1803 """
1803 """
1804 cls.query().delete()
1804 cls.query().delete()
1805 Session().commit()
1805 Session().commit()
1806
1806
1807 @classmethod
1807 @classmethod
1808 def _get_cache_key(cls, key):
1808 def _get_cache_key(cls, key):
1809 """
1809 """
1810 Wrapper for generating a unique cache key for this instance and "key".
1810 Wrapper for generating a unique cache key for this instance and "key".
1811 key must / will start with a repo_name which will be stored in .cache_args .
1811 key must / will start with a repo_name which will be stored in .cache_args .
1812 """
1812 """
1813 import rhodecode
1813 import rhodecode
1814 prefix = rhodecode.CONFIG.get('instance_id', '')
1814 prefix = rhodecode.CONFIG.get('instance_id', '')
1815 return "%s%s" % (prefix, key)
1815 return "%s%s" % (prefix, key)
1816
1816
1817 @classmethod
1817 @classmethod
1818 def set_invalidate(cls, repo_name):
1818 def set_invalidate(cls, repo_name):
1819 """
1819 """
1820 Mark all caches of a repo as invalid in the database.
1820 Mark all caches of a repo as invalid in the database.
1821 """
1821 """
1822 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1822 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1823
1823
1824 try:
1824 try:
1825 for inv_obj in inv_objs:
1825 for inv_obj in inv_objs:
1826 log.debug('marking %s key for invalidation based on repo_name=%s'
1826 log.debug('marking %s key for invalidation based on repo_name=%s'
1827 % (inv_obj, safe_str(repo_name)))
1827 % (inv_obj, safe_str(repo_name)))
1828 inv_obj.cache_active = False
1828 inv_obj.cache_active = False
1829 Session().add(inv_obj)
1829 Session().add(inv_obj)
1830 Session().commit()
1830 Session().commit()
1831 except Exception:
1831 except Exception:
1832 log.error(traceback.format_exc())
1832 log.error(traceback.format_exc())
1833 Session().rollback()
1833 Session().rollback()
1834
1834
1835 @classmethod
1835 @classmethod
1836 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1836 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1837 """
1837 """
1838 Mark this cache key as active and currently cached.
1838 Mark this cache key as active and currently cached.
1839 Return True if the existing cache registration still was valid.
1839 Return True if the existing cache registration still was valid.
1840 Return False to indicate that it had been invalidated and caches should be refreshed.
1840 Return False to indicate that it had been invalidated and caches should be refreshed.
1841 """
1841 """
1842
1842
1843 key = (repo_name + '_' + kind) if kind else repo_name
1843 key = (repo_name + '_' + kind) if kind else repo_name
1844 cache_key = cls._get_cache_key(key)
1844 cache_key = cls._get_cache_key(key)
1845
1845
1846 if valid_cache_keys and cache_key in valid_cache_keys:
1846 if valid_cache_keys and cache_key in valid_cache_keys:
1847 return True
1847 return True
1848
1848
1849 try:
1849 try:
1850 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1850 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1851 if not inv_obj:
1851 if not inv_obj:
1852 inv_obj = CacheInvalidation(cache_key, repo_name)
1852 inv_obj = CacheInvalidation(cache_key, repo_name)
1853 was_valid = inv_obj.cache_active
1853 was_valid = inv_obj.cache_active
1854 inv_obj.cache_active = True
1854 inv_obj.cache_active = True
1855 Session().add(inv_obj)
1855 Session().add(inv_obj)
1856 Session().commit()
1856 Session().commit()
1857 return was_valid
1857 return was_valid
1858 except Exception:
1858 except Exception:
1859 log.error(traceback.format_exc())
1859 log.error(traceback.format_exc())
1860 Session().rollback()
1860 Session().rollback()
1861 return False
1861 return False
1862
1862
1863 @classmethod
1863 @classmethod
1864 def get_valid_cache_keys(cls):
1864 def get_valid_cache_keys(cls):
1865 """
1865 """
1866 Return opaque object with information of which caches still are valid
1866 Return opaque object with information of which caches still are valid
1867 and can be used without checking for invalidation.
1867 and can be used without checking for invalidation.
1868 """
1868 """
1869 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1869 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1870
1870
1871
1871
1872 class ChangesetComment(Base, BaseModel):
1872 class ChangesetComment(Base, BaseModel):
1873 __tablename__ = 'changeset_comments'
1873 __tablename__ = 'changeset_comments'
1874 __table_args__ = (
1874 __table_args__ = (
1875 Index('cc_revision_idx', 'revision'),
1875 Index('cc_revision_idx', 'revision'),
1876 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1876 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1877 'mysql_charset': 'utf8'},
1877 'mysql_charset': 'utf8'},
1878 )
1878 )
1879 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1879 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1880 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1880 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1881 revision = Column('revision', String(40), nullable=True)
1881 revision = Column('revision', String(40), nullable=True)
1882 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1882 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1883 line_no = Column('line_no', Unicode(10), nullable=True)
1883 line_no = Column('line_no', Unicode(10), nullable=True)
1884 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1884 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1885 f_path = Column('f_path', Unicode(1000), nullable=True)
1885 f_path = Column('f_path', Unicode(1000), nullable=True)
1886 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1886 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1887 text = Column('text', UnicodeText(25000), nullable=False)
1887 text = Column('text', UnicodeText(25000), nullable=False)
1888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1889 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1889 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1890
1890
1891 author = relationship('User', lazy='joined')
1891 author = relationship('User', lazy='joined')
1892 repo = relationship('Repository')
1892 repo = relationship('Repository')
1893 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1893 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1894 pull_request = relationship('PullRequest', lazy='joined')
1894 pull_request = relationship('PullRequest', lazy='joined')
1895
1895
1896 @classmethod
1896 @classmethod
1897 def get_users(cls, revision=None, pull_request_id=None):
1897 def get_users(cls, revision=None, pull_request_id=None):
1898 """
1898 """
1899 Returns user associated with this ChangesetComment. ie those
1899 Returns user associated with this ChangesetComment. ie those
1900 who actually commented
1900 who actually commented
1901
1901
1902 :param cls:
1902 :param cls:
1903 :param revision:
1903 :param revision:
1904 """
1904 """
1905 q = Session().query(User)\
1905 q = Session().query(User)\
1906 .join(ChangesetComment.author)
1906 .join(ChangesetComment.author)
1907 if revision:
1907 if revision:
1908 q = q.filter(cls.revision == revision)
1908 q = q.filter(cls.revision == revision)
1909 elif pull_request_id:
1909 elif pull_request_id:
1910 q = q.filter(cls.pull_request_id == pull_request_id)
1910 q = q.filter(cls.pull_request_id == pull_request_id)
1911 return q.all()
1911 return q.all()
1912
1912
1913
1913
1914 class ChangesetStatus(Base, BaseModel):
1914 class ChangesetStatus(Base, BaseModel):
1915 __tablename__ = 'changeset_statuses'
1915 __tablename__ = 'changeset_statuses'
1916 __table_args__ = (
1916 __table_args__ = (
1917 Index('cs_revision_idx', 'revision'),
1917 Index('cs_revision_idx', 'revision'),
1918 Index('cs_version_idx', 'version'),
1918 Index('cs_version_idx', 'version'),
1919 UniqueConstraint('repo_id', 'revision', 'version'),
1919 UniqueConstraint('repo_id', 'revision', 'version'),
1920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1921 'mysql_charset': 'utf8'}
1921 'mysql_charset': 'utf8'}
1922 )
1922 )
1923 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1923 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1924 STATUS_APPROVED = 'approved'
1924 STATUS_APPROVED = 'approved'
1925 STATUS_REJECTED = 'rejected'
1925 STATUS_REJECTED = 'rejected'
1926 STATUS_UNDER_REVIEW = 'under_review'
1926 STATUS_UNDER_REVIEW = 'under_review'
1927
1927
1928 STATUSES = [
1928 STATUSES = [
1929 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1929 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1930 (STATUS_APPROVED, _("Approved")),
1930 (STATUS_APPROVED, _("Approved")),
1931 (STATUS_REJECTED, _("Rejected")),
1931 (STATUS_REJECTED, _("Rejected")),
1932 (STATUS_UNDER_REVIEW, _("Under Review")),
1932 (STATUS_UNDER_REVIEW, _("Under Review")),
1933 ]
1933 ]
1934
1934
1935 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1935 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1936 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1936 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1938 revision = Column('revision', String(40), nullable=False)
1938 revision = Column('revision', String(40), nullable=False)
1939 status = Column('status', String(128), nullable=False, default=DEFAULT)
1939 status = Column('status', String(128), nullable=False, default=DEFAULT)
1940 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1940 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1941 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1941 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1942 version = Column('version', Integer(), nullable=False, default=0)
1942 version = Column('version', Integer(), nullable=False, default=0)
1943 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1943 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1944
1944
1945 author = relationship('User', lazy='joined')
1945 author = relationship('User', lazy='joined')
1946 repo = relationship('Repository')
1946 repo = relationship('Repository')
1947 comment = relationship('ChangesetComment', lazy='joined')
1947 comment = relationship('ChangesetComment', lazy='joined')
1948 pull_request = relationship('PullRequest', lazy='joined')
1948 pull_request = relationship('PullRequest', lazy='joined')
1949
1949
1950 def __unicode__(self):
1950 def __unicode__(self):
1951 return u"<%s('%s:%s')>" % (
1951 return u"<%s('%s:%s')>" % (
1952 self.__class__.__name__,
1952 self.__class__.__name__,
1953 self.status, self.author
1953 self.status, self.author
1954 )
1954 )
1955
1955
1956 @classmethod
1956 @classmethod
1957 def get_status_lbl(cls, value):
1957 def get_status_lbl(cls, value):
1958 return dict(cls.STATUSES).get(value)
1958 return dict(cls.STATUSES).get(value)
1959
1959
1960 @property
1960 @property
1961 def status_lbl(self):
1961 def status_lbl(self):
1962 return ChangesetStatus.get_status_lbl(self.status)
1962 return ChangesetStatus.get_status_lbl(self.status)
1963
1963
1964
1964
1965 class PullRequest(Base, BaseModel):
1965 class PullRequest(Base, BaseModel):
1966 __tablename__ = 'pull_requests'
1966 __tablename__ = 'pull_requests'
1967 __table_args__ = (
1967 __table_args__ = (
1968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1969 'mysql_charset': 'utf8'},
1969 'mysql_charset': 'utf8'},
1970 )
1970 )
1971
1971
1972 STATUS_NEW = u'new'
1972 STATUS_NEW = u'new'
1973 STATUS_OPEN = u'open'
1973 STATUS_OPEN = u'open'
1974 STATUS_CLOSED = u'closed'
1974 STATUS_CLOSED = u'closed'
1975
1975
1976 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1976 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1977 title = Column('title', Unicode(256), nullable=True)
1977 title = Column('title', Unicode(256), nullable=True)
1978 description = Column('description', UnicodeText(10240), nullable=True)
1978 description = Column('description', UnicodeText(10240), nullable=True)
1979 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1979 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1981 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1981 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1982 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1982 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1983 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1983 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1984 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1984 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1985 org_ref = Column('org_ref', Unicode(256), nullable=False)
1985 org_ref = Column('org_ref', Unicode(256), nullable=False)
1986 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1986 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1987 other_ref = Column('other_ref', Unicode(256), nullable=False)
1987 other_ref = Column('other_ref', Unicode(256), nullable=False)
1988
1988
1989 @hybrid_property
1989 @hybrid_property
1990 def revisions(self):
1990 def revisions(self):
1991 return self._revisions.split(':')
1991 return self._revisions.split(':')
1992
1992
1993 @revisions.setter
1993 @revisions.setter
1994 def revisions(self, val):
1994 def revisions(self, val):
1995 self._revisions = ':'.join(val)
1995 self._revisions = ':'.join(val)
1996
1996
1997 @property
1997 @property
1998 def org_ref_parts(self):
1998 def org_ref_parts(self):
1999 return self.org_ref.split(':')
1999 return self.org_ref.split(':')
2000
2000
2001 @property
2001 @property
2002 def other_ref_parts(self):
2002 def other_ref_parts(self):
2003 return self.other_ref.split(':')
2003 return self.other_ref.split(':')
2004
2004
2005 author = relationship('User', lazy='joined')
2005 author = relationship('User', lazy='joined')
2006 reviewers = relationship('PullRequestReviewers',
2006 reviewers = relationship('PullRequestReviewers',
2007 cascade="all, delete, delete-orphan")
2007 cascade="all, delete, delete-orphan")
2008 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2008 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2009 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2009 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2010 statuses = relationship('ChangesetStatus')
2010 statuses = relationship('ChangesetStatus')
2011 comments = relationship('ChangesetComment',
2011 comments = relationship('ChangesetComment',
2012 cascade="all, delete, delete-orphan")
2012 cascade="all, delete, delete-orphan")
2013
2013
2014 def is_closed(self):
2014 def is_closed(self):
2015 return self.status == self.STATUS_CLOSED
2015 return self.status == self.STATUS_CLOSED
2016
2016
2017 @property
2017 @property
2018 def last_review_status(self):
2018 def last_review_status(self):
2019 return self.statuses[-1].status if self.statuses else ''
2019 return self.statuses[-1].status if self.statuses else ''
2020
2020
2021 def __json__(self):
2021 def __json__(self):
2022 return dict(
2022 return dict(
2023 revisions=self.revisions
2023 revisions=self.revisions
2024 )
2024 )
2025
2025
2026
2026
2027 class PullRequestReviewers(Base, BaseModel):
2027 class PullRequestReviewers(Base, BaseModel):
2028 __tablename__ = 'pull_request_reviewers'
2028 __tablename__ = 'pull_request_reviewers'
2029 __table_args__ = (
2029 __table_args__ = (
2030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2031 'mysql_charset': 'utf8'},
2031 'mysql_charset': 'utf8'},
2032 )
2032 )
2033
2033
2034 def __init__(self, user=None, pull_request=None):
2034 def __init__(self, user=None, pull_request=None):
2035 self.user = user
2035 self.user = user
2036 self.pull_request = pull_request
2036 self.pull_request = pull_request
2037
2037
2038 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2038 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2039 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2039 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2041
2041
2042 user = relationship('User')
2042 user = relationship('User')
2043 pull_request = relationship('PullRequest')
2043 pull_request = relationship('PullRequest')
2044
2044
2045
2045
2046 class Notification(Base, BaseModel):
2046 class Notification(Base, BaseModel):
2047 __tablename__ = 'notifications'
2047 __tablename__ = 'notifications'
2048 __table_args__ = (
2048 __table_args__ = (
2049 Index('notification_type_idx', 'type'),
2049 Index('notification_type_idx', 'type'),
2050 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2050 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2051 'mysql_charset': 'utf8'},
2051 'mysql_charset': 'utf8'},
2052 )
2052 )
2053
2053
2054 TYPE_CHANGESET_COMMENT = u'cs_comment'
2054 TYPE_CHANGESET_COMMENT = u'cs_comment'
2055 TYPE_MESSAGE = u'message'
2055 TYPE_MESSAGE = u'message'
2056 TYPE_MENTION = u'mention'
2056 TYPE_MENTION = u'mention'
2057 TYPE_REGISTRATION = u'registration'
2057 TYPE_REGISTRATION = u'registration'
2058 TYPE_PULL_REQUEST = u'pull_request'
2058 TYPE_PULL_REQUEST = u'pull_request'
2059 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2059 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2060
2060
2061 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2061 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2062 subject = Column('subject', Unicode(512), nullable=True)
2062 subject = Column('subject', Unicode(512), nullable=True)
2063 body = Column('body', UnicodeText(50000), nullable=True)
2063 body = Column('body', UnicodeText(50000), nullable=True)
2064 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2064 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2065 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2065 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2066 type_ = Column('type', Unicode(256))
2066 type_ = Column('type', Unicode(256))
2067
2067
2068 created_by_user = relationship('User')
2068 created_by_user = relationship('User')
2069 notifications_to_users = relationship('UserNotification', lazy='joined',
2069 notifications_to_users = relationship('UserNotification', lazy='joined',
2070 cascade="all, delete, delete-orphan")
2070 cascade="all, delete, delete-orphan")
2071
2071
2072 @property
2072 @property
2073 def recipients(self):
2073 def recipients(self):
2074 return [x.user for x in UserNotification.query()\
2074 return [x.user for x in UserNotification.query()\
2075 .filter(UserNotification.notification == self)\
2075 .filter(UserNotification.notification == self)\
2076 .order_by(UserNotification.user_id.asc()).all()]
2076 .order_by(UserNotification.user_id.asc()).all()]
2077
2077
2078 @classmethod
2078 @classmethod
2079 def create(cls, created_by, subject, body, recipients, type_=None):
2079 def create(cls, created_by, subject, body, recipients, type_=None):
2080 if type_ is None:
2080 if type_ is None:
2081 type_ = Notification.TYPE_MESSAGE
2081 type_ = Notification.TYPE_MESSAGE
2082
2082
2083 notification = cls()
2083 notification = cls()
2084 notification.created_by_user = created_by
2084 notification.created_by_user = created_by
2085 notification.subject = subject
2085 notification.subject = subject
2086 notification.body = body
2086 notification.body = body
2087 notification.type_ = type_
2087 notification.type_ = type_
2088 notification.created_on = datetime.datetime.now()
2088 notification.created_on = datetime.datetime.now()
2089
2089
2090 for u in recipients:
2090 for u in recipients:
2091 assoc = UserNotification()
2091 assoc = UserNotification()
2092 assoc.notification = notification
2092 assoc.notification = notification
2093 u.notifications.append(assoc)
2093 u.notifications.append(assoc)
2094 Session().add(notification)
2094 Session().add(notification)
2095 return notification
2095 return notification
2096
2096
2097 @property
2097 @property
2098 def description(self):
2098 def description(self):
2099 from rhodecode.model.notification import NotificationModel
2099 from rhodecode.model.notification import NotificationModel
2100 return NotificationModel().make_description(self)
2100 return NotificationModel().make_description(self)
2101
2101
2102
2102
2103 class UserNotification(Base, BaseModel):
2103 class UserNotification(Base, BaseModel):
2104 __tablename__ = 'user_to_notification'
2104 __tablename__ = 'user_to_notification'
2105 __table_args__ = (
2105 __table_args__ = (
2106 UniqueConstraint('user_id', 'notification_id'),
2106 UniqueConstraint('user_id', 'notification_id'),
2107 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2107 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2108 'mysql_charset': 'utf8'}
2108 'mysql_charset': 'utf8'}
2109 )
2109 )
2110 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2110 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2111 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2111 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2112 read = Column('read', Boolean, default=False)
2112 read = Column('read', Boolean, default=False)
2113 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2113 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2114
2114
2115 user = relationship('User', lazy="joined")
2115 user = relationship('User', lazy="joined")
2116 notification = relationship('Notification', lazy="joined",
2116 notification = relationship('Notification', lazy="joined",
2117 order_by=lambda: Notification.created_on.desc(),)
2117 order_by=lambda: Notification.created_on.desc(),)
2118
2118
2119 def mark_as_read(self):
2119 def mark_as_read(self):
2120 self.read = True
2120 self.read = True
2121 Session().add(self)
2121 Session().add(self)
2122
2122
2123
2123
2124 class Gist(Base, BaseModel):
2124 class Gist(Base, BaseModel):
2125 __tablename__ = 'gists'
2125 __tablename__ = 'gists'
2126 __table_args__ = (
2126 __table_args__ = (
2127 Index('g_gist_access_id_idx', 'gist_access_id'),
2127 Index('g_gist_access_id_idx', 'gist_access_id'),
2128 Index('g_created_on_idx', 'created_on'),
2128 Index('g_created_on_idx', 'created_on'),
2129 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2129 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2130 'mysql_charset': 'utf8'}
2130 'mysql_charset': 'utf8'}
2131 )
2131 )
2132 GIST_PUBLIC = u'public'
2132 GIST_PUBLIC = u'public'
2133 GIST_PRIVATE = u'private'
2133 GIST_PRIVATE = u'private'
2134
2134
2135 gist_id = Column('gist_id', Integer(), primary_key=True)
2135 gist_id = Column('gist_id', Integer(), primary_key=True)
2136 gist_access_id = Column('gist_access_id', UnicodeText(1024))
2136 gist_access_id = Column('gist_access_id', UnicodeText(1024))
2137 gist_description = Column('gist_description', UnicodeText(1024))
2137 gist_description = Column('gist_description', UnicodeText(1024))
2138 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2138 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2139 gist_expires = Column('gist_expires', Float(), nullable=False)
2139 gist_expires = Column('gist_expires', Float(), nullable=False)
2140 gist_type = Column('gist_type', Unicode(128), nullable=False)
2140 gist_type = Column('gist_type', Unicode(128), nullable=False)
2141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2142 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2142 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2143
2143
2144 owner = relationship('User')
2144 owner = relationship('User')
2145
2145
2146 @classmethod
2146 @classmethod
2147 def get_or_404(cls, id_):
2147 def get_or_404(cls, id_):
2148 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2148 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2149 if not res:
2149 if not res:
2150 raise HTTPNotFound
2150 raise HTTPNotFound
2151 return res
2151 return res
2152
2152
2153 @classmethod
2153 @classmethod
2154 def get_by_access_id(cls, gist_access_id):
2154 def get_by_access_id(cls, gist_access_id):
2155 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2155 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2156
2156
2157 def gist_url(self):
2157 def gist_url(self):
2158 import rhodecode
2159 alias_url = rhodecode.CONFIG.get('gist_alias_url')
2160 if alias_url:
2161 return alias_url.replace('{gistid}', self.gist_access_id)
2162
2158 from pylons import url
2163 from pylons import url
2159 return url('gist', id=self.gist_access_id, qualified=True)
2164 return url('gist', id=self.gist_access_id, qualified=True)
2160
2165
2161 @classmethod
2166 @classmethod
2162 def base_path(cls):
2167 def base_path(cls):
2163 """
2168 """
2164 Returns base path when all gists are stored
2169 Returns base path when all gists are stored
2165
2170
2166 :param cls:
2171 :param cls:
2167 """
2172 """
2168 from rhodecode.model.gist import GIST_STORE_LOC
2173 from rhodecode.model.gist import GIST_STORE_LOC
2169 q = Session().query(RhodeCodeUi)\
2174 q = Session().query(RhodeCodeUi)\
2170 .filter(RhodeCodeUi.ui_key == URL_SEP)
2175 .filter(RhodeCodeUi.ui_key == URL_SEP)
2171 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2176 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2172 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2177 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2173
2178
2174 def get_api_data(self):
2179 def get_api_data(self):
2175 """
2180 """
2176 Common function for generating gist related data for API
2181 Common function for generating gist related data for API
2177 """
2182 """
2178 gist = self
2183 gist = self
2179 data = dict(
2184 data = dict(
2180 gist_id=gist.gist_id,
2185 gist_id=gist.gist_id,
2181 type=gist.gist_type,
2186 type=gist.gist_type,
2182 access_id=gist.gist_access_id,
2187 access_id=gist.gist_access_id,
2183 description=gist.gist_description,
2188 description=gist.gist_description,
2184 url=gist.gist_url(),
2189 url=gist.gist_url(),
2185 expires=gist.gist_expires,
2190 expires=gist.gist_expires,
2186 created_on=gist.created_on,
2191 created_on=gist.created_on,
2187 )
2192 )
2188 return data
2193 return data
2189
2194
2190 def __json__(self):
2195 def __json__(self):
2191 data = dict(
2196 data = dict(
2192 )
2197 )
2193 data.update(self.get_api_data())
2198 data.update(self.get_api_data())
2194 return data
2199 return data
2195 ## SCM functions
2200 ## SCM functions
2196
2201
2197 @property
2202 @property
2198 def scm_instance(self):
2203 def scm_instance(self):
2199 from rhodecode.lib.vcs import get_repo
2204 from rhodecode.lib.vcs import get_repo
2200 base_path = self.base_path()
2205 base_path = self.base_path()
2201 return get_repo(os.path.join(*map(safe_str,
2206 return get_repo(os.path.join(*map(safe_str,
2202 [base_path, self.gist_access_id])))
2207 [base_path, self.gist_access_id])))
2203
2208
2204
2209
2205 class DbMigrateVersion(Base, BaseModel):
2210 class DbMigrateVersion(Base, BaseModel):
2206 __tablename__ = 'db_migrate_version'
2211 __tablename__ = 'db_migrate_version'
2207 __table_args__ = (
2212 __table_args__ = (
2208 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2213 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2209 'mysql_charset': 'utf8'},
2214 'mysql_charset': 'utf8'},
2210 )
2215 )
2211 repository_id = Column('repository_id', String(250), primary_key=True)
2216 repository_id = Column('repository_id', String(250), primary_key=True)
2212 repository_path = Column('repository_path', Text)
2217 repository_path = Column('repository_path', Text)
2213 version = Column('version', Integer)
2218 version = Column('version', Integer)
@@ -1,89 +1,92 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('gist')}:${c.gist.gist_access_id} &middot; ${c.rhodecode_name}
5 ${_('gist')}:${c.gist.gist_access_id} &middot; ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('Gist')} &middot; gist:${c.gist.gist_access_id}
9 ${_('Gist')} &middot; gist:${c.gist.gist_access_id}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('gists')}
13 ${self.menu('gists')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 %if c.rhodecode_user.username != 'default':
21 %if c.rhodecode_user.username != 'default':
22 <ul class="links">
22 <ul class="links">
23 <li>
23 <li>
24 <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span>
24 <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span>
25 </li>
25 </li>
26 </ul>
26 </ul>
27 %endif
27 %endif
28 </div>
28 </div>
29 <div class="table">
29 <div class="table">
30 <div id="files_data">
30 <div id="files_data">
31 <div id="body" class="codeblock">
31 <div id="body" class="codeblock">
32 <div class="code-header">
32 <div class="code-header">
33 <div class="stats">
33 <div class="stats">
34 <div class="left" style="margin: -4px 0px 0px 0px">
34 <div class="left" style="margin: -4px 0px 0px 0px">
35 %if c.gist.gist_type == 'public':
35 %if c.gist.gist_type == 'public':
36 <div class="ui-btn green badge">${_('Public gist')}</div>
36 <div class="ui-btn green badge">${_('Public gist')}</div>
37 %else:
37 %else:
38 <div class="ui-btn yellow badge">${_('Private gist')}</div>
38 <div class="ui-btn yellow badge">${_('Private gist')}</div>
39 %endif
39 %endif
40 </div>
40 </div>
41 <span style="color: #AAA">
41 <div class="left item ${'' if c.gist.gist_description else 'last'}" style="color: #AAA">
42 %if c.gist.gist_expires == -1:
42 %if c.gist.gist_expires == -1:
43 ${_('Expires')}: ${_('never')}
43 ${_('Expires')}: ${_('never')}
44 %else:
44 %else:
45 ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
45 ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
46 %endif
46 %endif
47 </span>
47 </div>
48 <div class="left item last">${c.gist.gist_description}</div>
48 <div class="left item last">
49 ${c.gist.gist_description}
50 </div>
49 <div class="buttons">
51 <div class="buttons">
50 ## only owner should see that
52 ## only owner should see that
51 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
53 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
52 ##${h.link_to(_('Edit'),h.url(''),class_="ui-btn")}
54 ##${h.link_to(_('Edit'),h.url(''),class_="ui-btn")}
53 ${h.form(url('gist', id=c.gist.gist_id),method='delete')}
55 ${h.form(url('gist', id=c.gist.gist_id),method='delete')}
54 ${h.submit('remove_gist', _('Delete'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this gist')+"');")}
56 ${h.submit('remove_gist', _('Delete'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this gist')+"');")}
55 ${h.end_form()}
57 ${h.end_form()}
56 %endif
58 %endif
57 </div>
59 </div>
58 </div>
60 </div>
59
61
60 <div class="author">
62 <div class="author">
61 <div class="gravatar">
63 <div class="gravatar">
62 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.file_changeset.author),16)}"/>
64 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.file_changeset.author),16)}"/>
63 </div>
65 </div>
64 <div title="${c.file_changeset.author}" class="user">${h.person(c.file_changeset.author)} - ${_('created')} ${h.age(c.file_changeset.date)}</div>
66 <div title="${c.file_changeset.author}" class="user">${h.person(c.file_changeset.author)} - ${_('created')} ${h.age(c.file_changeset.date)}</div>
65 </div>
67 </div>
66 <div class="commit">${h.urlify_commit(c.file_changeset.message,c.repo_name)}</div>
68 <div class="commit">${h.urlify_commit(c.file_changeset.message,c.repo_name)}</div>
67 </div>
69 </div>
68 </div>
70 </div>
69
71
70 ## iterate over the files
72 ## iterate over the files
71 % for file in c.files:
73 % for file in c.files:
72 <div style="border: 1px solid #EEE;margin-top:20px">
74 <div style="border: 1px solid #EEE;margin-top:20px">
73 <div id="${h.FID('G', file.path)}" class="stats" style="border-bottom: 1px solid #DDD;padding: 8px 14px;">
75 <div id="${h.FID('G', file.path)}" class="stats" style="border-bottom: 1px solid #DDD;padding: 8px 14px;">
74 <b>${file.path}</b>
76 <a href="${c.gist.gist_url()}"></a>
77 <b style="margin:0px 0px 0px 4px">${file.path}</b>
75 ##<div class="buttons">
78 ##<div class="buttons">
76 ## ${h.link_to(_('Show as raw'),h.url(''),class_="ui-btn")}
79 ## ${h.link_to(_('Show as raw'),h.url(''),class_="ui-btn")}
77 ##</div>
80 ##</div>
78 </div>
81 </div>
79 <div class="code-body">
82 <div class="code-body">
80 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
83 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
81 </div>
84 </div>
82 </div>
85 </div>
83 %endfor
86 %endfor
84 </div>
87 </div>
85 </div>
88 </div>
86
89
87
90
88 </div>
91 </div>
89 </%def>
92 </%def>
General Comments 0
You need to be logged in to leave comments. Login now