##// END OF EJS Templates
merge: merged default into stable
marcink -
r16:170c5398 merge v4.0.1 stable
parent child Browse files
Show More
@@ -0,0 +1,17 b''
1 |RCE| 4.0.1 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2016-05-25
8
9 Fixes
10 ^^^^^
11
12 - fixed default session to be file based instead of memory which causes
13 problems in multi-worker setup
14 - ui: fixing forks table #3959
15 - ui: fixed gravatars misalignment issues
16 - logging: fixed excesive formatting on auth logging
17 - pull requests: better ref selection when opening PRs from changelog
@@ -1,5 +1,6 b''
1 1 [bumpversion]
2 current_version = 4.0.0
2 current_version = 4.0.1
3 3 message = release: Bump version {current_version} to {new_version}
4 4
5 5 [bumpversion:file:rhodecode/VERSION]
6
@@ -1,39 +1,47 b''
1 1
2 2 WEBPACK=./node_modules/webpack/bin/webpack.js
3 3 GRUNT=grunt
4 4 NODE_PATH=./node_modules
5 5 FLAKE8=flake8 setup.py pytest_pylons/ rhodecode/ --select=E124 --ignore=E711,E712,E510,E121,E122,E126,E127,E128,E501,F401 --max-line-length=100 --exclude=*rhodecode/lib/dbmigrate/*,*rhodecode/tests/*,*rhodecode/lib/vcs/utils/*
6 6 CI_PREFIX=enterprise
7 7
8 .PHONY: help clean test test-clean test-lint test-only
8 .PHONY: docs docs-clean ci-docs clean test test-clean test-lint test-only
9
10
11 docs:
12 (cd docs; nix-build default.nix -o result; make clean html)
9 13
10 help:
11 @echo "TODO: describe Makefile"
14 docs-clean:
15 (cd docs; make clean)
16
17 ci-docs: docs;
18
12 19
13 20 clean: test-clean
14 21 find . -type f \( -iname '*.c' -o -iname '*.pyc' -o -iname '*.so' \) -exec rm '{}' ';'
15 22
16 23 test: test-clean test-lint test-only
17 24
18 25 test-clean:
19 26 rm -rf coverage.xml htmlcov junit.xml pylint.log result
20 27
21 28 test-lint:
22 29 if [ "$$IN_NIX_SHELL" = "1" ]; then \
23 30 $(FLAKE8); \
24 31 else \
25 32 $(FLAKE8) --format=pylint --exit-zero > pylint.log; \
26 33 fi
27 34
28 35 test-only:
29 36 PYTHONHASHSEED=random py.test -vv -r xw --cov=rhodecode --cov-report=term-missing --cov-report=html rhodecode/tests/
30 37
31 38 web-build:
32 39 NODE_PATH=$(NODE_PATH) $(GRUNT)
33 40
34 41 web-test:
35 42 @echo "no test for our javascript, yet!"
36 43
37 44 docs-bootstrap:
38 45 (cd docs; nix-build default.nix -o result)
39 46 @echo "Please go to docs folder and run make html"
47
@@ -1,575 +1,577 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode Enterprise - configuration file #
4 4 # Built-in functions and variables #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 # #
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10 debug = true
11 11 pdebug = false
12 12 ################################################################################
13 13 ## Uncomment and replace with the email address which should receive ##
14 14 ## any error reports after an application crash ##
15 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 16 ################################################################################
17 17 #email_to = admin@localhost
18 18 #error_email_from = paste_error@localhost
19 19 #app_email_from = rhodecode-noreply@localhost
20 20 #error_message =
21 21 #email_prefix = [RhodeCode]
22 22
23 23 #smtp_server = mail.server.com
24 24 #smtp_username =
25 25 #smtp_password =
26 26 #smtp_port =
27 27 #smtp_use_tls = false
28 28 #smtp_use_ssl = true
29 29 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
30 30 #smtp_auth =
31 31
32 32 [server:main]
33 33 ## COMMON ##
34 34 host = 127.0.0.1
35 35 port = 5000
36 36
37 37 ##########################
38 38 ## WAITRESS WSGI SERVER ##
39 39 ##########################
40 40 use = egg:waitress#main
41 41 ## number of worker threads
42 42 threads = 5
43 43 ## MAX BODY SIZE 100GB
44 44 max_request_body_size = 107374182400
45 45 ## Use poll instead of select, fixes file descriptors limits problems.
46 46 ## May not work on old windows systems.
47 47 asyncore_use_poll = true
48 48
49 49
50 50 ##########################
51 51 ## GUNICORN WSGI SERVER ##
52 52 ##########################
53 53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 54 #use = egg:gunicorn#main
55 55 ## Sets the number of process workers. You must set `instance_id = *`
56 56 ## when this option is set to more than one worker, recommended
57 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 58 ## The `instance_id = *` must be set in the [app:main] section below
59 59 #workers = 1
60 60 ## number of threads for each of the worker, must be set to 1 for gevent
61 61 ## generally recommened to be at 1
62 62 #threads = 1
63 63 ## process name
64 64 #proc_name = rhodecode
65 65 ## type of worker class, one of sync, gevent
66 66 ## recommended for bigger setup is using of of other than sync one
67 67 #worker_class = sync
68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 #worker_connections = 10
68 70 ## max number of requests that worker will handle before being gracefully
69 71 ## restarted, could prevent memory leaks
70 72 #max_requests = 1000
71 73 #max_requests_jitter = 30
72 74 ## ammount of time a worker can spend with handling a request before it
73 75 ## gets killed and restarted. Set to 6hrs
74 76 #timeout = 21600
75 77
76 78
77 79 ## prefix middleware for RhodeCode, disables force_https flag.
78 80 ## allows to set RhodeCode under a prefix in server.
79 81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
80 82 #[filter:proxy-prefix]
81 83 #use = egg:PasteDeploy#prefix
82 84 #prefix = /<your-prefix>
83 85
84 86 [app:main]
85 87 use = egg:rhodecode-enterprise-ce
86 88 ## enable proxy prefix middleware, defined below
87 89 #filter-with = proxy-prefix
88 90
89 91 # During development the we want to have the debug toolbar enabled
90 92 pyramid.includes =
91 93 pyramid_debugtoolbar
92 94 rhodecode.utils.debugtoolbar
93 95 rhodecode.lib.middleware.request_wrapper
94 96
95 97 pyramid.reload_templates = true
96 98
97 99 debugtoolbar.hosts = 0.0.0.0/0
98 100 debugtoolbar.exclude_prefixes =
99 101 /css
100 102 /fonts
101 103 /images
102 104 /js
103 105
104 106 ## RHODECODE PLUGINS ##
105 107 rhodecode.includes =
106 108 rhodecode.api
107 109
108 110
109 111 # api prefix url
110 112 rhodecode.api.url = /_admin/api
111 113
112 114
113 115 ## END RHODECODE PLUGINS ##
114 116
115 117 full_stack = true
116 118
117 119 ## Serve static files via RhodeCode, disable to serve them via HTTP server
118 120 static_files = true
119 121
120 122 ## Optional Languages
121 123 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
122 124 lang = en
123 125
124 126 ## perform a full repository scan on each server start, this should be
125 127 ## set to false after first startup, to allow faster server restarts.
126 128 startup.import_repos = false
127 129
128 130 ## Uncomment and set this path to use archive download cache.
129 131 ## Once enabled, generated archives will be cached at this location
130 132 ## and served from the cache during subsequent requests for the same archive of
131 133 ## the repository.
132 134 #archive_cache_dir = /tmp/tarballcache
133 135
134 136 ## change this to unique ID for security
135 137 app_instance_uuid = rc-production
136 138
137 139 ## cut off limit for large diffs (size in bytes)
138 140 cut_off_limit_diff = 1024000
139 141 cut_off_limit_file = 256000
140 142
141 143 ## use cache version of scm repo everywhere
142 144 vcs_full_cache = true
143 145
144 146 ## force https in RhodeCode, fixes https redirects, assumes it's always https
145 147 ## Normally this is controlled by proper http flags sent from http server
146 148 force_https = false
147 149
148 150 ## use Strict-Transport-Security headers
149 151 use_htsts = false
150 152
151 153 ## number of commits stats will parse on each iteration
152 154 commit_parse_limit = 25
153 155
154 156 ## git rev filter option, --all is the default filter, if you need to
155 157 ## hide all refs in changelog switch this to --branches --tags
156 158 git_rev_filter = --branches --tags
157 159
158 160 # Set to true if your repos are exposed using the dumb protocol
159 161 git_update_server_info = false
160 162
161 163 ## RSS/ATOM feed options
162 164 rss_cut_off_limit = 256000
163 165 rss_items_per_page = 10
164 166 rss_include_diff = false
165 167
166 168 ## gist URL alias, used to create nicer urls for gist. This should be an
167 169 ## url that does rewrites to _admin/gists/<gistid>.
168 170 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
169 171 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
170 172 gist_alias_url =
171 173
172 174 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
173 175 ## used for access.
174 176 ## Adding ?auth_token = <token> to the url authenticates this request as if it
175 177 ## came from the the logged in user who own this authentication token.
176 178 ##
177 179 ## Syntax is <ControllerClass>:<function_pattern>.
178 180 ## To enable access to raw_files put `FilesController:raw`.
179 181 ## To enable access to patches add `ChangesetController:changeset_patch`.
180 182 ## The list should be "," separated and on a single line.
181 183 ##
182 184 ## Recommended controllers to enable:
183 185 # ChangesetController:changeset_patch,
184 186 # ChangesetController:changeset_raw,
185 187 # FilesController:raw,
186 188 # FilesController:archivefile,
187 189 # GistsController:*,
188 190 api_access_controllers_whitelist =
189 191
190 192 ## default encoding used to convert from and to unicode
191 193 ## can be also a comma separated list of encoding in case of mixed encodings
192 194 default_encoding = UTF-8
193 195
194 196 ## instance-id prefix
195 197 ## a prefix key for this instance used for cache invalidation when running
196 198 ## multiple instances of rhodecode, make sure it's globally unique for
197 199 ## all running rhodecode instances. Leave empty if you don't use it
198 200 instance_id =
199 201
200 202 ## alternative return HTTP header for failed authentication. Default HTTP
201 203 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
202 204 ## handling that causing a series of failed authentication calls.
203 205 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
204 206 ## This will be served instead of default 401 on bad authnetication
205 207 auth_ret_code =
206 208
207 209 ## use special detection method when serving auth_ret_code, instead of serving
208 210 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
209 211 ## and then serve auth_ret_code to clients
210 212 auth_ret_code_detection = false
211 213
212 214 ## locking return code. When repository is locked return this HTTP code. 2XX
213 215 ## codes don't break the transactions while 4XX codes do
214 216 lock_ret_code = 423
215 217
216 218 ## allows to change the repository location in settings page
217 219 allow_repo_location_change = true
218 220
219 221 ## allows to setup custom hooks in settings page
220 222 allow_custom_hooks_settings = true
221 223
222 224 ## generated license token, goto license page in RhodeCode settings to obtain
223 225 ## new token
224 226 license_token =
225 227
226 228 ## supervisor connection uri, for managing supervisor and logs.
227 229 supervisor.uri =
228 230 ## supervisord group name/id we only want this RC instance to handle
229 231 supervisor.group_id = dev
230 232
231 233 ## Display extended labs settings
232 234 labs_settings_active = true
233 235
234 236 ####################################
235 237 ### CELERY CONFIG ####
236 238 ####################################
237 239 use_celery = false
238 240 broker.host = localhost
239 241 broker.vhost = rabbitmqhost
240 242 broker.port = 5672
241 243 broker.user = rabbitmq
242 244 broker.password = qweqwe
243 245
244 246 celery.imports = rhodecode.lib.celerylib.tasks
245 247
246 248 celery.result.backend = amqp
247 249 celery.result.dburi = amqp://
248 250 celery.result.serialier = json
249 251
250 252 #celery.send.task.error.emails = true
251 253 #celery.amqp.task.result.expires = 18000
252 254
253 255 celeryd.concurrency = 2
254 256 #celeryd.log.file = celeryd.log
255 257 celeryd.log.level = debug
256 258 celeryd.max.tasks.per.child = 1
257 259
258 260 ## tasks will never be sent to the queue, but executed locally instead.
259 261 celery.always.eager = false
260 262
261 263 ####################################
262 264 ### BEAKER CACHE ####
263 265 ####################################
264 266 # default cache dir for templates. Putting this into a ramdisk
265 267 ## can boost performance, eg. %(here)s/data_ramdisk
266 268 cache_dir = %(here)s/data
267 269
268 270 ## locking and default file storage for Beaker. Putting this into a ramdisk
269 271 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
270 272 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
271 273 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
272 274
273 275 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
274 276
275 277 beaker.cache.super_short_term.type = memory
276 278 beaker.cache.super_short_term.expire = 10
277 279 beaker.cache.super_short_term.key_length = 256
278 280
279 281 beaker.cache.short_term.type = memory
280 282 beaker.cache.short_term.expire = 60
281 283 beaker.cache.short_term.key_length = 256
282 284
283 285 beaker.cache.long_term.type = memory
284 286 beaker.cache.long_term.expire = 36000
285 287 beaker.cache.long_term.key_length = 256
286 288
287 289 beaker.cache.sql_cache_short.type = memory
288 290 beaker.cache.sql_cache_short.expire = 10
289 291 beaker.cache.sql_cache_short.key_length = 256
290 292
291 293 # default is memory cache, configure only if required
292 294 # using multi-node or multi-worker setup
293 295 #beaker.cache.auth_plugins.type = ext:database
294 296 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
295 297 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
296 298 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
297 299 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
298 300 #beaker.cache.auth_plugins.sa.pool_size = 10
299 301 #beaker.cache.auth_plugins.sa.max_overflow = 0
300 302
301 303 beaker.cache.repo_cache_long.type = memorylru_base
302 304 beaker.cache.repo_cache_long.max_items = 4096
303 305 beaker.cache.repo_cache_long.expire = 2592000
304 306
305 307 # default is memorylru_base cache, configure only if required
306 308 # using multi-node or multi-worker setup
307 309 #beaker.cache.repo_cache_long.type = ext:memcached
308 310 #beaker.cache.repo_cache_long.url = localhost:11211
309 311 #beaker.cache.repo_cache_long.expire = 1209600
310 312 #beaker.cache.repo_cache_long.key_length = 256
311 313
312 314 ####################################
313 315 ### BEAKER SESSION ####
314 316 ####################################
315 317
316 318 ## .session.type is type of storage options for the session, current allowed
317 319 ## types are file, ext:memcached, ext:database, and memory(default).
318 320 beaker.session.type = file
319 321 beaker.session.data_dir = %(here)s/data/sessions/data
320 322
321 323 ## db based session, fast, and allows easy management over logged in users ##
322 324 #beaker.session.type = ext:database
323 325 #beaker.session.table_name = db_session
324 326 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
325 327 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
326 328 #beaker.session.sa.pool_recycle = 3600
327 329 #beaker.session.sa.echo = false
328 330
329 331 beaker.session.key = rhodecode
330 332 beaker.session.secret = develop-rc-uytcxaz
331 333 beaker.session.lock_dir = %(here)s/data/sessions/lock
332 334
333 335 ## Secure encrypted cookie. Requires AES and AES python libraries
334 336 ## you must disable beaker.session.secret to use this
335 337 #beaker.session.encrypt_key = <key_for_encryption>
336 338 #beaker.session.validate_key = <validation_key>
337 339
338 340 ## sets session as invalid(also logging out user) if it haven not been
339 341 ## accessed for given amount of time in seconds
340 342 beaker.session.timeout = 2592000
341 343 beaker.session.httponly = true
342 344 #beaker.session.cookie_path = /<your-prefix>
343 345
344 346 ## uncomment for https secure cookie
345 347 beaker.session.secure = false
346 348
347 349 ## auto save the session to not to use .save()
348 350 beaker.session.auto = false
349 351
350 352 ## default cookie expiration time in seconds, set to `true` to set expire
351 353 ## at browser close
352 354 #beaker.session.cookie_expires = 3600
353 355
354 356 ###################################
355 357 ## SEARCH INDEXING CONFIGURATION ##
356 358 ###################################
357 359
358 360 search.module = rhodecode.lib.index.whoosh
359 361 search.location = %(here)s/data/index
360 362
361 363 ###################################
362 364 ## ERROR AND LOG HANDLING SYSTEM ##
363 365 ###################################
364 366
365 367 ## Appenlight is tailored to work with RhodeCode, see
366 368 ## http://appenlight.com for details how to obtain an account
367 369
368 370 ## appenlight integration enabled
369 371 appenlight = false
370 372
371 373 appenlight.server_url = https://api.appenlight.com
372 374 appenlight.api_key = YOUR_API_KEY
373 375 ;appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
374 376
375 377 # used for JS client
376 378 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
377 379
378 380 ## TWEAK AMOUNT OF INFO SENT HERE
379 381
380 382 ## enables 404 error logging (default False)
381 383 appenlight.report_404 = false
382 384
383 385 ## time in seconds after request is considered being slow (default 1)
384 386 appenlight.slow_request_time = 1
385 387
386 388 ## record slow requests in application
387 389 ## (needs to be enabled for slow datastore recording and time tracking)
388 390 appenlight.slow_requests = true
389 391
390 392 ## enable hooking to application loggers
391 393 appenlight.logging = true
392 394
393 395 ## minimum log level for log capture
394 396 appenlight.logging.level = WARNING
395 397
396 398 ## send logs only from erroneous/slow requests
397 399 ## (saves API quota for intensive logging)
398 400 appenlight.logging_on_error = false
399 401
400 402 ## list of additonal keywords that should be grabbed from environ object
401 403 ## can be string with comma separated list of words in lowercase
402 404 ## (by default client will always send following info:
403 405 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
404 406 ## start with HTTP* this list be extended with additional keywords here
405 407 appenlight.environ_keys_whitelist =
406 408
407 409 ## list of keywords that should be blanked from request object
408 410 ## can be string with comma separated list of words in lowercase
409 411 ## (by default client will always blank keys that contain following words
410 412 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
411 413 ## this list be extended with additional keywords set here
412 414 appenlight.request_keys_blacklist =
413 415
414 416 ## list of namespaces that should be ignores when gathering log entries
415 417 ## can be string with comma separated list of namespaces
416 418 ## (by default the client ignores own entries: appenlight_client.client)
417 419 appenlight.log_namespace_blacklist =
418 420
419 421
420 422 ################################################################################
421 423 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
422 424 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
423 425 ## execute malicious code after an exception is raised. ##
424 426 ################################################################################
425 427 #set debug = false
426 428
427 429
428 430 ##############
429 431 ## STYLING ##
430 432 ##############
431 433 debug_style = true
432 434
433 435 #########################################################
434 436 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
435 437 #########################################################
436 438 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
437 439 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
438 440 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
439 441
440 442 # see sqlalchemy docs for other advanced settings
441 443
442 444 ## print the sql statements to output
443 445 sqlalchemy.db1.echo = false
444 446 ## recycle the connections after this ammount of seconds
445 447 sqlalchemy.db1.pool_recycle = 3600
446 448 sqlalchemy.db1.convert_unicode = true
447 449
448 450 ## the number of connections to keep open inside the connection pool.
449 451 ## 0 indicates no limit
450 452 #sqlalchemy.db1.pool_size = 5
451 453
452 454 ## the number of connections to allow in connection pool "overflow", that is
453 455 ## connections that can be opened above and beyond the pool_size setting,
454 456 ## which defaults to five.
455 457 #sqlalchemy.db1.max_overflow = 10
456 458
457 459
458 460 ##################
459 461 ### VCS CONFIG ###
460 462 ##################
461 463 vcs.server.enable = true
462 464 vcs.server = localhost:9900
463 465 # Available protocols: pyro4, http
464 466 vcs.server.protocol = pyro4
465 467
466 468 # available impl:
467 469 # vcsserver.scm_app (EE only, for testing),
468 470 # rhodecode.lib.middleware.utils.scm_app_http
469 471 # pyro4
470 472 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
471 473
472 474 vcs.server.log_level = debug
473 475 vcs.start_server = true
474 476 vcs.backends = hg, git, svn
475 477 vcs.connection_timeout = 3600
476 478 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
477 479 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
478 480 #vcs.svn.compatible_version = pre-1.8-compatible
479 481
480 482 ################################
481 483 ### LOGGING CONFIGURATION ####
482 484 ################################
483 485 [loggers]
484 486 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
485 487
486 488 [handlers]
487 489 keys = console, console_sql
488 490
489 491 [formatters]
490 492 keys = generic, color_formatter, color_formatter_sql
491 493
492 494 #############
493 495 ## LOGGERS ##
494 496 #############
495 497 [logger_root]
496 498 level = NOTSET
497 499 handlers = console
498 500
499 501 [logger_routes]
500 502 level = DEBUG
501 503 handlers =
502 504 qualname = routes.middleware
503 505 ## "level = DEBUG" logs the route matched and routing variables.
504 506 propagate = 1
505 507
506 508 [logger_beaker]
507 509 level = DEBUG
508 510 handlers =
509 511 qualname = beaker.container
510 512 propagate = 1
511 513
512 514 [logger_pyro4]
513 515 level = DEBUG
514 516 handlers =
515 517 qualname = Pyro4
516 518 propagate = 1
517 519
518 520 [logger_templates]
519 521 level = INFO
520 522 handlers =
521 523 qualname = pylons.templating
522 524 propagate = 1
523 525
524 526 [logger_rhodecode]
525 527 level = DEBUG
526 528 handlers =
527 529 qualname = rhodecode
528 530 propagate = 1
529 531
530 532 [logger_sqlalchemy]
531 533 level = INFO
532 534 handlers = console_sql
533 535 qualname = sqlalchemy.engine
534 536 propagate = 0
535 537
536 538 [logger_whoosh_indexer]
537 539 level = DEBUG
538 540 handlers =
539 541 qualname = whoosh_indexer
540 542 propagate = 1
541 543
542 544 ##############
543 545 ## HANDLERS ##
544 546 ##############
545 547
546 548 [handler_console]
547 549 class = StreamHandler
548 550 args = (sys.stderr,)
549 551 level = DEBUG
550 552 formatter = color_formatter
551 553
552 554 [handler_console_sql]
553 555 class = StreamHandler
554 556 args = (sys.stderr,)
555 557 level = DEBUG
556 558 formatter = color_formatter_sql
557 559
558 560 ################
559 561 ## FORMATTERS ##
560 562 ################
561 563
562 564 [formatter_generic]
563 565 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
564 566 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
565 567 datefmt = %Y-%m-%d %H:%M:%S
566 568
567 569 [formatter_color_formatter]
568 570 class = rhodecode.lib.logging_formatter.ColorFormatter
569 571 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
570 572 datefmt = %Y-%m-%d %H:%M:%S
571 573
572 574 [formatter_color_formatter_sql]
573 575 class = rhodecode.lib.logging_formatter.ColorFormatterSql
574 576 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
575 577 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,541 +1,551 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode Enterprise - configuration file #
4 4 # Built-in functions and variables #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 # #
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10 debug = true
11 11 pdebug = false
12 12 ################################################################################
13 13 ## Uncomment and replace with the email address which should receive ##
14 14 ## any error reports after an application crash ##
15 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 16 ################################################################################
17 17 #email_to = admin@localhost
18 18 #error_email_from = paste_error@localhost
19 19 #app_email_from = rhodecode-noreply@localhost
20 20 #error_message =
21 21 #email_prefix = [RhodeCode]
22 22
23 23 #smtp_server = mail.server.com
24 24 #smtp_username =
25 25 #smtp_password =
26 26 #smtp_port =
27 27 #smtp_use_tls = false
28 28 #smtp_use_ssl = true
29 29 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
30 30 #smtp_auth =
31 31
32 32 [server:main]
33 33 ## COMMON ##
34 34 host = 127.0.0.1
35 35 port = 5000
36 36
37 37 ##########################
38 38 ## WAITRESS WSGI SERVER ##
39 39 ##########################
40 40 use = egg:waitress#main
41 41 ## number of worker threads
42 42 threads = 5
43 43 ## MAX BODY SIZE 100GB
44 44 max_request_body_size = 107374182400
45 45 ## Use poll instead of select, fixes file descriptors limits problems.
46 46 ## May not work on old windows systems.
47 47 asyncore_use_poll = true
48 48
49 49
50 50 ##########################
51 51 ## GUNICORN WSGI SERVER ##
52 52 ##########################
53 53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 54 #use = egg:gunicorn#main
55 55 ## Sets the number of process workers. You must set `instance_id = *`
56 56 ## when this option is set to more than one worker, recommended
57 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 58 ## The `instance_id = *` must be set in the [app:main] section below
59 59 #workers = 1
60 60 ## number of threads for each of the worker, must be set to 1 for gevent
61 61 ## generally recommened to be at 1
62 62 #threads = 1
63 63 ## process name
64 64 #proc_name = rhodecode
65 65 ## type of worker class, one of sync, gevent
66 66 ## recommended for bigger setup is using of of other than sync one
67 67 #worker_class = sync
68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 #worker_connections = 10
68 70 ## max number of requests that worker will handle before being gracefully
69 71 ## restarted, could prevent memory leaks
70 72 #max_requests = 1000
71 73 #max_requests_jitter = 30
72 74 ## ammount of time a worker can spend with handling a request before it
73 75 ## gets killed and restarted. Set to 6hrs
74 76 #timeout = 21600
75 77
76 78
77 79 ## prefix middleware for RhodeCode, disables force_https flag.
78 80 ## allows to set RhodeCode under a prefix in server.
79 81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
80 82 #[filter:proxy-prefix]
81 83 #use = egg:PasteDeploy#prefix
82 84 #prefix = /<your-prefix>
83 85
84 86 [app:main]
85 87 use = egg:rhodecode-enterprise-ce
86 88 ## enable proxy prefix middleware, defined below
87 89 #filter-with = proxy-prefix
88 90
89 91 full_stack = true
90 92
91 93 ## Serve static files via RhodeCode, disable to serve them via HTTP server
92 94 static_files = true
93 95
94 96 ## Optional Languages
95 97 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
96 98 lang = en
97 99
98 100 ## perform a full repository scan on each server start, this should be
99 101 ## set to false after first startup, to allow faster server restarts.
100 102 startup.import_repos = false
101 103
102 104 ## Uncomment and set this path to use archive download cache.
103 105 ## Once enabled, generated archives will be cached at this location
104 106 ## and served from the cache during subsequent requests for the same archive of
105 107 ## the repository.
106 108 #archive_cache_dir = /tmp/tarballcache
107 109
108 110 ## change this to unique ID for security
109 111 app_instance_uuid = rc-production
110 112
111 113 ## cut off limit for large diffs (size in bytes)
112 114 cut_off_limit_diff = 1024000
113 115 cut_off_limit_file = 256000
114 116
115 117 ## use cache version of scm repo everywhere
116 118 vcs_full_cache = true
117 119
118 120 ## force https in RhodeCode, fixes https redirects, assumes it's always https
119 121 ## Normally this is controlled by proper http flags sent from http server
120 122 force_https = false
121 123
122 124 ## use Strict-Transport-Security headers
123 125 use_htsts = false
124 126
125 127 ## number of commits stats will parse on each iteration
126 128 commit_parse_limit = 25
127 129
128 130 ## git rev filter option, --all is the default filter, if you need to
129 131 ## hide all refs in changelog switch this to --branches --tags
130 132 git_rev_filter = --branches --tags
131 133
132 134 # Set to true if your repos are exposed using the dumb protocol
133 135 git_update_server_info = false
134 136
135 137 ## RSS/ATOM feed options
136 138 rss_cut_off_limit = 256000
137 139 rss_items_per_page = 10
138 140 rss_include_diff = false
139 141
140 142 ## gist URL alias, used to create nicer urls for gist. This should be an
141 143 ## url that does rewrites to _admin/gists/<gistid>.
142 144 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
143 145 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
144 146 gist_alias_url =
145 147
146 148 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
147 149 ## used for access.
148 150 ## Adding ?auth_token = <token> to the url authenticates this request as if it
149 151 ## came from the the logged in user who own this authentication token.
150 152 ##
151 153 ## Syntax is <ControllerClass>:<function_pattern>.
152 154 ## To enable access to raw_files put `FilesController:raw`.
153 155 ## To enable access to patches add `ChangesetController:changeset_patch`.
154 156 ## The list should be "," separated and on a single line.
155 157 ##
156 158 ## Recommended controllers to enable:
157 159 # ChangesetController:changeset_patch,
158 160 # ChangesetController:changeset_raw,
159 161 # FilesController:raw,
160 162 # FilesController:archivefile,
161 163 # GistsController:*,
162 164 api_access_controllers_whitelist =
163 165
164 166 ## default encoding used to convert from and to unicode
165 167 ## can be also a comma separated list of encoding in case of mixed encodings
166 168 default_encoding = UTF-8
167 169
168 170 ## instance-id prefix
169 171 ## a prefix key for this instance used for cache invalidation when running
170 172 ## multiple instances of rhodecode, make sure it's globally unique for
171 173 ## all running rhodecode instances. Leave empty if you don't use it
172 174 instance_id =
173 175
174 176 ## alternative return HTTP header for failed authentication. Default HTTP
175 177 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
176 178 ## handling that causing a series of failed authentication calls.
177 179 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
178 180 ## This will be served instead of default 401 on bad authnetication
179 181 auth_ret_code =
180 182
181 183 ## use special detection method when serving auth_ret_code, instead of serving
182 184 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
183 185 ## and then serve auth_ret_code to clients
184 186 auth_ret_code_detection = false
185 187
186 188 ## locking return code. When repository is locked return this HTTP code. 2XX
187 189 ## codes don't break the transactions while 4XX codes do
188 190 lock_ret_code = 423
189 191
190 192 ## allows to change the repository location in settings page
191 193 allow_repo_location_change = true
192 194
193 195 ## allows to setup custom hooks in settings page
194 196 allow_custom_hooks_settings = true
195 197
196 198 ## generated license token, goto license page in RhodeCode settings to obtain
197 199 ## new token
198 200 license_token =
199 201
200 202 ## supervisor connection uri, for managing supervisor and logs.
201 203 supervisor.uri =
202 204 ## supervisord group name/id we only want this RC instance to handle
203 205 supervisor.group_id = prod
204 206
205 207 ## Display extended labs settings
206 208 labs_settings_active = true
207 209
208 210 ####################################
209 211 ### CELERY CONFIG ####
210 212 ####################################
211 213 use_celery = false
212 214 broker.host = localhost
213 215 broker.vhost = rabbitmqhost
214 216 broker.port = 5672
215 217 broker.user = rabbitmq
216 218 broker.password = qweqwe
217 219
218 220 celery.imports = rhodecode.lib.celerylib.tasks
219 221
220 222 celery.result.backend = amqp
221 223 celery.result.dburi = amqp://
222 224 celery.result.serialier = json
223 225
224 226 #celery.send.task.error.emails = true
225 227 #celery.amqp.task.result.expires = 18000
226 228
227 229 celeryd.concurrency = 2
228 230 #celeryd.log.file = celeryd.log
229 231 celeryd.log.level = debug
230 232 celeryd.max.tasks.per.child = 1
231 233
232 234 ## tasks will never be sent to the queue, but executed locally instead.
233 235 celery.always.eager = false
234 236
235 237 ####################################
236 238 ### BEAKER CACHE ####
237 239 ####################################
238 240 # default cache dir for templates. Putting this into a ramdisk
239 241 ## can boost performance, eg. %(here)s/data_ramdisk
240 242 cache_dir = %(here)s/data
241 243
242 244 ## locking and default file storage for Beaker. Putting this into a ramdisk
243 245 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
244 246 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
245 247 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
246 248
247 249 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
248 250
249 251 beaker.cache.super_short_term.type = memory
250 252 beaker.cache.super_short_term.expire = 10
251 253 beaker.cache.super_short_term.key_length = 256
252 254
253 255 beaker.cache.short_term.type = memory
254 256 beaker.cache.short_term.expire = 60
255 257 beaker.cache.short_term.key_length = 256
256 258
257 259 beaker.cache.long_term.type = memory
258 260 beaker.cache.long_term.expire = 36000
259 261 beaker.cache.long_term.key_length = 256
260 262
261 263 beaker.cache.sql_cache_short.type = memory
262 264 beaker.cache.sql_cache_short.expire = 10
263 265 beaker.cache.sql_cache_short.key_length = 256
264 266
265 267 # default is memory cache, configure only if required
266 268 # using multi-node or multi-worker setup
267 269 #beaker.cache.auth_plugins.type = ext:database
268 270 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
269 271 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
270 272 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
271 273 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
272 274 #beaker.cache.auth_plugins.sa.pool_size = 10
273 275 #beaker.cache.auth_plugins.sa.max_overflow = 0
274 276
275 277 beaker.cache.repo_cache_long.type = memorylru_base
276 278 beaker.cache.repo_cache_long.max_items = 4096
277 279 beaker.cache.repo_cache_long.expire = 2592000
278 280
279 281 # default is memorylru_base cache, configure only if required
280 282 # using multi-node or multi-worker setup
281 283 #beaker.cache.repo_cache_long.type = ext:memcached
282 284 #beaker.cache.repo_cache_long.url = localhost:11211
283 285 #beaker.cache.repo_cache_long.expire = 1209600
284 286 #beaker.cache.repo_cache_long.key_length = 256
285 287
286 288 ####################################
287 289 ### BEAKER SESSION ####
288 290 ####################################
289 291
290 292 ## .session.type is type of storage options for the session, current allowed
291 ## types are file(default), ext:memcached, ext:database, and memory.
292 #beaker.session.type = file
293 ## types are file, ext:memcached, ext:database, and memory(default).
294 beaker.session.type = file
295 beaker.session.data_dir = %(here)s/data/sessions/data
293 296
294 297 ## db based session, fast, and allows easy management over logged in users ##
295 298 #beaker.session.type = ext:database
296 #beaker.session.lock_dir = %(here)s/data/cache/session_db_lock
297 299 #beaker.session.table_name = db_session
298 300 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
299 301 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
300 302 #beaker.session.sa.pool_recycle = 3600
301 303 #beaker.session.sa.echo = false
302 304
303 305 beaker.session.key = rhodecode
304 306 beaker.session.secret = production-rc-uytcxaz
307 #beaker.session.lock_dir = %(here)s/data/sessions/lock
305 308
306 309 ## Secure encrypted cookie. Requires AES and AES python libraries
307 310 ## you must disable beaker.session.secret to use this
308 311 #beaker.session.encrypt_key = <key_for_encryption>
309 312 #beaker.session.validate_key = <validation_key>
310 313
311 314 ## sets session as invalid(also logging out user) if it haven not been
312 315 ## accessed for given amount of time in seconds
313 316 beaker.session.timeout = 2592000
314 317 beaker.session.httponly = true
315 318 #beaker.session.cookie_path = /<your-prefix>
316 319
317 320 ## uncomment for https secure cookie
318 321 beaker.session.secure = false
319 322
320 323 ## auto save the session to not to use .save()
321 324 beaker.session.auto = false
322 325
323 326 ## default cookie expiration time in seconds, set to `true` to set expire
324 327 ## at browser close
325 328 #beaker.session.cookie_expires = 3600
326 329
327 330 ###################################
328 331 ## SEARCH INDEXING CONFIGURATION ##
329 332 ###################################
330 333
331 334 search.module = rhodecode.lib.index.whoosh
332 335 search.location = %(here)s/data/index
333 336
334 337 ###################################
335 338 ## ERROR AND LOG HANDLING SYSTEM ##
336 339 ###################################
337 340
338 341 ## Appenlight is tailored to work with RhodeCode, see
339 342 ## http://appenlight.com for details how to obtain an account
340 343
341 344 ## appenlight integration enabled
342 345 appenlight = false
343 346
344 347 appenlight.server_url = https://api.appenlight.com
345 348 appenlight.api_key = YOUR_API_KEY
346 349 ;appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
347 350
348 351 # used for JS client
349 352 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
350 353
351 354 ## TWEAK AMOUNT OF INFO SENT HERE
352 355
353 356 ## enables 404 error logging (default False)
354 357 appenlight.report_404 = false
355 358
356 359 ## time in seconds after request is considered being slow (default 1)
357 360 appenlight.slow_request_time = 1
358 361
359 362 ## record slow requests in application
360 363 ## (needs to be enabled for slow datastore recording and time tracking)
361 364 appenlight.slow_requests = true
362 365
363 366 ## enable hooking to application loggers
364 367 appenlight.logging = true
365 368
366 369 ## minimum log level for log capture
367 370 appenlight.logging.level = WARNING
368 371
369 372 ## send logs only from erroneous/slow requests
370 373 ## (saves API quota for intensive logging)
371 374 appenlight.logging_on_error = false
372 375
373 376 ## list of additonal keywords that should be grabbed from environ object
374 377 ## can be string with comma separated list of words in lowercase
375 378 ## (by default client will always send following info:
376 379 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
377 380 ## start with HTTP* this list be extended with additional keywords here
378 381 appenlight.environ_keys_whitelist =
379 382
380 383 ## list of keywords that should be blanked from request object
381 384 ## can be string with comma separated list of words in lowercase
382 385 ## (by default client will always blank keys that contain following words
383 386 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
384 387 ## this list be extended with additional keywords set here
385 388 appenlight.request_keys_blacklist =
386 389
387 390 ## list of namespaces that should be ignores when gathering log entries
388 391 ## can be string with comma separated list of namespaces
389 392 ## (by default the client ignores own entries: appenlight_client.client)
390 393 appenlight.log_namespace_blacklist =
391 394
392 395
393 396 ################################################################################
394 397 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
395 398 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
396 399 ## execute malicious code after an exception is raised. ##
397 400 ################################################################################
398 401 set debug = false
399 402
400 403
401 404 ##############
402 405 ## STYLING ##
403 406 ##############
404 407 debug_style = false
405 408
406 409 #########################################################
407 410 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
408 411 #########################################################
409 412 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
410 413 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
411 414 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
412 415
413 416 # see sqlalchemy docs for other advanced settings
414 417
415 418 ## print the sql statements to output
416 419 sqlalchemy.db1.echo = false
417 420 ## recycle the connections after this ammount of seconds
418 421 sqlalchemy.db1.pool_recycle = 3600
419 422 sqlalchemy.db1.convert_unicode = true
420 423
421 424 ## the number of connections to keep open inside the connection pool.
422 425 ## 0 indicates no limit
423 426 #sqlalchemy.db1.pool_size = 5
424 427
425 428 ## the number of connections to allow in connection pool "overflow", that is
426 429 ## connections that can be opened above and beyond the pool_size setting,
427 430 ## which defaults to five.
428 431 #sqlalchemy.db1.max_overflow = 10
429 432
430 433
431 434 ##################
432 435 ### VCS CONFIG ###
433 436 ##################
434 437 vcs.server.enable = true
435 438 vcs.server = localhost:9900
436 439 # Available protocols: pyro4, http
437 440 vcs.server.protocol = pyro4
441
442 # available impl:
443 # vcsserver.scm_app (EE only, for testing),
444 # rhodecode.lib.middleware.utils.scm_app_http
445 # pyro4
446 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
447
438 448 vcs.server.log_level = info
439 449 vcs.start_server = false
440 450 vcs.backends = hg, git, svn
441 451 vcs.connection_timeout = 3600
442 452 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
443 453 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
444 454 #vcs.svn.compatible_version = pre-1.8-compatible
445 455
446 456 ################################
447 457 ### LOGGING CONFIGURATION ####
448 458 ################################
449 459 [loggers]
450 460 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
451 461
452 462 [handlers]
453 463 keys = console, console_sql
454 464
455 465 [formatters]
456 466 keys = generic, color_formatter, color_formatter_sql
457 467
458 468 #############
459 469 ## LOGGERS ##
460 470 #############
461 471 [logger_root]
462 472 level = NOTSET
463 473 handlers = console
464 474
465 475 [logger_routes]
466 476 level = DEBUG
467 477 handlers =
468 478 qualname = routes.middleware
469 479 ## "level = DEBUG" logs the route matched and routing variables.
470 480 propagate = 1
471 481
472 482 [logger_beaker]
473 483 level = DEBUG
474 484 handlers =
475 485 qualname = beaker.container
476 486 propagate = 1
477 487
478 488 [logger_pyro4]
479 489 level = DEBUG
480 490 handlers =
481 491 qualname = Pyro4
482 492 propagate = 1
483 493
484 494 [logger_templates]
485 495 level = INFO
486 496 handlers =
487 497 qualname = pylons.templating
488 498 propagate = 1
489 499
490 500 [logger_rhodecode]
491 501 level = DEBUG
492 502 handlers =
493 503 qualname = rhodecode
494 504 propagate = 1
495 505
496 506 [logger_sqlalchemy]
497 507 level = INFO
498 508 handlers = console_sql
499 509 qualname = sqlalchemy.engine
500 510 propagate = 0
501 511
502 512 [logger_whoosh_indexer]
503 513 level = DEBUG
504 514 handlers =
505 515 qualname = whoosh_indexer
506 516 propagate = 1
507 517
508 518 ##############
509 519 ## HANDLERS ##
510 520 ##############
511 521
512 522 [handler_console]
513 523 class = StreamHandler
514 524 args = (sys.stderr,)
515 525 level = INFO
516 526 formatter = generic
517 527
518 528 [handler_console_sql]
519 529 class = StreamHandler
520 530 args = (sys.stderr,)
521 531 level = WARN
522 532 formatter = generic
523 533
524 534 ################
525 535 ## FORMATTERS ##
526 536 ################
527 537
528 538 [formatter_generic]
529 539 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
530 540 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
531 541 datefmt = %Y-%m-%d %H:%M:%S
532 542
533 543 [formatter_color_formatter]
534 544 class = rhodecode.lib.logging_formatter.ColorFormatter
535 545 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
536 546 datefmt = %Y-%m-%d %H:%M:%S
537 547
538 548 [formatter_color_formatter_sql]
539 549 class = rhodecode.lib.logging_formatter.ColorFormatterSql
540 550 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
541 551 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,75 +1,76 b''
1 1 .. _rhodecode-release-notes-ref:
2 2
3 3 Release Notes
4 4 =============
5 5
6 6 |RCE| 4.x Versions
7 7 ------------------
8 8
9 release-notes-4.0.1.rst
9 10 release-notes-4.0.0.rst
10 11
11 12 |RCE| 3.x Versions
12 13 ------------------
13 14
14 15 .. toctree::
15 16 :maxdepth: 1
16 17
17 18 release-notes-3.8.4.rst
18 19 release-notes-3.8.3.rst
19 20 release-notes-3.8.2.rst
20 21 release-notes-3.8.1.rst
21 22 release-notes-3.8.0.rst
22 23 release-notes-3.7.1.rst
23 24 release-notes-3.7.0.rst
24 25 release-notes-3.6.1.rst
25 26 release-notes-3.6.0.rst
26 27 release-notes-3.5.2.rst
27 28 release-notes-3.5.1.rst
28 29 release-notes-3.5.0.rst
29 30 release-notes-3.4.1.rst
30 31 release-notes-3.4.0.rst
31 32 release-notes-3.3.4.rst
32 33 release-notes-3.3.3.rst
33 34 release-notes-3.3.2.rst
34 35 release-notes-3.3.1.rst
35 36 release-notes-3.3.0.rst
36 37 release-notes-3.2.3.rst
37 38 release-notes-3.2.2.rst
38 39 release-notes-3.2.1.rst
39 40 release-notes-3.2.0.rst
40 41 release-notes-3.1.1.rst
41 42 release-notes-3.1.0.rst
42 43 release-notes-3.0.2.rst
43 44 release-notes-3.0.1.rst
44 45 release-notes-3.0.0.rst
45 46
46 47 |RCE| 2.x Versions
47 48 ------------------
48 49
49 50 .. toctree::
50 51 :maxdepth: 1
51 52
52 53 release-notes-2.2.8.rst
53 54 release-notes-2.2.7.rst
54 55 release-notes-2.2.6.rst
55 56 release-notes-2.2.5.rst
56 57 release-notes-2.2.4.rst
57 58 release-notes-2.2.3.rst
58 59 release-notes-2.2.2.rst
59 60 release-notes-2.2.1.rst
60 61 release-notes-2.2.0.rst
61 62 release-notes-2.1.0.rst
62 63 release-notes-2.0.2.rst
63 64 release-notes-2.0.1.rst
64 65 release-notes-2.0.0.rst
65 66
66 67 |RCE| 1.x Versions
67 68 ------------------
68 69
69 70 .. toctree::
70 71 :maxdepth: 1
71 72
72 73 release-notes-1.7.2.rst
73 74 release-notes-1.7.1.rst
74 75 release-notes-1.7.0.rst
75 76 release-notes-1.6.0.rst
@@ -1,1273 +1,1273 b''
1 1 {
2 2 Babel = super.buildPythonPackage {
3 3 name = "Babel-1.3";
4 4 buildInputs = with self; [];
5 5 doCheck = false;
6 6 propagatedBuildInputs = with self; [pytz];
7 7 src = fetchurl {
8 8 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
9 9 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
10 10 };
11 11 };
12 12 Beaker = super.buildPythonPackage {
13 13 name = "Beaker-1.7.0";
14 14 buildInputs = with self; [];
15 15 doCheck = false;
16 16 propagatedBuildInputs = with self; [];
17 17 src = fetchurl {
18 18 url = "https://pypi.python.org/packages/97/8e/409d2e7c009b8aa803dc9e6f239f1db7c3cdf578249087a404e7c27a505d/Beaker-1.7.0.tar.gz";
19 19 md5 = "386be3f7fe427358881eee4622b428b3";
20 20 };
21 21 };
22 22 CProfileV = super.buildPythonPackage {
23 23 name = "CProfileV-1.0.6";
24 24 buildInputs = with self; [];
25 25 doCheck = false;
26 26 propagatedBuildInputs = with self; [bottle];
27 27 src = fetchurl {
28 28 url = "https://pypi.python.org/packages/eb/df/983a0b6cfd3ac94abf023f5011cb04f33613ace196e33f53c86cf91850d5/CProfileV-1.0.6.tar.gz";
29 29 md5 = "08c7c242b6e64237bc53c5d13537e03d";
30 30 };
31 31 };
32 32 Fabric = super.buildPythonPackage {
33 33 name = "Fabric-1.10.0";
34 34 buildInputs = with self; [];
35 35 doCheck = false;
36 36 propagatedBuildInputs = with self; [paramiko];
37 37 src = fetchurl {
38 38 url = "https://pypi.python.org/packages/e3/5f/b6ebdb5241d5ec9eab582a5c8a01255c1107da396f849e538801d2fe64a5/Fabric-1.10.0.tar.gz";
39 39 md5 = "2cb96473387f0e7aa035210892352f4a";
40 40 };
41 41 };
42 42 FormEncode = super.buildPythonPackage {
43 43 name = "FormEncode-1.2.4";
44 44 buildInputs = with self; [];
45 45 doCheck = false;
46 46 propagatedBuildInputs = with self; [];
47 47 src = fetchurl {
48 48 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
49 49 md5 = "6bc17fb9aed8aea198975e888e2077f4";
50 50 };
51 51 };
52 52 Jinja2 = super.buildPythonPackage {
53 53 name = "Jinja2-2.7.3";
54 54 buildInputs = with self; [];
55 55 doCheck = false;
56 56 propagatedBuildInputs = with self; [MarkupSafe];
57 57 src = fetchurl {
58 58 url = "https://pypi.python.org/packages/b0/73/eab0bca302d6d6a0b5c402f47ad1760dc9cb2dd14bbc1873ad48db258e4d/Jinja2-2.7.3.tar.gz";
59 59 md5 = "b9dffd2f3b43d673802fe857c8445b1a";
60 60 };
61 61 };
62 62 Mako = super.buildPythonPackage {
63 63 name = "Mako-1.0.1";
64 64 buildInputs = with self; [];
65 65 doCheck = false;
66 66 propagatedBuildInputs = with self; [MarkupSafe];
67 67 src = fetchurl {
68 68 url = "https://pypi.python.org/packages/8e/a4/aa56533ecaa5f22ca92428f74e074d0c9337282933c722391902c8f9e0f8/Mako-1.0.1.tar.gz";
69 69 md5 = "9f0aafd177b039ef67b90ea350497a54";
70 70 };
71 71 };
72 72 Markdown = super.buildPythonPackage {
73 73 name = "Markdown-2.6.2";
74 74 buildInputs = with self; [];
75 75 doCheck = false;
76 76 propagatedBuildInputs = with self; [];
77 77 src = fetchurl {
78 78 url = "https://pypi.python.org/packages/62/8b/83658b5f6c220d5fcde9f9852d46ea54765d734cfbc5a9f4c05bfc36db4d/Markdown-2.6.2.tar.gz";
79 79 md5 = "256d19afcc564dc4ce4c229bb762f7ae";
80 80 };
81 81 };
82 82 MarkupSafe = super.buildPythonPackage {
83 83 name = "MarkupSafe-0.23";
84 84 buildInputs = with self; [];
85 85 doCheck = false;
86 86 propagatedBuildInputs = with self; [];
87 87 src = fetchurl {
88 88 url = "https://pypi.python.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-0.23.tar.gz";
89 89 md5 = "f5ab3deee4c37cd6a922fb81e730da6e";
90 90 };
91 91 };
92 92 MySQL-python = super.buildPythonPackage {
93 93 name = "MySQL-python-1.2.5";
94 94 buildInputs = with self; [];
95 95 doCheck = false;
96 96 propagatedBuildInputs = with self; [];
97 97 src = fetchurl {
98 98 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
99 99 md5 = "654f75b302db6ed8dc5a898c625e030c";
100 100 };
101 101 };
102 102 Paste = super.buildPythonPackage {
103 103 name = "Paste-2.0.2";
104 104 buildInputs = with self; [];
105 105 doCheck = false;
106 106 propagatedBuildInputs = with self; [six];
107 107 src = fetchurl {
108 108 url = "https://pypi.python.org/packages/d5/8d/0f8ac40687b97ff3e07ebd1369be20bdb3f93864d2dc3c2ff542edb4ce50/Paste-2.0.2.tar.gz";
109 109 md5 = "4bfc8a7eaf858f6309d2ac0f40fc951c";
110 110 };
111 111 };
112 112 PasteDeploy = super.buildPythonPackage {
113 113 name = "PasteDeploy-1.5.2";
114 114 buildInputs = with self; [];
115 115 doCheck = false;
116 116 propagatedBuildInputs = with self; [];
117 117 src = fetchurl {
118 118 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
119 119 md5 = "352b7205c78c8de4987578d19431af3b";
120 120 };
121 121 };
122 122 PasteScript = super.buildPythonPackage {
123 123 name = "PasteScript-1.7.5";
124 124 buildInputs = with self; [];
125 125 doCheck = false;
126 126 propagatedBuildInputs = with self; [Paste PasteDeploy];
127 127 src = fetchurl {
128 128 url = "https://pypi.python.org/packages/a5/05/fc60efa7c2f17a1dbaeccb2a903a1e90902d92b9d00eebabe3095829d806/PasteScript-1.7.5.tar.gz";
129 129 md5 = "4c72d78dcb6bb993f30536842c16af4d";
130 130 };
131 131 };
132 132 Pygments = super.buildPythonPackage {
133 133 name = "Pygments-2.0.2";
134 134 buildInputs = with self; [];
135 135 doCheck = false;
136 136 propagatedBuildInputs = with self; [];
137 137 src = fetchurl {
138 138 url = "https://pypi.python.org/packages/f4/c6/bdbc5a8a112256b2b6136af304dbae93d8b1ef8738ff2d12a51018800e46/Pygments-2.0.2.tar.gz";
139 139 md5 = "238587a1370d62405edabd0794b3ec4a";
140 140 };
141 141 };
142 142 Pylons = super.buildPythonPackage {
143 143 name = "Pylons-1.0.1";
144 144 buildInputs = with self; [];
145 145 doCheck = false;
146 146 propagatedBuildInputs = with self; [Routes WebHelpers Beaker Paste PasteDeploy PasteScript FormEncode simplejson decorator nose Mako WebError WebTest Tempita MarkupSafe WebOb];
147 147 src = fetchurl {
148 148 url = "https://pypi.python.org/packages/a2/69/b835a6bad00acbfeed3f33c6e44fa3f936efc998c795bfb15c61a79ecf62/Pylons-1.0.1.tar.gz";
149 149 md5 = "6cb880d75fa81213192142b07a6e4915";
150 150 };
151 151 };
152 152 Pyro4 = super.buildPythonPackage {
153 153 name = "Pyro4-4.41";
154 154 buildInputs = with self; [];
155 155 doCheck = false;
156 156 propagatedBuildInputs = with self; [serpent];
157 157 src = fetchurl {
158 158 url = "https://pypi.python.org/packages/56/2b/89b566b4bf3e7f8ba790db2d1223852f8cb454c52cab7693dd41f608ca2a/Pyro4-4.41.tar.gz";
159 159 md5 = "ed69e9bfafa9c06c049a87cb0c4c2b6c";
160 160 };
161 161 };
162 162 Routes = super.buildPythonPackage {
163 163 name = "Routes-1.13";
164 164 buildInputs = with self; [];
165 165 doCheck = false;
166 166 propagatedBuildInputs = with self; [repoze.lru];
167 167 src = fetchurl {
168 168 url = "https://pypi.python.org/packages/88/d3/259c3b3cde8837eb9441ab5f574a660e8a4acea8f54a078441d4d2acac1c/Routes-1.13.tar.gz";
169 169 md5 = "d527b0ab7dd9172b1275a41f97448783";
170 170 };
171 171 };
172 172 SQLAlchemy = super.buildPythonPackage {
173 173 name = "SQLAlchemy-0.9.9";
174 174 buildInputs = with self; [];
175 175 doCheck = false;
176 176 propagatedBuildInputs = with self; [];
177 177 src = fetchurl {
178 178 url = "https://pypi.python.org/packages/28/f7/1bbfd0d8597e8c358d5e15a166a486ad82fc5579b4e67b6ef7c05b1d182b/SQLAlchemy-0.9.9.tar.gz";
179 179 md5 = "8a10a9bd13ed3336ef7333ac2cc679ff";
180 180 };
181 181 };
182 182 Sphinx = super.buildPythonPackage {
183 183 name = "Sphinx-1.2.2";
184 184 buildInputs = with self; [];
185 185 doCheck = false;
186 186 propagatedBuildInputs = with self; [Pygments docutils Jinja2];
187 187 src = fetchurl {
188 188 url = "https://pypi.python.org/packages/0a/50/34017e6efcd372893a416aba14b84a1a149fc7074537b0e9cb6ca7b7abe9/Sphinx-1.2.2.tar.gz";
189 189 md5 = "3dc73ccaa8d0bfb2d62fb671b1f7e8a4";
190 190 };
191 191 };
192 192 Tempita = super.buildPythonPackage {
193 193 name = "Tempita-0.5.2";
194 194 buildInputs = with self; [];
195 195 doCheck = false;
196 196 propagatedBuildInputs = with self; [];
197 197 src = fetchurl {
198 198 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
199 199 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
200 200 };
201 201 };
202 202 URLObject = super.buildPythonPackage {
203 203 name = "URLObject-2.4.0";
204 204 buildInputs = with self; [];
205 205 doCheck = false;
206 206 propagatedBuildInputs = with self; [];
207 207 src = fetchurl {
208 208 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
209 209 md5 = "2ed819738a9f0a3051f31dc9924e3065";
210 210 };
211 211 };
212 212 WebError = super.buildPythonPackage {
213 213 name = "WebError-0.10.3";
214 214 buildInputs = with self; [];
215 215 doCheck = false;
216 216 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
217 217 src = fetchurl {
218 218 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
219 219 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
220 220 };
221 221 };
222 222 WebHelpers = super.buildPythonPackage {
223 223 name = "WebHelpers-1.3";
224 224 buildInputs = with self; [];
225 225 doCheck = false;
226 226 propagatedBuildInputs = with self; [MarkupSafe];
227 227 src = fetchurl {
228 228 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
229 229 md5 = "32749ffadfc40fea51075a7def32588b";
230 230 };
231 231 };
232 232 WebHelpers2 = super.buildPythonPackage {
233 233 name = "WebHelpers2-2.0";
234 234 buildInputs = with self; [];
235 235 doCheck = false;
236 236 propagatedBuildInputs = with self; [MarkupSafe six];
237 237 src = fetchurl {
238 238 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
239 239 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
240 240 };
241 241 };
242 242 WebOb = super.buildPythonPackage {
243 243 name = "WebOb-1.3.1";
244 244 buildInputs = with self; [];
245 245 doCheck = false;
246 246 propagatedBuildInputs = with self; [];
247 247 src = fetchurl {
248 248 url = "https://pypi.python.org/packages/16/78/adfc0380b8a0d75b2d543fa7085ba98a573b1ae486d9def88d172b81b9fa/WebOb-1.3.1.tar.gz";
249 249 md5 = "20918251c5726956ba8fef22d1556177";
250 250 };
251 251 };
252 252 WebTest = super.buildPythonPackage {
253 253 name = "WebTest-1.4.3";
254 254 buildInputs = with self; [];
255 255 doCheck = false;
256 256 propagatedBuildInputs = with self; [WebOb];
257 257 src = fetchurl {
258 258 url = "https://pypi.python.org/packages/51/3d/84fd0f628df10b30c7db87895f56d0158e5411206b721ca903cb51bfd948/WebTest-1.4.3.zip";
259 259 md5 = "631ce728bed92c681a4020a36adbc353";
260 260 };
261 261 };
262 262 Whoosh = super.buildPythonPackage {
263 263 name = "Whoosh-2.7.0";
264 264 buildInputs = with self; [];
265 265 doCheck = false;
266 266 propagatedBuildInputs = with self; [];
267 267 src = fetchurl {
268 268 url = "https://pypi.python.org/packages/1c/dc/2f0231ff3875ded36df8c1ab851451e51a237dc0e5a86d3d96036158da94/Whoosh-2.7.0.zip";
269 269 md5 = "7abfd970f16fadc7311960f3fa0bc7a9";
270 270 };
271 271 };
272 272 alembic = super.buildPythonPackage {
273 273 name = "alembic-0.8.4";
274 274 buildInputs = with self; [];
275 275 doCheck = false;
276 276 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor];
277 277 src = fetchurl {
278 278 url = "https://pypi.python.org/packages/ca/7e/299b4499b5c75e5a38c5845145ad24755bebfb8eec07a2e1c366b7181eeb/alembic-0.8.4.tar.gz";
279 279 md5 = "5f95d8ee62b443f9b37eb5bee76c582d";
280 280 };
281 281 };
282 282 amqplib = super.buildPythonPackage {
283 283 name = "amqplib-1.0.2";
284 284 buildInputs = with self; [];
285 285 doCheck = false;
286 286 propagatedBuildInputs = with self; [];
287 287 src = fetchurl {
288 288 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
289 289 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
290 290 };
291 291 };
292 292 anyjson = super.buildPythonPackage {
293 293 name = "anyjson-0.3.3";
294 294 buildInputs = with self; [];
295 295 doCheck = false;
296 296 propagatedBuildInputs = with self; [];
297 297 src = fetchurl {
298 298 url = "https://pypi.python.org/packages/c3/4d/d4089e1a3dd25b46bebdb55a992b0797cff657b4477bc32ce28038fdecbc/anyjson-0.3.3.tar.gz";
299 299 md5 = "2ea28d6ec311aeeebaf993cb3008b27c";
300 300 };
301 301 };
302 302 appenlight-client = super.buildPythonPackage {
303 303 name = "appenlight-client-0.6.14";
304 304 buildInputs = with self; [];
305 305 doCheck = false;
306 306 propagatedBuildInputs = with self; [WebOb requests];
307 307 src = fetchurl {
308 308 url = "https://pypi.python.org/packages/4d/e0/23fee3ebada8143f707e65c06bcb82992040ee64ea8355e044ed55ebf0c1/appenlight_client-0.6.14.tar.gz";
309 309 md5 = "578c69b09f4356d898fff1199b98a95c";
310 310 };
311 311 };
312 312 authomatic = super.buildPythonPackage {
313 313 name = "authomatic-0.1.0.post1";
314 314 buildInputs = with self; [];
315 315 doCheck = false;
316 316 propagatedBuildInputs = with self; [];
317 317 src = fetchurl {
318 318 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
319 319 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
320 320 };
321 321 };
322 322 backport-ipaddress = super.buildPythonPackage {
323 323 name = "backport-ipaddress-0.1";
324 324 buildInputs = with self; [];
325 325 doCheck = false;
326 326 propagatedBuildInputs = with self; [];
327 327 src = fetchurl {
328 328 url = "https://pypi.python.org/packages/d3/30/54c6dab05a4dec44db25ff309f1fbb6b7a8bde3f2bade38bb9da67bbab8f/backport_ipaddress-0.1.tar.gz";
329 329 md5 = "9c1f45f4361f71b124d7293a60006c05";
330 330 };
331 331 };
332 332 bottle = super.buildPythonPackage {
333 333 name = "bottle-0.12.8";
334 334 buildInputs = with self; [];
335 335 doCheck = false;
336 336 propagatedBuildInputs = with self; [];
337 337 src = fetchurl {
338 338 url = "https://pypi.python.org/packages/52/df/e4a408f3a7af396d186d4ecd3b389dd764f0f943b4fa8d257bfe7b49d343/bottle-0.12.8.tar.gz";
339 339 md5 = "13132c0a8f607bf860810a6ee9064c5b";
340 340 };
341 341 };
342 342 bumpversion = super.buildPythonPackage {
343 343 name = "bumpversion-0.5.3";
344 344 buildInputs = with self; [];
345 345 doCheck = false;
346 346 propagatedBuildInputs = with self; [];
347 347 src = fetchurl {
348 348 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
349 349 md5 = "c66a3492eafcf5ad4b024be9fca29820";
350 350 };
351 351 };
352 352 celery = super.buildPythonPackage {
353 353 name = "celery-2.2.10";
354 354 buildInputs = with self; [];
355 355 doCheck = false;
356 356 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
357 357 src = fetchurl {
358 358 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
359 359 md5 = "898bc87e54f278055b561316ba73e222";
360 360 };
361 361 };
362 362 certifi = super.buildPythonPackage {
363 name = "certifi-2016.02.28";
363 name = "certifi-2016.2.28";
364 364 buildInputs = with self; [];
365 365 doCheck = false;
366 366 propagatedBuildInputs = with self; [];
367 367 src = fetchurl {
368 368 url = "https://pypi.python.org/packages/5c/f8/f6c54727c74579c6bbe5926f5deb9677c5810a33e11da58d1a4e2d09d041/certifi-2016.2.28.tar.gz";
369 369 md5 = "5d672aa766e1f773c75cfeccd02d3650";
370 370 };
371 371 };
372 372 click = super.buildPythonPackage {
373 373 name = "click-5.1";
374 374 buildInputs = with self; [];
375 375 doCheck = false;
376 376 propagatedBuildInputs = with self; [];
377 377 src = fetchurl {
378 378 url = "https://pypi.python.org/packages/b7/34/a496632c4fb6c1ee76efedf77bb8d28b29363d839953d95095b12defe791/click-5.1.tar.gz";
379 379 md5 = "9c5323008cccfe232a8b161fc8196d41";
380 380 };
381 381 };
382 382 colander = super.buildPythonPackage {
383 383 name = "colander-1.2";
384 384 buildInputs = with self; [];
385 385 doCheck = false;
386 386 propagatedBuildInputs = with self; [translationstring iso8601];
387 387 src = fetchurl {
388 388 url = "https://pypi.python.org/packages/14/23/c9ceba07a6a1dc0eefbb215fc0dc64aabc2b22ee756bc0f0c13278fa0887/colander-1.2.tar.gz";
389 389 md5 = "83db21b07936a0726e588dae1914b9ed";
390 390 };
391 391 };
392 392 configobj = super.buildPythonPackage {
393 393 name = "configobj-5.0.6";
394 394 buildInputs = with self; [];
395 395 doCheck = false;
396 396 propagatedBuildInputs = with self; [six];
397 397 src = fetchurl {
398 398 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
399 399 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
400 400 };
401 401 };
402 402 cov-core = super.buildPythonPackage {
403 403 name = "cov-core-1.15.0";
404 404 buildInputs = with self; [];
405 405 doCheck = false;
406 406 propagatedBuildInputs = with self; [coverage];
407 407 src = fetchurl {
408 408 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
409 409 md5 = "f519d4cb4c4e52856afb14af52919fe6";
410 410 };
411 411 };
412 412 coverage = super.buildPythonPackage {
413 413 name = "coverage-3.7.1";
414 414 buildInputs = with self; [];
415 415 doCheck = false;
416 416 propagatedBuildInputs = with self; [];
417 417 src = fetchurl {
418 418 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
419 419 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
420 420 };
421 421 };
422 422 cssselect = super.buildPythonPackage {
423 423 name = "cssselect-0.9.1";
424 424 buildInputs = with self; [];
425 425 doCheck = false;
426 426 propagatedBuildInputs = with self; [];
427 427 src = fetchurl {
428 428 url = "https://pypi.python.org/packages/aa/e5/9ee1460d485b94a6d55732eb7ad5b6c084caf73dd6f9cb0bb7d2a78fafe8/cssselect-0.9.1.tar.gz";
429 429 md5 = "c74f45966277dc7a0f768b9b0f3522ac";
430 430 };
431 431 };
432 432 decorator = super.buildPythonPackage {
433 433 name = "decorator-3.4.2";
434 434 buildInputs = with self; [];
435 435 doCheck = false;
436 436 propagatedBuildInputs = with self; [];
437 437 src = fetchurl {
438 438 url = "https://pypi.python.org/packages/35/3a/42566eb7a2cbac774399871af04e11d7ae3fc2579e7dae85213b8d1d1c57/decorator-3.4.2.tar.gz";
439 439 md5 = "9e0536870d2b83ae27d58dbf22582f4d";
440 440 };
441 441 };
442 442 docutils = super.buildPythonPackage {
443 443 name = "docutils-0.12";
444 444 buildInputs = with self; [];
445 445 doCheck = false;
446 446 propagatedBuildInputs = with self; [];
447 447 src = fetchurl {
448 448 url = "https://pypi.python.org/packages/37/38/ceda70135b9144d84884ae2fc5886c6baac4edea39550f28bcd144c1234d/docutils-0.12.tar.gz";
449 449 md5 = "4622263b62c5c771c03502afa3157768";
450 450 };
451 451 };
452 452 dogpile.cache = super.buildPythonPackage {
453 453 name = "dogpile.cache-0.5.7";
454 454 buildInputs = with self; [];
455 455 doCheck = false;
456 456 propagatedBuildInputs = with self; [dogpile.core];
457 457 src = fetchurl {
458 458 url = "https://pypi.python.org/packages/07/74/2a83bedf758156d9c95d112691bbad870d3b77ccbcfb781b4ef836ea7d96/dogpile.cache-0.5.7.tar.gz";
459 459 md5 = "3e58ce41af574aab41d78e9c4190f194";
460 460 };
461 461 };
462 462 dogpile.core = super.buildPythonPackage {
463 463 name = "dogpile.core-0.4.1";
464 464 buildInputs = with self; [];
465 465 doCheck = false;
466 466 propagatedBuildInputs = with self; [];
467 467 src = fetchurl {
468 468 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
469 469 md5 = "01cb19f52bba3e95c9b560f39341f045";
470 470 };
471 471 };
472 472 dulwich = super.buildPythonPackage {
473 473 name = "dulwich-0.12.0";
474 474 buildInputs = with self; [];
475 475 doCheck = false;
476 476 propagatedBuildInputs = with self; [];
477 477 src = fetchurl {
478 478 url = "https://pypi.python.org/packages/6f/04/fbe561b6d45c0ec758330d5b7f5ba4b6cb4f1ca1ab49859d2fc16320da75/dulwich-0.12.0.tar.gz";
479 479 md5 = "f3a8a12bd9f9dd8c233e18f3d49436fa";
480 480 };
481 481 };
482 482 ecdsa = super.buildPythonPackage {
483 483 name = "ecdsa-0.11";
484 484 buildInputs = with self; [];
485 485 doCheck = false;
486 486 propagatedBuildInputs = with self; [];
487 487 src = fetchurl {
488 488 url = "https://pypi.python.org/packages/6c/3f/92fe5dcdcaa7bd117be21e5520c9a54375112b66ec000d209e9e9519fad1/ecdsa-0.11.tar.gz";
489 489 md5 = "8ef586fe4dbb156697d756900cb41d7c";
490 490 };
491 491 };
492 492 elasticsearch = super.buildPythonPackage {
493 493 name = "elasticsearch-1.9.0";
494 494 buildInputs = with self; [];
495 495 doCheck = false;
496 496 propagatedBuildInputs = with self; [urllib3];
497 497 src = fetchurl {
498 498 url = "https://pypi.python.org/packages/13/9b/540e311b31a10c2a904acfb08030c656047e5c7ba479d35df2799e5dccfe/elasticsearch-1.9.0.tar.gz";
499 499 md5 = "3550390baea1639479f79758d66ab032";
500 500 };
501 501 };
502 502 flake8 = super.buildPythonPackage {
503 503 name = "flake8-2.4.1";
504 504 buildInputs = with self; [];
505 505 doCheck = false;
506 506 propagatedBuildInputs = with self; [pyflakes pep8 mccabe];
507 507 src = fetchurl {
508 508 url = "https://pypi.python.org/packages/8f/b5/9a73c66c7dba273bac8758398f060c008a25f3e84531063b42503b5d0a95/flake8-2.4.1.tar.gz";
509 509 md5 = "ed45d3db81a3b7c88bd63c6e37ca1d65";
510 510 };
511 511 };
512 512 future = super.buildPythonPackage {
513 513 name = "future-0.14.3";
514 514 buildInputs = with self; [];
515 515 doCheck = false;
516 516 propagatedBuildInputs = with self; [];
517 517 src = fetchurl {
518 518 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
519 519 md5 = "e94079b0bd1fc054929e8769fc0f6083";
520 520 };
521 521 };
522 522 futures = super.buildPythonPackage {
523 523 name = "futures-3.0.2";
524 524 buildInputs = with self; [];
525 525 doCheck = false;
526 526 propagatedBuildInputs = with self; [];
527 527 src = fetchurl {
528 528 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
529 529 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
530 530 };
531 531 };
532 532 gnureadline = super.buildPythonPackage {
533 533 name = "gnureadline-6.3.3";
534 534 buildInputs = with self; [];
535 535 doCheck = false;
536 536 propagatedBuildInputs = with self; [];
537 537 src = fetchurl {
538 538 url = "https://pypi.python.org/packages/3a/ee/2c3f568b0a74974791ac590ec742ef6133e2fbd287a074ba72a53fa5e97c/gnureadline-6.3.3.tar.gz";
539 539 md5 = "c4af83c9a3fbeac8f2da9b5a7c60e51c";
540 540 };
541 541 };
542 542 gprof2dot = super.buildPythonPackage {
543 543 name = "gprof2dot-2015.12.1";
544 544 buildInputs = with self; [];
545 545 doCheck = false;
546 546 propagatedBuildInputs = with self; [];
547 547 src = fetchurl {
548 548 url = "https://pypi.python.org/packages/b9/34/7bf93c1952d40fa5c95ad963f4d8344b61ef58558632402eca18e6c14127/gprof2dot-2015.12.1.tar.gz";
549 549 md5 = "e23bf4e2f94db032750c193384b4165b";
550 550 };
551 551 };
552 552 greenlet = super.buildPythonPackage {
553 553 name = "greenlet-0.4.7";
554 554 buildInputs = with self; [];
555 555 doCheck = false;
556 556 propagatedBuildInputs = with self; [];
557 557 src = fetchurl {
558 558 url = "https://pypi.python.org/packages/7a/9f/a1a0d9bdf3203ae1502c5a8434fe89d323599d78a106985bc327351a69d4/greenlet-0.4.7.zip";
559 559 md5 = "c2333a8ff30fa75c5d5ec0e67b461086";
560 560 };
561 561 };
562 562 gunicorn = super.buildPythonPackage {
563 563 name = "gunicorn-19.6.0";
564 564 buildInputs = with self; [];
565 565 doCheck = false;
566 566 propagatedBuildInputs = with self; [];
567 567 src = fetchurl {
568 568 url = "https://pypi.python.org/packages/84/ce/7ea5396efad1cef682bbc4068e72a0276341d9d9d0f501da609fab9fcb80/gunicorn-19.6.0.tar.gz";
569 569 md5 = "338e5e8a83ea0f0625f768dba4597530";
570 570 };
571 571 };
572 572 infrae.cache = super.buildPythonPackage {
573 573 name = "infrae.cache-1.0.1";
574 574 buildInputs = with self; [];
575 575 doCheck = false;
576 576 propagatedBuildInputs = with self; [Beaker repoze.lru];
577 577 src = fetchurl {
578 578 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
579 579 md5 = "b09076a766747e6ed2a755cc62088e32";
580 580 };
581 581 };
582 582 invoke = super.buildPythonPackage {
583 583 name = "invoke-0.11.1";
584 584 buildInputs = with self; [];
585 585 doCheck = false;
586 586 propagatedBuildInputs = with self; [];
587 587 src = fetchurl {
588 588 url = "https://pypi.python.org/packages/d3/bb/36a5558ea19882073def7b0edeef4a0e6282056fed96506dd10b1d532bd4/invoke-0.11.1.tar.gz";
589 589 md5 = "3d4ecbe26779ceef1046ecf702c9c4a8";
590 590 };
591 591 };
592 592 ipdb = super.buildPythonPackage {
593 593 name = "ipdb-0.8";
594 594 buildInputs = with self; [];
595 595 doCheck = false;
596 596 propagatedBuildInputs = with self; [ipython];
597 597 src = fetchurl {
598 598 url = "https://pypi.python.org/packages/f0/25/d7dd430ced6cd8dc242a933c8682b5dbf32eb4011d82f87e34209e5ec845/ipdb-0.8.zip";
599 599 md5 = "96dca0712efa01aa5eaf6b22071dd3ed";
600 600 };
601 601 };
602 602 ipython = super.buildPythonPackage {
603 603 name = "ipython-3.1.0";
604 604 buildInputs = with self; [];
605 605 doCheck = false;
606 606 propagatedBuildInputs = with self; [gnureadline];
607 607 src = fetchurl {
608 608 url = "https://pypi.python.org/packages/06/91/120c0835254c120af89f066afaabf81289bc2726c1fc3ca0555df6882f58/ipython-3.1.0.tar.gz";
609 609 md5 = "a749d90c16068687b0ec45a27e72ef8f";
610 610 };
611 611 };
612 612 iso8601 = super.buildPythonPackage {
613 613 name = "iso8601-0.1.11";
614 614 buildInputs = with self; [];
615 615 doCheck = false;
616 616 propagatedBuildInputs = with self; [];
617 617 src = fetchurl {
618 618 url = "https://pypi.python.org/packages/c0/75/c9209ee4d1b5975eb8c2cba4428bde6b61bd55664a98290dd015cdb18e98/iso8601-0.1.11.tar.gz";
619 619 md5 = "b06d11cd14a64096f907086044f0fe38";
620 620 };
621 621 };
622 622 itsdangerous = super.buildPythonPackage {
623 623 name = "itsdangerous-0.24";
624 624 buildInputs = with self; [];
625 625 doCheck = false;
626 626 propagatedBuildInputs = with self; [];
627 627 src = fetchurl {
628 628 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
629 629 md5 = "a3d55aa79369aef5345c036a8a26307f";
630 630 };
631 631 };
632 632 kombu = super.buildPythonPackage {
633 633 name = "kombu-1.5.1";
634 634 buildInputs = with self; [];
635 635 doCheck = false;
636 636 propagatedBuildInputs = with self; [anyjson amqplib];
637 637 src = fetchurl {
638 638 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
639 639 md5 = "50662f3c7e9395b3d0721fb75d100b63";
640 640 };
641 641 };
642 642 lxml = super.buildPythonPackage {
643 643 name = "lxml-3.4.4";
644 644 buildInputs = with self; [];
645 645 doCheck = false;
646 646 propagatedBuildInputs = with self; [];
647 647 src = fetchurl {
648 648 url = "https://pypi.python.org/packages/63/c7/4f2a2a4ad6c6fa99b14be6b3c1cece9142e2d915aa7c43c908677afc8fa4/lxml-3.4.4.tar.gz";
649 649 md5 = "a9a65972afc173ec7a39c585f4eea69c";
650 650 };
651 651 };
652 652 mccabe = super.buildPythonPackage {
653 653 name = "mccabe-0.3";
654 654 buildInputs = with self; [];
655 655 doCheck = false;
656 656 propagatedBuildInputs = with self; [];
657 657 src = fetchurl {
658 658 url = "https://pypi.python.org/packages/c9/2e/75231479e11a906b64ac43bad9d0bb534d00080b18bdca8db9da46e1faf7/mccabe-0.3.tar.gz";
659 659 md5 = "81640948ff226f8c12b3277059489157";
660 660 };
661 661 };
662 662 meld3 = super.buildPythonPackage {
663 663 name = "meld3-1.0.2";
664 664 buildInputs = with self; [];
665 665 doCheck = false;
666 666 propagatedBuildInputs = with self; [];
667 667 src = fetchurl {
668 668 url = "https://pypi.python.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz";
669 669 md5 = "3ccc78cd79cffd63a751ad7684c02c91";
670 670 };
671 671 };
672 672 mock = super.buildPythonPackage {
673 673 name = "mock-1.0.1";
674 674 buildInputs = with self; [];
675 675 doCheck = false;
676 676 propagatedBuildInputs = with self; [];
677 677 src = fetchurl {
678 678 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
679 679 md5 = "869f08d003c289a97c1a6610faf5e913";
680 680 };
681 681 };
682 682 msgpack-python = super.buildPythonPackage {
683 683 name = "msgpack-python-0.4.6";
684 684 buildInputs = with self; [];
685 685 doCheck = false;
686 686 propagatedBuildInputs = with self; [];
687 687 src = fetchurl {
688 688 url = "https://pypi.python.org/packages/15/ce/ff2840885789ef8035f66cd506ea05bdb228340307d5e71a7b1e3f82224c/msgpack-python-0.4.6.tar.gz";
689 689 md5 = "8b317669314cf1bc881716cccdaccb30";
690 690 };
691 691 };
692 692 nose = super.buildPythonPackage {
693 693 name = "nose-1.3.6";
694 694 buildInputs = with self; [];
695 695 doCheck = false;
696 696 propagatedBuildInputs = with self; [];
697 697 src = fetchurl {
698 698 url = "https://pypi.python.org/packages/70/c7/469e68148d17a0d3db5ed49150242fd70a74a8147b8f3f8b87776e028d99/nose-1.3.6.tar.gz";
699 699 md5 = "0ca546d81ca8309080fc80cb389e7a16";
700 700 };
701 701 };
702 702 objgraph = super.buildPythonPackage {
703 703 name = "objgraph-2.0.0";
704 704 buildInputs = with self; [];
705 705 doCheck = false;
706 706 propagatedBuildInputs = with self; [];
707 707 src = fetchurl {
708 708 url = "https://pypi.python.org/packages/d7/33/ace750b59247496ed769b170586c5def7202683f3d98e737b75b767ff29e/objgraph-2.0.0.tar.gz";
709 709 md5 = "25b0d5e5adc74aa63ead15699614159c";
710 710 };
711 711 };
712 712 packaging = super.buildPythonPackage {
713 713 name = "packaging-15.2";
714 714 buildInputs = with self; [];
715 715 doCheck = false;
716 716 propagatedBuildInputs = with self; [];
717 717 src = fetchurl {
718 718 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
719 719 md5 = "c16093476f6ced42128bf610e5db3784";
720 720 };
721 721 };
722 722 paramiko = super.buildPythonPackage {
723 723 name = "paramiko-1.15.1";
724 724 buildInputs = with self; [];
725 725 doCheck = false;
726 726 propagatedBuildInputs = with self; [pycrypto ecdsa];
727 727 src = fetchurl {
728 728 url = "https://pypi.python.org/packages/04/2b/a22d2a560c1951abbbf95a0628e245945565f70dc082d9e784666887222c/paramiko-1.15.1.tar.gz";
729 729 md5 = "48c274c3f9b1282932567b21f6acf3b5";
730 730 };
731 731 };
732 732 pep8 = super.buildPythonPackage {
733 733 name = "pep8-1.5.7";
734 734 buildInputs = with self; [];
735 735 doCheck = false;
736 736 propagatedBuildInputs = with self; [];
737 737 src = fetchurl {
738 738 url = "https://pypi.python.org/packages/8b/de/259f5e735897ada1683489dd514b2a1c91aaa74e5e6b68f80acf128a6368/pep8-1.5.7.tar.gz";
739 739 md5 = "f6adbdd69365ecca20513c709f9b7c93";
740 740 };
741 741 };
742 742 psutil = super.buildPythonPackage {
743 743 name = "psutil-2.2.1";
744 744 buildInputs = with self; [];
745 745 doCheck = false;
746 746 propagatedBuildInputs = with self; [];
747 747 src = fetchurl {
748 748 url = "https://pypi.python.org/packages/df/47/ee54ef14dd40f8ce831a7581001a5096494dc99fe71586260ca6b531fe86/psutil-2.2.1.tar.gz";
749 749 md5 = "1a2b58cd9e3a53528bb6148f0c4d5244";
750 750 };
751 751 };
752 752 psycopg2 = super.buildPythonPackage {
753 753 name = "psycopg2-2.6";
754 754 buildInputs = with self; [];
755 755 doCheck = false;
756 756 propagatedBuildInputs = with self; [];
757 757 src = fetchurl {
758 758 url = "https://pypi.python.org/packages/dd/c7/9016ff8ff69da269b1848276eebfb264af5badf6b38caad805426771f04d/psycopg2-2.6.tar.gz";
759 759 md5 = "fbbb039a8765d561a1c04969bbae7c74";
760 760 };
761 761 };
762 762 py = super.buildPythonPackage {
763 763 name = "py-1.4.29";
764 764 buildInputs = with self; [];
765 765 doCheck = false;
766 766 propagatedBuildInputs = with self; [];
767 767 src = fetchurl {
768 768 url = "https://pypi.python.org/packages/2a/bc/a1a4a332ac10069b8e5e25136a35e08a03f01fd6ab03d819889d79a1fd65/py-1.4.29.tar.gz";
769 769 md5 = "c28e0accba523a29b35a48bb703fb96c";
770 770 };
771 771 };
772 772 py-bcrypt = super.buildPythonPackage {
773 773 name = "py-bcrypt-0.4";
774 774 buildInputs = with self; [];
775 775 doCheck = false;
776 776 propagatedBuildInputs = with self; [];
777 777 src = fetchurl {
778 778 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
779 779 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
780 780 };
781 781 };
782 782 pycrypto = super.buildPythonPackage {
783 783 name = "pycrypto-2.6.1";
784 784 buildInputs = with self; [];
785 785 doCheck = false;
786 786 propagatedBuildInputs = with self; [];
787 787 src = fetchurl {
788 788 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
789 789 md5 = "55a61a054aa66812daf5161a0d5d7eda";
790 790 };
791 791 };
792 792 pycurl = super.buildPythonPackage {
793 793 name = "pycurl-7.19.5";
794 794 buildInputs = with self; [];
795 795 doCheck = false;
796 796 propagatedBuildInputs = with self; [];
797 797 src = fetchurl {
798 798 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
799 799 md5 = "47b4eac84118e2606658122104e62072";
800 800 };
801 801 };
802 802 pyelasticsearch = super.buildPythonPackage {
803 803 name = "pyelasticsearch-1.4";
804 804 buildInputs = with self; [];
805 805 doCheck = false;
806 806 propagatedBuildInputs = with self; [certifi elasticsearch urllib3 simplejson six];
807 807 src = fetchurl {
808 808 url = "https://pypi.python.org/packages/2f/3a/7643cfcfc4cbdbb20ada800bbd54ac9705d0c047d7b8f8d5eeeb3047b4eb/pyelasticsearch-1.4.tar.gz";
809 809 md5 = "ed61ebb7b253364e55b4923d11e17049";
810 810 };
811 811 };
812 812 pyflakes = super.buildPythonPackage {
813 813 name = "pyflakes-0.8.1";
814 814 buildInputs = with self; [];
815 815 doCheck = false;
816 816 propagatedBuildInputs = with self; [];
817 817 src = fetchurl {
818 818 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
819 819 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
820 820 };
821 821 };
822 822 pyparsing = super.buildPythonPackage {
823 823 name = "pyparsing-1.5.7";
824 824 buildInputs = with self; [];
825 825 doCheck = false;
826 826 propagatedBuildInputs = with self; [];
827 827 src = fetchurl {
828 828 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
829 829 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
830 830 };
831 831 };
832 832 pyramid = super.buildPythonPackage {
833 833 name = "pyramid-1.6.1";
834 834 buildInputs = with self; [];
835 835 doCheck = false;
836 836 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
837 837 src = fetchurl {
838 838 url = "https://pypi.python.org/packages/30/b3/fcc4a2a4800cbf21989e00454b5828cf1f7fe35c63e0810b350e56d4c475/pyramid-1.6.1.tar.gz";
839 839 md5 = "b18688ff3cc33efdbb098a35b45dd122";
840 840 };
841 841 };
842 842 pyramid-beaker = super.buildPythonPackage {
843 843 name = "pyramid-beaker-0.8";
844 844 buildInputs = with self; [];
845 845 doCheck = false;
846 846 propagatedBuildInputs = with self; [pyramid Beaker];
847 847 src = fetchurl {
848 848 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
849 849 md5 = "22f14be31b06549f80890e2c63a93834";
850 850 };
851 851 };
852 852 pyramid-debugtoolbar = super.buildPythonPackage {
853 853 name = "pyramid-debugtoolbar-2.4.2";
854 854 buildInputs = with self; [];
855 855 doCheck = false;
856 856 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
857 857 src = fetchurl {
858 858 url = "https://pypi.python.org/packages/89/00/ed5426ee41ed747ba3ffd30e8230841a6878286ea67d480b1444d24f06a2/pyramid_debugtoolbar-2.4.2.tar.gz";
859 859 md5 = "073ea67086cc4bd5decc3a000853642d";
860 860 };
861 861 };
862 862 pyramid-jinja2 = super.buildPythonPackage {
863 863 name = "pyramid-jinja2-2.5";
864 864 buildInputs = with self; [];
865 865 doCheck = false;
866 866 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
867 867 src = fetchurl {
868 868 url = "https://pypi.python.org/packages/a1/80/595e26ffab7deba7208676b6936b7e5a721875710f982e59899013cae1ed/pyramid_jinja2-2.5.tar.gz";
869 869 md5 = "07cb6547204ac5e6f0b22a954ccee928";
870 870 };
871 871 };
872 872 pyramid-mako = super.buildPythonPackage {
873 873 name = "pyramid-mako-1.0.2";
874 874 buildInputs = with self; [];
875 875 doCheck = false;
876 876 propagatedBuildInputs = with self; [pyramid Mako];
877 877 src = fetchurl {
878 878 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
879 879 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
880 880 };
881 881 };
882 882 pysqlite = super.buildPythonPackage {
883 883 name = "pysqlite-2.6.3";
884 884 buildInputs = with self; [];
885 885 doCheck = false;
886 886 propagatedBuildInputs = with self; [];
887 887 src = fetchurl {
888 888 url = "https://pypi.python.org/packages/5c/a6/1c429cd4c8069cf4bfbd0eb4d592b3f4042155a8202df83d7e9b93aa3dc2/pysqlite-2.6.3.tar.gz";
889 889 md5 = "7ff1cedee74646b50117acff87aa1cfa";
890 890 };
891 891 };
892 892 pytest = super.buildPythonPackage {
893 893 name = "pytest-2.8.5";
894 894 buildInputs = with self; [];
895 895 doCheck = false;
896 896 propagatedBuildInputs = with self; [py];
897 897 src = fetchurl {
898 898 url = "https://pypi.python.org/packages/b1/3d/d7ea9b0c51e0cacded856e49859f0a13452747491e842c236bbab3714afe/pytest-2.8.5.zip";
899 899 md5 = "8493b06f700862f1294298d6c1b715a9";
900 900 };
901 901 };
902 902 pytest-catchlog = super.buildPythonPackage {
903 903 name = "pytest-catchlog-1.2.2";
904 904 buildInputs = with self; [];
905 905 doCheck = false;
906 906 propagatedBuildInputs = with self; [py pytest];
907 907 src = fetchurl {
908 908 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
909 909 md5 = "09d890c54c7456c818102b7ff8c182c8";
910 910 };
911 911 };
912 912 pytest-cov = super.buildPythonPackage {
913 913 name = "pytest-cov-1.8.1";
914 914 buildInputs = with self; [];
915 915 doCheck = false;
916 916 propagatedBuildInputs = with self; [py pytest coverage cov-core];
917 917 src = fetchurl {
918 918 url = "https://pypi.python.org/packages/11/4b/b04646e97f1721878eb21e9f779102d84dd044d324382263b1770a3e4838/pytest-cov-1.8.1.tar.gz";
919 919 md5 = "76c778afa2494088270348be42d759fc";
920 920 };
921 921 };
922 922 pytest-profiling = super.buildPythonPackage {
923 923 name = "pytest-profiling-1.0.1";
924 924 buildInputs = with self; [];
925 925 doCheck = false;
926 926 propagatedBuildInputs = with self; [six pytest gprof2dot];
927 927 src = fetchurl {
928 928 url = "https://pypi.python.org/packages/d8/67/8ffab73406e22870e07fa4dc8dce1d7689b26dba8efd00161c9b6fc01ec0/pytest-profiling-1.0.1.tar.gz";
929 929 md5 = "354404eb5b3fd4dc5eb7fffbb3d9b68b";
930 930 };
931 931 };
932 932 pytest-runner = super.buildPythonPackage {
933 933 name = "pytest-runner-2.7.1";
934 934 buildInputs = with self; [];
935 935 doCheck = false;
936 936 propagatedBuildInputs = with self; [];
937 937 src = fetchurl {
938 938 url = "https://pypi.python.org/packages/99/6b/c4ff4418d3424d4475b7af60724fd4a5cdd91ed8e489dc9443281f0052bc/pytest-runner-2.7.1.tar.gz";
939 939 md5 = "e56f0bc8d79a6bd91772b44ef4215c7e";
940 940 };
941 941 };
942 942 pytest-timeout = super.buildPythonPackage {
943 943 name = "pytest-timeout-0.4";
944 944 buildInputs = with self; [];
945 945 doCheck = false;
946 946 propagatedBuildInputs = with self; [pytest];
947 947 src = fetchurl {
948 948 url = "https://pypi.python.org/packages/24/48/5f6bd4b8026a26e1dd427243d560a29a0f1b24a5c7cffca4bf049a7bb65b/pytest-timeout-0.4.tar.gz";
949 949 md5 = "03b28aff69cbbfb959ed35ade5fde262";
950 950 };
951 951 };
952 952 python-dateutil = super.buildPythonPackage {
953 953 name = "python-dateutil-1.5";
954 954 buildInputs = with self; [];
955 955 doCheck = false;
956 956 propagatedBuildInputs = with self; [];
957 957 src = fetchurl {
958 958 url = "https://pypi.python.org/packages/b4/7c/df59c89a753eb33c7c44e1dd42de0e9bc2ccdd5a4d576e0bfad97cc280cb/python-dateutil-1.5.tar.gz";
959 959 md5 = "0dcb1de5e5cad69490a3b6ab63f0cfa5";
960 960 };
961 961 };
962 962 python-editor = super.buildPythonPackage {
963 963 name = "python-editor-1.0";
964 964 buildInputs = with self; [];
965 965 doCheck = false;
966 966 propagatedBuildInputs = with self; [];
967 967 src = fetchurl {
968 968 url = "https://pypi.python.org/packages/f5/d9/01eb441489c8bd2adb33ee4f3aea299a3db531a584cb39c57a0ecf516d9c/python-editor-1.0.tar.gz";
969 969 md5 = "a5ead611360b17b52507297d8590b4e8";
970 970 };
971 971 };
972 972 python-ldap = super.buildPythonPackage {
973 973 name = "python-ldap-2.4.19";
974 974 buildInputs = with self; [];
975 975 doCheck = false;
976 976 propagatedBuildInputs = with self; [setuptools];
977 977 src = fetchurl {
978 978 url = "https://pypi.python.org/packages/42/81/1b64838c82e64f14d4e246ff00b52e650a35c012551b891ada2b85d40737/python-ldap-2.4.19.tar.gz";
979 979 md5 = "b941bf31d09739492aa19ef679e94ae3";
980 980 };
981 981 };
982 982 python-memcached = super.buildPythonPackage {
983 983 name = "python-memcached-1.57";
984 984 buildInputs = with self; [];
985 985 doCheck = false;
986 986 propagatedBuildInputs = with self; [six];
987 987 src = fetchurl {
988 988 url = "https://pypi.python.org/packages/52/9d/eebc0dcbc5c7c66840ad207dfc1baa376dadb74912484bff73819cce01e6/python-memcached-1.57.tar.gz";
989 989 md5 = "de21f64b42b2d961f3d4ad7beb5468a1";
990 990 };
991 991 };
992 992 python-pam = super.buildPythonPackage {
993 993 name = "python-pam-1.8.2";
994 994 buildInputs = with self; [];
995 995 doCheck = false;
996 996 propagatedBuildInputs = with self; [];
997 997 src = fetchurl {
998 998 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
999 999 md5 = "db71b6b999246fb05d78ecfbe166629d";
1000 1000 };
1001 1001 };
1002 1002 pytz = super.buildPythonPackage {
1003 1003 name = "pytz-2015.4";
1004 1004 buildInputs = with self; [];
1005 1005 doCheck = false;
1006 1006 propagatedBuildInputs = with self; [];
1007 1007 src = fetchurl {
1008 1008 url = "https://pypi.python.org/packages/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
1009 1009 md5 = "233f2a2b370d03f9b5911700cc9ebf3c";
1010 1010 };
1011 1011 };
1012 1012 pyzmq = super.buildPythonPackage {
1013 1013 name = "pyzmq-14.6.0";
1014 1014 buildInputs = with self; [];
1015 1015 doCheck = false;
1016 1016 propagatedBuildInputs = with self; [];
1017 1017 src = fetchurl {
1018 1018 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1019 1019 md5 = "395b5de95a931afa5b14c9349a5b8024";
1020 1020 };
1021 1021 };
1022 1022 recaptcha-client = super.buildPythonPackage {
1023 1023 name = "recaptcha-client-1.0.6";
1024 1024 buildInputs = with self; [];
1025 1025 doCheck = false;
1026 1026 propagatedBuildInputs = with self; [];
1027 1027 src = fetchurl {
1028 1028 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1029 1029 md5 = "74228180f7e1fb76c4d7089160b0d919";
1030 1030 };
1031 1031 };
1032 1032 repoze.lru = super.buildPythonPackage {
1033 1033 name = "repoze.lru-0.6";
1034 1034 buildInputs = with self; [];
1035 1035 doCheck = false;
1036 1036 propagatedBuildInputs = with self; [];
1037 1037 src = fetchurl {
1038 1038 url = "https://pypi.python.org/packages/6e/1e/aa15cc90217e086dc8769872c8778b409812ff036bf021b15795638939e4/repoze.lru-0.6.tar.gz";
1039 1039 md5 = "2c3b64b17a8e18b405f55d46173e14dd";
1040 1040 };
1041 1041 };
1042 1042 requests = super.buildPythonPackage {
1043 1043 name = "requests-2.9.1";
1044 1044 buildInputs = with self; [];
1045 1045 doCheck = false;
1046 1046 propagatedBuildInputs = with self; [];
1047 1047 src = fetchurl {
1048 1048 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1049 1049 md5 = "0b7f480d19012ec52bab78292efd976d";
1050 1050 };
1051 1051 };
1052 1052 rhodecode-enterprise-ce = super.buildPythonPackage {
1053 name = "rhodecode-enterprise-ce-4.0.0";
1054 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-runner pytest-cov];
1053 name = "rhodecode-enterprise-ce-4.0.1";
1054 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1055 1055 doCheck = true;
1056 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors psutil py-bcrypt];
1056 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors psutil py-bcrypt];
1057 1057 src = ./.;
1058 1058 };
1059 1059 rhodecode-tools = super.buildPythonPackage {
1060 1060 name = "rhodecode-tools-0.7.1";
1061 1061 buildInputs = with self; [];
1062 1062 doCheck = false;
1063 1063 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests Whoosh pyelasticsearch];
1064 1064 src = fetchurl {
1065 1065 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.7.1.zip";
1066 1066 md5 = "91daea803aaa264ce7a8213bc2220d4c";
1067 1067 };
1068 1068 };
1069 1069 serpent = super.buildPythonPackage {
1070 1070 name = "serpent-1.12";
1071 1071 buildInputs = with self; [];
1072 1072 doCheck = false;
1073 1073 propagatedBuildInputs = with self; [];
1074 1074 src = fetchurl {
1075 1075 url = "https://pypi.python.org/packages/3b/19/1e0e83b47c09edaef8398655088036e7e67386b5c48770218ebb339fbbd5/serpent-1.12.tar.gz";
1076 1076 md5 = "05869ac7b062828b34f8f927f0457b65";
1077 1077 };
1078 1078 };
1079 1079 setproctitle = super.buildPythonPackage {
1080 1080 name = "setproctitle-1.1.8";
1081 1081 buildInputs = with self; [];
1082 1082 doCheck = false;
1083 1083 propagatedBuildInputs = with self; [];
1084 1084 src = fetchurl {
1085 1085 url = "https://pypi.python.org/packages/33/c3/ad367a4f4f1ca90468863ae727ac62f6edb558fc09a003d344a02cfc6ea6/setproctitle-1.1.8.tar.gz";
1086 1086 md5 = "728f4c8c6031bbe56083a48594027edd";
1087 1087 };
1088 1088 };
1089 1089 setuptools = super.buildPythonPackage {
1090 1090 name = "setuptools-20.8.1";
1091 1091 buildInputs = with self; [];
1092 1092 doCheck = false;
1093 1093 propagatedBuildInputs = with self; [];
1094 1094 src = fetchurl {
1095 1095 url = "https://pypi.python.org/packages/c4/19/c1bdc88b53da654df43770f941079dbab4e4788c2dcb5658fb86259894c7/setuptools-20.8.1.zip";
1096 1096 md5 = "fe58a5cac0df20bb83942b252a4b0543";
1097 1097 };
1098 1098 };
1099 1099 setuptools-scm = super.buildPythonPackage {
1100 1100 name = "setuptools-scm-1.11.0";
1101 1101 buildInputs = with self; [];
1102 1102 doCheck = false;
1103 1103 propagatedBuildInputs = with self; [];
1104 1104 src = fetchurl {
1105 1105 url = "https://pypi.python.org/packages/cd/5f/e3a038292358058d83d764a47d09114aa5a8003ed4529518f9e580f1a94f/setuptools_scm-1.11.0.tar.gz";
1106 1106 md5 = "4c5c896ba52e134bbc3507bac6400087";
1107 1107 };
1108 1108 };
1109 1109 simplejson = super.buildPythonPackage {
1110 1110 name = "simplejson-3.7.2";
1111 1111 buildInputs = with self; [];
1112 1112 doCheck = false;
1113 1113 propagatedBuildInputs = with self; [];
1114 1114 src = fetchurl {
1115 1115 url = "https://pypi.python.org/packages/6d/89/7f13f099344eea9d6722779a1f165087cb559598107844b1ac5dbd831fb1/simplejson-3.7.2.tar.gz";
1116 1116 md5 = "a5fc7d05d4cb38492285553def5d4b46";
1117 1117 };
1118 1118 };
1119 1119 six = super.buildPythonPackage {
1120 1120 name = "six-1.9.0";
1121 1121 buildInputs = with self; [];
1122 1122 doCheck = false;
1123 1123 propagatedBuildInputs = with self; [];
1124 1124 src = fetchurl {
1125 1125 url = "https://pypi.python.org/packages/16/64/1dc5e5976b17466fd7d712e59cbe9fb1e18bec153109e5ba3ed6c9102f1a/six-1.9.0.tar.gz";
1126 1126 md5 = "476881ef4012262dfc8adc645ee786c4";
1127 1127 };
1128 1128 };
1129 1129 subprocess32 = super.buildPythonPackage {
1130 1130 name = "subprocess32-3.2.6";
1131 1131 buildInputs = with self; [];
1132 1132 doCheck = false;
1133 1133 propagatedBuildInputs = with self; [];
1134 1134 src = fetchurl {
1135 1135 url = "https://pypi.python.org/packages/28/8d/33ccbff51053f59ae6c357310cac0e79246bbed1d345ecc6188b176d72c3/subprocess32-3.2.6.tar.gz";
1136 1136 md5 = "754c5ab9f533e764f931136974b618f1";
1137 1137 };
1138 1138 };
1139 1139 supervisor = super.buildPythonPackage {
1140 1140 name = "supervisor-3.1.3";
1141 1141 buildInputs = with self; [];
1142 1142 doCheck = false;
1143 1143 propagatedBuildInputs = with self; [meld3];
1144 1144 src = fetchurl {
1145 1145 url = "https://pypi.python.org/packages/a6/41/65ad5bd66230b173eb4d0b8810230f3a9c59ef52ae066e540b6b99895db7/supervisor-3.1.3.tar.gz";
1146 1146 md5 = "aad263c4fbc070de63dd354864d5e552";
1147 1147 };
1148 1148 };
1149 1149 transifex-client = super.buildPythonPackage {
1150 1150 name = "transifex-client-0.10";
1151 1151 buildInputs = with self; [];
1152 1152 doCheck = false;
1153 1153 propagatedBuildInputs = with self; [];
1154 1154 src = fetchurl {
1155 1155 url = "https://pypi.python.org/packages/f3/4e/7b925192aee656fb3e04fa6381c8b3dc40198047c3b4a356f6cfd642c809/transifex-client-0.10.tar.gz";
1156 1156 md5 = "5549538d84b8eede6b254cd81ae024fa";
1157 1157 };
1158 1158 };
1159 1159 translationstring = super.buildPythonPackage {
1160 1160 name = "translationstring-1.3";
1161 1161 buildInputs = with self; [];
1162 1162 doCheck = false;
1163 1163 propagatedBuildInputs = with self; [];
1164 1164 src = fetchurl {
1165 1165 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1166 1166 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1167 1167 };
1168 1168 };
1169 1169 trollius = super.buildPythonPackage {
1170 1170 name = "trollius-1.0.4";
1171 1171 buildInputs = with self; [];
1172 1172 doCheck = false;
1173 1173 propagatedBuildInputs = with self; [futures];
1174 1174 src = fetchurl {
1175 1175 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1176 1176 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1177 1177 };
1178 1178 };
1179 1179 uWSGI = super.buildPythonPackage {
1180 1180 name = "uWSGI-2.0.11.2";
1181 1181 buildInputs = with self; [];
1182 1182 doCheck = false;
1183 1183 propagatedBuildInputs = with self; [];
1184 1184 src = fetchurl {
1185 1185 url = "https://pypi.python.org/packages/9b/78/918db0cfab0546afa580c1e565209c49aaf1476bbfe491314eadbe47c556/uwsgi-2.0.11.2.tar.gz";
1186 1186 md5 = "1f02dcbee7f6f61de4b1fd68350cf16f";
1187 1187 };
1188 1188 };
1189 1189 urllib3 = super.buildPythonPackage {
1190 1190 name = "urllib3-1.15.1";
1191 1191 buildInputs = with self; [];
1192 1192 doCheck = false;
1193 1193 propagatedBuildInputs = with self; [];
1194 1194 src = fetchurl {
1195 1195 url = "https://pypi.python.org/packages/49/26/a7d12ea00cb4b9fa1e13b5980e5a04a1fe7c477eb8f657ce0b757a7a497d/urllib3-1.15.1.tar.gz";
1196 1196 md5 = "5be254b0dbb55d1307ede99e1895c8dd";
1197 1197 };
1198 1198 };
1199 1199 venusian = super.buildPythonPackage {
1200 1200 name = "venusian-1.0";
1201 1201 buildInputs = with self; [];
1202 1202 doCheck = false;
1203 1203 propagatedBuildInputs = with self; [];
1204 1204 src = fetchurl {
1205 1205 url = "https://pypi.python.org/packages/86/20/1948e0dfc4930ddde3da8c33612f6a5717c0b4bc28f591a5c5cf014dd390/venusian-1.0.tar.gz";
1206 1206 md5 = "dccf2eafb7113759d60c86faf5538756";
1207 1207 };
1208 1208 };
1209 1209 waitress = super.buildPythonPackage {
1210 1210 name = "waitress-0.8.9";
1211 1211 buildInputs = with self; [];
1212 1212 doCheck = false;
1213 1213 propagatedBuildInputs = with self; [setuptools];
1214 1214 src = fetchurl {
1215 1215 url = "https://pypi.python.org/packages/ee/65/fc9dee74a909a1187ca51e4f15ad9c4d35476e4ab5813f73421505c48053/waitress-0.8.9.tar.gz";
1216 1216 md5 = "da3f2e62b3676be5dd630703a68e2a04";
1217 1217 };
1218 1218 };
1219 1219 wsgiref = super.buildPythonPackage {
1220 1220 name = "wsgiref-0.1.2";
1221 1221 buildInputs = with self; [];
1222 1222 doCheck = false;
1223 1223 propagatedBuildInputs = with self; [];
1224 1224 src = fetchurl {
1225 1225 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1226 1226 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1227 1227 };
1228 1228 };
1229 1229 zope.cachedescriptors = super.buildPythonPackage {
1230 1230 name = "zope.cachedescriptors-4.0.0";
1231 1231 buildInputs = with self; [];
1232 1232 doCheck = false;
1233 1233 propagatedBuildInputs = with self; [setuptools];
1234 1234 src = fetchurl {
1235 1235 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
1236 1236 md5 = "8d308de8c936792c8e758058fcb7d0f0";
1237 1237 };
1238 1238 };
1239 1239 zope.deprecation = super.buildPythonPackage {
1240 1240 name = "zope.deprecation-4.1.2";
1241 1241 buildInputs = with self; [];
1242 1242 doCheck = false;
1243 1243 propagatedBuildInputs = with self; [setuptools];
1244 1244 src = fetchurl {
1245 1245 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
1246 1246 md5 = "e9a663ded58f4f9f7881beb56cae2782";
1247 1247 };
1248 1248 };
1249 1249 zope.event = super.buildPythonPackage {
1250 1250 name = "zope.event-4.0.3";
1251 1251 buildInputs = with self; [];
1252 1252 doCheck = false;
1253 1253 propagatedBuildInputs = with self; [setuptools];
1254 1254 src = fetchurl {
1255 1255 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
1256 1256 md5 = "9a3780916332b18b8b85f522bcc3e249";
1257 1257 };
1258 1258 };
1259 1259 zope.interface = super.buildPythonPackage {
1260 1260 name = "zope.interface-4.1.3";
1261 1261 buildInputs = with self; [];
1262 1262 doCheck = false;
1263 1263 propagatedBuildInputs = with self; [setuptools];
1264 1264 src = fetchurl {
1265 1265 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
1266 1266 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
1267 1267 };
1268 1268 };
1269 1269
1270 1270 ### Test requirements
1271 1271
1272
1272
1273 1273 }
@@ -1,1 +1,1 b''
1 4.0.0 No newline at end of file
1 4.0.1 No newline at end of file
@@ -1,166 +1,163 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for Jasig CAS
23 23 http://www.jasig.org/cas
24 24 """
25 25
26 26
27 27 import colander
28 28 import logging
29 29 import rhodecode
30 30 import urllib
31 31 import urllib2
32 32
33 33 from pylons.i18n.translation import lazy_ugettext as _
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35
36 36 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
37 37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 38 from rhodecode.authentication.routes import AuthnPluginResourceBase
39 from rhodecode.lib.ext_json import formatted_json
40 39 from rhodecode.lib.utils2 import safe_unicode
41 40 from rhodecode.model.db import User
42 41
43 42 log = logging.getLogger(__name__)
44 43
45 44
46 45 def plugin_factory(plugin_id, *args, **kwds):
47 46 """
48 47 Factory function that is called during plugin discovery.
49 48 It returns the plugin instance.
50 49 """
51 50 plugin = RhodeCodeAuthPlugin(plugin_id)
52 51 return plugin
53 52
54 53
55 54 class JasigCasAuthnResource(AuthnPluginResourceBase):
56 55 pass
57 56
58 57
59 58 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
60 59 service_url = colander.SchemaNode(
61 60 colander.String(),
62 61 default='https://domain.com/cas/v1/tickets',
63 62 description=_('The url of the Jasig CAS REST service'),
64 63 title=_('URL'),
65 64 widget='string')
66 65
67 66
68 67 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
69 68
70 69 def includeme(self, config):
71 70 config.add_authn_plugin(self)
72 71 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
73 72 config.add_view(
74 73 'rhodecode.authentication.views.AuthnPluginViewBase',
75 74 attr='settings_get',
76 75 request_method='GET',
77 76 route_name='auth_home',
78 77 context=JasigCasAuthnResource)
79 78 config.add_view(
80 79 'rhodecode.authentication.views.AuthnPluginViewBase',
81 80 attr='settings_post',
82 81 request_method='POST',
83 82 route_name='auth_home',
84 83 context=JasigCasAuthnResource)
85 84
86 85 def get_settings_schema(self):
87 86 return JasigCasSettingsSchema()
88 87
89 88 def get_display_name(self):
90 89 return _('Jasig-CAS')
91 90
92 91 @hybrid_property
93 92 def name(self):
94 93 return "jasig-cas"
95 94
96 95 @hybrid_property
97 96 def is_container_auth(self):
98 97 return True
99 98
100 99 def use_fake_password(self):
101 100 return True
102 101
103 102 def user_activation_state(self):
104 103 def_user_perms = User.get_default_user().AuthUser.permissions['global']
105 104 return 'hg.extern_activate.auto' in def_user_perms
106 105
107 106 def auth(self, userobj, username, password, settings, **kwargs):
108 107 """
109 108 Given a user object (which may be null), username, a plaintext password,
110 109 and a settings object (containing all the keys needed as listed in settings()),
111 110 authenticate this user's login attempt.
112 111
113 112 Return None on failure. On success, return a dictionary of the form:
114 113
115 114 see: RhodeCodeAuthPluginBase.auth_func_attrs
116 115 This is later validated for correctness
117 116 """
118 117 if not username or not password:
119 118 log.debug('Empty username or password skipping...')
120 119 return None
121 120
122 log.debug("Jasig CAS settings: \n%s" % (formatted_json(settings)))
121 log.debug("Jasig CAS settings: %s", settings)
123 122 params = urllib.urlencode({'username': username, 'password': password})
124 123 headers = {"Content-type": "application/x-www-form-urlencoded",
125 124 "Accept": "text/plain",
126 125 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
127 126 url = settings["service_url"]
128 127
129 log.debug("Sent Jasig CAS: \n%s"
130 % (formatted_json({"url": url,
131 "body": params,
132 "headers": headers})))
128 log.debug("Sent Jasig CAS: \n%s",
129 {"url": url, "body": params, "headers": headers})
133 130 request = urllib2.Request(url, params, headers)
134 131 try:
135 132 response = urllib2.urlopen(request)
136 133 except urllib2.HTTPError as e:
137 134 log.debug("HTTPError when requesting Jasig CAS (status code: %d)" % e.code)
138 135 return None
139 136 except urllib2.URLError as e:
140 137 log.debug("URLError when requesting Jasig CAS url: %s " % url)
141 138 return None
142 139
143 140 # old attrs fetched from RhodeCode database
144 141 admin = getattr(userobj, 'admin', False)
145 142 active = getattr(userobj, 'active', True)
146 143 email = getattr(userobj, 'email', '')
147 144 username = getattr(userobj, 'username', username)
148 145 firstname = getattr(userobj, 'firstname', '')
149 146 lastname = getattr(userobj, 'lastname', '')
150 147 extern_type = getattr(userobj, 'extern_type', '')
151 148
152 149 user_attrs = {
153 150 'username': username,
154 151 'firstname': safe_unicode(firstname or username),
155 152 'lastname': safe_unicode(lastname or ''),
156 153 'groups': [],
157 154 'email': email or '',
158 155 'admin': admin or False,
159 156 'active': active,
160 157 'active_from_extern': True,
161 158 'extern_name': username,
162 159 'extern_type': extern_type,
163 160 }
164 161
165 162 log.info('user %s authenticated correctly' % user_attrs['username'])
166 163 return user_attrs
@@ -1,448 +1,447 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for LDAP
23 23 """
24 24
25 25
26 26 import colander
27 27 import logging
28 28 import traceback
29 29
30 30 from pylons.i18n.translation import lazy_ugettext as _
31 31 from sqlalchemy.ext.hybrid import hybrid_property
32 32
33 33 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
34 34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 35 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 36 from rhodecode.lib.exceptions import (
37 37 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
38 38 )
39 from rhodecode.lib.ext_json import formatted_json
40 39 from rhodecode.lib.utils2 import safe_unicode, safe_str
41 40 from rhodecode.model.db import User
42 41 from rhodecode.model.validators import Missing
43 42
44 43 log = logging.getLogger(__name__)
45 44
46 45 try:
47 46 import ldap
48 47 except ImportError:
49 48 # means that python-ldap is not installed
50 49 ldap = Missing()
51 50
52 51
53 52 def plugin_factory(plugin_id, *args, **kwds):
54 53 """
55 54 Factory function that is called during plugin discovery.
56 55 It returns the plugin instance.
57 56 """
58 57 plugin = RhodeCodeAuthPlugin(plugin_id)
59 58 return plugin
60 59
61 60
62 61 class LdapAuthnResource(AuthnPluginResourceBase):
63 62 pass
64 63
65 64
66 65 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
67 66 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
68 67 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
69 68 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
70 69
71 70 host = colander.SchemaNode(
72 71 colander.String(),
73 72 default='',
74 73 description=_('Host of the LDAP Server'),
75 74 title=_('LDAP Host'),
76 75 widget='string')
77 76 port = colander.SchemaNode(
78 77 colander.Int(),
79 78 default=389,
80 79 description=_('Port that the LDAP server is listening on'),
81 80 title=_('Port'),
82 81 validator=colander.Range(min=0, max=65536),
83 82 widget='int')
84 83 dn_user = colander.SchemaNode(
85 84 colander.String(),
86 85 default='',
87 86 description=_('User to connect to LDAP'),
88 87 missing='',
89 88 title=_('Account'),
90 89 widget='string')
91 90 dn_pass = colander.SchemaNode(
92 91 colander.String(),
93 92 default='',
94 93 description=_('Password to connect to LDAP'),
95 94 missing='',
96 95 title=_('Password'),
97 96 widget='password')
98 97 tls_kind = colander.SchemaNode(
99 98 colander.String(),
100 99 default=tls_kind_choices[0],
101 100 description=_('TLS Type'),
102 101 title=_('Connection Security'),
103 102 validator=colander.OneOf(tls_kind_choices),
104 103 widget='select')
105 104 tls_reqcert = colander.SchemaNode(
106 105 colander.String(),
107 106 default=tls_reqcert_choices[0],
108 107 description=_('Require Cert over TLS?'),
109 108 title=_('Certificate Checks'),
110 109 validator=colander.OneOf(tls_reqcert_choices),
111 110 widget='select')
112 111 base_dn = colander.SchemaNode(
113 112 colander.String(),
114 113 default='',
115 114 description=_('Base DN to search (e.g., dc=mydomain,dc=com)'),
116 115 missing='',
117 116 title=_('Base DN'),
118 117 widget='string')
119 118 filter = colander.SchemaNode(
120 119 colander.String(),
121 120 default='',
122 121 description=_('Filter to narrow results (e.g., ou=Users, etc)'),
123 122 missing='',
124 123 title=_('LDAP Search Filter'),
125 124 widget='string')
126 125 search_scope = colander.SchemaNode(
127 126 colander.String(),
128 127 default=search_scope_choices[0],
129 128 description=_('How deep to search LDAP'),
130 129 title=_('LDAP Search Scope'),
131 130 validator=colander.OneOf(search_scope_choices),
132 131 widget='select')
133 132 attr_login = colander.SchemaNode(
134 133 colander.String(),
135 134 default='',
136 135 description=_('LDAP Attribute to map to user name'),
137 136 title=_('Login Attribute'),
138 137 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
139 138 widget='string')
140 139 attr_firstname = colander.SchemaNode(
141 140 colander.String(),
142 141 default='',
143 142 description=_('LDAP Attribute to map to first name'),
144 143 missing='',
145 144 title=_('First Name Attribute'),
146 145 widget='string')
147 146 attr_lastname = colander.SchemaNode(
148 147 colander.String(),
149 148 default='',
150 149 description=_('LDAP Attribute to map to last name'),
151 150 missing='',
152 151 title=_('Last Name Attribute'),
153 152 widget='string')
154 153 attr_email = colander.SchemaNode(
155 154 colander.String(),
156 155 default='',
157 156 description=_('LDAP Attribute to map to email address'),
158 157 missing='',
159 158 title=_('Email Attribute'),
160 159 widget='string')
161 160
162 161
163 162 class AuthLdap(object):
164 163
165 164 def _build_servers(self):
166 165 return ', '.join(
167 166 ["{}://{}:{}".format(
168 167 self.ldap_server_type, host.strip(), self.LDAP_SERVER_PORT)
169 168 for host in self.SERVER_ADDRESSES])
170 169
171 170 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
172 171 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
173 172 search_scope='SUBTREE', attr_login='uid',
174 173 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))'):
175 174 if isinstance(ldap, Missing):
176 175 raise LdapImportError("Missing or incompatible ldap library")
177 176
178 177 self.ldap_version = ldap_version
179 178 self.ldap_server_type = 'ldap'
180 179
181 180 self.TLS_KIND = tls_kind
182 181
183 182 if self.TLS_KIND == 'LDAPS':
184 183 port = port or 689
185 184 self.ldap_server_type += 's'
186 185
187 186 OPT_X_TLS_DEMAND = 2
188 187 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
189 188 OPT_X_TLS_DEMAND)
190 189 # split server into list
191 190 self.SERVER_ADDRESSES = server.split(',')
192 191 self.LDAP_SERVER_PORT = port
193 192
194 193 # USE FOR READ ONLY BIND TO LDAP SERVER
195 194 self.attr_login = attr_login
196 195
197 196 self.LDAP_BIND_DN = safe_str(bind_dn)
198 197 self.LDAP_BIND_PASS = safe_str(bind_pass)
199 198 self.LDAP_SERVER = self._build_servers()
200 199 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
201 200 self.BASE_DN = safe_str(base_dn)
202 201 self.LDAP_FILTER = safe_str(ldap_filter)
203 202
204 203 def _get_ldap_server(self):
205 204 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
206 205 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
207 206 '/etc/openldap/cacerts')
208 207 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
209 208 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
210 209 ldap.set_option(ldap.OPT_TIMEOUT, 20)
211 210 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
212 211 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
213 212 if self.TLS_KIND != 'PLAIN':
214 213 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
215 214 server = ldap.initialize(self.LDAP_SERVER)
216 215 if self.ldap_version == 2:
217 216 server.protocol = ldap.VERSION2
218 217 else:
219 218 server.protocol = ldap.VERSION3
220 219
221 220 if self.TLS_KIND == 'START_TLS':
222 221 server.start_tls_s()
223 222
224 223 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
225 224 log.debug('Trying simple_bind with password and given DN: %s',
226 225 self.LDAP_BIND_DN)
227 226 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
228 227
229 228 return server
230 229
231 230 def get_uid(self, username):
232 231 from rhodecode.lib.helpers import chop_at
233 232 uid = username
234 233 for server_addr in self.SERVER_ADDRESSES:
235 234 uid = chop_at(username, "@%s" % server_addr)
236 235 return uid
237 236
238 237 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
239 238 try:
240 239 log.debug('Trying simple bind with %s', dn)
241 240 server.simple_bind_s(dn, safe_str(password))
242 241 user = server.search_ext_s(
243 242 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
244 243 _, attrs = user
245 244 return attrs
246 245
247 246 except ldap.INVALID_CREDENTIALS:
248 247 log.debug(
249 248 "LDAP rejected password for user '%s': %s, org_exc:",
250 249 username, dn, exc_info=True)
251 250
252 251 def authenticate_ldap(self, username, password):
253 252 """
254 253 Authenticate a user via LDAP and return his/her LDAP properties.
255 254
256 255 Raises AuthenticationError if the credentials are rejected, or
257 256 EnvironmentError if the LDAP server can't be reached.
258 257
259 258 :param username: username
260 259 :param password: password
261 260 """
262 261
263 262 uid = self.get_uid(username)
264 263
265 264 if not password:
266 265 msg = "Authenticating user %s with blank password not allowed"
267 266 log.warning(msg, username)
268 267 raise LdapPasswordError(msg)
269 268 if "," in username:
270 269 raise LdapUsernameError("invalid character in username: ,")
271 270 try:
272 271 server = self._get_ldap_server()
273 272 filter_ = '(&%s(%s=%s))' % (
274 273 self.LDAP_FILTER, self.attr_login, username)
275 274 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
276 275 filter_, self.LDAP_SERVER)
277 276 lobjects = server.search_ext_s(
278 277 self.BASE_DN, self.SEARCH_SCOPE, filter_)
279 278
280 279 if not lobjects:
281 280 raise ldap.NO_SUCH_OBJECT()
282 281
283 282 for (dn, _attrs) in lobjects:
284 283 if dn is None:
285 284 continue
286 285
287 286 user_attrs = self.fetch_attrs_from_simple_bind(
288 287 server, dn, username, password)
289 288 if user_attrs:
290 289 break
291 290
292 291 else:
293 292 log.debug("No matching LDAP objects for authentication "
294 293 "of '%s' (%s)", uid, username)
295 294 raise LdapPasswordError('Failed to authenticate user '
296 295 'with given password')
297 296
298 297 except ldap.NO_SUCH_OBJECT:
299 298 log.debug("LDAP says no such user '%s' (%s), org_exc:",
300 299 uid, username, exc_info=True)
301 300 raise LdapUsernameError()
302 301 except ldap.SERVER_DOWN:
303 302 org_exc = traceback.format_exc()
304 303 raise LdapConnectionError(
305 304 "LDAP can't access authentication "
306 305 "server, org_exc:%s" % org_exc)
307 306
308 307 return dn, user_attrs
309 308
310 309
311 310 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
312 311 # used to define dynamic binding in the
313 312 DYNAMIC_BIND_VAR = '$login'
314 313
315 314 def includeme(self, config):
316 315 config.add_authn_plugin(self)
317 316 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
318 317 config.add_view(
319 318 'rhodecode.authentication.views.AuthnPluginViewBase',
320 319 attr='settings_get',
321 320 request_method='GET',
322 321 route_name='auth_home',
323 322 context=LdapAuthnResource)
324 323 config.add_view(
325 324 'rhodecode.authentication.views.AuthnPluginViewBase',
326 325 attr='settings_post',
327 326 request_method='POST',
328 327 route_name='auth_home',
329 328 context=LdapAuthnResource)
330 329
331 330 def get_settings_schema(self):
332 331 return LdapSettingsSchema()
333 332
334 333 def get_display_name(self):
335 334 return _('LDAP')
336 335
337 336 @hybrid_property
338 337 def name(self):
339 338 return "ldap"
340 339
341 340 def use_fake_password(self):
342 341 return True
343 342
344 343 def user_activation_state(self):
345 344 def_user_perms = User.get_default_user().AuthUser.permissions['global']
346 345 return 'hg.extern_activate.auto' in def_user_perms
347 346
348 347 def try_dynamic_binding(self, username, password, current_args):
349 348 """
350 349 Detects marker inside our original bind, and uses dynamic auth if
351 350 present
352 351 """
353 352
354 353 org_bind = current_args['bind_dn']
355 354 passwd = current_args['bind_pass']
356 355
357 356 def has_bind_marker(username):
358 357 if self.DYNAMIC_BIND_VAR in username:
359 358 return True
360 359
361 360 # we only passed in user with "special" variable
362 361 if org_bind and has_bind_marker(org_bind) and not passwd:
363 362 log.debug('Using dynamic user/password binding for ldap '
364 363 'authentication. Replacing `%s` with username',
365 364 self.DYNAMIC_BIND_VAR)
366 365 current_args['bind_dn'] = org_bind.replace(
367 366 self.DYNAMIC_BIND_VAR, username)
368 367 current_args['bind_pass'] = password
369 368
370 369 return current_args
371 370
372 371 def auth(self, userobj, username, password, settings, **kwargs):
373 372 """
374 373 Given a user object (which may be null), username, a plaintext password,
375 374 and a settings object (containing all the keys needed as listed in
376 375 settings()), authenticate this user's login attempt.
377 376
378 377 Return None on failure. On success, return a dictionary of the form:
379 378
380 379 see: RhodeCodeAuthPluginBase.auth_func_attrs
381 380 This is later validated for correctness
382 381 """
383 382
384 383 if not username or not password:
385 384 log.debug('Empty username or password skipping...')
386 385 return None
387 386
388 387 ldap_args = {
389 388 'server': settings.get('host', ''),
390 389 'base_dn': settings.get('base_dn', ''),
391 390 'port': settings.get('port'),
392 391 'bind_dn': settings.get('dn_user'),
393 392 'bind_pass': settings.get('dn_pass'),
394 393 'tls_kind': settings.get('tls_kind'),
395 394 'tls_reqcert': settings.get('tls_reqcert'),
396 395 'search_scope': settings.get('search_scope'),
397 396 'attr_login': settings.get('attr_login'),
398 397 'ldap_version': 3,
399 398 'ldap_filter': settings.get('filter'),
400 399 }
401 400
402 401 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
403 402
404 403 log.debug('Checking for ldap authentication.')
405 404
406 405 try:
407 406 aldap = AuthLdap(**ldap_args)
408 407 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
409 408 log.debug('Got ldap DN response %s', user_dn)
410 409
411 410 def get_ldap_attr(k):
412 411 return ldap_attrs.get(settings.get(k), [''])[0]
413 412
414 413 # old attrs fetched from RhodeCode database
415 414 admin = getattr(userobj, 'admin', False)
416 415 active = getattr(userobj, 'active', True)
417 416 email = getattr(userobj, 'email', '')
418 417 username = getattr(userobj, 'username', username)
419 418 firstname = getattr(userobj, 'firstname', '')
420 419 lastname = getattr(userobj, 'lastname', '')
421 420 extern_type = getattr(userobj, 'extern_type', '')
422 421
423 422 groups = []
424 423 user_attrs = {
425 424 'username': username,
426 425 'firstname': safe_unicode(
427 426 get_ldap_attr('attr_firstname') or firstname),
428 427 'lastname': safe_unicode(
429 428 get_ldap_attr('attr_lastname') or lastname),
430 429 'groups': groups,
431 430 'email': get_ldap_attr('attr_email' or email),
432 431 'admin': admin,
433 432 'active': active,
434 433 "active_from_extern": None,
435 434 'extern_name': user_dn,
436 435 'extern_type': extern_type,
437 436 }
438 log.debug('ldap user: \n%s', formatted_json(user_attrs))
437 log.debug('ldap user: %s', user_attrs)
439 438 log.info('user %s authenticated correctly', user_attrs['username'])
440 439
441 440 return user_attrs
442 441
443 442 except (LdapUsernameError, LdapPasswordError, LdapImportError):
444 443 log.exception("LDAP related exception")
445 444 return None
446 445 except (Exception,):
447 446 log.exception("Other exception")
448 447 return None
@@ -1,156 +1,155 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 """
21 21 RhodeCode authentication library for PAM
22 22 """
23 23
24 24 import colander
25 25 import grp
26 26 import logging
27 27 import pam
28 28 import pwd
29 29 import re
30 30 import socket
31 31
32 32 from pylons.i18n.translation import lazy_ugettext as _
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34
35 35 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
36 36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 37 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 from rhodecode.lib.ext_json import formatted_json
39 38
40 39 log = logging.getLogger(__name__)
41 40
42 41
43 42 def plugin_factory(plugin_id, *args, **kwds):
44 43 """
45 44 Factory function that is called during plugin discovery.
46 45 It returns the plugin instance.
47 46 """
48 47 plugin = RhodeCodeAuthPlugin(plugin_id)
49 48 return plugin
50 49
51 50
52 51 class PamAuthnResource(AuthnPluginResourceBase):
53 52 pass
54 53
55 54
56 55 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
57 56 service = colander.SchemaNode(
58 57 colander.String(),
59 58 default='login',
60 59 description=_('PAM service name to use for authentication.'),
61 60 title=_('PAM service name'),
62 61 widget='string')
63 62 gecos = colander.SchemaNode(
64 63 colander.String(),
65 64 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
66 65 description=_('Regular expression for extracting user name/email etc. '
67 66 'from Unix userinfo.'),
68 67 title=_('Gecos Regex'),
69 68 widget='string')
70 69
71 70
72 71 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
73 72 # PAM authentication can be slow. Repository operations involve a lot of
74 73 # auth calls. Little caching helps speedup push/pull operations significantly
75 74 AUTH_CACHE_TTL = 4
76 75
77 76 def includeme(self, config):
78 77 config.add_authn_plugin(self)
79 78 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
80 79 config.add_view(
81 80 'rhodecode.authentication.views.AuthnPluginViewBase',
82 81 attr='settings_get',
83 82 request_method='GET',
84 83 route_name='auth_home',
85 84 context=PamAuthnResource)
86 85 config.add_view(
87 86 'rhodecode.authentication.views.AuthnPluginViewBase',
88 87 attr='settings_post',
89 88 request_method='POST',
90 89 route_name='auth_home',
91 90 context=PamAuthnResource)
92 91
93 92 def get_display_name(self):
94 93 return _('PAM')
95 94
96 95 @hybrid_property
97 96 def name(self):
98 97 return "pam"
99 98
100 99 def get_settings_schema(self):
101 100 return PamSettingsSchema()
102 101
103 102 def use_fake_password(self):
104 103 return True
105 104
106 105 def auth(self, userobj, username, password, settings, **kwargs):
107 106 if not username or not password:
108 107 log.debug('Empty username or password skipping...')
109 108 return None
110 109
111 110 auth_result = pam.authenticate(username, password, settings["service"])
112 111
113 112 if not auth_result:
114 113 log.error("PAM was unable to authenticate user: %s" % (username, ))
115 114 return None
116 115
117 116 log.debug('Got PAM response %s' % (auth_result, ))
118 117
119 118 # old attrs fetched from RhodeCode database
120 119 default_email = "%s@%s" % (username, socket.gethostname())
121 120 admin = getattr(userobj, 'admin', False)
122 121 active = getattr(userobj, 'active', True)
123 122 email = getattr(userobj, 'email', '') or default_email
124 123 username = getattr(userobj, 'username', username)
125 124 firstname = getattr(userobj, 'firstname', '')
126 125 lastname = getattr(userobj, 'lastname', '')
127 126 extern_type = getattr(userobj, 'extern_type', '')
128 127
129 128 user_attrs = {
130 129 'username': username,
131 130 'firstname': firstname,
132 131 'lastname': lastname,
133 132 'groups': [g.gr_name for g in grp.getgrall()
134 133 if username in g.gr_mem],
135 134 'email': email,
136 135 'admin': admin,
137 136 'active': active,
138 137 'active_from_extern': None,
139 138 'extern_name': username,
140 139 'extern_type': extern_type,
141 140 }
142 141
143 142 try:
144 143 user_data = pwd.getpwnam(username)
145 144 regex = settings["gecos"]
146 145 match = re.search(regex, user_data.pw_gecos)
147 146 if match:
148 147 user_attrs["firstname"] = match.group('first_name')
149 148 user_attrs["lastname"] = match.group('last_name')
150 149 except Exception:
151 150 log.warning("Cannot extract additional info for PAM user")
152 151 pass
153 152
154 log.debug("pamuser: \n%s" % formatted_json(user_attrs))
153 log.debug("pamuser: %s", user_attrs)
155 154 log.info('user %s authenticated correctly' % user_attrs['username'])
156 155 return user_attrs
@@ -1,440 +1,441 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 23 It's implemented with basic auth function
24 24 """
25 25
26 26 import os
27 27 import logging
28 28 import importlib
29 29 from functools import wraps
30 30
31 31 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 32 from webob.exc import (
33 33 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34 34
35 35 import rhodecode
36 36 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 37 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 38 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 39 from rhodecode.lib.exceptions import (
40 40 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 41 NotAllowedToCreateUserError)
42 42 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 43 from rhodecode.lib.middleware import appenlight
44 44 from rhodecode.lib.middleware.utils import scm_app
45 45 from rhodecode.lib.utils import is_valid_repo
46 46 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
47 47 from rhodecode.model import meta
48 48 from rhodecode.model.db import User, Repository
49 49 from rhodecode.model.scm import ScmModel
50 50 from rhodecode.model.settings import SettingsModel
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 def initialize_generator(factory):
56 56 """
57 57 Initializes the returned generator by draining its first element.
58 58
59 59 This can be used to give a generator an initializer, which is the code
60 60 up to the first yield statement. This decorator enforces that the first
61 61 produced element has the value ``"__init__"`` to make its special
62 62 purpose very explicit in the using code.
63 63 """
64 64
65 65 @wraps(factory)
66 66 def wrapper(*args, **kwargs):
67 67 gen = factory(*args, **kwargs)
68 68 try:
69 69 init = gen.next()
70 70 except StopIteration:
71 71 raise ValueError('Generator must yield at least one element.')
72 72 if init != "__init__":
73 73 raise ValueError('First yielded element must be "__init__".')
74 74 return gen
75 75 return wrapper
76 76
77 77
78 78 class SimpleVCS(object):
79 79 """Common functionality for SCM HTTP handlers."""
80 80
81 81 SCM = 'unknown'
82 82
83 83 def __init__(self, application, config):
84 84 self.application = application
85 85 self.config = config
86 86 # base path of repo locations
87 87 self.basepath = self.config['base_path']
88 88 # authenticate this VCS request using authfunc
89 89 auth_ret_code_detection = \
90 90 str2bool(self.config.get('auth_ret_code_detection', False))
91 91 self.authenticate = BasicAuth('', authenticate,
92 92 config.get('auth_ret_code'),
93 93 auth_ret_code_detection)
94 94 self.ip_addr = '0.0.0.0'
95 95
96 96 @property
97 97 def scm_app(self):
98 98 custom_implementation = self.config.get('vcs.scm_app_implementation')
99 99 if custom_implementation:
100 100 log.info(
101 101 "Using custom implementation of scm_app: %s",
102 102 custom_implementation)
103 103 scm_app_impl = importlib.import_module(custom_implementation)
104 104 else:
105 105 scm_app_impl = scm_app
106 106 return scm_app_impl
107 107
108 108 def _get_by_id(self, repo_name):
109 109 """
110 110 Gets a special pattern _<ID> from clone url and tries to replace it
111 111 with a repository_name for support of _<ID> non changable urls
112 112
113 113 :param repo_name:
114 114 """
115 115
116 116 data = repo_name.split('/')
117 117 if len(data) >= 2:
118 118 from rhodecode.model.repo import RepoModel
119 119 by_id_match = RepoModel().get_repo_by_id(repo_name)
120 120 if by_id_match:
121 121 data[1] = by_id_match.repo_name
122 122
123 123 return safe_str('/'.join(data))
124 124
125 125 def _invalidate_cache(self, repo_name):
126 126 """
127 127 Set's cache for this repository for invalidation on next access
128 128
129 129 :param repo_name: full repo name, also a cache key
130 130 """
131 131 ScmModel().mark_for_invalidation(repo_name)
132 132
133 133 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
134 134 db_repo = Repository.get_by_repo_name(repo_name)
135 135 if not db_repo:
136 log.debug('Repository `%s` not found inside the database.')
136 log.debug('Repository `%s` not found inside the database.',
137 repo_name)
137 138 return False
138 139
139 140 if db_repo.repo_type != scm_type:
140 141 log.warning(
141 142 'Repository `%s` have incorrect scm_type, expected %s got %s',
142 143 repo_name, db_repo.repo_type, scm_type)
143 144 return False
144 145
145 146 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
146 147
147 148 def valid_and_active_user(self, user):
148 149 """
149 150 Checks if that user is not empty, and if it's actually object it checks
150 151 if he's active.
151 152
152 153 :param user: user object or None
153 154 :return: boolean
154 155 """
155 156 if user is None:
156 157 return False
157 158
158 159 elif user.active:
159 160 return True
160 161
161 162 return False
162 163
163 164 def _check_permission(self, action, user, repo_name, ip_addr=None):
164 165 """
165 166 Checks permissions using action (push/pull) user and repository
166 167 name
167 168
168 169 :param action: push or pull action
169 170 :param user: user instance
170 171 :param repo_name: repository name
171 172 """
172 173 # check IP
173 174 inherit = user.inherit_default_permissions
174 175 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
175 176 inherit_from_default=inherit)
176 177 if ip_allowed:
177 178 log.info('Access for IP:%s allowed', ip_addr)
178 179 else:
179 180 return False
180 181
181 182 if action == 'push':
182 183 if not HasPermissionAnyMiddleware('repository.write',
183 184 'repository.admin')(user,
184 185 repo_name):
185 186 return False
186 187
187 188 else:
188 189 # any other action need at least read permission
189 190 if not HasPermissionAnyMiddleware('repository.read',
190 191 'repository.write',
191 192 'repository.admin')(user,
192 193 repo_name):
193 194 return False
194 195
195 196 return True
196 197
197 198 def _check_ssl(self, environ, start_response):
198 199 """
199 200 Checks the SSL check flag and returns False if SSL is not present
200 201 and required True otherwise
201 202 """
202 203 org_proto = environ['wsgi._org_proto']
203 204 # check if we have SSL required ! if not it's a bad request !
204 205 require_ssl = str2bool(
205 206 SettingsModel().get_ui_by_key('push_ssl').ui_value)
206 207 if require_ssl and org_proto == 'http':
207 208 log.debug('proto is %s and SSL is required BAD REQUEST !',
208 209 org_proto)
209 210 return False
210 211 return True
211 212
212 213 def __call__(self, environ, start_response):
213 214 try:
214 215 return self._handle_request(environ, start_response)
215 216 except Exception:
216 217 log.exception("Exception while handling request")
217 218 appenlight.track_exception(environ)
218 219 return HTTPInternalServerError()(environ, start_response)
219 220 finally:
220 221 meta.Session.remove()
221 222
222 223 def _handle_request(self, environ, start_response):
223 224
224 225 if not self._check_ssl(environ, start_response):
225 226 reason = ('SSL required, while RhodeCode was unable '
226 227 'to detect this as SSL request')
227 228 log.debug('User not allowed to proceed, %s', reason)
228 229 return HTTPNotAcceptable(reason)(environ, start_response)
229 230
230 231 ip_addr = get_ip_addr(environ)
231 232 username = None
232 233
233 234 # skip passing error to error controller
234 235 environ['pylons.status_code_redirect'] = True
235 236
236 237 # ======================================================================
237 238 # EXTRACT REPOSITORY NAME FROM ENV
238 239 # ======================================================================
239 240 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
240 241 repo_name = self._get_repository_name(environ)
241 242 environ['REPO_NAME'] = repo_name
242 243 log.debug('Extracted repo name is %s', repo_name)
243 244
244 245 # check for type, presence in database and on filesystem
245 246 if not self.is_valid_and_existing_repo(
246 247 repo_name, self.basepath, self.SCM):
247 248 return HTTPNotFound()(environ, start_response)
248 249
249 250 # ======================================================================
250 251 # GET ACTION PULL or PUSH
251 252 # ======================================================================
252 253 action = self._get_action(environ)
253 254
254 255 # ======================================================================
255 256 # CHECK ANONYMOUS PERMISSION
256 257 # ======================================================================
257 258 if action in ['pull', 'push']:
258 259 anonymous_user = User.get_default_user()
259 260 username = anonymous_user.username
260 261 if anonymous_user.active:
261 262 # ONLY check permissions if the user is activated
262 263 anonymous_perm = self._check_permission(
263 264 action, anonymous_user, repo_name, ip_addr)
264 265 else:
265 266 anonymous_perm = False
266 267
267 268 if not anonymous_user.active or not anonymous_perm:
268 269 if not anonymous_user.active:
269 270 log.debug('Anonymous access is disabled, running '
270 271 'authentication')
271 272
272 273 if not anonymous_perm:
273 274 log.debug('Not enough credentials to access this '
274 275 'repository as anonymous user')
275 276
276 277 username = None
277 278 # ==============================================================
278 279 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
279 280 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
280 281 # ==============================================================
281 282
282 283 # try to auth based on environ, container auth methods
283 284 log.debug('Running PRE-AUTH for container based authentication')
284 285 pre_auth = authenticate('', '', environ,VCS_TYPE)
285 286 if pre_auth and pre_auth.get('username'):
286 287 username = pre_auth['username']
287 288 log.debug('PRE-AUTH got %s as username', username)
288 289
289 290 # If not authenticated by the container, running basic auth
290 291 if not username:
291 292 self.authenticate.realm = \
292 293 safe_str(self.config['rhodecode_realm'])
293 294
294 295 try:
295 296 result = self.authenticate(environ)
296 297 except (UserCreationError, NotAllowedToCreateUserError) as e:
297 298 log.error(e)
298 299 reason = safe_str(e)
299 300 return HTTPNotAcceptable(reason)(environ, start_response)
300 301
301 302 if isinstance(result, str):
302 303 AUTH_TYPE.update(environ, 'basic')
303 304 REMOTE_USER.update(environ, result)
304 305 username = result
305 306 else:
306 307 return result.wsgi_application(environ, start_response)
307 308
308 309 # ==============================================================
309 310 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
310 311 # ==============================================================
311 312 user = User.get_by_username(username)
312 313 if not self.valid_and_active_user(user):
313 314 return HTTPForbidden()(environ, start_response)
314 315 username = user.username
315 316 user.update_lastactivity()
316 317 meta.Session().commit()
317 318
318 319 # check user attributes for password change flag
319 320 user_obj = user
320 321 if user_obj and user_obj.username != User.DEFAULT_USER and \
321 322 user_obj.user_data.get('force_password_change'):
322 323 reason = 'password change required'
323 324 log.debug('User not allowed to authenticate, %s', reason)
324 325 return HTTPNotAcceptable(reason)(environ, start_response)
325 326
326 327 # check permissions for this repository
327 328 perm = self._check_permission(action, user, repo_name, ip_addr)
328 329 if not perm:
329 330 return HTTPForbidden()(environ, start_response)
330 331
331 332 # extras are injected into UI object and later available
332 333 # in hooks executed by rhodecode
333 334 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
334 335 extras = vcs_operation_context(
335 336 environ, repo_name=repo_name, username=username,
336 337 action=action, scm=self.SCM,
337 338 check_locking=check_locking)
338 339
339 340 # ======================================================================
340 341 # REQUEST HANDLING
341 342 # ======================================================================
342 343 str_repo_name = safe_str(repo_name)
343 344 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
344 345 log.debug('Repository path is %s', repo_path)
345 346
346 347 fix_PATH()
347 348
348 349 log.info(
349 350 '%s action on %s repo "%s" by "%s" from %s',
350 351 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
351 352 return self._generate_vcs_response(
352 353 environ, start_response, repo_path, repo_name, extras, action)
353 354
354 355 @initialize_generator
355 356 def _generate_vcs_response(
356 357 self, environ, start_response, repo_path, repo_name, extras,
357 358 action):
358 359 """
359 360 Returns a generator for the response content.
360 361
361 362 This method is implemented as a generator, so that it can trigger
362 363 the cache validation after all content sent back to the client. It
363 364 also handles the locking exceptions which will be triggered when
364 365 the first chunk is produced by the underlying WSGI application.
365 366 """
366 367 callback_daemon, extras = self._prepare_callback_daemon(extras)
367 368 config = self._create_config(extras, repo_name)
368 369 log.debug('HOOKS extras is %s', extras)
369 370 app = self._create_wsgi_app(repo_path, repo_name, config)
370 371
371 372 try:
372 373 with callback_daemon:
373 374 try:
374 375 response = app(environ, start_response)
375 376 finally:
376 377 # This statement works together with the decorator
377 378 # "initialize_generator" above. The decorator ensures that
378 379 # we hit the first yield statement before the generator is
379 380 # returned back to the WSGI server. This is needed to
380 381 # ensure that the call to "app" above triggers the
381 382 # needed callback to "start_response" before the
382 383 # generator is actually used.
383 384 yield "__init__"
384 385
385 386 for chunk in response:
386 387 yield chunk
387 388 except Exception as exc:
388 389 # TODO: johbo: Improve "translating" back the exception.
389 390 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
390 391 exc = HTTPLockedRC(*exc.args)
391 392 _code = rhodecode.CONFIG.get('lock_ret_code')
392 393 log.debug('Repository LOCKED ret code %s!', (_code,))
393 394 elif getattr(exc, '_vcs_kind', None) == 'requirement':
394 395 log.debug(
395 396 'Repository requires features unknown to this Mercurial')
396 397 exc = HTTPRequirementError(*exc.args)
397 398 else:
398 399 raise
399 400
400 401 for chunk in exc(environ, start_response):
401 402 yield chunk
402 403 finally:
403 404 # invalidate cache on push
404 405 if action == 'push':
405 406 self._invalidate_cache(repo_name)
406 407
407 408 def _get_repository_name(self, environ):
408 409 """Get repository name out of the environmnent
409 410
410 411 :param environ: WSGI environment
411 412 """
412 413 raise NotImplementedError()
413 414
414 415 def _get_action(self, environ):
415 416 """Map request commands into a pull or push command.
416 417
417 418 :param environ: WSGI environment
418 419 """
419 420 raise NotImplementedError()
420 421
421 422 def _create_wsgi_app(self, repo_path, repo_name, config):
422 423 """Return the WSGI app that will finally handle the request."""
423 424 raise NotImplementedError()
424 425
425 426 def _create_config(self, extras, repo_name):
426 427 """Create a Pyro safe config representation."""
427 428 raise NotImplementedError()
428 429
429 430 def _prepare_callback_daemon(self, extras):
430 431 return prepare_callback_daemon(
431 432 extras, protocol=self.config.get('vcs.hooks.protocol'),
432 433 use_direct_calls=self.config.get('vcs.hooks.direct_calls'))
433 434
434 435
435 436 def _should_check_locking(query_string):
436 437 # this is kind of hacky, but due to how mercurial handles client-server
437 438 # server see all operation on commit; bookmarks, phases and
438 439 # obsolescence marker in different transaction, we don't want to check
439 440 # locking on those
440 441 return query_string not in ['cmd=listkeys']
@@ -1,1128 +1,1130 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 pull request model for RhodeCode
24 24 """
25 25
26 26 from collections import namedtuple
27 27 import json
28 28 import logging
29 29 import datetime
30 30
31 31 from pylons.i18n.translation import _
32 32 from pylons.i18n.translation import lazy_ugettext
33 33
34 34 import rhodecode
35 35 from rhodecode.lib import helpers as h, hooks_utils, diffs
36 36 from rhodecode.lib.compat import OrderedDict
37 37 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
38 38 from rhodecode.lib.markup_renderer import (
39 39 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
40 40 from rhodecode.lib.utils import action_logger
41 41 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe
42 42 from rhodecode.lib.vcs.backends.base import (
43 43 Reference, MergeResponse, MergeFailureReason)
44 44 from rhodecode.lib.vcs.exceptions import (
45 45 CommitDoesNotExistError, EmptyRepositoryError)
46 46 from rhodecode.model import BaseModel
47 47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 48 from rhodecode.model.comment import ChangesetCommentsModel
49 49 from rhodecode.model.db import (
50 50 PullRequest, PullRequestReviewers, Notification, ChangesetStatus,
51 51 PullRequestVersion, ChangesetComment)
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.notification import NotificationModel, \
54 54 EmailNotificationModel
55 55 from rhodecode.model.scm import ScmModel
56 56 from rhodecode.model.settings import VcsSettingsModel
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class PullRequestModel(BaseModel):
63 63
64 64 cls = PullRequest
65 65
66 66 DIFF_CONTEXT = 3
67 67
68 68 MERGE_STATUS_MESSAGES = {
69 69 MergeFailureReason.NONE: lazy_ugettext(
70 70 'This pull request can be automatically merged.'),
71 71 MergeFailureReason.UNKNOWN: lazy_ugettext(
72 72 'This pull request cannot be merged because of an unhandled'
73 73 ' exception.'),
74 74 MergeFailureReason.MERGE_FAILED: lazy_ugettext(
75 75 'This pull request cannot be merged because of conflicts.'),
76 76 MergeFailureReason.PUSH_FAILED: lazy_ugettext(
77 77 'This pull request could not be merged because push to target'
78 78 ' failed.'),
79 79 MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
80 80 'This pull request cannot be merged because the target is not a'
81 81 ' head.'),
82 82 MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
83 83 'This pull request cannot be merged because the source contains'
84 84 ' more branches than the target.'),
85 85 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
86 86 'This pull request cannot be merged because the target has'
87 87 ' multiple heads.'),
88 88 MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
89 89 'This pull request cannot be merged because the target repository'
90 90 ' is locked.'),
91 91 MergeFailureReason.MISSING_COMMIT: lazy_ugettext(
92 92 'This pull request cannot be merged because the target or the '
93 93 'source reference is missing.'),
94 94 }
95 95
96 96 def __get_pull_request(self, pull_request):
97 97 return self._get_instance(PullRequest, pull_request)
98 98
99 99 def _check_perms(self, perms, pull_request, user, api=False):
100 100 if not api:
101 101 return h.HasRepoPermissionAny(*perms)(
102 102 user=user, repo_name=pull_request.target_repo.repo_name)
103 103 else:
104 104 return h.HasRepoPermissionAnyApi(*perms)(
105 105 user=user, repo_name=pull_request.target_repo.repo_name)
106 106
107 107 def check_user_read(self, pull_request, user, api=False):
108 108 _perms = ('repository.admin', 'repository.write', 'repository.read',)
109 109 return self._check_perms(_perms, pull_request, user, api)
110 110
111 111 def check_user_merge(self, pull_request, user, api=False):
112 112 _perms = ('repository.admin', 'repository.write', 'hg.admin',)
113 113 return self._check_perms(_perms, pull_request, user, api)
114 114
115 115 def check_user_update(self, pull_request, user, api=False):
116 116 owner = user.user_id == pull_request.user_id
117 117 return self.check_user_merge(pull_request, user, api) or owner
118 118
119 119 def check_user_change_status(self, pull_request, user, api=False):
120 120 reviewer = user.user_id in [x.user_id for x in
121 121 pull_request.reviewers]
122 122 return self.check_user_update(pull_request, user, api) or reviewer
123 123
124 124 def get(self, pull_request):
125 125 return self.__get_pull_request(pull_request)
126 126
127 127 def _prepare_get_all_query(self, repo_name, source=False, statuses=None,
128 128 opened_by=None, order_by=None,
129 129 order_dir='desc'):
130 130 repo = self._get_repo(repo_name)
131 131 q = PullRequest.query()
132 132 # source or target
133 133 if source:
134 134 q = q.filter(PullRequest.source_repo == repo)
135 135 else:
136 136 q = q.filter(PullRequest.target_repo == repo)
137 137
138 138 # closed,opened
139 139 if statuses:
140 140 q = q.filter(PullRequest.status.in_(statuses))
141 141
142 142 # opened by filter
143 143 if opened_by:
144 144 q = q.filter(PullRequest.user_id.in_(opened_by))
145 145
146 146 if order_by:
147 147 order_map = {
148 148 'name_raw': PullRequest.pull_request_id,
149 149 'title': PullRequest.title,
150 150 'updated_on_raw': PullRequest.updated_on
151 151 }
152 152 if order_dir == 'asc':
153 153 q = q.order_by(order_map[order_by].asc())
154 154 else:
155 155 q = q.order_by(order_map[order_by].desc())
156 156
157 157 return q
158 158
159 159 def count_all(self, repo_name, source=False, statuses=None,
160 160 opened_by=None):
161 161 """
162 162 Count the number of pull requests for a specific repository.
163 163
164 164 :param repo_name: target or source repo
165 165 :param source: boolean flag to specify if repo_name refers to source
166 166 :param statuses: list of pull request statuses
167 167 :param opened_by: author user of the pull request
168 168 :returns: int number of pull requests
169 169 """
170 170 q = self._prepare_get_all_query(
171 171 repo_name, source=source, statuses=statuses, opened_by=opened_by)
172 172
173 173 return q.count()
174 174
175 175 def get_all(self, repo_name, source=False, statuses=None, opened_by=None,
176 176 offset=0, length=None, order_by=None, order_dir='desc'):
177 177 """
178 178 Get all pull requests for a specific repository.
179 179
180 180 :param repo_name: target or source repo
181 181 :param source: boolean flag to specify if repo_name refers to source
182 182 :param statuses: list of pull request statuses
183 183 :param opened_by: author user of the pull request
184 184 :param offset: pagination offset
185 185 :param length: length of returned list
186 186 :param order_by: order of the returned list
187 187 :param order_dir: 'asc' or 'desc' ordering direction
188 188 :returns: list of pull requests
189 189 """
190 190 q = self._prepare_get_all_query(
191 191 repo_name, source=source, statuses=statuses, opened_by=opened_by,
192 192 order_by=order_by, order_dir=order_dir)
193 193
194 194 if length:
195 195 pull_requests = q.limit(length).offset(offset).all()
196 196 else:
197 197 pull_requests = q.all()
198 198
199 199 return pull_requests
200 200
201 201 def count_awaiting_review(self, repo_name, source=False, statuses=None,
202 202 opened_by=None):
203 203 """
204 204 Count the number of pull requests for a specific repository that are
205 205 awaiting review.
206 206
207 207 :param repo_name: target or source repo
208 208 :param source: boolean flag to specify if repo_name refers to source
209 209 :param statuses: list of pull request statuses
210 210 :param opened_by: author user of the pull request
211 211 :returns: int number of pull requests
212 212 """
213 213 pull_requests = self.get_awaiting_review(
214 214 repo_name, source=source, statuses=statuses, opened_by=opened_by)
215 215
216 216 return len(pull_requests)
217 217
218 218 def get_awaiting_review(self, repo_name, source=False, statuses=None,
219 219 opened_by=None, offset=0, length=None,
220 220 order_by=None, order_dir='desc'):
221 221 """
222 222 Get all pull requests for a specific repository that are awaiting
223 223 review.
224 224
225 225 :param repo_name: target or source repo
226 226 :param source: boolean flag to specify if repo_name refers to source
227 227 :param statuses: list of pull request statuses
228 228 :param opened_by: author user of the pull request
229 229 :param offset: pagination offset
230 230 :param length: length of returned list
231 231 :param order_by: order of the returned list
232 232 :param order_dir: 'asc' or 'desc' ordering direction
233 233 :returns: list of pull requests
234 234 """
235 235 pull_requests = self.get_all(
236 236 repo_name, source=source, statuses=statuses, opened_by=opened_by,
237 237 order_by=order_by, order_dir=order_dir)
238 238
239 239 _filtered_pull_requests = []
240 240 for pr in pull_requests:
241 241 status = pr.calculated_review_status()
242 242 if status in [ChangesetStatus.STATUS_NOT_REVIEWED,
243 243 ChangesetStatus.STATUS_UNDER_REVIEW]:
244 244 _filtered_pull_requests.append(pr)
245 245 if length:
246 246 return _filtered_pull_requests[offset:offset+length]
247 247 else:
248 248 return _filtered_pull_requests
249 249
250 250 def count_awaiting_my_review(self, repo_name, source=False, statuses=None,
251 251 opened_by=None, user_id=None):
252 252 """
253 253 Count the number of pull requests for a specific repository that are
254 254 awaiting review from a specific user.
255 255
256 256 :param repo_name: target or source repo
257 257 :param source: boolean flag to specify if repo_name refers to source
258 258 :param statuses: list of pull request statuses
259 259 :param opened_by: author user of the pull request
260 260 :param user_id: reviewer user of the pull request
261 261 :returns: int number of pull requests
262 262 """
263 263 pull_requests = self.get_awaiting_my_review(
264 264 repo_name, source=source, statuses=statuses, opened_by=opened_by,
265 265 user_id=user_id)
266 266
267 267 return len(pull_requests)
268 268
269 269 def get_awaiting_my_review(self, repo_name, source=False, statuses=None,
270 270 opened_by=None, user_id=None, offset=0,
271 271 length=None, order_by=None, order_dir='desc'):
272 272 """
273 273 Get all pull requests for a specific repository that are awaiting
274 274 review from a specific user.
275 275
276 276 :param repo_name: target or source repo
277 277 :param source: boolean flag to specify if repo_name refers to source
278 278 :param statuses: list of pull request statuses
279 279 :param opened_by: author user of the pull request
280 280 :param user_id: reviewer user of the pull request
281 281 :param offset: pagination offset
282 282 :param length: length of returned list
283 283 :param order_by: order of the returned list
284 284 :param order_dir: 'asc' or 'desc' ordering direction
285 285 :returns: list of pull requests
286 286 """
287 287 pull_requests = self.get_all(
288 288 repo_name, source=source, statuses=statuses, opened_by=opened_by,
289 289 order_by=order_by, order_dir=order_dir)
290 290
291 291 _my = PullRequestModel().get_not_reviewed(user_id)
292 292 my_participation = []
293 293 for pr in pull_requests:
294 294 if pr in _my:
295 295 my_participation.append(pr)
296 296 _filtered_pull_requests = my_participation
297 297 if length:
298 298 return _filtered_pull_requests[offset:offset+length]
299 299 else:
300 300 return _filtered_pull_requests
301 301
302 302 def get_not_reviewed(self, user_id):
303 303 return [
304 304 x.pull_request for x in PullRequestReviewers.query().filter(
305 305 PullRequestReviewers.user_id == user_id).all()
306 306 ]
307 307
308 308 def get_versions(self, pull_request):
309 309 """
310 310 returns version of pull request sorted by ID descending
311 311 """
312 312 return PullRequestVersion.query()\
313 313 .filter(PullRequestVersion.pull_request == pull_request)\
314 314 .order_by(PullRequestVersion.pull_request_version_id.asc())\
315 315 .all()
316 316
317 317 def create(self, created_by, source_repo, source_ref, target_repo,
318 318 target_ref, revisions, reviewers, title, description=None):
319 319 created_by_user = self._get_user(created_by)
320 320 source_repo = self._get_repo(source_repo)
321 321 target_repo = self._get_repo(target_repo)
322 322
323 323 pull_request = PullRequest()
324 324 pull_request.source_repo = source_repo
325 325 pull_request.source_ref = source_ref
326 326 pull_request.target_repo = target_repo
327 327 pull_request.target_ref = target_ref
328 328 pull_request.revisions = revisions
329 329 pull_request.title = title
330 330 pull_request.description = description
331 331 pull_request.author = created_by_user
332 332
333 333 Session().add(pull_request)
334 334 Session().flush()
335 335
336 336 # members / reviewers
337 337 for user_id in set(reviewers):
338 338 user = self._get_user(user_id)
339 339 reviewer = PullRequestReviewers(user, pull_request)
340 340 Session().add(reviewer)
341 341
342 342 # Set approval status to "Under Review" for all commits which are
343 343 # part of this pull request.
344 344 ChangesetStatusModel().set_status(
345 345 repo=target_repo,
346 346 status=ChangesetStatus.STATUS_UNDER_REVIEW,
347 347 user=created_by_user,
348 348 pull_request=pull_request
349 349 )
350 350
351 351 self.notify_reviewers(pull_request, reviewers)
352 352 self._trigger_pull_request_hook(
353 353 pull_request, created_by_user, 'create')
354 354
355 355 return pull_request
356 356
357 357 def _trigger_pull_request_hook(self, pull_request, user, action):
358 358 pull_request = self.__get_pull_request(pull_request)
359 359 target_scm = pull_request.target_repo.scm_instance()
360 360 if action == 'create':
361 361 trigger_hook = hooks_utils.trigger_log_create_pull_request_hook
362 362 elif action == 'merge':
363 363 trigger_hook = hooks_utils.trigger_log_merge_pull_request_hook
364 364 elif action == 'close':
365 365 trigger_hook = hooks_utils.trigger_log_close_pull_request_hook
366 366 elif action == 'review_status_change':
367 367 trigger_hook = hooks_utils.trigger_log_review_pull_request_hook
368 368 elif action == 'update':
369 369 trigger_hook = hooks_utils.trigger_log_update_pull_request_hook
370 370 else:
371 371 return
372 372
373 373 trigger_hook(
374 374 username=user.username,
375 375 repo_name=pull_request.target_repo.repo_name,
376 376 repo_alias=target_scm.alias,
377 377 pull_request=pull_request)
378 378
379 379 def _get_commit_ids(self, pull_request):
380 380 """
381 381 Return the commit ids of the merged pull request.
382 382
383 383 This method is not dealing correctly yet with the lack of autoupdates
384 384 nor with the implicit target updates.
385 385 For example: if a commit in the source repo is already in the target it
386 386 will be reported anyways.
387 387 """
388 388 merge_rev = pull_request.merge_rev
389 389 if merge_rev is None:
390 390 raise ValueError('This pull request was not merged yet')
391 391
392 392 commit_ids = list(pull_request.revisions)
393 393 if merge_rev not in commit_ids:
394 394 commit_ids.append(merge_rev)
395 395
396 396 return commit_ids
397 397
398 398 def merge(self, pull_request, user, extras):
399 399 merge_state = self._merge_pull_request(pull_request, user, extras)
400 400 if merge_state.executed:
401 401 self._comment_and_close_pr(pull_request, user, merge_state)
402 402 self._log_action('user_merged_pull_request', user, pull_request)
403 403 return merge_state
404 404
405 405 def _merge_pull_request(self, pull_request, user, extras):
406 406 target_vcs = pull_request.target_repo.scm_instance()
407 407 source_vcs = pull_request.source_repo.scm_instance()
408 408 target_ref = self._refresh_reference(
409 409 pull_request.target_ref_parts, target_vcs)
410 410
411 411 message = _(
412 412 'Merge pull request #%(pr_id)s from '
413 413 '%(source_repo)s %(source_ref_name)s\n\n %(pr_title)s') % {
414 414 'pr_id': pull_request.pull_request_id,
415 415 'source_repo': source_vcs.name,
416 416 'source_ref_name': pull_request.source_ref_parts.name,
417 417 'pr_title': pull_request.title
418 418 }
419 419
420 420 workspace_id = self._workspace_id(pull_request)
421 421 protocol = rhodecode.CONFIG.get('vcs.hooks.protocol')
422 422 use_direct_calls = rhodecode.CONFIG.get('vcs.hooks.direct_calls')
423 423
424 424 callback_daemon, extras = prepare_callback_daemon(
425 425 extras, protocol=protocol, use_direct_calls=use_direct_calls)
426 426
427 427 with callback_daemon:
428 428 # TODO: johbo: Implement a clean way to run a config_override
429 429 # for a single call.
430 430 target_vcs.config.set(
431 431 'rhodecode', 'RC_SCM_DATA', json.dumps(extras))
432 432 merge_state = target_vcs.merge(
433 433 target_ref, source_vcs, pull_request.source_ref_parts,
434 434 workspace_id, user_name=user.username,
435 435 user_email=user.email, message=message)
436 436 return merge_state
437 437
438 438 def _comment_and_close_pr(self, pull_request, user, merge_state):
439 439 pull_request.merge_rev = merge_state.merge_commit_id
440 440 pull_request.updated_on = datetime.datetime.now()
441 441
442 442 ChangesetCommentsModel().create(
443 443 text=unicode(_('Pull request merged and closed')),
444 444 repo=pull_request.target_repo.repo_id,
445 445 user=user.user_id,
446 446 pull_request=pull_request.pull_request_id,
447 447 f_path=None,
448 448 line_no=None,
449 449 closing_pr=True
450 450 )
451 451
452 452 Session().add(pull_request)
453 453 Session().flush()
454 454 # TODO: paris: replace invalidation with less radical solution
455 455 ScmModel().mark_for_invalidation(
456 456 pull_request.target_repo.repo_name)
457 457 self._trigger_pull_request_hook(pull_request, user, 'merge')
458 458
459 459 def has_valid_update_type(self, pull_request):
460 460 source_ref_type = pull_request.source_ref_parts.type
461 461 return source_ref_type in ['book', 'branch', 'tag']
462 462
463 463 def update_commits(self, pull_request):
464 464 """
465 465 Get the updated list of commits for the pull request
466 466 and return the new pull request version and the list
467 467 of commits processed by this update action
468 468 """
469 469
470 470 pull_request = self.__get_pull_request(pull_request)
471 471 source_ref_type = pull_request.source_ref_parts.type
472 472 source_ref_name = pull_request.source_ref_parts.name
473 473 source_ref_id = pull_request.source_ref_parts.commit_id
474 474
475 475 if not self.has_valid_update_type(pull_request):
476 476 log.debug(
477 477 "Skipping update of pull request %s due to ref type: %s",
478 478 pull_request, source_ref_type)
479 479 return (None, None)
480 480
481 481 source_repo = pull_request.source_repo.scm_instance()
482 482 source_commit = source_repo.get_commit(commit_id=source_ref_name)
483 483 if source_ref_id == source_commit.raw_id:
484 484 log.debug("Nothing changed in pull request %s", pull_request)
485 485 return (None, None)
486 486
487 487 # Finally there is a need for an update
488 488 pull_request_version = self._create_version_from_snapshot(pull_request)
489 489 self._link_comments_to_version(pull_request_version)
490 490
491 491 target_ref_type = pull_request.target_ref_parts.type
492 492 target_ref_name = pull_request.target_ref_parts.name
493 493 target_ref_id = pull_request.target_ref_parts.commit_id
494 494 target_repo = pull_request.target_repo.scm_instance()
495 495
496 496 if target_ref_type in ('tag', 'branch', 'book'):
497 497 target_commit = target_repo.get_commit(target_ref_name)
498 498 else:
499 499 target_commit = target_repo.get_commit(target_ref_id)
500 500
501 501 # re-compute commit ids
502 502 old_commit_ids = set(pull_request.revisions)
503 503 pre_load = ["author", "branch", "date", "message"]
504 504 commit_ranges = target_repo.compare(
505 505 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
506 506 pre_load=pre_load)
507 507
508 508 ancestor = target_repo.get_common_ancestor(
509 509 target_commit.raw_id, source_commit.raw_id, source_repo)
510 510
511 511 pull_request.source_ref = '%s:%s:%s' % (
512 512 source_ref_type, source_ref_name, source_commit.raw_id)
513 513 pull_request.target_ref = '%s:%s:%s' % (
514 514 target_ref_type, target_ref_name, ancestor)
515 515 pull_request.revisions = [
516 516 commit.raw_id for commit in reversed(commit_ranges)]
517 517 pull_request.updated_on = datetime.datetime.now()
518 518 Session().add(pull_request)
519 519 new_commit_ids = set(pull_request.revisions)
520 520
521 521 changes = self._calculate_commit_id_changes(
522 522 old_commit_ids, new_commit_ids)
523 523
524 524 old_diff_data, new_diff_data = self._generate_update_diffs(
525 525 pull_request, pull_request_version)
526 526
527 527 ChangesetCommentsModel().outdate_comments(
528 528 pull_request, old_diff_data=old_diff_data,
529 529 new_diff_data=new_diff_data)
530 530
531 531 file_changes = self._calculate_file_changes(
532 532 old_diff_data, new_diff_data)
533 533
534 534 # Add an automatic comment to the pull request
535 535 update_comment = ChangesetCommentsModel().create(
536 536 text=self._render_update_message(changes, file_changes),
537 537 repo=pull_request.target_repo,
538 538 user=pull_request.author,
539 539 pull_request=pull_request,
540 540 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER)
541 541
542 542 # Update status to "Under Review" for added commits
543 543 for commit_id in changes.added:
544 544 ChangesetStatusModel().set_status(
545 545 repo=pull_request.source_repo,
546 546 status=ChangesetStatus.STATUS_UNDER_REVIEW,
547 547 comment=update_comment,
548 548 user=pull_request.author,
549 549 pull_request=pull_request,
550 550 revision=commit_id)
551 551
552 552 log.debug(
553 553 'Updated pull request %s, added_ids: %s, common_ids: %s, '
554 554 'removed_ids: %s', pull_request.pull_request_id,
555 555 changes.added, changes.common, changes.removed)
556 556 log.debug('Updated pull request with the following file changes: %s',
557 557 file_changes)
558 558
559 559 log.info(
560 560 "Updated pull request %s from commit %s to commit %s, "
561 561 "stored new version %s of this pull request.",
562 562 pull_request.pull_request_id, source_ref_id,
563 563 pull_request.source_ref_parts.commit_id,
564 564 pull_request_version.pull_request_version_id)
565 565 Session().commit()
566 566 self._trigger_pull_request_hook(pull_request, pull_request.author,
567 567 'update')
568 568 return (pull_request_version, changes)
569 569
570 570 def _create_version_from_snapshot(self, pull_request):
571 571 version = PullRequestVersion()
572 572 version.title = pull_request.title
573 573 version.description = pull_request.description
574 574 version.status = pull_request.status
575 575 version.created_on = pull_request.created_on
576 576 version.updated_on = pull_request.updated_on
577 577 version.user_id = pull_request.user_id
578 578 version.source_repo = pull_request.source_repo
579 579 version.source_ref = pull_request.source_ref
580 580 version.target_repo = pull_request.target_repo
581 581 version.target_ref = pull_request.target_ref
582 582
583 583 version._last_merge_source_rev = pull_request._last_merge_source_rev
584 584 version._last_merge_target_rev = pull_request._last_merge_target_rev
585 585 version._last_merge_status = pull_request._last_merge_status
586 586 version.merge_rev = pull_request.merge_rev
587 587
588 588 version.revisions = pull_request.revisions
589 589 version.pull_request = pull_request
590 590 Session().add(version)
591 591 Session().flush()
592 592
593 593 return version
594 594
595 595 def _generate_update_diffs(self, pull_request, pull_request_version):
596 596 diff_context = (
597 597 self.DIFF_CONTEXT +
598 598 ChangesetCommentsModel.needed_extra_diff_context())
599 599 old_diff = self._get_diff_from_pr_or_version(
600 600 pull_request_version, context=diff_context)
601 601 new_diff = self._get_diff_from_pr_or_version(
602 602 pull_request, context=diff_context)
603 603
604 604 old_diff_data = diffs.DiffProcessor(old_diff)
605 605 old_diff_data.prepare()
606 606 new_diff_data = diffs.DiffProcessor(new_diff)
607 607 new_diff_data.prepare()
608 608
609 609 return old_diff_data, new_diff_data
610 610
611 611 def _link_comments_to_version(self, pull_request_version):
612 612 """
613 613 Link all unlinked comments of this pull request to the given version.
614 614
615 615 :param pull_request_version: The `PullRequestVersion` to which
616 616 the comments shall be linked.
617 617
618 618 """
619 619 pull_request = pull_request_version.pull_request
620 620 comments = ChangesetComment.query().filter(
621 621 # TODO: johbo: Should we query for the repo at all here?
622 622 # Pending decision on how comments of PRs are to be related
623 623 # to either the source repo, the target repo or no repo at all.
624 624 ChangesetComment.repo_id == pull_request.target_repo.repo_id,
625 625 ChangesetComment.pull_request == pull_request,
626 626 ChangesetComment.pull_request_version == None)
627 627
628 628 # TODO: johbo: Find out why this breaks if it is done in a bulk
629 629 # operation.
630 630 for comment in comments:
631 631 comment.pull_request_version_id = (
632 632 pull_request_version.pull_request_version_id)
633 633 Session().add(comment)
634 634
635 635 def _calculate_commit_id_changes(self, old_ids, new_ids):
636 636 added = new_ids.difference(old_ids)
637 637 common = old_ids.intersection(new_ids)
638 638 removed = old_ids.difference(new_ids)
639 639 return ChangeTuple(added, common, removed)
640 640
641 641 def _calculate_file_changes(self, old_diff_data, new_diff_data):
642 642
643 643 old_files = OrderedDict()
644 644 for diff_data in old_diff_data.parsed_diff:
645 645 old_files[diff_data['filename']] = md5_safe(diff_data['raw_diff'])
646 646
647 647 added_files = []
648 648 modified_files = []
649 649 removed_files = []
650 650 for diff_data in new_diff_data.parsed_diff:
651 651 new_filename = diff_data['filename']
652 652 new_hash = md5_safe(diff_data['raw_diff'])
653 653
654 654 old_hash = old_files.get(new_filename)
655 655 if not old_hash:
656 656 # file is not present in old diff, means it's added
657 657 added_files.append(new_filename)
658 658 else:
659 659 if new_hash != old_hash:
660 660 modified_files.append(new_filename)
661 661 # now remove a file from old, since we have seen it already
662 662 del old_files[new_filename]
663 663
664 664 # removed files is when there are present in old, but not in NEW,
665 665 # since we remove old files that are present in new diff, left-overs
666 666 # if any should be the removed files
667 667 removed_files.extend(old_files.keys())
668 668
669 669 return FileChangeTuple(added_files, modified_files, removed_files)
670 670
671 671 def _render_update_message(self, changes, file_changes):
672 672 """
673 673 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
674 674 so it's always looking the same disregarding on which default
675 675 renderer system is using.
676 676
677 677 :param changes: changes named tuple
678 678 :param file_changes: file changes named tuple
679 679
680 680 """
681 681 new_status = ChangesetStatus.get_status_lbl(
682 682 ChangesetStatus.STATUS_UNDER_REVIEW)
683 683
684 684 changed_files = (
685 685 file_changes.added + file_changes.modified + file_changes.removed)
686 686
687 687 params = {
688 688 'under_review_label': new_status,
689 689 'added_commits': changes.added,
690 690 'removed_commits': changes.removed,
691 691 'changed_files': changed_files,
692 692 'added_files': file_changes.added,
693 693 'modified_files': file_changes.modified,
694 694 'removed_files': file_changes.removed,
695 695 }
696 696 renderer = RstTemplateRenderer()
697 697 return renderer.render('pull_request_update.mako', **params)
698 698
699 699 def edit(self, pull_request, title, description):
700 700 pull_request = self.__get_pull_request(pull_request)
701 701 if pull_request.is_closed():
702 702 raise ValueError('This pull request is closed')
703 703 if title:
704 704 pull_request.title = title
705 705 pull_request.description = description
706 706 pull_request.updated_on = datetime.datetime.now()
707 707 Session().add(pull_request)
708 708
709 709 def update_reviewers(self, pull_request, reviewers_ids):
710 710 reviewers_ids = set(reviewers_ids)
711 711 pull_request = self.__get_pull_request(pull_request)
712 712 current_reviewers = PullRequestReviewers.query()\
713 713 .filter(PullRequestReviewers.pull_request ==
714 714 pull_request).all()
715 715 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
716 716
717 717 ids_to_add = reviewers_ids.difference(current_reviewers_ids)
718 718 ids_to_remove = current_reviewers_ids.difference(reviewers_ids)
719 719
720 720 log.debug("Adding %s reviewers", ids_to_add)
721 721 log.debug("Removing %s reviewers", ids_to_remove)
722 722 changed = False
723 723 for uid in ids_to_add:
724 724 changed = True
725 725 _usr = self._get_user(uid)
726 726 reviewer = PullRequestReviewers(_usr, pull_request)
727 727 Session().add(reviewer)
728 728
729 729 self.notify_reviewers(pull_request, ids_to_add)
730 730
731 731 for uid in ids_to_remove:
732 732 changed = True
733 733 reviewer = PullRequestReviewers.query()\
734 734 .filter(PullRequestReviewers.user_id == uid,
735 735 PullRequestReviewers.pull_request == pull_request)\
736 736 .scalar()
737 737 if reviewer:
738 738 Session().delete(reviewer)
739 739 if changed:
740 740 pull_request.updated_on = datetime.datetime.now()
741 741 Session().add(pull_request)
742 742
743 743 return ids_to_add, ids_to_remove
744 744
745 745 def notify_reviewers(self, pull_request, reviewers_ids):
746 746 # notification to reviewers
747 747 if not reviewers_ids:
748 748 return
749 749
750 750 pull_request_obj = pull_request
751 751 # get the current participants of this pull request
752 752 recipients = reviewers_ids
753 753 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST
754 754
755 755 pr_source_repo = pull_request_obj.source_repo
756 756 pr_target_repo = pull_request_obj.target_repo
757 757
758 758 pr_url = h.url(
759 759 'pullrequest_show',
760 760 repo_name=pr_target_repo.repo_name,
761 761 pull_request_id=pull_request_obj.pull_request_id,
762 762 qualified=True,)
763 763
764 764 # set some variables for email notification
765 765 pr_target_repo_url = h.url(
766 766 'summary_home',
767 767 repo_name=pr_target_repo.repo_name,
768 768 qualified=True)
769 769
770 770 pr_source_repo_url = h.url(
771 771 'summary_home',
772 772 repo_name=pr_source_repo.repo_name,
773 773 qualified=True)
774 774
775 775 # pull request specifics
776 776 pull_request_commits = [
777 777 (x.raw_id, x.message)
778 778 for x in map(pr_source_repo.get_commit, pull_request.revisions)]
779 779
780 780 kwargs = {
781 781 'user': pull_request.author,
782 782 'pull_request': pull_request_obj,
783 783 'pull_request_commits': pull_request_commits,
784 784
785 785 'pull_request_target_repo': pr_target_repo,
786 786 'pull_request_target_repo_url': pr_target_repo_url,
787 787
788 788 'pull_request_source_repo': pr_source_repo,
789 789 'pull_request_source_repo_url': pr_source_repo_url,
790 790
791 791 'pull_request_url': pr_url,
792 792 }
793 793
794 794 # pre-generate the subject for notification itself
795 795 (subject,
796 796 _h, _e, # we don't care about those
797 797 body_plaintext) = EmailNotificationModel().render_email(
798 798 notification_type, **kwargs)
799 799
800 800 # create notification objects, and emails
801 801 NotificationModel().create(
802 802 created_by=pull_request.author,
803 803 notification_subject=subject,
804 804 notification_body=body_plaintext,
805 805 notification_type=notification_type,
806 806 recipients=recipients,
807 807 email_kwargs=kwargs,
808 808 )
809 809
810 810 def delete(self, pull_request):
811 811 pull_request = self.__get_pull_request(pull_request)
812 812 self._cleanup_merge_workspace(pull_request)
813 813 Session().delete(pull_request)
814 814
815 815 def close_pull_request(self, pull_request, user):
816 816 pull_request = self.__get_pull_request(pull_request)
817 817 self._cleanup_merge_workspace(pull_request)
818 818 pull_request.status = PullRequest.STATUS_CLOSED
819 819 pull_request.updated_on = datetime.datetime.now()
820 820 Session().add(pull_request)
821 821 self._trigger_pull_request_hook(
822 822 pull_request, pull_request.author, 'close')
823 823 self._log_action('user_closed_pull_request', user, pull_request)
824 824
825 825 def close_pull_request_with_comment(self, pull_request, user, repo,
826 826 message=None):
827 827 status = ChangesetStatus.STATUS_REJECTED
828 828
829 829 if not message:
830 830 message = (
831 831 _('Status change %(transition_icon)s %(status)s') % {
832 832 'transition_icon': '>',
833 833 'status': ChangesetStatus.get_status_lbl(status)})
834 834
835 835 internal_message = _('Closing with') + ' ' + message
836 836
837 837 comm = ChangesetCommentsModel().create(
838 838 text=internal_message,
839 839 repo=repo.repo_id,
840 840 user=user.user_id,
841 841 pull_request=pull_request.pull_request_id,
842 842 f_path=None,
843 843 line_no=None,
844 844 status_change=ChangesetStatus.get_status_lbl(status),
845 845 closing_pr=True
846 846 )
847 847
848 848 ChangesetStatusModel().set_status(
849 849 repo.repo_id,
850 850 status,
851 851 user.user_id,
852 852 comm,
853 853 pull_request=pull_request.pull_request_id
854 854 )
855 855 Session().flush()
856 856
857 857 PullRequestModel().close_pull_request(
858 858 pull_request.pull_request_id, user)
859 859
860 860 def merge_status(self, pull_request):
861 861 if not self._is_merge_enabled(pull_request):
862 862 return False, _('Server-side pull request merging is disabled.')
863 863 if pull_request.is_closed():
864 864 return False, _('This pull request is closed.')
865 865 merge_possible, msg = self._check_repo_requirements(
866 866 target=pull_request.target_repo, source=pull_request.source_repo)
867 867 if not merge_possible:
868 868 return merge_possible, msg
869 869
870 870 try:
871 871 resp = self._try_merge(pull_request)
872 872 status = resp.possible, self.merge_status_message(
873 873 resp.failure_reason)
874 874 except NotImplementedError:
875 875 status = False, _('Pull request merging is not supported.')
876 876
877 877 return status
878 878
879 879 def _check_repo_requirements(self, target, source):
880 880 """
881 881 Check if `target` and `source` have compatible requirements.
882 882
883 883 Currently this is just checking for largefiles.
884 884 """
885 885 target_has_largefiles = self._has_largefiles(target)
886 886 source_has_largefiles = self._has_largefiles(source)
887 887 merge_possible = True
888 888 message = u''
889 889
890 890 if target_has_largefiles != source_has_largefiles:
891 891 merge_possible = False
892 892 if source_has_largefiles:
893 893 message = _(
894 894 'Target repository large files support is disabled.')
895 895 else:
896 896 message = _(
897 897 'Source repository large files support is disabled.')
898 898
899 899 return merge_possible, message
900 900
901 901 def _has_largefiles(self, repo):
902 902 largefiles_ui = VcsSettingsModel(repo=repo).get_ui_settings(
903 903 'extensions', 'largefiles')
904 904 return largefiles_ui and largefiles_ui[0].active
905 905
906 906 def _try_merge(self, pull_request):
907 907 """
908 908 Try to merge the pull request and return the merge status.
909 909 """
910 910 target_vcs = pull_request.target_repo.scm_instance()
911 911 target_ref = self._refresh_reference(
912 912 pull_request.target_ref_parts, target_vcs)
913 913
914 914 target_locked = pull_request.target_repo.locked
915 915 if target_locked and target_locked[0]:
916 916 merge_state = MergeResponse(
917 917 False, False, None, MergeFailureReason.TARGET_IS_LOCKED)
918 918 elif self._needs_merge_state_refresh(pull_request, target_ref):
919 919 merge_state = self._refresh_merge_state(
920 920 pull_request, target_vcs, target_ref)
921 921 else:
922 922 possible = pull_request.\
923 923 _last_merge_status == MergeFailureReason.NONE
924 924 merge_state = MergeResponse(
925 925 possible, False, None, pull_request._last_merge_status)
926 926 return merge_state
927 927
928 928 def _refresh_reference(self, reference, vcs_repository):
929 929 if reference.type in ('branch', 'book'):
930 930 name_or_id = reference.name
931 931 else:
932 932 name_or_id = reference.commit_id
933 933 refreshed_commit = vcs_repository.get_commit(name_or_id)
934 934 refreshed_reference = Reference(
935 935 reference.type, reference.name, refreshed_commit.raw_id)
936 936 return refreshed_reference
937 937
938 938 def _needs_merge_state_refresh(self, pull_request, target_reference):
939 939 return not(
940 940 pull_request.revisions and
941 941 pull_request.revisions[0] == pull_request._last_merge_source_rev and
942 942 target_reference.commit_id == pull_request._last_merge_target_rev)
943 943
944 944 def _refresh_merge_state(self, pull_request, target_vcs, target_reference):
945 945 workspace_id = self._workspace_id(pull_request)
946 946 source_vcs = pull_request.source_repo.scm_instance()
947 947 merge_state = target_vcs.merge(
948 948 target_reference, source_vcs, pull_request.source_ref_parts,
949 949 workspace_id, dry_run=True)
950 950
951 951 # Do not store the response if there was an unknown error.
952 952 if merge_state.failure_reason != MergeFailureReason.UNKNOWN:
953 953 pull_request._last_merge_source_rev = pull_request.\
954 954 source_ref_parts.commit_id
955 955 pull_request._last_merge_target_rev = target_reference.commit_id
956 956 pull_request._last_merge_status = (
957 957 merge_state.failure_reason)
958 958 Session().add(pull_request)
959 959 Session().flush()
960 960
961 961 return merge_state
962 962
963 963 def _workspace_id(self, pull_request):
964 964 workspace_id = 'pr-%s' % pull_request.pull_request_id
965 965 return workspace_id
966 966
967 967 def merge_status_message(self, status_code):
968 968 """
969 969 Return a human friendly error message for the given merge status code.
970 970 """
971 971 return self.MERGE_STATUS_MESSAGES[status_code]
972 972
973 973 def generate_repo_data(self, repo, commit_id=None, branch=None,
974 974 bookmark=None):
975 975 all_refs, selected_ref = \
976 976 self._get_repo_pullrequest_sources(
977 977 repo.scm_instance(), commit_id=commit_id,
978 978 branch=branch, bookmark=bookmark)
979 979
980 980 refs_select2 = []
981 981 for element in all_refs:
982 982 children = [{'id': x[0], 'text': x[1]} for x in element[0]]
983 983 refs_select2.append({'text': element[1], 'children': children})
984 984
985 985 return {
986 986 'user': {
987 987 'user_id': repo.user.user_id,
988 988 'username': repo.user.username,
989 989 'firstname': repo.user.firstname,
990 990 'lastname': repo.user.lastname,
991 991 'gravatar_link': h.gravatar_url(repo.user.email, 14),
992 992 },
993 993 'description': h.chop_at_smart(repo.description, '\n'),
994 994 'refs': {
995 995 'all_refs': all_refs,
996 996 'selected_ref': selected_ref,
997 997 'select2_refs': refs_select2
998 998 }
999 999 }
1000 1000
1001 1001 def generate_pullrequest_title(self, source, source_ref, target):
1002 1002 return '{source}#{at_ref} to {target}'.format(
1003 1003 source=source,
1004 1004 at_ref=source_ref,
1005 1005 target=target,
1006 1006 )
1007 1007
1008 1008 def _cleanup_merge_workspace(self, pull_request):
1009 1009 # Merging related cleanup
1010 1010 target_scm = pull_request.target_repo.scm_instance()
1011 1011 workspace_id = 'pr-%s' % pull_request.pull_request_id
1012 1012
1013 1013 try:
1014 1014 target_scm.cleanup_merge_workspace(workspace_id)
1015 1015 except NotImplementedError:
1016 1016 pass
1017 1017
1018 1018 def _get_repo_pullrequest_sources(
1019 1019 self, repo, commit_id=None, branch=None, bookmark=None):
1020 1020 """
1021 1021 Return a structure with repo's interesting commits, suitable for
1022 1022 the selectors in pullrequest controller
1023 1023
1024 1024 :param commit_id: a commit that must be in the list somehow
1025 1025 and selected by default
1026 1026 :param branch: a branch that must be in the list and selected
1027 1027 by default - even if closed
1028 1028 :param bookmark: a bookmark that must be in the list and selected
1029 1029 """
1030 1030
1031 1031 commit_id = safe_str(commit_id) if commit_id else None
1032 1032 branch = safe_str(branch) if branch else None
1033 1033 bookmark = safe_str(bookmark) if bookmark else None
1034 1034
1035 1035 selected = None
1036 1036
1037 1037 # order matters: first source that has commit_id in it will be selected
1038 1038 sources = []
1039 1039 sources.append(('book', repo.bookmarks.items(), _('Bookmarks'), bookmark))
1040 1040 sources.append(('branch', repo.branches.items(), _('Branches'), branch))
1041 1041
1042 1042 if commit_id:
1043 1043 ref_commit = (h.short_id(commit_id), commit_id)
1044 1044 sources.append(('rev', [ref_commit], _('Commit IDs'), commit_id))
1045 1045
1046 1046 sources.append(
1047 1047 ('branch', repo.branches_closed.items(), _('Closed Branches'), branch),
1048 1048 )
1049 1049
1050 1050 groups = []
1051 1051 for group_key, ref_list, group_name, match in sources:
1052 1052 group_refs = []
1053 1053 for ref_name, ref_id in ref_list:
1054 1054 ref_key = '%s:%s:%s' % (group_key, ref_name, ref_id)
1055 1055 group_refs.append((ref_key, ref_name))
1056 1056
1057 if not selected and match in (ref_id, ref_name):
1058 selected = ref_key
1057 if not selected:
1058 if set([commit_id, match]) & set([ref_id, ref_name]):
1059 selected = ref_key
1060
1059 1061 if group_refs:
1060 1062 groups.append((group_refs, group_name))
1061 1063
1062 1064 if not selected:
1063 1065 ref = commit_id or branch or bookmark
1064 1066 if ref:
1065 1067 raise CommitDoesNotExistError(
1066 1068 'No commit refs could be found matching: %s' % ref)
1067 1069 elif repo.DEFAULT_BRANCH_NAME in repo.branches:
1068 1070 selected = 'branch:%s:%s' % (
1069 1071 repo.DEFAULT_BRANCH_NAME,
1070 1072 repo.branches[repo.DEFAULT_BRANCH_NAME]
1071 1073 )
1072 1074 elif repo.commit_ids:
1073 1075 rev = repo.commit_ids[0]
1074 1076 selected = 'rev:%s:%s' % (rev, rev)
1075 1077 else:
1076 1078 raise EmptyRepositoryError()
1077 1079 return groups, selected
1078 1080
1079 1081 def get_diff(self, pull_request, context=DIFF_CONTEXT):
1080 1082 pull_request = self.__get_pull_request(pull_request)
1081 1083 return self._get_diff_from_pr_or_version(pull_request, context=context)
1082 1084
1083 1085 def _get_diff_from_pr_or_version(self, pr_or_version, context):
1084 1086 source_repo = pr_or_version.source_repo
1085 1087
1086 1088 # we swap org/other ref since we run a simple diff on one repo
1087 1089 target_ref_id = pr_or_version.target_ref_parts.commit_id
1088 1090 source_ref_id = pr_or_version.source_ref_parts.commit_id
1089 1091 target_commit = source_repo.get_commit(
1090 1092 commit_id=safe_str(target_ref_id))
1091 1093 source_commit = source_repo.get_commit(commit_id=safe_str(source_ref_id))
1092 1094 vcs_repo = source_repo.scm_instance()
1093 1095
1094 1096 # TODO: johbo: In the context of an update, we cannot reach
1095 1097 # the old commit anymore with our normal mechanisms. It needs
1096 1098 # some sort of special support in the vcs layer to avoid this
1097 1099 # workaround.
1098 1100 if (source_commit.raw_id == vcs_repo.EMPTY_COMMIT_ID and
1099 1101 vcs_repo.alias == 'git'):
1100 1102 source_commit.raw_id = safe_str(source_ref_id)
1101 1103
1102 1104 log.debug('calculating diff between '
1103 1105 'source_ref:%s and target_ref:%s for repo `%s`',
1104 1106 target_ref_id, source_ref_id,
1105 1107 safe_unicode(vcs_repo.path))
1106 1108
1107 1109 vcs_diff = vcs_repo.get_diff(
1108 1110 commit1=target_commit, commit2=source_commit, context=context)
1109 1111 return vcs_diff
1110 1112
1111 1113 def _is_merge_enabled(self, pull_request):
1112 1114 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
1113 1115 settings = settings_model.get_general_settings()
1114 1116 return settings.get('rhodecode_pr_merge_enabled', False)
1115 1117
1116 1118 def _log_action(self, action, user, pull_request):
1117 1119 action_logger(
1118 1120 user,
1119 1121 '{action}:{pr_id}'.format(
1120 1122 action=action, pr_id=pull_request.pull_request_id),
1121 1123 pull_request.target_repo)
1122 1124
1123 1125
1124 1126 ChangeTuple = namedtuple('ChangeTuple',
1125 1127 ['added', 'common', 'removed'])
1126 1128
1127 1129 FileChangeTuple = namedtuple('FileChangeTuple',
1128 1130 ['added', 'modified', 'removed'])
@@ -1,600 +1,605 b''
1 1 // Default styles
2 2
3 3 .diff-collapse {
4 4 margin: @padding 0;
5 5 text-align: right;
6 6 }
7 7
8 8 .diff-container {
9 9 margin-bottom: @space;
10 10
11 11 .diffblock {
12 12 margin-bottom: @space;
13 13 }
14 14
15 15 &.hidden {
16 16 display: none;
17 17 overflow: hidden;
18 18 }
19 19 }
20 20
21 21 .compare_view_files {
22 22
23 23 .diff-container {
24 24
25 25 .diffblock {
26 26 margin-bottom: 0;
27 27 }
28 28 }
29 29 }
30 30
31 31 div.diffblock .sidebyside {
32 32 background: #ffffff;
33 33 }
34 34
35 35 div.diffblock {
36 36 overflow-x: auto;
37 37 overflow-y: hidden;
38 38 clear: both;
39 39 padding: 0px;
40 40 background: @grey6;
41 41 border: @border-thickness solid @grey5;
42 42 -webkit-border-radius: @border-radius @border-radius 0px 0px;
43 43 border-radius: @border-radius @border-radius 0px 0px;
44 44
45 45
46 46 .comments-number {
47 47 float: right;
48 48 }
49 49
50 50 // BEGIN CODE-HEADER STYLES
51 51
52 52 .code-header {
53 53 background: @grey6;
54 54 padding: 10px 0 10px 0;
55 55 height: auto;
56 56 width: 100%;
57 57
58 58 .hash {
59 59 float: left;
60 60 padding: 2px 0 0 2px;
61 61 }
62 62
63 63 .date {
64 64 float: left;
65 65 text-transform: uppercase;
66 66 padding: 4px 0px 0px 2px;
67 67 }
68 68
69 69 div {
70 70 margin-left: 4px;
71 71 }
72 72
73 73 div.compare_header {
74 74 min-height: 40px;
75 75 margin: 0;
76 76 padding: 0 @padding;
77 77
78 78 .drop-menu {
79 79 float:left;
80 80 display: block;
81 81 margin:0 0 @padding 0;
82 82 }
83 83
84 84 .compare-label {
85 85 float: left;
86 86 clear: both;
87 87 display: inline-block;
88 88 min-width: 5em;
89 89 margin: 0;
90 90 padding: @button-padding @button-padding @button-padding 0;
91 91 font-family: @text-semibold;
92 92 }
93 93
94 94 .compare-buttons {
95 95 float: left;
96 96 margin: 0;
97 97 padding: 0 0 @padding;
98 98
99 99 .btn {
100 100 margin: 0 @padding 0 0;
101 101 }
102 102 }
103 103 }
104 104
105 105 }
106 106
107 107 .parents {
108 108 float: left;
109 109 width: 100px;
110 110 font-weight: 400;
111 111 vertical-align: middle;
112 112 padding: 0px 2px 0px 2px;
113 113 background-color: @grey6;
114 114
115 115 #parent_link {
116 116 margin: 00px 2px;
117 117
118 118 &.double {
119 119 margin: 0px 2px;
120 120 }
121 121
122 122 &.disabled{
123 123 margin-right: @padding;
124 124 }
125 125 }
126 126 }
127 127
128 128 .children {
129 129 float: right;
130 130 width: 100px;
131 131 font-weight: 400;
132 132 vertical-align: middle;
133 133 text-align: right;
134 134 padding: 0px 2px 0px 2px;
135 135 background-color: @grey6;
136 136
137 137 #child_link {
138 138 margin: 0px 2px;
139 139
140 140 &.double {
141 141 margin: 0px 2px;
142 142 }
143 143
144 144 &.disabled{
145 145 margin-right: @padding;
146 146 }
147 147 }
148 148 }
149 149
150 150 .changeset_header {
151 151 height: 16px;
152 152
153 153 & > div{
154 154 margin-right: @padding;
155 155 }
156 156 }
157 157
158 158 .changeset_file {
159 159 text-align: left;
160 160 float: left;
161 161 padding: 0;
162 162
163 163 a{
164 164 display: inline-block;
165 165 margin-right: 0.5em;
166 166 }
167 167
168 168 #selected_mode{
169 169 margin-left: 0;
170 170 }
171 171 }
172 172
173 173 .diff-menu-wrapper {
174 174 float: left;
175 175 }
176 176
177 177 .diff-menu {
178 178 position: absolute;
179 179 background: none repeat scroll 0 0 #FFFFFF;
180 180 border-color: #003367 @grey3 @grey3;
181 181 border-right: 1px solid @grey3;
182 182 border-style: solid solid solid;
183 183 border-width: @border-thickness;
184 184 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
185 185 margin-top: 5px;
186 186 margin-left: 1px;
187 187 }
188 188
189 189 .diff-actions, .editor-actions {
190 190 float: left;
191 191
192 192 input{
193 193 margin: 0 0.5em 0 0;
194 194 }
195 195 }
196 196
197 197 // END CODE-HEADER STYLES
198 198
199 199 // BEGIN CODE-BODY STYLES
200 200
201 201 .code-body {
202 202 background: white;
203 203 padding: 0;
204 204 background-color: #ffffff;
205 205 position: relative;
206 206 max-width: none;
207 207 box-sizing: border-box;
208 208 // TODO: johbo: Parent has overflow: auto, this forces the child here
209 209 // to have the intended size and to scroll. Should be simplified.
210 210 width: 100%;
211 211 overflow-x: auto;
212 212 }
213 213
214 214 pre.raw {
215 215 background: white;
216 216 color: @grey1;
217 217 }
218 218 // END CODE-BODY STYLES
219 219
220 220 }
221 221
222 222
223 223 table.code-difftable {
224 224 border-collapse: collapse;
225 225 width: 99%;
226 226 border-radius: 0px !important;
227 227
228 228 td {
229 229 padding: 0 !important;
230 230 background: none !important;
231 231 border: 0 !important;
232 232 }
233 233
234 234 .context {
235 235 background: none repeat scroll 0 0 #DDE7EF;
236 236 }
237 237
238 238 .add {
239 239 background: none repeat scroll 0 0 #DDFFDD;
240 240
241 241 ins {
242 242 background: none repeat scroll 0 0 #AAFFAA;
243 243 text-decoration: none;
244 244 }
245 245 }
246 246
247 247 .del {
248 248 background: none repeat scroll 0 0 #FFDDDD;
249 249
250 250 del {
251 251 background: none repeat scroll 0 0 #FFAAAA;
252 252 text-decoration: none;
253 253 }
254 254 }
255 255
256 256 /** LINE NUMBERS **/
257 257 .lineno {
258 258 padding-left: 2px;
259 259 padding-right: 2px;
260 260 text-align: right;
261 261 width: 32px;
262 262 -moz-user-select: none;
263 263 -webkit-user-select: none;
264 264 border-right: @border-thickness solid @grey5 !important;
265 265 border-left: 0px solid #CCC !important;
266 266 border-top: 0px solid #CCC !important;
267 267 border-bottom: none !important;
268 268
269 269 a {
270 270 &:extend(pre);
271 271 text-align: right;
272 272 padding-right: 2px;
273 273 cursor: pointer;
274 274 display: block;
275 275 width: 32px;
276 276 }
277 277 }
278 278
279 279 .context {
280 280 cursor: auto;
281 281 &:extend(pre);
282 282 }
283 283
284 284 .lineno-inline {
285 285 background: none repeat scroll 0 0 #FFF !important;
286 286 padding-left: 2px;
287 287 padding-right: 2px;
288 288 text-align: right;
289 289 width: 30px;
290 290 -moz-user-select: none;
291 291 -webkit-user-select: none;
292 292 }
293 293
294 294 /** CODE **/
295 295 .code {
296 296 display: block;
297 297 width: 100%;
298 298
299 299 td {
300 300 margin: 0;
301 301 padding: 0;
302 302 }
303 303
304 304 pre {
305 305 margin: 0;
306 306 padding: 0;
307 307 margin-left: .5em;
308 308 }
309 309 }
310 310 }
311 311
312 312
313 313 // Comments
314 314
315 315 div.comment:target {
316 316 border-left: 6px solid @comment-highlight-color;
317 317 padding-left: 3px;
318 318 margin-left: -9px;
319 319 }
320 320
321 321 //TODO: anderson: can't get an absolute number out of anything, so had to put the
322 322 //current values that might change. But to make it clear I put as a calculation
323 323 @comment-max-width: 1065px;
324 324 @pr-extra-margin: 34px;
325 325 @pr-border-spacing: 4px;
326 326 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
327 327
328 328 // Pull Request
329 329 .cs_files .code-difftable {
330 330 border: @border-thickness solid @grey5; //borders only on PRs
331 331
332 332 .comment-inline-form,
333 333 div.comment {
334 334 width: @pr-comment-width;
335 335 }
336 336 }
337 337
338 338 // Changeset
339 339 .code-difftable {
340 340 .comment-inline-form,
341 341 div.comment {
342 342 width: @comment-max-width;
343 343 }
344 344 }
345 345
346 346 //Style page
347 347 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
348 348 #style-page .code-difftable{
349 349 .comment-inline-form,
350 350 div.comment {
351 351 width: @comment-max-width - @style-extra-margin;
352 352 }
353 353 }
354 354
355 355 #context-bar > h2 {
356 356 font-size: 20px;
357 357 }
358 358
359 359 #context-bar > h2> a {
360 360 font-size: 20px;
361 361 }
362 362 // end of defaults
363 363
364 364 .file_diff_buttons {
365 365 padding: 0 0 @padding;
366 366
367 367 .drop-menu {
368 368 float: left;
369 369 margin: 0 @padding 0 0;
370 370 }
371 371 .btn {
372 372 margin: 0 @padding 0 0;
373 373 }
374 374 }
375 375
376 376 .code-body.textarea.editor {
377 377 max-width: none;
378 378 padding: 15px;
379 379 }
380 380
381 381 td.injected_diff{
382 382 max-width: 1178px;
383 383 overflow-x: auto;
384 384 overflow-y: hidden;
385 385
386 386 div.diff-container,
387 387 div.diffblock{
388 388 max-width: 100%;
389 389 }
390 390
391 391 div.code-body {
392 392 max-width: 1124px;
393 393 overflow-x: auto;
394 394 padding: 0;
395 395 }
396 396 div.diffblock {
397 397 border: none;
398 398 }
399 399
400 400 &.inline-form {
401 401 width: 99%
402 402 }
403 403 }
404 404
405 405
406 406 table.code-difftable {
407 407 width: 100%;
408 408 }
409 409
410 410 /** PYGMENTS COLORING **/
411 411 div.codeblock {
412 412
413 413 // TODO: johbo: Added interim to get rid of the margin around
414 414 // Select2 widgets. This needs further cleanup.
415 415 margin-top: @padding;
416 416
417 417 overflow: auto;
418 418 padding: 0px;
419 419 border: @border-thickness solid @grey5;
420 420 background: @grey6;
421 421 .border-radius(@border-radius);
422 422
423 423 #remove_gist {
424 424 float: right;
425 425 }
426 426
427 427 .author {
428 428 clear: both;
429 429 vertical-align: middle;
430 430 font-family: @text-bold;
431 431 }
432 432
433 433 .btn-mini {
434 434 float: left;
435 435 margin: 0 5px 0 0;
436 436 }
437 437
438 438 .code-header {
439 439 padding: @padding;
440 440 border-bottom: @border-thickness solid @grey5;
441 441
442 .rc-user {
443 min-width: 0;
444 margin-right: .5em;
445 }
446
442 447 .stats {
443 448 clear: both;
444 449 margin: 0 0 @padding 0;
445 450 padding: 0;
446 451 .left {
447 452 float: left;
448 453 clear: left;
449 454 max-width: 75%;
450 455 margin: 0 0 @padding 0;
451 456
452 457 &.item {
453 458 margin-right: @padding;
454 459 &.last { border-right: none; }
455 460 }
456 461 }
457 462 .buttons { float: right; }
458 463 .author {
459 464 height: 25px; margin-left: 15px; font-weight: bold;
460 465 }
461 466 }
462 467
463 468 .commit {
464 469 margin: 5px 0 0 26px;
465 470 font-weight: normal;
466 471 white-space: pre-wrap;
467 472 }
468 473 }
469 474
470 475 .message {
471 476 position: relative;
472 477 margin: @padding;
473 478
474 479 .codeblock-label {
475 480 margin: 0 0 1em 0;
476 481 }
477 482 }
478 483
479 484 .code-body {
480 485 padding: @padding;
481 486 background-color: #ffffff;
482 487 min-width: 100%;
483 488 box-sizing: border-box;
484 489 // TODO: johbo: Parent has overflow: auto, this forces the child here
485 490 // to have the intended size and to scroll. Should be simplified.
486 491 width: 100%;
487 492 overflow-x: auto;
488 493 }
489 494 }
490 495
491 496 .code-highlighttable,
492 497 div.codeblock .code-body table {
493 498 width: 0 !important;
494 499 border: 0px !important;
495 500 margin: 0;
496 501 letter-spacing: normal;
497 502
498 503
499 504 td {
500 505 border: 0px !important;
501 506 vertical-align: top;
502 507 }
503 508 }
504 509
505 510 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
506 511 div.search-code-body {
507 512 background-color: #ffffff; padding: 5px 0 5px 10px;
508 513 pre {
509 514 .match { background-color: #faffa6;}
510 515 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
511 516 }
512 517 }
513 518
514 519 div.annotatediv { margin-left: 2px; margin-right: 4px; }
515 520 .code-highlight {
516 521 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
517 522 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
518 523 pre div:target {background-color: @comment-highlight-color !important;}
519 524 }
520 525
521 526 .linenos a { text-decoration: none; }
522 527
523 528 .CodeMirror-selected { background: @rchighlightblue; }
524 529 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
525 530 .CodeMirror ::selection { background: @rchighlightblue; }
526 531 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
527 532
528 533 .code { display: block; border:0px !important; }
529 534 .code-highlight,
530 535 .codehilite {
531 536 .hll { background-color: #ffffcc }
532 537 .c { color: #408080; font-style: italic } /* Comment */
533 538 .err, .codehilite .err { border: @border-thickness solid #FF0000 } /* Error */
534 539 .k { color: #008000; font-weight: bold } /* Keyword */
535 540 .o { color: #666666 } /* Operator */
536 541 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
537 542 .cp { color: #BC7A00 } /* Comment.Preproc */
538 543 .c1 { color: #408080; font-style: italic } /* Comment.Single */
539 544 .cs { color: #408080; font-style: italic } /* Comment.Special */
540 545 .gd { color: #A00000 } /* Generic.Deleted */
541 546 .ge { font-style: italic } /* Generic.Emph */
542 547 .gr { color: #FF0000 } /* Generic.Error */
543 548 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
544 549 .gi { color: #00A000 } /* Generic.Inserted */
545 550 .go { color: #808080 } /* Generic.Output */
546 551 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
547 552 .gs { font-weight: bold } /* Generic.Strong */
548 553 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
549 554 .gt { color: #0040D0 } /* Generic.Traceback */
550 555 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
551 556 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
552 557 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
553 558 .kp { color: #008000 } /* Keyword.Pseudo */
554 559 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
555 560 .kt { color: #B00040 } /* Keyword.Type */
556 561 .m { color: #666666 } /* Literal.Number */
557 562 .s { color: #BA2121 } /* Literal.String */
558 563 .na { color: #7D9029 } /* Name.Attribute */
559 564 .nb { color: #008000 } /* Name.Builtin */
560 565 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
561 566 .no { color: #880000 } /* Name.Constant */
562 567 .nd { color: #AA22FF } /* Name.Decorator */
563 568 .ni { color: #999999; font-weight: bold } /* Name.Entity */
564 569 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
565 570 .nf { color: #0000FF } /* Name.Function */
566 571 .nl { color: #A0A000 } /* Name.Label */
567 572 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
568 573 .nt { color: #008000; font-weight: bold } /* Name.Tag */
569 574 .nv { color: #19177C } /* Name.Variable */
570 575 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
571 576 .w { color: #bbbbbb } /* Text.Whitespace */
572 577 .mf { color: #666666 } /* Literal.Number.Float */
573 578 .mh { color: #666666 } /* Literal.Number.Hex */
574 579 .mi { color: #666666 } /* Literal.Number.Integer */
575 580 .mo { color: #666666 } /* Literal.Number.Oct */
576 581 .sb { color: #BA2121 } /* Literal.String.Backtick */
577 582 .sc { color: #BA2121 } /* Literal.String.Char */
578 583 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
579 584 .s2 { color: #BA2121 } /* Literal.String.Double */
580 585 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
581 586 .sh { color: #BA2121 } /* Literal.String.Heredoc */
582 587 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
583 588 .sx { color: #008000 } /* Literal.String.Other */
584 589 .sr { color: #BB6688 } /* Literal.String.Regex */
585 590 .s1 { color: #BA2121 } /* Literal.String.Single */
586 591 .ss { color: #19177C } /* Literal.String.Symbol */
587 592 .bp { color: #008000 } /* Name.Builtin.Pseudo */
588 593 .vc { color: #19177C } /* Name.Variable.Class */
589 594 .vg { color: #19177C } /* Name.Variable.Global */
590 595 .vi { color: #19177C } /* Name.Variable.Instance */
591 596 .il { color: #666666 } /* Literal.Number.Integer.Long */
592 597 }
593 598
594 599 /* customized pre blocks for markdown/rst */
595 600 pre.literal-block, .codehilite pre{
596 601 padding: @padding;
597 602 border: 1px solid @grey6;
598 603 .border-radius(@border-radius);
599 604 background-color: @grey7;
600 605 }
@@ -1,356 +1,361 b''
1 1 // comments.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5
6 6 // Comments
7 7 .comments {
8 8 width: 100%;
9 9 }
10 10
11 11 tr.inline-comments div {
12 12 max-width: 100%;
13 13
14 14 p {
15 15 white-space: normal;
16 16 }
17 17
18 18 code, pre, .code, dd {
19 19 overflow-x: auto;
20 20 width: 1062px;
21 21 }
22 22
23 23 dd {
24 24 width: auto;
25 25 }
26 26 }
27 27
28 28 #injected_page_comments {
29 29 .comment-previous-link,
30 30 .comment-next-link,
31 31 .comment-links-divider {
32 32 display: none;
33 33 }
34 34 }
35 35
36 36 .add-comment {
37 37 margin-bottom: 10px;
38 38 }
39 39 .hide-comment-button .add-comment {
40 40 display: none;
41 41 }
42 42
43 43 .comment-bubble {
44 44 color: @grey4;
45 45 margin-top: 4px;
46 46 margin-right: 30px;
47 47 visibility: hidden;
48 48 }
49 49
50 50 .comment {
51 51 margin: @padding 0;
52 52 padding: 4px 0 0 0;
53 53 line-height: 1em;
54 54
55 .rc-user {
56 min-width: 0;
57 margin: -2px .5em 0 0;
58 }
59
55 60 .meta {
56 61 position: relative;
57 62 width: 100%;
58 63 margin: 0 0 .5em 0;
59 64
60 65 &:hover .permalink {
61 66 visibility: visible;
62 67 color: @rcblue;
63 68 }
64 69 }
65 70
66 71 .author,
67 72 .date {
68 73 display: inline;
69 74 margin: 0 .5 0 0;
70 75 padding: 0 .5 0 0;
71 76
72 77 &:after {
73 78 content: ' | ';
74 79 color: @grey5;
75 80 }
76 81 }
77 82
78 83 .status-change,
79 84 .permalink,
80 85 .changeset-status-lbl {
81 86 display: inline;
82 87 }
83 88
84 89 .permalink {
85 90 visibility: hidden;
86 91 }
87 92
88 93 .comment-links-divider {
89 94 display: inline;
90 95 }
91 96
92 97 .comment-links-block {
93 98 float:right;
94 99 text-align: right;
95 100 min-width: 85px;
96 101
97 102 [class^="icon-"]:before,
98 103 [class*=" icon-"]:before {
99 104 margin-left: 0;
100 105 margin-right: 0;
101 106 }
102 107 }
103 108
104 109 .comment-previous-link {
105 110 display: inline-block;
106 111
107 112 .arrow_comment_link{
108 113 cursor: pointer;
109 114 i {
110 115 font-size:10px;
111 116 }
112 117 }
113 118 .arrow_comment_link.disabled {
114 119 cursor: default;
115 120 color: @grey5;
116 121 }
117 122 }
118 123
119 124 .comment-next-link {
120 125 display: inline-block;
121 126
122 127 .arrow_comment_link{
123 128 cursor: pointer;
124 129 i {
125 130 font-size:10px;
126 131 }
127 132 }
128 133 .arrow_comment_link.disabled {
129 134 cursor: default;
130 135 color: @grey5;
131 136 }
132 137 }
133 138
134 139 .flag_status {
135 140 display: inline-block;
136 141 margin: -2px .5em 0 .25em
137 142 }
138 143
139 144 .delete-comment {
140 145 display: inline-block;
141 146 color: @rcblue;
142 147
143 148 &:hover {
144 149 cursor: pointer;
145 150 }
146 151 }
147 152
148 153
149 154 .text {
150 155 clear: both;
151 156 border: @border-thickness solid @grey5;
152 157 .border-radius(@border-radius);
153 158 .box-sizing(border-box);
154 159
155 160 .markdown-block p,
156 161 .rst-block p {
157 162 margin: .5em 0 !important;
158 163 // TODO: lisa: This is needed because of other rst !important rules :[
159 164 }
160 165 }
161 166 }
162 167
163 168 .show-outdated-comments {
164 169 display: inline;
165 170 color: @rcblue;
166 171 }
167 172
168 173 .outdated {
169 174 display: none;
170 175 opacity: 0.6;
171 176
172 177 .comment {
173 178 margin: 0 0 @padding;
174 179
175 180 .date:after {
176 181 content: none;
177 182 }
178 183 }
179 184 .outdated_comment_block {
180 185 padding: 0 0 @space 0;
181 186 }
182 187 }
183 188
184 189 // Comment Form
185 190 div.comment-form {
186 191 margin-top: 20px;
187 192 }
188 193
189 194 .comment-form strong {
190 195 display: block;
191 196 margin-bottom: 15px;
192 197 }
193 198
194 199 .comment-form textarea {
195 200 width: 100%;
196 201 height: 100px;
197 202 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
198 203 }
199 204
200 205 form.comment-form {
201 206 margin-top: 10px;
202 207 margin-left: 10px;
203 208 }
204 209
205 210 .comment-inline-form .comment-block-ta,
206 211 .comment-form .comment-block-ta,
207 212 .comment-form .preview-box {
208 213 border: @border-thickness solid @grey5;
209 214 .border-radius(@border-radius);
210 215 .box-sizing(border-box);
211 216 background-color: white;
212 217 }
213 218
214 219 .comment-form-submit {
215 220 margin-top: 5px;
216 221 margin-left: 525px;
217 222 }
218 223
219 224 .file-comments {
220 225 display: none;
221 226 }
222 227
223 228 .comment-form .preview-box.unloaded,
224 229 .comment-inline-form .preview-box.unloaded {
225 230 height: 50px;
226 231 text-align: center;
227 232 padding: 20px;
228 233 background-color: white;
229 234 }
230 235
231 236 .comment-footer {
232 237 position: relative;
233 238 width: 100%;
234 239 min-height: 42px;
235 240
236 241 .status_box,
237 242 .cancel-button {
238 243 float: left;
239 244 display: inline-block;
240 245 }
241 246
242 247 .action-buttons {
243 248 float: right;
244 249 display: inline-block;
245 250 }
246 251 }
247 252
248 253 .comment-form {
249 254
250 255 .comment {
251 256 margin-left: 10px;
252 257 }
253 258
254 259 .comment-help {
255 260 color: @grey4;
256 261 padding: 5px 0 5px 0;
257 262 }
258 263
259 264 .comment-title {
260 265 padding: 5px 0 5px 0;
261 266 }
262 267
263 268 .comment-button {
264 269 display: inline-block;
265 270 }
266 271
267 272 .comment-button .comment-button-input {
268 273 margin-right: 0;
269 274 }
270 275
271 276 .comment-footer {
272 277 margin-bottom: 110px;
273 278 }
274 279 }
275 280
276 281
277 282 .comment-form-login {
278 283 .comment-help {
279 284 padding: 0.9em; //same as the button
280 285 }
281 286
282 287 div.clearfix {
283 288 clear: both;
284 289 width: 100%;
285 290 display: block;
286 291 }
287 292 }
288 293
289 294 .preview-box {
290 295 padding: 10px;
291 296 min-height: 100px;
292 297 margin-bottom: 15px;
293 298 background-color: white;
294 299 border: @border-thickness solid #ccc;
295 300 .border-radius(@border-radius);
296 301 .box-sizing(border-box);
297 302 }
298 303
299 304 .add-another-button {
300 305 margin-left: 10px;
301 306 margin-top: 10px;
302 307 margin-bottom: 10px;
303 308 }
304 309
305 310 .comment .buttons {
306 311 float: right;
307 312 margin: -1px 0px 0px 0px;
308 313 }
309 314
310 315 // Inline Comment Form
311 316 .injected_diff .comment-inline-form,
312 317 .comment-inline-form {
313 318 background-color: @grey6;
314 319 margin-top: 10px;
315 320 margin-bottom: 20px;
316 321 }
317 322
318 323 .inline-form {
319 324 padding: 10px 7px;
320 325 }
321 326
322 327 .inline-form div {
323 328 max-width: 100%;
324 329 }
325 330
326 331 .overlay {
327 332 display: none;
328 333 position: absolute;
329 334 width: 100%;
330 335 text-align: center;
331 336 vertical-align: middle;
332 337 font-size: 16px;
333 338 background: none repeat scroll 0 0 white;
334 339
335 340 &.submitting {
336 341 display: block;
337 342 opacity: 0.5;
338 343 z-index: 100;
339 344 }
340 345 }
341 346 .comment-inline-form .overlay.submitting .overlay-text {
342 347 margin-top: 5%;
343 348 }
344 349
345 350 .comment-inline-form .clearfix,
346 351 .comment-form .clearfix {
347 352 .border-radius(@border-radius);
348 353 margin: 0px;
349 354 }
350 355
351 356 .hide-inline-form-button {
352 357 margin-left: 5px;
353 358 }
354 359 .comment-button .hide-inline-form {
355 360 background: white;
356 361 }
@@ -1,2076 +1,2087 b''
1 1 //Primary CSS
2 2
3 3 //--- IMPORTS ------------------//
4 4
5 5 @import 'helpers';
6 6 @import 'mixins';
7 7 @import 'rcicons';
8 8 @import 'fonts';
9 9 @import 'variables';
10 10 @import 'bootstrap-variables';
11 11 @import 'form-bootstrap';
12 12 @import 'codemirror';
13 13 @import 'legacy_code_styles';
14 14 @import 'progress-bar';
15 15
16 16 @import 'type';
17 17 @import 'alerts';
18 18 @import 'buttons';
19 19 @import 'tags';
20 20 @import 'code-block';
21 21 @import 'examples';
22 22 @import 'login';
23 23 @import 'main-content';
24 24 @import 'select2';
25 25 @import 'comments';
26 26 @import 'panels-bootstrap';
27 27 @import 'panels';
28 28
29 29
30 30 //--- BASE ------------------//
31 31 .noscript-error {
32 32 top: 0;
33 33 left: 0;
34 34 width: 100%;
35 35 z-index: 101;
36 36 text-align: center;
37 37 font-family: @text-semibold;
38 38 font-size: 120%;
39 39 color: white;
40 40 background-color: @alert2;
41 41 padding: 5px 0 5px 0;
42 42 }
43 43
44 44 html {
45 45 display: table;
46 46 height: 100%;
47 47 width: 100%;
48 48 }
49 49
50 50 body {
51 51 display: table-cell;
52 52 width: 100%;
53 53 }
54 54
55 55 //--- LAYOUT ------------------//
56 56
57 57 .hidden{
58 58 display: none !important;
59 59 }
60 60
61 61 .box{
62 62 float: left;
63 63 width: 100%;
64 64 }
65 65
66 66 .browser-header {
67 67 clear: both;
68 68 }
69 69 .main {
70 70 clear: both;
71 71 padding:0 0 @pagepadding;
72 72 height: auto;
73 73
74 74 &:after { //clearfix
75 75 content:"";
76 76 clear:both;
77 77 width:100%;
78 78 display:block;
79 79 }
80 80 }
81 81
82 82 .action-link{
83 83 margin-left: @padding;
84 84 padding-left: @padding;
85 85 border-left: @border-thickness solid @border-default-color;
86 86 }
87 87
88 88 input + .action-link, .action-link.first{
89 89 border-left: none;
90 90 }
91 91
92 92 .action-link.last{
93 93 margin-right: @padding;
94 94 padding-right: @padding;
95 95 }
96 96
97 97 .action-link.active,
98 98 .action-link.active a{
99 99 color: @grey4;
100 100 }
101 101
102 102 ul.simple-list{
103 103 list-style: none;
104 104 margin: 0;
105 105 padding: 0;
106 106 }
107 107
108 108 .main-content {
109 109 padding-bottom: @pagepadding;
110 110 }
111 111
112 112 .wrapper {
113 113 position: relative;
114 114 max-width: @wrapper-maxwidth;
115 115 margin: 0 auto;
116 116 }
117 117
118 118 #content {
119 119 clear: both;
120 120 padding: 0 @contentpadding;
121 121 }
122 122
123 123 .advanced-settings-fields{
124 124 input{
125 125 margin-left: @textmargin;
126 126 margin-right: @padding/2;
127 127 }
128 128 }
129 129
130 130 .cs_files_title {
131 131 margin: @pagepadding 0 0;
132 132 }
133 133
134 134 input.inline[type="file"] {
135 135 display: inline;
136 136 }
137 137
138 138 .error_page {
139 139 margin: 10% auto;
140 140
141 141 h1 {
142 142 color: @grey2;
143 143 }
144 144
145 145 .error-branding {
146 146 font-family: @text-semibold;
147 147 color: @grey4;
148 148 }
149 149
150 150 .error_message {
151 151 font-family: @text-regular;
152 152 }
153 153
154 154 .sidebar {
155 155 min-height: 275px;
156 156 margin: 0;
157 157 padding: 0 0 @sidebarpadding @sidebarpadding;
158 158 border: none;
159 159 }
160 160
161 161 .main-content {
162 162 position: relative;
163 163 margin: 0 @sidebarpadding @sidebarpadding;
164 164 padding: 0 0 0 @sidebarpadding;
165 165 border-left: @border-thickness solid @grey5;
166 166
167 167 @media (max-width:767px) {
168 168 clear: both;
169 169 width: 100%;
170 170 margin: 0;
171 171 border: none;
172 172 }
173 173 }
174 174
175 175 .inner-column {
176 176 float: left;
177 177 width: 29.75%;
178 178 min-height: 150px;
179 179 margin: @sidebarpadding 2% 0 0;
180 180 padding: 0 2% 0 0;
181 181 border-right: @border-thickness solid @grey5;
182 182
183 183 @media (max-width:767px) {
184 184 clear: both;
185 185 width: 100%;
186 186 border: none;
187 187 }
188 188
189 189 ul {
190 190 padding-left: 1.25em;
191 191 }
192 192
193 193 &:last-child {
194 194 margin: @sidebarpadding 0 0;
195 195 border: none;
196 196 }
197 197
198 198 h4 {
199 199 margin: 0 0 @padding;
200 200 font-family: @text-semibold;
201 201 }
202 202 }
203 203 }
204 204 .error-page-logo {
205 205 width: 130px;
206 206 height: 160px;
207 207 }
208 208
209 209 // HEADER
210 210 .header {
211 211
212 212 // TODO: johbo: Fix login pages, so that they work without a min-height
213 213 // for the header and then remove the min-height. I chose a smaller value
214 214 // intentionally here to avoid rendering issues in the main navigation.
215 215 min-height: 49px;
216 216
217 217 position: relative;
218 218 vertical-align: bottom;
219 219 padding: 0 @header-padding;
220 220 background-color: @grey2;
221 221 color: @grey5;
222 222
223 223 .title {
224 224 overflow: visible;
225 225 }
226 226
227 227 &:before,
228 228 &:after {
229 229 content: "";
230 230 clear: both;
231 231 width: 100%;
232 232 }
233 233
234 234 // TODO: johbo: Avoids breaking "Repositories" chooser
235 235 .select2-container .select2-choice .select2-arrow {
236 236 display: none;
237 237 }
238 238 }
239 239
240 240 #header-inner {
241 241 &.title {
242 242 margin: 0;
243 243 }
244 244 &:before,
245 245 &:after {
246 246 content: "";
247 247 clear: both;
248 248 }
249 249 }
250 250
251 251 // Gists
252 252 #files_data {
253 253 clear: both; //for firefox
254 254 }
255 255 #gistid {
256 256 margin-right: @padding;
257 257 }
258 258
259 259 // Global Settings Editor
260 260 .textarea.editor {
261 261 float: left;
262 262 position: relative;
263 263 max-width: @texteditor-width;
264 264
265 265 select {
266 266 position: absolute;
267 267 top:10px;
268 268 right:0;
269 269 }
270 270
271 271 .CodeMirror {
272 272 margin: 0;
273 273 }
274 274
275 275 .help-block {
276 276 margin: 0 0 @padding;
277 277 padding:.5em;
278 278 background-color: @grey6;
279 279 }
280 280 }
281 281
282 282 ul.auth_plugins {
283 283 margin: @padding 0 @padding @legend-width;
284 284 padding: 0;
285 285
286 286 li {
287 287 margin-bottom: @padding;
288 288 line-height: 1em;
289 289 list-style-type: none;
290 290
291 291 .auth_buttons .btn {
292 292 margin-right: @padding;
293 293 }
294 294
295 295 &:before { content: none; }
296 296 }
297 297 }
298 298
299 299 // Pull Requests
300 300
301 301 .pullrequestlist {
302 302 max-width: @pullrequest-width;
303 303 margin-bottom: @space;
304 304
305 305 // Tweaks for "My Account" / "Pull requests"
306 306 .prwrapper {
307 307 clear: left;
308 308
309 309 .pr {
310 310 margin: 0;
311 311 padding: 0;
312 312 border-bottom: none;
313 313 }
314 314
315 315 // TODO: johbo: Replace with something that makes up an inline form or
316 316 // similar.
317 317 .repolist_actions {
318 318 display: inline-block;
319 319 }
320 320 }
321 321
322 322 }
323 323
324 324 .pullrequests_section_head {
325 325 display: block;
326 326 clear: both;
327 327 margin: @padding 0;
328 328 font-family: @text-bold;
329 329 }
330 330
331 331 .pr-origininfo, .pr-targetinfo {
332 332 position: relative;
333 333
334 334 .tag {
335 335 display: inline-block;
336 336 margin: 0 1em .5em 0;
337 337 }
338 338
339 339 .clone-url {
340 340 display: inline-block;
341 341 margin: 0 0 .5em 0;
342 342 padding: 0;
343 343 line-height: 1.2em;
344 344 }
345 345 }
346 346
347 347 .pr-pullinfo {
348 348 clear: both;
349 349 margin: .5em 0;
350 350 }
351 351
352 352 #pr-title-input {
353 353 width: 72%;
354 354 font-size: 1em;
355 355 font-family: @text-bold;
356 356 margin: 0;
357 357 padding: 0 0 0 @padding/4;
358 358 line-height: 1.7em;
359 359 color: @text-color;
360 360 letter-spacing: .02em;
361 361 }
362 362
363 363 #pullrequest_title {
364 364 width: 100%;
365 365 box-sizing: border-box;
366 366 }
367 367
368 368 #pr_open_message {
369 369 border: @border-thickness solid #fff;
370 370 border-radius: @border-radius;
371 371 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
372 372 text-align: right;
373 373 overflow: hidden;
374 374 }
375 375
376 376 .pr-submit-button {
377 377 float: right;
378 378 margin: 0 0 0 5px;
379 379 }
380 380
381 381 .pr-spacing-container {
382 382 padding: 20px;
383 383 clear: both
384 384 }
385 385
386 386 #pr-description-input {
387 387 margin-bottom: 0;
388 388 }
389 389
390 390 .pr-description-label {
391 391 vertical-align: top;
392 392 }
393 393
394 394 .perms_section_head {
395 395 min-width: 625px;
396 396
397 397 h2 {
398 398 margin-bottom: 0;
399 399 }
400 400
401 401 .label-checkbox {
402 402 float: left;
403 403 }
404 404
405 405 &.field {
406 406 margin: @space 0 @padding;
407 407 }
408 408
409 409 &:first-child.field {
410 410 margin-top: 0;
411 411
412 412 .label {
413 413 margin-top: 0;
414 414 padding-top: 0;
415 415 }
416 416
417 417 .radios {
418 418 padding-top: 0;
419 419 }
420 420 }
421 421
422 422 .radios {
423 423 float: right;
424 424 position: relative;
425 425 width: 405px;
426 426 }
427 427 }
428 428
429 429 //--- MODULES ------------------//
430 430
431 431
432 432 // Fixed Sidebar Column
433 433 .sidebar-col-wrapper {
434 434 padding-left: @sidebar-all-width;
435 435
436 436 .sidebar {
437 437 width: @sidebar-width;
438 438 margin-left: -@sidebar-all-width;
439 439 }
440 440 }
441 441
442 442 .sidebar-col-wrapper.scw-small {
443 443 padding-left: @sidebar-small-all-width;
444 444
445 445 .sidebar {
446 446 width: @sidebar-small-width;
447 447 margin-left: -@sidebar-small-all-width;
448 448 }
449 449 }
450 450
451 451
452 452 // FOOTER
453 453 #footer {
454 454 padding: 0;
455 455 text-align: center;
456 456 vertical-align: middle;
457 457 color: @grey2;
458 458 background-color: @grey6;
459 459
460 460 p {
461 461 margin: 0;
462 462 padding: 1em;
463 463 line-height: 1em;
464 464 }
465 465
466 466 .server-instance { //server instance
467 467 display: none;
468 468 }
469 469
470 470 .title {
471 471 float: none;
472 472 margin: 0 auto;
473 473 }
474 474 }
475 475
476 476 button.close {
477 477 padding: 0;
478 478 cursor: pointer;
479 479 background: transparent;
480 480 border: 0;
481 481 .box-shadow(none);
482 482 -webkit-appearance: none;
483 483 }
484 484
485 485 .close {
486 486 float: right;
487 487 font-size: 21px;
488 488 font-family: @text-bootstrap;
489 489 line-height: 1em;
490 490 font-weight: bold;
491 491 color: @grey2;
492 492
493 493 &:hover,
494 494 &:focus {
495 495 color: @grey1;
496 496 text-decoration: none;
497 497 cursor: pointer;
498 498 }
499 499 }
500 500
501 501 // GRID
502 502 .sorting,
503 503 .sorting_desc,
504 504 .sorting_asc {
505 505 cursor: pointer;
506 506 }
507 507 .sorting_desc:after {
508 508 content: "\00A0\25B2";
509 509 font-size: .75em;
510 510 }
511 511 .sorting_asc:after {
512 512 content: "\00A0\25BC";
513 513 font-size: .68em;
514 514 }
515 515
516 516
517 517 .user_auth_tokens {
518 518
519 519 &.truncate {
520 520 white-space: nowrap;
521 521 overflow: hidden;
522 522 text-overflow: ellipsis;
523 523 }
524 524
525 525 .fields .field .input {
526 526 margin: 0;
527 527 }
528 528
529 529 input#description {
530 530 width: 100px;
531 531 margin: 0;
532 532 }
533 533
534 534 .drop-menu {
535 535 // TODO: johbo: Remove this, should work out of the box when
536 536 // having multiple inputs inline
537 537 margin: 0 0 0 5px;
538 538 }
539 539 }
540 540 #user_list_table {
541 541 .closed {
542 542 background-color: @grey6;
543 543 }
544 544 }
545 545
546 546
547 547 input {
548 548 &.disabled {
549 549 opacity: .5;
550 550 }
551 551 }
552 552
553 553 // remove extra padding in firefox
554 554 input::-moz-focus-inner { border:0; padding:0 }
555 555
556 556 .adjacent input {
557 557 margin-bottom: @padding;
558 558 }
559 559
560 560 .permissions_boxes {
561 561 display: block;
562 562 }
563 563
564 564 //TODO: lisa: this should be in tables
565 565 .show_more_col {
566 566 width: 20px;
567 567 }
568 568
569 569 //FORMS
570 570
571 571 .medium-inline,
572 572 input#description.medium-inline {
573 573 display: inline;
574 574 width: @medium-inline-input-width;
575 575 min-width: 100px;
576 576 }
577 577
578 578 select {
579 579 //reset
580 580 -webkit-appearance: none;
581 581 -moz-appearance: none;
582 582
583 583 display: inline-block;
584 584 height: 28px;
585 585 width: auto;
586 586 margin: 0 @padding @padding 0;
587 587 padding: 0 18px 0 8px;
588 588 line-height:1em;
589 589 font-size: @basefontsize;
590 590 border: @border-thickness solid @rcblue;
591 591 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
592 592 color: @rcblue;
593 593
594 594 &:after {
595 595 content: "\00A0\25BE";
596 596 }
597 597
598 598 &:focus {
599 599 outline: none;
600 600 }
601 601 }
602 602
603 603 option {
604 604 &:focus {
605 605 outline: none;
606 606 }
607 607 }
608 608
609 609 input,
610 610 textarea {
611 611 padding: @input-padding;
612 612 border: @input-border-thickness solid @border-highlight-color;
613 613 .border-radius (@border-radius);
614 614 font-family: @text-light;
615 615 font-size: @basefontsize;
616 616
617 617 &.input-sm {
618 618 padding: 5px;
619 619 }
620 620
621 621 &#description {
622 622 min-width: @input-description-minwidth;
623 623 min-height: 1em;
624 624 padding: 10px;
625 625 }
626 626 }
627 627
628 628 .field-sm {
629 629 input,
630 630 textarea {
631 631 padding: 5px;
632 632 }
633 633 }
634 634
635 635 textarea {
636 636 display: block;
637 637 clear: both;
638 638 width: 100%;
639 639 min-height: 100px;
640 640 margin-bottom: @padding;
641 641 .box-sizing(border-box);
642 642 overflow: auto;
643 643 }
644 644
645 645 label {
646 646 font-family: @text-light;
647 647 }
648 648
649 649 // GRAVATARS
650 650 // centers gravatar on username to the right
651 651
652 652 .gravatar {
653 653 display: inline;
654 654 min-width: 16px;
655 655 min-height: 16px;
656 656 margin: -5px 0;
657 657 padding: 0;
658 658 line-height: 1em;
659 659 border: 1px solid @grey4;
660 660
661 661 &.gravatar-large {
662 662 margin: -0.5em .25em -0.5em 0;
663 663 }
664 664
665 665 & + .user {
666 666 display: inline;
667 667 margin: 0;
668 668 padding: 0 0 0 .17em;
669 669 line-height: 1em;
670 670 }
671 671 }
672 672
673 673 .rc-user { // gravatar + user wrapper
674 float: left;
674 675 position: relative;
675 676 min-width: 100px;
676 677 max-width: 200px;
677 678 min-height: (@gravatar-size + @border-thickness * 2); // account for border
678 679 display: block;
679 680 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
680 681
681 682
682 683 .gravatar {
683 684 display: block;
684 685 position: absolute;
685 686 top: 0;
686 687 left: 0;
687 688 min-width: @gravatar-size;
688 689 min-height: @gravatar-size;
689 690 margin: 0;
690 691 }
691 692
692 693 .user {
693 694 display: block;
694 695 max-width: 175px;
695 696 padding-top: 2px;
696 697 overflow: hidden;
697 698 text-overflow: ellipsis;
698 699 }
699 700 }
700 701
701 702 .gist-gravatar,
702 703 .journal_container {
703 704 .gravatar-large {
704 705 margin: 0 .5em -10px 0;
705 706 }
706 707 }
707 708
708 709
709 710 // ADMIN SETTINGS
710 711
711 712 // Tag Patterns
712 713 .tag_patterns {
713 714 .tag_input {
714 715 margin-bottom: @padding;
715 716 }
716 717 }
717 718
718 719 .locked_input {
719 720 position: relative;
720 721
721 722 input {
722 723 display: inline;
723 724 margin-top: 3px;
724 725 }
725 726
726 727 br {
727 728 display: none;
728 729 }
729 730
730 731 .error-message {
731 732 float: left;
732 733 width: 100%;
733 734 }
734 735
735 736 .lock_input_button {
736 737 display: inline;
737 738 }
738 739
739 740 .help-block {
740 741 clear: both;
741 742 }
742 743 }
743 744
744 745 // Notifications
745 746
746 747 .notifications_buttons {
747 748 margin: 0 0 @space 0;
748 749 padding: 0;
749 750
750 751 .btn {
751 752 display: inline-block;
752 753 }
753 754 }
754 755
755 756 .notification-list {
756 757
757 758 div {
758 759 display: inline-block;
759 760 vertical-align: middle;
760 761 }
761 762
762 763 .container {
763 764 display: block;
764 765 margin: 0 0 @padding 0;
765 766 }
766 767
767 768 .delete-notifications {
768 769 margin-left: @padding;
769 770 text-align: right;
770 771 cursor: pointer;
771 772 }
772 773
773 774 .read-notifications {
774 775 margin-left: @padding/2;
775 776 text-align: right;
776 777 width: 35px;
777 778 cursor: pointer;
778 779 }
779 780
780 781 .icon-minus-sign {
781 782 color: @alert2;
782 783 }
783 784
784 785 .icon-ok-sign {
785 786 color: @alert1;
786 787 }
787 788 }
788 789
789 790 .user_settings {
790 791 float: left;
791 792 clear: both;
792 793 display: block;
793 794 width: 100%;
794 795
795 796 .gravatar_box {
796 797 margin-bottom: @padding;
797 798
798 799 &:after {
799 800 content: " ";
800 801 clear: both;
801 802 width: 100%;
802 803 }
803 804 }
804 805
805 806 .fields .field {
806 807 clear: both;
807 808 }
808 809 }
809 810
810 811 .advanced_settings {
811 812 margin-bottom: @space;
812 813
813 814 .help-block {
814 815 margin-left: 0;
815 816 }
816 817
817 818 button + .help-block {
818 819 margin-top: @padding;
819 820 }
820 821 }
821 822
822 823 // admin settings radio buttons and labels
823 824 .label-2 {
824 825 float: left;
825 826 width: @label2-width;
826 827
827 828 label {
828 829 color: @grey1;
829 830 }
830 831 }
831 832 .checkboxes {
832 833 float: left;
833 834 width: @checkboxes-width;
834 835 margin-bottom: @padding;
835 836
836 837 .checkbox {
837 838 width: 100%;
838 839
839 840 label {
840 841 margin: 0;
841 842 padding: 0;
842 843 }
843 844 }
844 845
845 846 .checkbox + .checkbox {
846 847 display: inline-block;
847 848 }
848 849
849 850 label {
850 851 margin-right: 1em;
851 852 }
852 853 }
853 854
854 855 // CHANGELOG
855 856 .container_header {
856 857 float: left;
857 858 display: block;
858 859 width: 100%;
859 860 margin: @padding 0 @padding;
860 861
861 862 #filter_changelog {
862 863 float: left;
863 864 margin-right: @padding;
864 865 }
865 866
866 867 .breadcrumbs_light {
867 868 display: inline-block;
868 869 }
869 870 }
870 871
871 872 .info_box {
872 873 float: right;
873 874 }
874 875
875 876
876 877 #graph_nodes {
877 878 padding-top: 43px;
878 879 }
879 880
880 881 #graph_content{
881 882
882 883 // adjust for table headers so that graph renders properly
883 884 // #graph_nodes padding - table cell padding
884 885 padding-top: (@space - (@basefontsize * 2.4));
885 886
886 887 &.graph_full_width {
887 888 width: 100%;
888 889 max-width: 100%;
889 890 }
890 891 }
891 892
892 893 #graph {
893 894 .flag_status {
894 895 margin: 0;
895 896 }
896 897
897 898 .pagination-left {
898 899 float: left;
899 900 clear: both;
900 901 }
901 902
902 903 .log-container {
903 904 max-width: 345px;
904 905
905 906 .message{
906 907 max-width: 340px;
907 908 }
908 909 }
909 910
910 911 .graph-col-wrapper {
911 912 padding-left: 110px;
912 913
913 914 #graph_nodes {
914 915 width: 100px;
915 916 margin-left: -110px;
916 917 float: left;
917 918 clear: left;
918 919 }
919 920 }
920 921 }
921 922
922 923 #filter_changelog {
923 924 float: left;
924 925 }
925 926
926 927
927 928 //--- THEME ------------------//
928 929
929 930 #logo {
930 931 float: left;
931 932 margin: 9px 0 0 0;
932 933
933 934 .header {
934 935 background-color: transparent;
935 936 }
936 937
937 938 a {
938 939 display: inline-block;
939 940 }
940 941
941 942 img {
942 943 height:30px;
943 944 }
944 945 }
945 946
946 947 .logo-wrapper {
947 948 float:left;
948 949 }
949 950
950 951 .branding{
951 952 float: left;
952 953 padding: 9px 2px;
953 954 line-height: 1em;
954 955 font-size: @navigation-fontsize;
955 956 }
956 957
957 958 img {
958 959 border: none;
959 960 outline: none;
960 961 }
961 962 user-profile-header
962 963 label {
963 964
964 965 input[type="checkbox"] {
965 966 margin-right: 1em;
966 967 }
967 968 input[type="radio"] {
968 969 margin-right: 1em;
969 970 }
970 971 }
971 972
972 973 .flag_status {
973 974 margin: 2px 8px 6px 2px;
974 975 &.under_review {
975 976 .circle(5px, @alert3);
976 977 }
977 978 &.approved {
978 979 .circle(5px, @alert1);
979 980 }
980 981 &.rejected,
981 982 &.forced_closed{
982 983 .circle(5px, @alert2);
983 984 }
984 985 &.not_reviewed {
985 986 .circle(5px, @grey5);
986 987 }
987 988 }
988 989
989 990 .flag_status_comment_box {
990 991 margin: 5px 6px 0px 2px;
991 992 }
992 993 .test_pattern_preview {
993 994 margin: @space 0;
994 995
995 996 p {
996 997 margin-bottom: 0;
997 998 border-bottom: @border-thickness solid @border-default-color;
998 999 color: @grey3;
999 1000 }
1000 1001
1001 1002 .btn {
1002 1003 margin-bottom: @padding;
1003 1004 }
1004 1005 }
1005 1006 #test_pattern_result {
1006 1007 display: none;
1007 1008 &:extend(pre);
1008 1009 padding: .9em;
1009 1010 color: @grey3;
1010 1011 background-color: @grey7;
1011 1012 border-right: @border-thickness solid @border-default-color;
1012 1013 border-bottom: @border-thickness solid @border-default-color;
1013 1014 border-left: @border-thickness solid @border-default-color;
1014 1015 }
1015 1016
1016 1017 #repo_vcs_settings {
1017 1018 #inherit_overlay_vcs_default {
1018 1019 display: none;
1019 1020 }
1020 1021 #inherit_overlay_vcs_custom {
1021 1022 display: custom;
1022 1023 }
1023 1024 &.inherited {
1024 1025 #inherit_overlay_vcs_default {
1025 1026 display: block;
1026 1027 }
1027 1028 #inherit_overlay_vcs_custom {
1028 1029 display: none;
1029 1030 }
1030 1031 }
1031 1032 }
1032 1033
1033 1034 .issue-tracker-link {
1034 1035 color: @rcblue;
1035 1036 }
1036 1037
1037 1038 // Issue Tracker Table Show/Hide
1038 1039 #repo_issue_tracker {
1039 1040 #inherit_overlay {
1040 1041 display: none;
1041 1042 }
1042 1043 #custom_overlay {
1043 1044 display: custom;
1044 1045 }
1045 1046 &.inherited {
1046 1047 #inherit_overlay {
1047 1048 display: block;
1048 1049 }
1049 1050 #custom_overlay {
1050 1051 display: none;
1051 1052 }
1052 1053 }
1053 1054 }
1054 1055 table.issuetracker {
1055 1056 &.readonly {
1056 1057 tr, td {
1057 1058 color: @grey3;
1058 1059 }
1059 1060 }
1060 1061 .edit {
1061 1062 display: none;
1062 1063 }
1063 1064 .editopen {
1064 1065 .edit {
1065 1066 display: inline;
1066 1067 }
1067 1068 .entry {
1068 1069 display: none;
1069 1070 }
1070 1071 }
1071 1072 tr td.td-action {
1072 1073 min-width: 117px;
1073 1074 }
1074 1075 td input {
1075 1076 max-width: none;
1076 1077 min-width: 30px;
1077 1078 width: 80%;
1078 1079 }
1079 1080 .issuetracker_pref input {
1080 1081 width: 40%;
1081 1082 }
1082 1083 input.edit_issuetracker_update {
1083 1084 margin-right: 0;
1084 1085 width: auto;
1085 1086 }
1086 1087 }
1087 1088
1088 1089 //Permissions Settings
1089 1090 #add_perm {
1090 1091 margin: 0 0 @padding;
1091 1092 cursor: pointer;
1092 1093 }
1093 1094
1094 1095 .perm_ac {
1095 1096 input {
1096 1097 width: 95%;
1097 1098 }
1098 1099 }
1099 1100
1100 1101 .autocomplete-suggestions {
1101 1102 width: auto !important; // overrides autocomplete.js
1102 1103 margin: 0;
1103 1104 border: @border-thickness solid @rcblue;
1104 1105 border-radius: @border-radius;
1105 1106 color: @rcblue;
1106 1107 background-color: white;
1107 1108 }
1108 1109 .autocomplete-selected {
1109 1110 background: #F0F0F0;
1110 1111 }
1111 1112 .ac-container-wrap {
1112 1113 margin: 0;
1113 1114 padding: 8px;
1114 1115 border-bottom: @border-thickness solid @rclightblue;
1115 1116 list-style-type: none;
1116 1117 cursor: pointer;
1117 1118
1118 1119 &:hover {
1119 1120 background-color: @rclightblue;
1120 1121 }
1121 1122
1122 1123 img {
1123 1124 margin-right: 1em;
1124 1125 }
1125 1126
1126 1127 strong {
1127 1128 font-weight: normal;
1128 1129 }
1129 1130 }
1130 1131
1131 1132 // Settings Dropdown
1132 1133 .user-menu .container {
1133 1134 padding: 0 4px;
1134 1135 margin: 0;
1135 1136 }
1136 1137
1137 1138 .user-menu .gravatar {
1138 1139 cursor: pointer;
1139 1140 }
1140 1141
1141 1142 .codeblock {
1142 1143 margin-bottom: @padding;
1143 1144 clear: both;
1144 1145
1145 1146 .stats{
1146 1147 overflow: hidden;
1147 1148 }
1148 1149
1149 1150 .message{
1150 1151 textarea{
1151 1152 margin: 0;
1152 1153 }
1153 1154 }
1154 1155
1155 1156 .code-header {
1156 1157 .stats {
1157 1158 line-height: 2em;
1158 1159
1159 1160 .revision_id {
1160 1161 margin-left: 0;
1161 1162 }
1162 1163 .buttons {
1163 1164 padding-right: 0;
1164 1165 }
1165 1166 }
1166 1167
1167 1168 .item{
1168 1169 margin-right: 0.5em;
1169 1170 }
1170 1171 }
1171 1172
1172 1173 #editor_container{
1173 1174 position: relative;
1174 1175 margin: @padding;
1175 1176 }
1176 1177 }
1177 1178
1178 1179 #file_history_container {
1179 1180 display: none;
1180 1181 }
1181 1182
1182 1183 .file-history-inner {
1183 1184 margin-bottom: 10px;
1184 1185 }
1185 1186
1186 1187 // Pull Requests
1187 1188 .summary-details {
1188 1189 width: 72%;
1189 1190 }
1190 1191 .pr-summary {
1191 1192 border-bottom: @border-thickness solid @grey5;
1192 1193 margin-bottom: @space;
1193 1194 }
1194 1195 .reviewers-title {
1195 1196 width: 25%;
1196 1197 min-width: 200px;
1197 1198 }
1198 1199 .reviewers {
1199 1200 width: 25%;
1200 1201 min-width: 200px;
1201 1202 }
1202 1203 .reviewers ul li {
1203 1204 position: relative;
1204 1205 width: 100%;
1205 1206 margin-bottom: 8px;
1206 1207 }
1207 1208 .reviewers_member {
1208 1209 width: 100%;
1209 1210 overflow: auto;
1210 1211 }
1211 1212 .reviewer_status {
1212 1213 display: inline-block;
1213 1214 vertical-align: top;
1214 1215 width: 7%;
1215 1216 min-width: 20px;
1216 1217 height: 1.2em;
1217 1218 margin-top: 3px;
1218 1219 line-height: 1em;
1219 1220 }
1220 1221
1221 1222 .reviewer_name {
1222 1223 display: inline-block;
1223 1224 max-width: 83%;
1224 1225 padding-right: 20px;
1225 1226 vertical-align: middle;
1227 line-height: 1;
1228
1229 .rc-user {
1230 min-width: 0;
1231 margin: -2px 1em 0 0;
1232 }
1233
1234 .reviewer {
1235 float: left;
1236 }
1226 1237 }
1227 1238
1228 1239 .reviewer_member_remove {
1229 1240 position: absolute;
1230 1241 right: 0;
1231 1242 top: 0;
1232 1243 width: 16px;
1233 1244 margin-bottom: 10px;
1234 1245 padding: 0;
1235 1246 color: black;
1236 1247 }
1237 1248 .reviewer_member_status {
1238 1249 margin-top: 5px;
1239 1250 }
1240 1251 .pr-summary #summary{
1241 1252 width: 100%;
1242 1253 }
1243 1254 .pr-summary .action_button:hover {
1244 1255 border: 0;
1245 1256 cursor: pointer;
1246 1257 }
1247 1258 .pr-details-title {
1248 1259 padding-bottom: 8px;
1249 1260 border-bottom: @border-thickness solid @grey5;
1250 1261 .action_button {
1251 1262 color: @rcblue;
1252 1263 }
1253 1264 }
1254 1265 .pr-details-content {
1255 1266 margin-top: @textmargin;
1256 margin-bottom: @textmargin/2;
1267 margin-bottom: @textmargin;
1257 1268 }
1258 1269 .pr-description {
1259 1270 white-space:pre-wrap;
1260 1271 }
1261 1272 .group_members {
1262 1273 margin-top: 0;
1263 1274 padding: 0;
1264 1275 list-style: outside none none;
1265 1276 }
1266 1277 .reviewer_ac .ac-input {
1267 1278 width: 92%;
1268 1279 margin-bottom: 1em;
1269 1280 }
1270 1281 #update_commits {
1271 1282 float: right;
1272 1283 }
1273 1284 .compare_view_commits tr{
1274 1285 height: 20px;
1275 1286 }
1276 1287 .compare_view_commits td {
1277 1288 vertical-align: top;
1278 1289 padding-top: 10px;
1279 1290 }
1280 1291 .compare_view_commits .author {
1281 1292 margin-left: 5px;
1282 1293 }
1283 1294
1284 1295 .compare_view_files {
1285 1296 width: 100%;
1286 1297
1287 1298 td {
1288 1299 vertical-align: middle;
1289 1300 }
1290 1301 }
1291 1302
1292 1303 .compare_view_filepath {
1293 1304 color: @grey1;
1294 1305 }
1295 1306
1296 1307 .show_more {
1297 1308 display: inline-block;
1298 1309 position: relative;
1299 1310 vertical-align: middle;
1300 1311 width: 4px;
1301 1312 height: @basefontsize;
1302 1313
1303 1314 &:after {
1304 1315 content: "\00A0\25BE";
1305 1316 display: inline-block;
1306 1317 width:10px;
1307 1318 line-height: 5px;
1308 1319 font-size: 12px;
1309 1320 cursor: pointer;
1310 1321 }
1311 1322 }
1312 1323
1313 1324 .journal_more .show_more {
1314 1325 display: inline;
1315 1326
1316 1327 &:after {
1317 1328 content: none;
1318 1329 }
1319 1330 }
1320 1331
1321 1332 .open .show_more:after,
1322 1333 .select2-dropdown-open .show_more:after {
1323 1334 .rotate(180deg);
1324 1335 margin-left: 4px;
1325 1336 }
1326 1337
1327 1338
1328 1339 .compare_view_commits .collapse_commit:after {
1329 1340 cursor: pointer;
1330 1341 content: "\00A0\25B4";
1331 1342 margin-left: -3px;
1332 1343 font-size: 17px;
1333 1344 color: @grey4;
1334 1345 }
1335 1346
1336 1347 .diff_links {
1337 1348 margin-left: 8px;
1338 1349 }
1339 1350
1340 1351 p.ancestor {
1341 1352 margin: @padding 0;
1342 1353 }
1343 1354
1344 1355 .cs_icon_td input[type="checkbox"] {
1345 1356 display: none;
1346 1357 }
1347 1358
1348 1359 .cs_icon_td .expand_file_icon:after {
1349 1360 cursor: pointer;
1350 1361 content: "\00A0\25B6";
1351 1362 font-size: 12px;
1352 1363 color: @grey4;
1353 1364 }
1354 1365
1355 1366 .cs_icon_td .collapse_file_icon:after {
1356 1367 cursor: pointer;
1357 1368 content: "\00A0\25BC";
1358 1369 font-size: 12px;
1359 1370 color: @grey4;
1360 1371 }
1361 1372
1362 1373 /*new binary
1363 1374 NEW_FILENODE = 1
1364 1375 DEL_FILENODE = 2
1365 1376 MOD_FILENODE = 3
1366 1377 RENAMED_FILENODE = 4
1367 1378 COPIED_FILENODE = 5
1368 1379 CHMOD_FILENODE = 6
1369 1380 BIN_FILENODE = 7
1370 1381 */
1371 1382 .cs_files_expand {
1372 1383 font-size: @basefontsize + 5px;
1373 1384 line-height: 1.8em;
1374 1385 float: right;
1375 1386 }
1376 1387
1377 1388 .cs_files_expand span{
1378 1389 color: @rcblue;
1379 1390 cursor: pointer;
1380 1391 }
1381 1392 .cs_files {
1382 1393 clear: both;
1383 1394 padding-bottom: @padding;
1384 1395
1385 1396 .cur_cs {
1386 1397 margin: 10px 2px;
1387 1398 font-weight: bold;
1388 1399 }
1389 1400
1390 1401 .node {
1391 1402 float: left;
1392 1403 }
1393 1404
1394 1405 .changes {
1395 1406 float: right;
1396 1407 color: white;
1397 1408 font-size: @basefontsize - 4px;
1398 1409 margin-top: 4px;
1399 1410 opacity: 0.6;
1400 1411 filter: Alpha(opacity=60); /* IE8 and earlier */
1401 1412
1402 1413 .added {
1403 1414 background-color: @alert1;
1404 1415 float: left;
1405 1416 text-align: center;
1406 1417 }
1407 1418
1408 1419 .deleted {
1409 1420 background-color: @alert2;
1410 1421 float: left;
1411 1422 text-align: center;
1412 1423 }
1413 1424
1414 1425 .bin {
1415 1426 background-color: @alert1;
1416 1427 text-align: center;
1417 1428 }
1418 1429
1419 1430 /*new binary*/
1420 1431 .bin.bin1 {
1421 1432 background-color: @alert1;
1422 1433 text-align: center;
1423 1434 }
1424 1435
1425 1436 /*deleted binary*/
1426 1437 .bin.bin2 {
1427 1438 background-color: @alert2;
1428 1439 text-align: center;
1429 1440 }
1430 1441
1431 1442 /*mod binary*/
1432 1443 .bin.bin3 {
1433 1444 background-color: @grey2;
1434 1445 text-align: center;
1435 1446 }
1436 1447
1437 1448 /*rename file*/
1438 1449 .bin.bin4 {
1439 1450 background-color: @alert4;
1440 1451 text-align: center;
1441 1452 }
1442 1453
1443 1454 /*copied file*/
1444 1455 .bin.bin5 {
1445 1456 background-color: @alert4;
1446 1457 text-align: center;
1447 1458 }
1448 1459
1449 1460 /*chmod file*/
1450 1461 .bin.bin6 {
1451 1462 background-color: @grey2;
1452 1463 text-align: center;
1453 1464 }
1454 1465 }
1455 1466 }
1456 1467
1457 1468 .cs_files .cs_added, .cs_files .cs_A,
1458 1469 .cs_files .cs_added, .cs_files .cs_M,
1459 1470 .cs_files .cs_added, .cs_files .cs_D {
1460 1471 height: 16px;
1461 1472 padding-right: 10px;
1462 1473 margin-top: 7px;
1463 1474 text-align: left;
1464 1475 }
1465 1476
1466 1477 .cs_icon_td {
1467 1478 min-width: 16px;
1468 1479 width: 16px;
1469 1480 }
1470 1481
1471 1482 .pull-request-merge {
1472 1483 padding: 10px 0;
1473 1484 margin-top: 10px;
1474 1485 margin-bottom: 20px;
1475 1486 }
1476 1487
1477 1488 .pull-request-merge .pull-request-wrap {
1478 1489 height: 25px;
1479 1490 padding: 5px 0;
1480 1491 }
1481 1492
1482 1493 .pull-request-merge span {
1483 1494 margin-right: 10px;
1484 1495 }
1485 1496 #close_pull_request {
1486 1497 margin-right: 0px;
1487 1498 }
1488 1499
1489 1500 .empty_data {
1490 1501 color: @grey4;
1491 1502 }
1492 1503
1493 1504 #changeset_compare_view_content {
1494 1505 margin-bottom: @space;
1495 1506 clear: both;
1496 1507 width: 100%;
1497 1508 box-sizing: border-box;
1498 1509 .border-radius(@border-radius);
1499 1510
1500 1511 .help-block {
1501 1512 margin: @padding 0;
1502 1513 color: @text-color;
1503 1514 }
1504 1515
1505 1516 .empty_data {
1506 1517 margin: @padding 0;
1507 1518 }
1508 1519
1509 1520 .alert {
1510 1521 margin-bottom: @space;
1511 1522 }
1512 1523 }
1513 1524
1514 1525 .table_disp {
1515 1526 .status {
1516 1527 width: auto;
1517 1528
1518 1529 .flag_status {
1519 1530 float: left;
1520 1531 }
1521 1532 }
1522 1533 }
1523 1534
1524 1535 .status_box_menu {
1525 1536 margin: 0;
1526 1537 }
1527 1538
1528 1539 .notification-table{
1529 1540 margin-bottom: @space;
1530 1541 display: table;
1531 1542 width: 100%;
1532 1543
1533 1544 .container{
1534 1545 display: table-row;
1535 1546
1536 1547 .notification-header{
1537 1548 border-bottom: @border-thickness solid @border-default-color;
1538 1549 }
1539 1550
1540 1551 .notification-subject{
1541 1552 display: table-cell;
1542 1553 }
1543 1554 }
1544 1555 }
1545 1556
1546 1557 // Notifications
1547 1558 .notification-header{
1548 1559 display: table;
1549 1560 width: 100%;
1550 1561 padding: floor(@basefontsize/2) 0;
1551 1562 line-height: 1em;
1552 1563
1553 1564 .desc, .delete-notifications, .read-notifications{
1554 1565 display: table-cell;
1555 1566 text-align: left;
1556 1567 }
1557 1568
1558 1569 .desc{
1559 1570 width: 1163px;
1560 1571 }
1561 1572
1562 1573 .delete-notifications, .read-notifications{
1563 1574 width: 35px;
1564 1575 min-width: 35px; //fixes when only one button is displayed
1565 1576 }
1566 1577 }
1567 1578
1568 1579 .notification-body {
1569 1580 .markdown-block,
1570 1581 .rst-block {
1571 1582 padding: @padding 0;
1572 1583 }
1573 1584
1574 1585 .notification-subject {
1575 1586 padding: @textmargin 0;
1576 1587 border-bottom: @border-thickness solid @border-default-color;
1577 1588 }
1578 1589 }
1579 1590
1580 1591
1581 1592 .notifications_buttons{
1582 1593 float: right;
1583 1594 }
1584 1595
1585 1596 // Repositories
1586 1597
1587 1598 #summary.fields{
1588 1599 display: table;
1589 1600
1590 1601 .field{
1591 1602 display: table-row;
1592 1603
1593 1604 .label-summary{
1594 1605 display: table-cell;
1595 1606 min-width: @label-summary-minwidth;
1596 1607 padding-top: @padding/2;
1597 1608 padding-bottom: @padding/2;
1598 1609 padding-right: @padding/2;
1599 1610 }
1600 1611
1601 1612 .input{
1602 1613 display: table-cell;
1603 1614 padding: @padding/2;
1604 1615
1605 1616 input{
1606 1617 min-width: 29em;
1607 1618 padding: @padding/4;
1608 1619 }
1609 1620 }
1610 1621 .statistics, .downloads{
1611 1622 .disabled{
1612 1623 color: @grey4;
1613 1624 }
1614 1625 }
1615 1626 }
1616 1627 }
1617 1628
1618 1629 #summary{
1619 1630 width: 70%;
1620 1631 }
1621 1632
1622 1633
1623 1634 // Journal
1624 1635 .journal.title {
1625 1636 h5 {
1626 1637 float: left;
1627 1638 margin: 0;
1628 1639 width: 70%;
1629 1640 }
1630 1641
1631 1642 ul {
1632 1643 float: right;
1633 1644 display: inline-block;
1634 1645 margin: 0;
1635 1646 width: 30%;
1636 1647 text-align: right;
1637 1648
1638 1649 li {
1639 1650 display: inline;
1640 1651 font-size: @journal-fontsize;
1641 1652 line-height: 1em;
1642 1653
1643 1654 &:before { content: none; }
1644 1655 }
1645 1656 }
1646 1657 }
1647 1658
1648 1659 .filterexample {
1649 1660 position: absolute;
1650 1661 top: 95px;
1651 1662 left: @contentpadding;
1652 1663 color: @rcblue;
1653 1664 font-size: 11px;
1654 1665 font-family: @text-regular;
1655 1666 cursor: help;
1656 1667
1657 1668 &:hover {
1658 1669 color: @rcdarkblue;
1659 1670 }
1660 1671
1661 1672 @media (max-width:768px) {
1662 1673 position: relative;
1663 1674 top: auto;
1664 1675 left: auto;
1665 1676 display: block;
1666 1677 }
1667 1678 }
1668 1679
1669 1680
1670 1681 #journal{
1671 1682 margin-bottom: @space;
1672 1683
1673 1684 .journal_day{
1674 1685 margin-bottom: @textmargin/2;
1675 1686 padding-bottom: @textmargin/2;
1676 1687 font-size: @journal-fontsize;
1677 1688 border-bottom: @border-thickness solid @border-default-color;
1678 1689 }
1679 1690
1680 1691 .journal_container{
1681 1692 margin-bottom: @space;
1682 1693
1683 1694 .journal_user{
1684 1695 display: inline-block;
1685 1696 }
1686 1697 .journal_action_container{
1687 1698 display: block;
1688 1699 margin-top: @textmargin;
1689 1700
1690 1701 div{
1691 1702 display: inline;
1692 1703 }
1693 1704
1694 1705 div.journal_action_params{
1695 1706 display: block;
1696 1707 }
1697 1708
1698 1709 div.journal_repo:after{
1699 1710 content: "\A";
1700 1711 white-space: pre;
1701 1712 }
1702 1713
1703 1714 div.date{
1704 1715 display: block;
1705 1716 margin-bottom: @textmargin;
1706 1717 }
1707 1718 }
1708 1719 }
1709 1720 }
1710 1721
1711 1722 // Files
1712 1723 .edit-file-title {
1713 1724 border-bottom: @border-thickness solid @border-default-color;
1714 1725
1715 1726 .breadcrumbs {
1716 1727 margin-bottom: 0;
1717 1728 }
1718 1729 }
1719 1730
1720 1731 .edit-file-fieldset {
1721 1732 margin-top: @sidebarpadding;
1722 1733
1723 1734 .fieldset {
1724 1735 .left-label {
1725 1736 width: 13%;
1726 1737 }
1727 1738 .right-content {
1728 1739 width: 87%;
1729 1740 max-width: 100%;
1730 1741 }
1731 1742 .filename-label {
1732 1743 margin-top: 13px;
1733 1744 }
1734 1745 .commit-message-label {
1735 1746 margin-top: 4px;
1736 1747 }
1737 1748 .file-upload-input {
1738 1749 input {
1739 1750 display: none;
1740 1751 }
1741 1752 }
1742 1753 p {
1743 1754 margin-top: 5px;
1744 1755 }
1745 1756
1746 1757 }
1747 1758 .custom-path-link {
1748 1759 margin-left: 5px;
1749 1760 }
1750 1761 #commit {
1751 1762 resize: vertical;
1752 1763 }
1753 1764 }
1754 1765
1755 1766 .delete-file-preview {
1756 1767 max-height: 250px;
1757 1768 }
1758 1769
1759 1770 .new-file,
1760 1771 #filter_activate,
1761 1772 #filter_deactivate {
1762 1773 float: left;
1763 1774 margin: 0 0 0 15px;
1764 1775 }
1765 1776
1766 1777 h3.files_location{
1767 1778 line-height: 2.4em;
1768 1779 }
1769 1780
1770 1781 .browser-nav {
1771 1782 display: table;
1772 1783 margin-bottom: @space;
1773 1784
1774 1785
1775 1786 .info_box {
1776 1787 display: inline-table;
1777 1788 height: 2.5em;
1778 1789
1779 1790 .browser-cur-rev, .info_box_elem {
1780 1791 display: table-cell;
1781 1792 vertical-align: middle;
1782 1793 }
1783 1794
1784 1795 .info_box_elem {
1785 1796 border-top: @border-thickness solid @rcblue;
1786 1797 border-bottom: @border-thickness solid @rcblue;
1787 1798
1788 1799 #at_rev, a {
1789 1800 padding: 0.6em 0.9em;
1790 1801 margin: 0;
1791 1802 .box-shadow(none);
1792 1803 border: 0;
1793 1804 height: 12px;
1794 1805 }
1795 1806
1796 1807 input#at_rev {
1797 1808 max-width: 50px;
1798 1809 text-align: right;
1799 1810 }
1800 1811
1801 1812 &.previous {
1802 1813 border: @border-thickness solid @rcblue;
1803 1814 .disabled {
1804 1815 color: @grey4;
1805 1816 cursor: not-allowed;
1806 1817 }
1807 1818 }
1808 1819
1809 1820 &.next {
1810 1821 border: @border-thickness solid @rcblue;
1811 1822 .disabled {
1812 1823 color: @grey4;
1813 1824 cursor: not-allowed;
1814 1825 }
1815 1826 }
1816 1827 }
1817 1828
1818 1829 .browser-cur-rev {
1819 1830
1820 1831 span{
1821 1832 margin: 0;
1822 1833 color: @rcblue;
1823 1834 height: 12px;
1824 1835 display: inline-block;
1825 1836 padding: 0.7em 1em ;
1826 1837 border: @border-thickness solid @rcblue;
1827 1838 margin-right: @padding;
1828 1839 }
1829 1840 }
1830 1841 }
1831 1842
1832 1843 .search_activate {
1833 1844 display: table-cell;
1834 1845 vertical-align: middle;
1835 1846
1836 1847 input, label{
1837 1848 margin: 0;
1838 1849 padding: 0;
1839 1850 }
1840 1851
1841 1852 input{
1842 1853 margin-left: @textmargin;
1843 1854 }
1844 1855
1845 1856 }
1846 1857 }
1847 1858
1848 1859 .file_author{
1849 1860 margin-bottom: @padding;
1850 1861
1851 1862 div{
1852 1863 display: inline-block;
1853 1864 margin-right: 0.5em;
1854 1865 }
1855 1866 }
1856 1867
1857 1868 .browser-cur-rev{
1858 1869 margin-bottom: @textmargin;
1859 1870 }
1860 1871
1861 1872 #node_filter_box_loading{
1862 1873 .info_text;
1863 1874 }
1864 1875
1865 1876 .browser-search {
1866 1877 margin: -25px 0px 5px 0px;
1867 1878 }
1868 1879
1869 1880 .node-filter {
1870 1881 font-size: @repo-title-fontsize;
1871 1882 padding: 4px 0px 0px 0px;
1872 1883
1873 1884 .node-filter-path {
1874 1885 float: left;
1875 1886 color: @grey4;
1876 1887 }
1877 1888 .node-filter-input {
1878 1889 float: left;
1879 1890 margin: -2px 0px 0px 2px;
1880 1891 input {
1881 1892 padding: 2px;
1882 1893 border: none;
1883 1894 font-size: @repo-title-fontsize;
1884 1895 }
1885 1896 }
1886 1897 }
1887 1898
1888 1899
1889 1900 .browser-result{
1890 1901 td a{
1891 1902 margin-left: 0.5em;
1892 1903 display: inline-block;
1893 1904
1894 1905 em{
1895 1906 font-family: @text-bold;
1896 1907 }
1897 1908 }
1898 1909 }
1899 1910
1900 1911 .browser-highlight{
1901 1912 background-color: @grey5-alpha;
1902 1913 }
1903 1914
1904 1915
1905 1916 // Search
1906 1917
1907 1918 .search-form{
1908 1919 #q {
1909 1920 width: @search-form-width;
1910 1921 }
1911 1922 .fields{
1912 1923 margin: 0 0 @space;
1913 1924 }
1914 1925
1915 1926 label{
1916 1927 display: inline-block;
1917 1928 margin-right: @textmargin;
1918 1929 padding-top: 0.25em;
1919 1930 }
1920 1931
1921 1932
1922 1933 .results{
1923 1934 clear: both;
1924 1935 margin: 0 0 @padding;
1925 1936 }
1926 1937 }
1927 1938
1928 1939 div.search-feedback-items {
1929 1940 display: inline-block;
1930 1941 padding:0px 0px 0px 96px;
1931 1942 }
1932 1943
1933 1944 div.search-code-body {
1934 1945 background-color: #ffffff; padding: 5px 0 5px 10px;
1935 1946 pre {
1936 1947 .match { background-color: #faffa6;}
1937 1948 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
1938 1949 }
1939 1950 }
1940 1951
1941 1952 .expand_commit.search {
1942 1953 .show_more.open {
1943 1954 height: auto;
1944 1955 max-height: none;
1945 1956 }
1946 1957 }
1947 1958
1948 1959 .search-results {
1949 1960
1950 1961 h2 {
1951 1962 margin-bottom: 0;
1952 1963 }
1953 1964 .codeblock {
1954 1965 border: none;
1955 1966 background: transparent;
1956 1967 }
1957 1968
1958 1969 .codeblock-header {
1959 1970 border: none;
1960 1971 background: transparent;
1961 1972 }
1962 1973
1963 1974 .code-body {
1964 1975 border: @border-thickness solid @border-default-color;
1965 1976 .border-radius(@border-radius);
1966 1977 }
1967 1978
1968 1979 .td-commit {
1969 1980 &:extend(pre);
1970 1981 border-bottom: @border-thickness solid @border-default-color;
1971 1982 }
1972 1983
1973 1984 .message {
1974 1985 height: auto;
1975 1986 max-width: 350px;
1976 1987 white-space: normal;
1977 1988 text-overflow: initial;
1978 1989 overflow: visible;
1979 1990
1980 1991 .match { background-color: #faffa6;}
1981 1992 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
1982 1993 }
1983 1994
1984 1995 }
1985 1996
1986 1997 table.rctable td.td-search-results div {
1987 1998 max-width: 100%;
1988 1999 }
1989 2000
1990 2001 #tip-box, .tip-box{
1991 2002 padding: @menupadding/2;
1992 2003 display: block;
1993 2004 border: @border-thickness solid @border-highlight-color;
1994 2005 .border-radius(@border-radius);
1995 2006 background-color: white;
1996 2007 z-index: 99;
1997 2008 white-space: pre-wrap;
1998 2009 }
1999 2010
2000 2011 #linktt {
2001 2012 width: 79px;
2002 2013 }
2003 2014
2004 2015 #help_kb .modal-content{
2005 2016 max-width: 750px;
2006 2017 margin: 10% auto;
2007 2018
2008 2019 table{
2009 2020 td,th{
2010 2021 border-bottom: none;
2011 2022 line-height: 2.5em;
2012 2023 }
2013 2024 th{
2014 2025 padding-bottom: @textmargin/2;
2015 2026 }
2016 2027 td.keys{
2017 2028 text-align: center;
2018 2029 }
2019 2030 }
2020 2031
2021 2032 .block-left{
2022 2033 width: 45%;
2023 2034 margin-right: 5%;
2024 2035 }
2025 2036 .modal-footer{
2026 2037 clear: both;
2027 2038 }
2028 2039 .key.tag{
2029 2040 padding: 0.5em;
2030 2041 background-color: @rcblue;
2031 2042 color: white;
2032 2043 border-color: @rcblue;
2033 2044 .box-shadow(none);
2034 2045 }
2035 2046 }
2036 2047
2037 2048
2038 2049
2039 2050 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2040 2051
2041 2052 @import 'statistics-graph';
2042 2053 @import 'tables';
2043 2054 @import 'forms';
2044 2055 @import 'diff';
2045 2056 @import 'summary';
2046 2057 @import 'navigation';
2047 2058
2048 2059 //--- SHOW/HIDE SECTIONS --//
2049 2060
2050 2061 .btn-collapse {
2051 2062 float: right;
2052 2063 text-align: right;
2053 2064 font-family: @text-light;
2054 2065 font-size: @basefontsize;
2055 2066 cursor: pointer;
2056 2067 border: none;
2057 2068 color: @rcblue;
2058 2069 }
2059 2070
2060 2071 table.rctable,
2061 2072 table.dataTable {
2062 2073 .btn-collapse {
2063 2074 float: right;
2064 2075 text-align: right;
2065 2076 }
2066 2077 }
2067 2078
2068 2079
2069 2080 // TODO: johbo: Fix for IE10, this avoids that we see a border
2070 2081 // and padding around checkboxes and radio boxes. Move to the right place,
2071 2082 // or better: Remove this once we did the form refactoring.
2072 2083 input[type=checkbox],
2073 2084 input[type=radio] {
2074 2085 padding: 0;
2075 2086 border: none;
2076 2087 }
@@ -1,47 +1,49 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.html"/>
3 3
4 4 % if c.forks_pager:
5 5 <table class="rctable fork_summary">
6 6 <tr>
7 <th>${_('Owner')}</th>
7 8 <th>${_('Fork')}</th>
8 9 <th>${_('Description')}</th>
9 10 <th>${_('Forked')}</th>
10 11 <th></th>
11 12 </tr>
12 13 % for f in c.forks_pager:
13 14 <tr>
14 15 <td class="td-user fork_user">
15 16 ${base.gravatar_with_user(f.user.email, 16)}
16 &frasl;
17 </td>
18 <td class="td-componentname">
17 19 ${h.link_to(f.repo_name,h.url('summary_home',repo_name=f.repo_name))}
18 20 </td>
19 21 <td class="td-description">
20 22 <div class="truncate">${f.description}</div>
21 23 </td>
22 24 <td class="td-time follower_date">
23 25 ${h.age_component(f.created_on)}
24 26 </td>
25 27 <td class="td-compare">
26 28 <a title="${_('Compare fork with %s' % c.repo_name)}"
27 29 href="${h.url('compare_url',repo_name=c.repo_name, source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1],target_repo=f.repo_name,target_ref_type=c.rhodecode_db_repo.landing_rev[0],target_ref=c.rhodecode_db_repo.landing_rev[1], merge=1)}"
28 30 class="btn-link"><i class="icon-loop"></i> ${_('Compare fork')}</a>
29 31 </td>
30 32 </tr>
31 33 % endfor
32 34 </table>
33 35 <div class="pagination-wh pagination-left">
34 36 <script type="text/javascript">
35 37 $(document).pjax('#forks .pager_link','#forks');
36 38 $(document).on('pjax:success',function(){
37 39 show_more_event();
38 40 timeagoActivate();
39 41 tooltip_activate();
40 42 show_changeset_tooltip();
41 43 });
42 44 </script>
43 45 ${c.forks_pager.pager('$link_previous ~2~ $link_next')}
44 46 </div>
45 47 % else:
46 48 ${_('There are no forks yet')}
47 49 % endif
@@ -1,568 +1,568 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()">
11 11 <span id="pr-title">
12 12 ${c.pull_request.title}
13 13 %if c.pull_request.is_closed():
14 14 (${_('Closed')})
15 15 %endif
16 16 </span>
17 17 <div id="pr-title-edit" class="input" style="display: none;">
18 18 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
19 19 </div>
20 20 </%def>
21 21
22 22 <%def name="menu_bar_nav()">
23 23 ${self.menu_items(active='repositories')}
24 24 </%def>
25 25
26 26 <%def name="menu_bar_subnav()">
27 27 ${self.repo_menu(active='showpullrequest')}
28 28 </%def>
29 29
30 30 <%def name="main()">
31 31 <script type="text/javascript">
32 32 // TODO: marcink switch this to pyroutes
33 33 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
34 34 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
35 35 </script>
36 36 <div class="box">
37 37 <div class="title">
38 38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 39 </div>
40 40
41 41 ${self.breadcrumbs()}
42 42
43 43
44 44 <div class="box pr-summary">
45 45 <div class="summary-details block-left">
46 46 <%summary = lambda n:{False:'summary-short'}.get(n)%>
47 47 <div class="pr-details-title">
48 48 ${_('Pull request #%s') % c.pull_request.pull_request_id} ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 49 %if c.allowed_to_update:
50 50 <span id="open_edit_pullrequest" class="block-right action_button">${_('Edit')}</span>
51 51 <span id="close_edit_pullrequest" class="block-right action_button" style="display: none;">${_('Close')}</span>
52 52 %endif
53 53 </div>
54 54
55 55 <div id="summary" class="fields pr-details-content">
56 56 <div class="field">
57 57 <div class="label-summary">
58 58 <label>${_('Origin')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 <div class="pr-origininfo">
62 62 ## branch link is only valid if it is a branch
63 63 <span class="tag">
64 64 %if c.pull_request.source_ref_parts.type == 'branch':
65 65 <a href="${h.url('changelog_home', repo_name=c.pull_request.source_repo.repo_name, branch=c.pull_request.source_ref_parts.name)}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
66 66 %else:
67 67 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
68 68 %endif
69 69 </span>
70 70 <span class="clone-url">
71 71 <a href="${h.url('summary_home', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
72 72 </span>
73 73 </div>
74 74 <div class="pr-pullinfo">
75 75 %if h.is_hg(c.pull_request.source_repo):
76 76 <input type="text" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
77 77 %elif h.is_git(c.pull_request.source_repo):
78 78 <input type="text" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
79 79 %endif
80 80 </div>
81 81 </div>
82 82 </div>
83 83 <div class="field">
84 84 <div class="label-summary">
85 85 <label>${_('Target')}:</label>
86 86 </div>
87 87 <div class="input">
88 88 <div class="pr-targetinfo">
89 89 ## branch link is only valid if it is a branch
90 90 <span class="tag">
91 91 %if c.pull_request.target_ref_parts.type == 'branch':
92 92 <a href="${h.url('changelog_home', repo_name=c.pull_request.target_repo.repo_name, branch=c.pull_request.target_ref_parts.name)}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
93 93 %else:
94 94 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
95 95 %endif
96 96 </span>
97 97 <span class="clone-url">
98 98 <a href="${h.url('summary_home', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
99 99 </span>
100 100 </div>
101 101 </div>
102 102 </div>
103 103 <div class="field">
104 104 <div class="label-summary">
105 105 <label>${_('Review')}:</label>
106 106 </div>
107 107 <div class="input">
108 108 %if c.pull_request_review_status:
109 109 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
110 110 <span class="changeset-status-lbl tooltip">
111 111 %if c.pull_request.is_closed():
112 112 ${_('Closed')},
113 113 %endif
114 114 ${h.commit_status_lbl(c.pull_request_review_status)}
115 115 </span>
116 116 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
117 117 %endif
118 118 </div>
119 119 </div>
120 120 <div class="field">
121 121 <div class="pr-description-label label-summary">
122 122 <label>${_('Description')}:</label>
123 123 </div>
124 124 <div id="pr-desc" class="input">
125 125 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
126 126 </div>
127 127 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
128 128 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
129 129 </div>
130 130 </div>
131 131 <div class="field">
132 132 <div class="label-summary">
133 133 <label>${_('Comments')}:</label>
134 134 </div>
135 135 <div class="input">
136 136 <div>
137 137 <div class="comments-number">
138 138 %if c.comments:
139 139 <a href="#comments">${ungettext("%d Pull request comment", "%d Pull request comments", len(c.comments)) % len(c.comments)}</a>,
140 140 %else:
141 141 ${ungettext("%d Pull request comment", "%d Pull request comments", len(c.comments)) % len(c.comments)}
142 142 %endif
143 143 %if c.inline_cnt:
144 144 ## this is replaced with a proper link to first comment via JS linkifyComments() func
145 145 <a href="#inline-comments" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
146 146 %else:
147 147 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
148 148 %endif
149 149
150 150 % if c.outdated_cnt:
151 151 ,${ungettext("%d Outdated Comment", "%d Outdated Comments", c.outdated_cnt) % c.outdated_cnt} <span id="show-outdated-comments" class="btn btn-link">${_('(Show)')}</span>
152 152 % endif
153 153 </div>
154 154 </div>
155 155 </div>
156 156 </div>
157 157 <div id="pr-save" class="field" style="display: none;">
158 158 <div class="label-summary"></div>
159 159 <div class="input">
160 160 <span id="edit_pull_request" class="btn btn-small">${_('Save Changes')}</span>
161 161 </div>
162 162 </div>
163 163 </div>
164 164 </div>
165 165 <div>
166 166 ## AUTHOR
167 167 <div class="reviewers-title block-right">
168 168 <div class="pr-details-title">
169 169 ${_('Author')}
170 170 </div>
171 171 </div>
172 172 <div class="block-right pr-details-content reviewers">
173 173 <ul class="group_members">
174 174 <li>
175 175 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
176 176 </li>
177 177 </ul>
178 178 </div>
179 179 ## REVIEWERS
180 180 <div class="reviewers-title block-right">
181 181 <div class="pr-details-title">
182 182 ${_('Pull request reviewers')}
183 183 %if c.allowed_to_update:
184 184 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
185 185 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
186 186 %endif
187 187 </div>
188 188 </div>
189 189 <div id="reviewers" class="block-right pr-details-content reviewers">
190 190 ## members goes here !
191 191 <ul id="review_members" class="group_members">
192 192 %for member,status in c.pull_request_reviewers:
193 193 <li id="reviewer_${member.user_id}">
194 194 <div class="reviewers_member">
195 195 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
196 196 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
197 197 </div>
198 <span id="reviewer_${member.user_id}_name" class="reviewer_name">
199 ${self.gravatar_with_user(member.email, 16)}
200 (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</span>
198 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
199 ${self.gravatar_with_user(member.email, 16)} <div class="reviewer">(${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
200 </div>
201 201 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="review_members" />
202 202 %if c.allowed_to_update:
203 203 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
204 204 <i class="icon-remove-sign" ></i>
205 205 </div>
206 206 %endif
207 207 </div>
208 208 </li>
209 209 %endfor
210 210 </ul>
211 211 %if not c.pull_request.is_closed():
212 212 <div id="add_reviewer_input" class='ac' style="display: none;">
213 213 %if c.allowed_to_update:
214 214 <div class="reviewer_ac">
215 215 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
216 216 <div id="reviewers_container"></div>
217 217 </div>
218 218 <div>
219 219 <span id="update_pull_request" class="btn btn-small">${_('Save Changes')}</span>
220 220 </div>
221 221 %endif
222 222 </div>
223 223 %endif
224 224 </div>
225 225 </div>
226 226 </div>
227 227 <div class="box">
228 228 ##DIFF
229 229 <div class="table" >
230 230 <div id="changeset_compare_view_content">
231 231 ##CS
232 232 % if c.missing_requirements:
233 233 <div class="box">
234 234 <div class="alert alert-warning">
235 235 <div>
236 236 <strong>${_('Missing requirements:')}</strong>
237 237 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
238 238 </div>
239 239 </div>
240 240 </div>
241 241 % elif c.missing_commits:
242 242 <div class="box">
243 243 <div class="alert alert-warning">
244 244 <div>
245 245 <strong>${_('Missing commits')}:</strong>
246 246 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
247 247 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
248 248 </div>
249 249 </div>
250 250 </div>
251 251 % endif
252 252 <div class="compare_view_commits_title">
253 253 % if c.allowed_to_update and not c.pull_request.is_closed():
254 254 <button id="update_commits" class="btn btn-small">${_('Update commits')}</button>
255 255 % endif
256 256 % if len(c.commit_ranges):
257 257 <h2>${ungettext('Compare View: %s commit','Compare View: %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}</h2>
258 258 % endif
259 259 </div>
260 260 % if not c.missing_commits:
261 261 <%include file="/compare/compare_commits.html" />
262 262 ## FILES
263 263 <div class="cs_files_title">
264 264 <span class="cs_files_expand">
265 265 <span id="expand_all_files">${_('Expand All')}</span> | <span id="collapse_all_files">${_('Collapse All')}</span>
266 266 </span>
267 267 <h2>
268 268 ${diff_block.diff_summary_text(len(c.files), c.lines_added, c.lines_deleted, c.limited_diff)}
269 269 </h2>
270 270 </div>
271 271 % endif
272 272 <div class="cs_files">
273 273 %if not c.files and not c.missing_commits:
274 274 <span class="empty_data">${_('No files')}</span>
275 275 %endif
276 276 <table class="compare_view_files">
277 277 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
278 278 %for FID, change, path, stats in c.files:
279 279 <tr class="cs_${change} collapse_file" fid="${FID}">
280 280 <td class="cs_icon_td">
281 281 <span class="collapse_file_icon" fid="${FID}"></span>
282 282 </td>
283 283 <td class="cs_icon_td">
284 284 <div class="flag_status not_reviewed hidden"></div>
285 285 </td>
286 286 <td class="cs_${change}" id="a_${FID}">
287 287 <div class="node">
288 288 <a href="#a_${FID}">
289 289 <i class="icon-file-${change.lower()}"></i>
290 290 ${h.safe_unicode(path)}
291 291 </a>
292 292 </div>
293 293 </td>
294 294 <td>
295 295 <div class="changes pull-right">${h.fancy_file_stats(stats)}</div>
296 296 <div class="comment-bubble pull-right" data-path="${path}">
297 297 <i class="icon-comment"></i>
298 298 </div>
299 299 </td>
300 300 </tr>
301 301 <tr fid="${FID}" id="diff_${FID}" class="diff_links">
302 302 <td></td>
303 303 <td></td>
304 304 <td class="cs_${change}">
305 305 %if c.target_repo.repo_name == c.repo_name:
306 306 ${diff_block.diff_menu(c.repo_name, h.safe_unicode(path), c.target_ref, c.source_ref, change)}
307 307 %else:
308 308 ## this is slightly different case later, since the other repo can have this
309 309 ## file in other state than the origin repo
310 310 ${diff_block.diff_menu(c.target_repo.repo_name, h.safe_unicode(path), c.target_ref, c.source_ref, change)}
311 311 %endif
312 312 </td>
313 313 <td class="td-actions rc-form">
314 314 </td>
315 315 </tr>
316 316 <tr id="tr_${FID}">
317 317 <td></td>
318 318 <td></td>
319 319 <td class="injected_diff" colspan="2">
320 320 ${diff_block.diff_block_simple([c.changes[FID]])}
321 321 </td>
322 322 </tr>
323 323
324 324 ## Loop through inline comments
325 325 % if c.outdated_comments.get(path,False):
326 326 <tr class="outdated">
327 327 <td></td>
328 328 <td></td>
329 329 <td colspan="2">
330 330 <p>${_('Outdated Inline Comments')}:</p>
331 331 </td>
332 332 </tr>
333 333 <tr class="outdated">
334 334 <td></td>
335 335 <td></td>
336 336 <td colspan="2" class="outdated_comment_block">
337 337 % for line, comments in c.outdated_comments[path].iteritems():
338 338 <div class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
339 339 % for co in comments:
340 340 ${comment.comment_block_outdated(co)}
341 341 % endfor
342 342 </div>
343 343 % endfor
344 344 </td>
345 345 </tr>
346 346 % endif
347 347 %endfor
348 348 ## Loop through inline comments for deleted files
349 349 %for path in c.deleted_files:
350 350 <tr class="outdated deleted">
351 351 <td></td>
352 352 <td></td>
353 353 <td>${path}</td>
354 354 </tr>
355 355 <tr class="outdated deleted">
356 356 <td></td>
357 357 <td></td>
358 358 <td>(${_('Removed')})</td>
359 359 </tr>
360 360 % if path in c.outdated_comments:
361 361 <tr class="outdated deleted">
362 362 <td></td>
363 363 <td></td>
364 364 <td colspan="2">
365 365 <p>${_('Outdated Inline Comments')}:</p>
366 366 </td>
367 367 </tr>
368 368 <tr class="outdated">
369 369 <td></td>
370 370 <td></td>
371 371 <td colspan="2" class="outdated_comment_block">
372 372 % for line, comments in c.outdated_comments[path].iteritems():
373 373 <div class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
374 374 % for co in comments:
375 375 ${comment.comment_block_outdated(co)}
376 376 % endfor
377 377 </div>
378 378 % endfor
379 379 </td>
380 380 </tr>
381 381 % endif
382 382 %endfor
383 383 </table>
384 384 </div>
385 385 % if c.limited_diff:
386 386 <h5>${_('Commit was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h5>
387 387 % endif
388 388 </div>
389 389 </div>
390 390
391 391 % if c.limited_diff:
392 392 <p>${_('Commit was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></p>
393 393 % endif
394 394
395 395 ## template for inline comment form
396 396 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
397 397 ${comment.comment_inline_form()}
398 398
399 399 ## render comments and inlines
400 400 ${comment.generate_comments(include_pull_request=True, is_pull_request=True)}
401 401
402 402 % if not c.pull_request.is_closed():
403 403 ## main comment form and it status
404 404 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
405 405 pull_request_id=c.pull_request.pull_request_id),
406 406 c.pull_request_review_status,
407 407 is_pull_request=True, change_status=c.allowed_to_change_status)}
408 408 %endif
409 409
410 410 <script type="text/javascript">
411 411 if (location.href.indexOf('#') != -1) {
412 412 var id = '#'+location.href.substring(location.href.indexOf('#') + 1).split('#');
413 413 var line = $('html').find(id);
414 414 offsetScroll(line, 70);
415 415 }
416 416 $(function(){
417 417 ReviewerAutoComplete('user');
418 418 // custom code mirror
419 419 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
420 420
421 421 var PRDetails = {
422 422 editButton: $('#open_edit_pullrequest'),
423 423 closeButton: $('#close_edit_pullrequest'),
424 424 viewFields: $('#pr-desc, #pr-title'),
425 425 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
426 426
427 427 init: function() {
428 428 var that = this;
429 429 this.editButton.on('click', function(e) { that.edit(); });
430 430 this.closeButton.on('click', function(e) { that.view(); });
431 431 },
432 432
433 433 edit: function(event) {
434 434 this.viewFields.hide();
435 435 this.editButton.hide();
436 436 this.editFields.show();
437 437 codeMirrorInstance.refresh();
438 438 },
439 439
440 440 view: function(event) {
441 441 this.editFields.hide();
442 442 this.closeButton.hide();
443 443 this.viewFields.show();
444 444 }
445 445 };
446 446
447 447 var ReviewersPanel = {
448 448 editButton: $('#open_edit_reviewers'),
449 449 closeButton: $('#close_edit_reviewers'),
450 450 addButton: $('#add_reviewer_input'),
451 451 removeButtons: $('.reviewer_member_remove'),
452 452
453 453 init: function() {
454 454 var that = this;
455 455 this.editButton.on('click', function(e) { that.edit(); });
456 456 this.closeButton.on('click', function(e) { that.close(); });
457 457 },
458 458
459 459 edit: function(event) {
460 460 this.editButton.hide();
461 461 this.closeButton.show();
462 462 this.addButton.show();
463 463 this.removeButtons.css('visibility', 'visible');
464 464 },
465 465
466 466 close: function(event) {
467 467 this.editButton.show();
468 468 this.closeButton.hide();
469 469 this.addButton.hide();
470 470 this.removeButtons.css('visibility', 'hidden');
471 471 }
472 472 };
473 473
474 474 PRDetails.init();
475 475 ReviewersPanel.init();
476 476
477 477 $('#show-outdated-comments').on('click', function(e){
478 478 var button = $(this);
479 479 var outdated = $('.outdated');
480 480 if (button.html() === "(Show)") {
481 481 button.html("(Hide)");
482 482 outdated.show();
483 483 } else {
484 484 button.html("(Show)");
485 485 outdated.hide();
486 486 }
487 487 });
488 488
489 489 $('.show-inline-comments').on('change', function(e){
490 490 var show = 'none';
491 491 var target = e.currentTarget;
492 492 if(target.checked){
493 493 show = ''
494 494 }
495 495 var boxid = $(target).attr('id_for');
496 496 var comments = $('#{0} .inline-comments'.format(boxid));
497 497 var fn_display = function(idx){
498 498 $(this).css('display', show);
499 499 };
500 500 $(comments).each(fn_display);
501 501 var btns = $('#{0} .inline-comments-button'.format(boxid));
502 502 $(btns).each(fn_display);
503 503 });
504 504
505 505 // inject comments into their proper positions
506 506 var file_comments = $('.inline-comment-placeholder');
507 507 %if c.pull_request.is_closed():
508 508 renderInlineComments(file_comments, false);
509 509 %else:
510 510 renderInlineComments(file_comments, true);
511 511 %endif
512 512 var commentTotals = {};
513 513 $.each(file_comments, function(i, comment) {
514 514 var path = $(comment).attr('path');
515 515 var comms = $(comment).children().length;
516 516 if (path in commentTotals) {
517 517 commentTotals[path] += comms;
518 518 } else {
519 519 commentTotals[path] = comms;
520 520 }
521 521 });
522 522 $.each(commentTotals, function(path, total) {
523 523 var elem = $('.comment-bubble[data-path="'+ path +'"]');
524 524 elem.css('visibility', 'visible');
525 525 elem.html(elem.html() + ' ' + total );
526 526 });
527 527
528 528 $('#merge_pull_request_form').submit(function() {
529 529 if (!$('#merge_pull_request').attr('disabled')) {
530 530 $('#merge_pull_request').attr('disabled', 'disabled');
531 531 }
532 532 return true;
533 533 });
534 534
535 535 $('#edit_pull_request').on('click', function(e){
536 536 var title = $('#pr-title-input').val();
537 537 var description = codeMirrorInstance.getValue();
538 538 editPullRequest(
539 539 "${c.repo_name}", "${c.pull_request.pull_request_id}",
540 540 title, description);
541 541 });
542 542
543 543 $('#update_pull_request').on('click', function(e){
544 544 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
545 545 });
546 546
547 547 $('#update_commits').on('click', function(e){
548 548 var isDisabled = !$(e.currentTarget).attr('disabled');
549 549 $(e.currentTarget).text(_TM['Updating...']);
550 550 $(e.currentTarget).attr('disabled', 'disabled');
551 551 if(isDisabled){
552 552 updateCommits("${c.repo_name}", "${c.pull_request.pull_request_id}");
553 553 }
554 554
555 555 });
556 556 // fixing issue with caches on firefox
557 557 $('#update_commits').removeAttr("disabled");
558 558
559 559 $('#close_pull_request').on('click', function(e){
560 560 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
561 561 });
562 562 })
563 563 </script>
564 564
565 565 </div>
566 566 </div>
567 567
568 568 </%def>
@@ -1,241 +1,243 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Import early to make sure things are patched up properly
4 4 from setuptools import setup, find_packages
5 5
6 6 import os
7 7 import sys
8 8 import platform
9 9
10 10 if sys.version_info < (2, 7):
11 11 raise Exception('RhodeCode requires Python 2.7 or later')
12 12
13 13
14 14 here = os.path.abspath(os.path.dirname(__file__))
15 15
16 16
17 17 def _get_meta_var(name, data, callback_handler=None):
18 18 import re
19 19 matches = re.compile(r'(?:%s)\s*=\s*(.*)' % name).search(data)
20 20 if matches:
21 21 if not callable(callback_handler):
22 22 callback_handler = lambda v: v
23 23
24 24 return callback_handler(eval(matches.groups()[0]))
25 25
26 26 _meta = open(os.path.join(here, 'rhodecode', '__init__.py'), 'rb')
27 27 _metadata = _meta.read()
28 28 _meta.close()
29 29
30 30 callback = lambda V: ('.'.join(map(str, V[:3])) + '.'.join(V[3:]))
31 31 __version__ = open(os.path.join('rhodecode', 'VERSION')).read().strip()
32 32 __license__ = _get_meta_var('__license__', _metadata)
33 33 __author__ = _get_meta_var('__author__', _metadata)
34 34 __url__ = _get_meta_var('__url__', _metadata)
35 35 # defines current platform
36 36 __platform__ = platform.system()
37 37
38 38 # Cygwin has different platform identifiers, but they all contain the
39 39 # term "CYGWIN"
40 40 is_windows = __platform__ == 'Windows' or 'CYGWIN' in __platform__
41 41
42 42 requirements = [
43 43 'Babel',
44 44 'Beaker',
45 45 'FormEncode',
46 46 'Mako',
47 47 'Markdown',
48 48 'MarkupSafe',
49 49 'MySQL-python',
50 50 'Paste',
51 51 'PasteDeploy',
52 52 'PasteScript',
53 53 'Pygments',
54 54 'Pylons',
55 55 'Pyro4',
56 56 'Routes',
57 57 'SQLAlchemy',
58 58 'Tempita',
59 59 'URLObject',
60 60 'WebError',
61 61 'WebHelpers',
62 62 'WebHelpers2',
63 63 'WebOb',
64 64 'WebTest',
65 65 'Whoosh',
66 66 'alembic',
67 67 'amqplib',
68 68 'anyjson',
69 69 'appenlight-client',
70 70 'authomatic',
71 71 'backport_ipaddress',
72 72 'celery',
73 73 'colander',
74 74 'decorator',
75 75 'docutils',
76 'gunicorn',
76 77 'infrae.cache',
77 78 'ipython',
78 79 'iso8601',
79 80 'kombu',
80 81 'msgpack-python',
81 82 'packaging',
82 83 'psycopg2',
83 84 'pycrypto',
84 85 'pycurl',
85 86 'pyparsing',
86 87 'pyramid',
87 88 'pyramid-debugtoolbar',
88 89 'pyramid-mako',
89 90 'pyramid-beaker',
90 91 'pysqlite',
91 92 'python-dateutil',
92 93 'python-ldap',
93 94 'python-memcached',
94 95 'python-pam',
95 96 'recaptcha-client',
96 97 'repoze.lru',
97 98 'requests',
98 99 'simplejson',
99 100 'waitress',
100 101 'zope.cachedescriptors',
101 102 ]
102 103
103 104 if is_windows:
104 105 pass
105 106 else:
106 107 requirements.append('psutil')
107 108 requirements.append('py-bcrypt')
108 109
109 110 test_requirements = [
110 111 'WebTest',
111 112 'configobj',
112 113 'cssselect',
113 114 'flake8',
114 115 'lxml',
115 116 'mock',
116 117 'pytest',
118 'pytest-cov',
117 119 'pytest-runner',
118 120 ]
119 121
120 122 setup_requirements = [
121 123 'PasteScript',
122 124 'pytest-runner',
123 125 ]
124 126
125 127 dependency_links = [
126 128 ]
127 129
128 130 classifiers = [
129 131 'Development Status :: 6 - Mature',
130 132 'Environment :: Web Environment',
131 133 'Framework :: Pylons',
132 134 'Intended Audience :: Developers',
133 135 'Operating System :: OS Independent',
134 136 'Programming Language :: Python',
135 137 'Programming Language :: Python :: 2.7',
136 138 ]
137 139
138 140
139 141 # additional files from project that goes somewhere in the filesystem
140 142 # relative to sys.prefix
141 143 data_files = []
142 144
143 145 # additional files that goes into package itself
144 146 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
145 147
146 148 description = ('RhodeCode is a fast and powerful management tool '
147 149 'for Mercurial and GIT with a built in push/pull server, '
148 150 'full text search and code-review.')
149 151
150 152 keywords = ' '.join([
151 153 'rhodecode', 'rhodiumcode', 'mercurial', 'git', 'code review',
152 154 'repo groups', 'ldap', 'repository management', 'hgweb replacement',
153 155 'hgwebdir', 'gitweb replacement', 'serving hgweb',
154 156 ])
155 157
156 158 # long description
157 159 README_FILE = 'README.rst'
158 160 CHANGELOG_FILE = 'CHANGES.rst'
159 161 try:
160 162 long_description = open(README_FILE).read() + '\n\n' + \
161 163 open(CHANGELOG_FILE).read()
162 164
163 165 except IOError, err:
164 166 sys.stderr.write(
165 167 '[WARNING] Cannot find file specified as long_description (%s)\n or '
166 168 'changelog (%s) skipping that file' % (README_FILE, CHANGELOG_FILE)
167 169 )
168 170 long_description = description
169 171
170 172 # packages
171 173 packages = find_packages()
172 174
173 175 paster_commands = [
174 176 'make-config=rhodecode.lib.paster_commands.make_config:Command',
175 177 'setup-rhodecode=rhodecode.lib.paster_commands.setup_rhodecode:Command',
176 178 'update-repoinfo=rhodecode.lib.paster_commands.update_repoinfo:Command',
177 179 'cache-keys=rhodecode.lib.paster_commands.cache_keys:Command',
178 180 'ishell=rhodecode.lib.paster_commands.ishell:Command',
179 181 'upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb',
180 182 'celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand',
181 183 ]
182 184
183 185 setup(
184 186 name='rhodecode-enterprise-ce',
185 187 version=__version__,
186 188 description=description,
187 189 long_description=long_description,
188 190 keywords=keywords,
189 191 license=__license__,
190 192 author=__author__,
191 193 author_email='marcin@rhodecode.com',
192 194 dependency_links=dependency_links,
193 195 url=__url__,
194 196 install_requires=requirements,
195 197 tests_require=test_requirements,
196 198 classifiers=classifiers,
197 199 setup_requires=setup_requirements,
198 200 data_files=data_files,
199 201 packages=packages,
200 202 include_package_data=True,
201 203 package_data=package_data,
202 204 message_extractors={
203 205 'rhodecode': [
204 206 ('**.py', 'python', None),
205 207 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
206 208 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
207 209 ('public/**', 'ignore', None),
208 210 ]
209 211 },
210 212 zip_safe=False,
211 213 paster_plugins=['PasteScript', 'Pylons'],
212 214 entry_points={
213 215 'enterprise.plugins1': [
214 216 'crowd=rhodecode.authentication.plugins.auth_crowd:plugin_factory',
215 217 'jasig_cas=rhodecode.authentication.plugins.auth_jasig_cas:plugin_factory',
216 218 'ldap=rhodecode.authentication.plugins.auth_ldap:plugin_factory',
217 219 'pam=rhodecode.authentication.plugins.auth_pam:plugin_factory',
218 220 'rhodecode=rhodecode.authentication.plugins.auth_rhodecode:plugin_factory',
219 221 ],
220 222 'paste.app_factory': [
221 223 'main=rhodecode.config.middleware:make_pyramid_app',
222 224 'pylons=rhodecode.config.middleware:make_app',
223 225 ],
224 226 'paste.app_install': [
225 227 'main=pylons.util:PylonsInstaller',
226 228 'pylons=pylons.util:PylonsInstaller',
227 229 ],
228 230 'paste.global_paster_command': paster_commands,
229 231 'pytest11': [
230 232 'pylons=rhodecode.tests.pylons_plugin',
231 233 'enterprise=rhodecode.tests.plugin',
232 234 ],
233 235 'console_scripts': [
234 236 'rcserver=rhodecode.rcserver:main',
235 237 ],
236 238 'beaker.backends': [
237 239 'memorylru_base=rhodecode.lib.memory_lru_debug:MemoryLRUNamespaceManagerBase',
238 240 'memorylru_debug=rhodecode.lib.memory_lru_debug:MemoryLRUNamespaceManagerDebug'
239 241 ]
240 242 },
241 243 )
General Comments 0
You need to be logged in to leave comments. Login now