##// END OF EJS Templates
configitems: register the 'web.templates' config
Boris Feld -
r34244:95f80c09 default
parent child Browse files
Show More
@@ -1,662 +1,665 b''
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11
12 12 from . import (
13 13 encoding,
14 14 error,
15 15 )
16 16
17 17 def loadconfigtable(ui, extname, configtable):
18 18 """update config item known to the ui with the extension ones"""
19 19 for section, items in configtable.items():
20 20 knownitems = ui._knownconfig.setdefault(section, {})
21 21 knownkeys = set(knownitems)
22 22 newkeys = set(items)
23 23 for key in sorted(knownkeys & newkeys):
24 24 msg = "extension '%s' overwrite config item '%s.%s'"
25 25 msg %= (extname, section, key)
26 26 ui.develwarn(msg, config='warn-config')
27 27
28 28 knownitems.update(items)
29 29
30 30 class configitem(object):
31 31 """represent a known config item
32 32
33 33 :section: the official config section where to find this item,
34 34 :name: the official name within the section,
35 35 :default: default value for this item,
36 36 :alias: optional list of tuples as alternatives.
37 37 """
38 38
39 39 def __init__(self, section, name, default=None, alias=()):
40 40 self.section = section
41 41 self.name = name
42 42 self.default = default
43 43 self.alias = list(alias)
44 44
45 45 coreitems = {}
46 46
47 47 def _register(configtable, *args, **kwargs):
48 48 item = configitem(*args, **kwargs)
49 49 section = configtable.setdefault(item.section, {})
50 50 if item.name in section:
51 51 msg = "duplicated config item registration for '%s.%s'"
52 52 raise error.ProgrammingError(msg % (item.section, item.name))
53 53 section[item.name] = item
54 54
55 55 # special value for case where the default is derived from other values
56 56 dynamicdefault = object()
57 57
58 58 # Registering actual config items
59 59
60 60 def getitemregister(configtable):
61 61 return functools.partial(_register, configtable)
62 62
63 63 coreconfigitem = getitemregister(coreitems)
64 64
65 65 coreconfigitem('auth', 'cookiefile',
66 66 default=None,
67 67 )
68 68 # bookmarks.pushing: internal hack for discovery
69 69 coreconfigitem('bookmarks', 'pushing',
70 70 default=list,
71 71 )
72 72 # bundle.mainreporoot: internal hack for bundlerepo
73 73 coreconfigitem('bundle', 'mainreporoot',
74 74 default='',
75 75 )
76 76 # bundle.reorder: experimental config
77 77 coreconfigitem('bundle', 'reorder',
78 78 default='auto',
79 79 )
80 80 coreconfigitem('censor', 'policy',
81 81 default='abort',
82 82 )
83 83 coreconfigitem('chgserver', 'idletimeout',
84 84 default=3600,
85 85 )
86 86 coreconfigitem('chgserver', 'skiphash',
87 87 default=False,
88 88 )
89 89 coreconfigitem('cmdserver', 'log',
90 90 default=None,
91 91 )
92 92 coreconfigitem('color', 'mode',
93 93 default='auto',
94 94 )
95 95 coreconfigitem('color', 'pagermode',
96 96 default=dynamicdefault,
97 97 )
98 98 coreconfigitem('commands', 'status.relative',
99 99 default=False,
100 100 )
101 101 coreconfigitem('commands', 'status.skipstates',
102 102 default=[],
103 103 )
104 104 coreconfigitem('commands', 'status.verbose',
105 105 default=False,
106 106 )
107 107 coreconfigitem('commands', 'update.requiredest',
108 108 default=False,
109 109 )
110 110 coreconfigitem('devel', 'all-warnings',
111 111 default=False,
112 112 )
113 113 coreconfigitem('devel', 'bundle2.debug',
114 114 default=False,
115 115 )
116 116 coreconfigitem('devel', 'check-locks',
117 117 default=False,
118 118 )
119 119 coreconfigitem('devel', 'check-relroot',
120 120 default=False,
121 121 )
122 122 coreconfigitem('devel', 'default-date',
123 123 default=None,
124 124 )
125 125 coreconfigitem('devel', 'deprec-warn',
126 126 default=False,
127 127 )
128 128 coreconfigitem('devel', 'disableloaddefaultcerts',
129 129 default=False,
130 130 )
131 131 coreconfigitem('devel', 'legacy.exchange',
132 132 default=list,
133 133 )
134 134 coreconfigitem('devel', 'servercafile',
135 135 default='',
136 136 )
137 137 coreconfigitem('devel', 'serverexactprotocol',
138 138 default='',
139 139 )
140 140 coreconfigitem('devel', 'serverrequirecert',
141 141 default=False,
142 142 )
143 143 coreconfigitem('devel', 'strip-obsmarkers',
144 144 default=True,
145 145 )
146 146 coreconfigitem('email', 'charsets',
147 147 default=list,
148 148 )
149 149 coreconfigitem('email', 'method',
150 150 default='smtp',
151 151 )
152 152 coreconfigitem('experimental', 'bundle-phases',
153 153 default=False,
154 154 )
155 155 coreconfigitem('experimental', 'bundle2-advertise',
156 156 default=True,
157 157 )
158 158 coreconfigitem('experimental', 'bundle2-output-capture',
159 159 default=False,
160 160 )
161 161 coreconfigitem('experimental', 'bundle2.pushback',
162 162 default=False,
163 163 )
164 164 coreconfigitem('experimental', 'bundle2lazylocking',
165 165 default=False,
166 166 )
167 167 coreconfigitem('experimental', 'bundlecomplevel',
168 168 default=None,
169 169 )
170 170 coreconfigitem('experimental', 'changegroup3',
171 171 default=False,
172 172 )
173 173 coreconfigitem('experimental', 'clientcompressionengines',
174 174 default=list,
175 175 )
176 176 coreconfigitem('experimental', 'copytrace',
177 177 default='on',
178 178 )
179 179 coreconfigitem('experimental', 'crecordtest',
180 180 default=None,
181 181 )
182 182 coreconfigitem('experimental', 'editortmpinhg',
183 183 default=False,
184 184 )
185 185 coreconfigitem('experimental', 'stabilization',
186 186 default=list,
187 187 alias=[('experimental', 'evolution')],
188 188 )
189 189 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
190 190 default=False,
191 191 alias=[('experimental', 'evolution.bundle-obsmarker')],
192 192 )
193 193 coreconfigitem('experimental', 'stabilization.track-operation',
194 194 default=False,
195 195 alias=[('experimental', 'evolution.track-operation')]
196 196 )
197 197 coreconfigitem('experimental', 'exportableenviron',
198 198 default=list,
199 199 )
200 200 coreconfigitem('experimental', 'extendedheader.index',
201 201 default=None,
202 202 )
203 203 coreconfigitem('experimental', 'extendedheader.similarity',
204 204 default=False,
205 205 )
206 206 coreconfigitem('experimental', 'format.compression',
207 207 default='zlib',
208 208 )
209 209 coreconfigitem('experimental', 'graphshorten',
210 210 default=False,
211 211 )
212 212 coreconfigitem('experimental', 'hook-track-tags',
213 213 default=False,
214 214 )
215 215 coreconfigitem('experimental', 'httppostargs',
216 216 default=False,
217 217 )
218 218 coreconfigitem('experimental', 'manifestv2',
219 219 default=False,
220 220 )
221 221 coreconfigitem('experimental', 'mergedriver',
222 222 default=None,
223 223 )
224 224 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
225 225 default=False,
226 226 )
227 227 coreconfigitem('experimental', 'rebase.multidest',
228 228 default=False,
229 229 )
230 230 coreconfigitem('experimental', 'revertalternateinteractivemode',
231 231 default=True,
232 232 )
233 233 coreconfigitem('experimental', 'revlogv2',
234 234 default=None,
235 235 )
236 236 coreconfigitem('experimental', 'spacemovesdown',
237 237 default=False,
238 238 )
239 239 coreconfigitem('experimental', 'treemanifest',
240 240 default=False,
241 241 )
242 242 coreconfigitem('experimental', 'updatecheck',
243 243 default=None,
244 244 )
245 245 coreconfigitem('format', 'aggressivemergedeltas',
246 246 default=False,
247 247 )
248 248 coreconfigitem('format', 'chunkcachesize',
249 249 default=None,
250 250 )
251 251 coreconfigitem('format', 'dotencode',
252 252 default=True,
253 253 )
254 254 coreconfigitem('format', 'generaldelta',
255 255 default=False,
256 256 )
257 257 coreconfigitem('format', 'manifestcachesize',
258 258 default=None,
259 259 )
260 260 coreconfigitem('format', 'maxchainlen',
261 261 default=None,
262 262 )
263 263 coreconfigitem('format', 'obsstore-version',
264 264 default=None,
265 265 )
266 266 coreconfigitem('format', 'usefncache',
267 267 default=True,
268 268 )
269 269 coreconfigitem('format', 'usegeneraldelta',
270 270 default=True,
271 271 )
272 272 coreconfigitem('format', 'usestore',
273 273 default=True,
274 274 )
275 275 coreconfigitem('hostsecurity', 'ciphers',
276 276 default=None,
277 277 )
278 278 coreconfigitem('hostsecurity', 'disabletls10warning',
279 279 default=False,
280 280 )
281 281 coreconfigitem('http_proxy', 'always',
282 282 default=False,
283 283 )
284 284 coreconfigitem('http_proxy', 'host',
285 285 default=None,
286 286 )
287 287 coreconfigitem('http_proxy', 'no',
288 288 default=list,
289 289 )
290 290 coreconfigitem('http_proxy', 'passwd',
291 291 default=None,
292 292 )
293 293 coreconfigitem('http_proxy', 'user',
294 294 default=None,
295 295 )
296 296 coreconfigitem('merge', 'followcopies',
297 297 default=True,
298 298 )
299 299 coreconfigitem('pager', 'ignore',
300 300 default=list,
301 301 )
302 302 coreconfigitem('patch', 'eol',
303 303 default='strict',
304 304 )
305 305 coreconfigitem('patch', 'fuzz',
306 306 default=2,
307 307 )
308 308 coreconfigitem('paths', 'default',
309 309 default=None,
310 310 )
311 311 coreconfigitem('paths', 'default-push',
312 312 default=None,
313 313 )
314 314 coreconfigitem('phases', 'checksubrepos',
315 315 default='follow',
316 316 )
317 317 coreconfigitem('phases', 'publish',
318 318 default=True,
319 319 )
320 320 coreconfigitem('profiling', 'enabled',
321 321 default=False,
322 322 )
323 323 coreconfigitem('profiling', 'format',
324 324 default='text',
325 325 )
326 326 coreconfigitem('profiling', 'freq',
327 327 default=1000,
328 328 )
329 329 coreconfigitem('profiling', 'limit',
330 330 default=30,
331 331 )
332 332 coreconfigitem('profiling', 'nested',
333 333 default=0,
334 334 )
335 335 coreconfigitem('profiling', 'sort',
336 336 default='inlinetime',
337 337 )
338 338 coreconfigitem('profiling', 'statformat',
339 339 default='hotpath',
340 340 )
341 341 coreconfigitem('progress', 'assume-tty',
342 342 default=False,
343 343 )
344 344 coreconfigitem('progress', 'changedelay',
345 345 default=1,
346 346 )
347 347 coreconfigitem('progress', 'clear-complete',
348 348 default=True,
349 349 )
350 350 coreconfigitem('progress', 'debug',
351 351 default=False,
352 352 )
353 353 coreconfigitem('progress', 'delay',
354 354 default=3,
355 355 )
356 356 coreconfigitem('progress', 'disable',
357 357 default=False,
358 358 )
359 359 coreconfigitem('progress', 'estimate',
360 360 default=2,
361 361 )
362 362 coreconfigitem('progress', 'refresh',
363 363 default=0.1,
364 364 )
365 365 coreconfigitem('progress', 'width',
366 366 default=dynamicdefault,
367 367 )
368 368 coreconfigitem('push', 'pushvars.server',
369 369 default=False,
370 370 )
371 371 coreconfigitem('server', 'bundle1',
372 372 default=True,
373 373 )
374 374 coreconfigitem('server', 'bundle1gd',
375 375 default=None,
376 376 )
377 377 coreconfigitem('server', 'compressionengines',
378 378 default=list,
379 379 )
380 380 coreconfigitem('server', 'concurrent-push-mode',
381 381 default='strict',
382 382 )
383 383 coreconfigitem('server', 'disablefullbundle',
384 384 default=False,
385 385 )
386 386 coreconfigitem('server', 'maxhttpheaderlen',
387 387 default=1024,
388 388 )
389 389 coreconfigitem('server', 'preferuncompressed',
390 390 default=False,
391 391 )
392 392 coreconfigitem('server', 'uncompressed',
393 393 default=True,
394 394 )
395 395 coreconfigitem('server', 'uncompressedallowsecret',
396 396 default=False,
397 397 )
398 398 coreconfigitem('server', 'validate',
399 399 default=False,
400 400 )
401 401 coreconfigitem('server', 'zliblevel',
402 402 default=-1,
403 403 )
404 404 coreconfigitem('smtp', 'host',
405 405 default=None,
406 406 )
407 407 coreconfigitem('smtp', 'local_hostname',
408 408 default=None,
409 409 )
410 410 coreconfigitem('smtp', 'password',
411 411 default=None,
412 412 )
413 413 coreconfigitem('smtp', 'tls',
414 414 default='none',
415 415 )
416 416 coreconfigitem('smtp', 'username',
417 417 default=None,
418 418 )
419 419 coreconfigitem('sparse', 'missingwarning',
420 420 default=True,
421 421 )
422 422 coreconfigitem('trusted', 'groups',
423 423 default=list,
424 424 )
425 425 coreconfigitem('trusted', 'users',
426 426 default=list,
427 427 )
428 428 coreconfigitem('ui', '_usedassubrepo',
429 429 default=False,
430 430 )
431 431 coreconfigitem('ui', 'allowemptycommit',
432 432 default=False,
433 433 )
434 434 coreconfigitem('ui', 'archivemeta',
435 435 default=True,
436 436 )
437 437 coreconfigitem('ui', 'askusername',
438 438 default=False,
439 439 )
440 440 coreconfigitem('ui', 'clonebundlefallback',
441 441 default=False,
442 442 )
443 443 coreconfigitem('ui', 'clonebundleprefers',
444 444 default=list,
445 445 )
446 446 coreconfigitem('ui', 'clonebundles',
447 447 default=True,
448 448 )
449 449 coreconfigitem('ui', 'color',
450 450 default='auto',
451 451 )
452 452 coreconfigitem('ui', 'commitsubrepos',
453 453 default=False,
454 454 )
455 455 coreconfigitem('ui', 'debug',
456 456 default=False,
457 457 )
458 458 coreconfigitem('ui', 'debugger',
459 459 default=None,
460 460 )
461 461 coreconfigitem('ui', 'fallbackencoding',
462 462 default=None,
463 463 )
464 464 coreconfigitem('ui', 'forcecwd',
465 465 default=None,
466 466 )
467 467 coreconfigitem('ui', 'forcemerge',
468 468 default=None,
469 469 )
470 470 coreconfigitem('ui', 'formatdebug',
471 471 default=False,
472 472 )
473 473 coreconfigitem('ui', 'formatjson',
474 474 default=False,
475 475 )
476 476 coreconfigitem('ui', 'formatted',
477 477 default=None,
478 478 )
479 479 coreconfigitem('ui', 'graphnodetemplate',
480 480 default=None,
481 481 )
482 482 coreconfigitem('ui', 'http2debuglevel',
483 483 default=None,
484 484 )
485 485 coreconfigitem('ui', 'interactive',
486 486 default=None,
487 487 )
488 488 coreconfigitem('ui', 'interface',
489 489 default=None,
490 490 )
491 491 coreconfigitem('ui', 'logblockedtimes',
492 492 default=False,
493 493 )
494 494 coreconfigitem('ui', 'logtemplate',
495 495 default=None,
496 496 )
497 497 coreconfigitem('ui', 'merge',
498 498 default=None,
499 499 )
500 500 coreconfigitem('ui', 'mergemarkers',
501 501 default='basic',
502 502 )
503 503 coreconfigitem('ui', 'mergemarkertemplate',
504 504 default=('{node|short} '
505 505 '{ifeq(tags, "tip", "", '
506 506 'ifeq(tags, "", "", "{tags} "))}'
507 507 '{if(bookmarks, "{bookmarks} ")}'
508 508 '{ifeq(branch, "default", "", "{branch} ")}'
509 509 '- {author|user}: {desc|firstline}')
510 510 )
511 511 coreconfigitem('ui', 'nontty',
512 512 default=False,
513 513 )
514 514 coreconfigitem('ui', 'origbackuppath',
515 515 default=None,
516 516 )
517 517 coreconfigitem('ui', 'paginate',
518 518 default=True,
519 519 )
520 520 coreconfigitem('ui', 'patch',
521 521 default=None,
522 522 )
523 523 coreconfigitem('ui', 'portablefilenames',
524 524 default='warn',
525 525 )
526 526 coreconfigitem('ui', 'promptecho',
527 527 default=False,
528 528 )
529 529 coreconfigitem('ui', 'quiet',
530 530 default=False,
531 531 )
532 532 coreconfigitem('ui', 'quietbookmarkmove',
533 533 default=False,
534 534 )
535 535 coreconfigitem('ui', 'remotecmd',
536 536 default='hg',
537 537 )
538 538 coreconfigitem('ui', 'report_untrusted',
539 539 default=True,
540 540 )
541 541 coreconfigitem('ui', 'rollback',
542 542 default=True,
543 543 )
544 544 coreconfigitem('ui', 'slash',
545 545 default=False,
546 546 )
547 547 coreconfigitem('ui', 'ssh',
548 548 default='ssh',
549 549 )
550 550 coreconfigitem('ui', 'statuscopies',
551 551 default=False,
552 552 )
553 553 coreconfigitem('ui', 'strict',
554 554 default=False,
555 555 )
556 556 coreconfigitem('ui', 'style',
557 557 default='',
558 558 )
559 559 coreconfigitem('ui', 'supportcontact',
560 560 default=None,
561 561 )
562 562 coreconfigitem('ui', 'textwidth',
563 563 default=78,
564 564 )
565 565 coreconfigitem('ui', 'timeout',
566 566 default='600',
567 567 )
568 568 coreconfigitem('ui', 'traceback',
569 569 default=False,
570 570 )
571 571 coreconfigitem('ui', 'tweakdefaults',
572 572 default=False,
573 573 )
574 574 coreconfigitem('ui', 'usehttp2',
575 575 default=False,
576 576 )
577 577 coreconfigitem('ui', 'username',
578 578 alias=[('ui', 'user')]
579 579 )
580 580 coreconfigitem('ui', 'verbose',
581 581 default=False,
582 582 )
583 583 coreconfigitem('verify', 'skipflags',
584 584 default=None,
585 585 )
586 586 coreconfigitem('web', 'accesslog',
587 587 default='-',
588 588 )
589 589 coreconfigitem('web', 'address',
590 590 default='',
591 591 )
592 592 coreconfigitem('web', 'allow_archive',
593 593 default=list,
594 594 )
595 595 coreconfigitem('web', 'allow_read',
596 596 default=list,
597 597 )
598 598 coreconfigitem('web', 'baseurl',
599 599 default=None,
600 600 )
601 601 coreconfigitem('web', 'cacerts',
602 602 default=None,
603 603 )
604 604 coreconfigitem('web', 'certificate',
605 605 default=None,
606 606 )
607 607 coreconfigitem('web', 'collapse',
608 608 default=False,
609 609 )
610 610 coreconfigitem('web', 'csp',
611 611 default=None,
612 612 )
613 613 coreconfigitem('web', 'deny_read',
614 614 default=list,
615 615 )
616 616 coreconfigitem('web', 'descend',
617 617 default=True,
618 618 )
619 619 coreconfigitem('web', 'description',
620 620 default="",
621 621 )
622 622 coreconfigitem('web', 'encoding',
623 623 default=lambda: encoding.encoding,
624 624 )
625 625 coreconfigitem('web', 'errorlog',
626 626 default='-',
627 627 )
628 628 coreconfigitem('web', 'ipv6',
629 629 default=False,
630 630 )
631 631 coreconfigitem('web', 'port',
632 632 default=8000,
633 633 )
634 634 coreconfigitem('web', 'prefix',
635 635 default='',
636 636 )
637 637 coreconfigitem('web', 'refreshinterval',
638 638 default=20,
639 639 )
640 640 coreconfigitem('web', 'stripes',
641 641 default=1,
642 642 )
643 643 coreconfigitem('web', 'style',
644 644 default='paper',
645 645 )
646 coreconfigitem('web', 'templates',
647 default=None,
648 )
646 649 coreconfigitem('worker', 'backgroundclose',
647 650 default=dynamicdefault,
648 651 )
649 652 # Windows defaults to a limit of 512 open files. A buffer of 128
650 653 # should give us enough headway.
651 654 coreconfigitem('worker', 'backgroundclosemaxqueue',
652 655 default=384,
653 656 )
654 657 coreconfigitem('worker', 'backgroundcloseminfilecount',
655 658 default=2048,
656 659 )
657 660 coreconfigitem('worker', 'backgroundclosethreadcount',
658 661 default=4,
659 662 )
660 663 coreconfigitem('worker', 'numcpus',
661 664 default=None,
662 665 )
@@ -1,541 +1,541 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import os
12 12 import re
13 13 import time
14 14
15 15 from ..i18n import _
16 16
17 17 from .common import (
18 18 ErrorResponse,
19 19 HTTP_NOT_FOUND,
20 20 HTTP_OK,
21 21 HTTP_SERVER_ERROR,
22 22 cspvalues,
23 23 get_contact,
24 24 get_mtime,
25 25 ismember,
26 26 paritygen,
27 27 staticfile,
28 28 )
29 29 from .request import wsgirequest
30 30
31 31 from .. import (
32 32 configitems,
33 33 encoding,
34 34 error,
35 35 hg,
36 36 profiling,
37 37 scmutil,
38 38 templater,
39 39 ui as uimod,
40 40 util,
41 41 )
42 42
43 43 from . import (
44 44 hgweb_mod,
45 45 webutil,
46 46 wsgicgi,
47 47 )
48 48
49 49 def cleannames(items):
50 50 return [(util.pconvert(name).strip('/'), path) for name, path in items]
51 51
52 52 def findrepos(paths):
53 53 repos = []
54 54 for prefix, root in cleannames(paths):
55 55 roothead, roottail = os.path.split(root)
56 56 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
57 57 # /bar/ be served as as foo/N .
58 58 # '*' will not search inside dirs with .hg (except .hg/patches),
59 59 # '**' will search inside dirs with .hg (and thus also find subrepos).
60 60 try:
61 61 recurse = {'*': False, '**': True}[roottail]
62 62 except KeyError:
63 63 repos.append((prefix, root))
64 64 continue
65 65 roothead = os.path.normpath(os.path.abspath(roothead))
66 66 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
67 67 repos.extend(urlrepos(prefix, roothead, paths))
68 68 return repos
69 69
70 70 def urlrepos(prefix, roothead, paths):
71 71 """yield url paths and filesystem paths from a list of repo paths
72 72
73 73 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
74 74 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
75 75 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
76 76 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
77 77 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
78 78 """
79 79 for path in paths:
80 80 path = os.path.normpath(path)
81 81 yield (prefix + '/' +
82 82 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
83 83
84 84 def geturlcgivars(baseurl, port):
85 85 """
86 86 Extract CGI variables from baseurl
87 87
88 88 >>> geturlcgivars(b"http://host.org/base", b"80")
89 89 ('host.org', '80', '/base')
90 90 >>> geturlcgivars(b"http://host.org:8000/base", b"80")
91 91 ('host.org', '8000', '/base')
92 92 >>> geturlcgivars(b'/base', 8000)
93 93 ('', '8000', '/base')
94 94 >>> geturlcgivars(b"base", b'8000')
95 95 ('', '8000', '/base')
96 96 >>> geturlcgivars(b"http://host", b'8000')
97 97 ('host', '8000', '/')
98 98 >>> geturlcgivars(b"http://host/", b'8000')
99 99 ('host', '8000', '/')
100 100 """
101 101 u = util.url(baseurl)
102 102 name = u.host or ''
103 103 if u.port:
104 104 port = u.port
105 105 path = u.path or ""
106 106 if not path.startswith('/'):
107 107 path = '/' + path
108 108
109 109 return name, str(port), path
110 110
111 111 class hgwebdir(object):
112 112 """HTTP server for multiple repositories.
113 113
114 114 Given a configuration, different repositories will be served depending
115 115 on the request path.
116 116
117 117 Instances are typically used as WSGI applications.
118 118 """
119 119 def __init__(self, conf, baseui=None):
120 120 self.conf = conf
121 121 self.baseui = baseui
122 122 self.ui = None
123 123 self.lastrefresh = 0
124 124 self.motd = None
125 125 self.refresh()
126 126
127 127 def refresh(self):
128 128 if self.ui:
129 129 refreshinterval = self.ui.configint('web', 'refreshinterval')
130 130 else:
131 131 item = configitems.coreitems['web']['refreshinterval']
132 132 refreshinterval = item.default
133 133
134 134 # refreshinterval <= 0 means to always refresh.
135 135 if (refreshinterval > 0 and
136 136 self.lastrefresh + refreshinterval > time.time()):
137 137 return
138 138
139 139 if self.baseui:
140 140 u = self.baseui.copy()
141 141 else:
142 142 u = uimod.ui.load()
143 143 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
144 144 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
145 145 # displaying bundling progress bar while serving feels wrong and may
146 146 # break some wsgi implementations.
147 147 u.setconfig('progress', 'disable', 'true', 'hgweb')
148 148
149 149 if not isinstance(self.conf, (dict, list, tuple)):
150 150 map = {'paths': 'hgweb-paths'}
151 151 if not os.path.exists(self.conf):
152 152 raise error.Abort(_('config file %s not found!') % self.conf)
153 153 u.readconfig(self.conf, remap=map, trust=True)
154 154 paths = []
155 155 for name, ignored in u.configitems('hgweb-paths'):
156 156 for path in u.configlist('hgweb-paths', name):
157 157 paths.append((name, path))
158 158 elif isinstance(self.conf, (list, tuple)):
159 159 paths = self.conf
160 160 elif isinstance(self.conf, dict):
161 161 paths = self.conf.items()
162 162
163 163 repos = findrepos(paths)
164 164 for prefix, root in u.configitems('collections'):
165 165 prefix = util.pconvert(prefix)
166 166 for path in scmutil.walkrepos(root, followsym=True):
167 167 repo = os.path.normpath(path)
168 168 name = util.pconvert(repo)
169 169 if name.startswith(prefix):
170 170 name = name[len(prefix):]
171 171 repos.append((name.lstrip('/'), repo))
172 172
173 173 self.repos = repos
174 174 self.ui = u
175 175 encoding.encoding = self.ui.config('web', 'encoding')
176 176 self.style = self.ui.config('web', 'style')
177 self.templatepath = self.ui.config('web', 'templates', None)
177 self.templatepath = self.ui.config('web', 'templates')
178 178 self.stripecount = self.ui.config('web', 'stripes')
179 179 if self.stripecount:
180 180 self.stripecount = int(self.stripecount)
181 181 self._baseurl = self.ui.config('web', 'baseurl')
182 182 prefix = self.ui.config('web', 'prefix')
183 183 if prefix.startswith('/'):
184 184 prefix = prefix[1:]
185 185 if prefix.endswith('/'):
186 186 prefix = prefix[:-1]
187 187 self.prefix = prefix
188 188 self.lastrefresh = time.time()
189 189
190 190 def run(self):
191 191 if not encoding.environ.get('GATEWAY_INTERFACE',
192 192 '').startswith("CGI/1."):
193 193 raise RuntimeError("This function is only intended to be "
194 194 "called while running as a CGI script.")
195 195 wsgicgi.launch(self)
196 196
197 197 def __call__(self, env, respond):
198 198 req = wsgirequest(env, respond)
199 199 return self.run_wsgi(req)
200 200
201 201 def read_allowed(self, ui, req):
202 202 """Check allow_read and deny_read config options of a repo's ui object
203 203 to determine user permissions. By default, with neither option set (or
204 204 both empty), allow all users to read the repo. There are two ways a
205 205 user can be denied read access: (1) deny_read is not empty, and the
206 206 user is unauthenticated or deny_read contains user (or *), and (2)
207 207 allow_read is not empty and the user is not in allow_read. Return True
208 208 if user is allowed to read the repo, else return False."""
209 209
210 210 user = req.env.get('REMOTE_USER')
211 211
212 212 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
213 213 if deny_read and (not user or ismember(ui, user, deny_read)):
214 214 return False
215 215
216 216 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
217 217 # by default, allow reading if no allow_read option has been set
218 218 if (not allow_read) or ismember(ui, user, allow_read):
219 219 return True
220 220
221 221 return False
222 222
223 223 def run_wsgi(self, req):
224 224 profile = self.ui.configbool('profiling', 'enabled')
225 225 with profiling.profile(self.ui, enabled=profile):
226 226 for r in self._runwsgi(req):
227 227 yield r
228 228
229 229 def _runwsgi(self, req):
230 230 try:
231 231 self.refresh()
232 232
233 233 csp, nonce = cspvalues(self.ui)
234 234 if csp:
235 235 req.headers.append(('Content-Security-Policy', csp))
236 236
237 237 virtual = req.env.get("PATH_INFO", "").strip('/')
238 238 tmpl = self.templater(req, nonce)
239 239 ctype = tmpl('mimetype', encoding=encoding.encoding)
240 240 ctype = templater.stringify(ctype)
241 241
242 242 # a static file
243 243 if virtual.startswith('static/') or 'static' in req.form:
244 244 if virtual.startswith('static/'):
245 245 fname = virtual[7:]
246 246 else:
247 247 fname = req.form['static'][0]
248 248 static = self.ui.config("web", "static", None,
249 249 untrusted=False)
250 250 if not static:
251 251 tp = self.templatepath or templater.templatepaths()
252 252 if isinstance(tp, str):
253 253 tp = [tp]
254 254 static = [os.path.join(p, 'static') for p in tp]
255 255 staticfile(static, fname, req)
256 256 return []
257 257
258 258 # top-level index
259 259
260 260 repos = dict(self.repos)
261 261
262 262 if (not virtual or virtual == 'index') and virtual not in repos:
263 263 req.respond(HTTP_OK, ctype)
264 264 return self.makeindex(req, tmpl)
265 265
266 266 # nested indexes and hgwebs
267 267
268 268 if virtual.endswith('/index') and virtual not in repos:
269 269 subdir = virtual[:-len('index')]
270 270 if any(r.startswith(subdir) for r in repos):
271 271 req.respond(HTTP_OK, ctype)
272 272 return self.makeindex(req, tmpl, subdir)
273 273
274 274 def _virtualdirs():
275 275 # Check the full virtual path, each parent, and the root ('')
276 276 if virtual != '':
277 277 yield virtual
278 278
279 279 for p in util.finddirs(virtual):
280 280 yield p
281 281
282 282 yield ''
283 283
284 284 for virtualrepo in _virtualdirs():
285 285 real = repos.get(virtualrepo)
286 286 if real:
287 287 req.env['REPO_NAME'] = virtualrepo
288 288 try:
289 289 # ensure caller gets private copy of ui
290 290 repo = hg.repository(self.ui.copy(), real)
291 291 return hgweb_mod.hgweb(repo).run_wsgi(req)
292 292 except IOError as inst:
293 293 msg = encoding.strtolocal(inst.strerror)
294 294 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
295 295 except error.RepoError as inst:
296 296 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
297 297
298 298 # browse subdirectories
299 299 subdir = virtual + '/'
300 300 if [r for r in repos if r.startswith(subdir)]:
301 301 req.respond(HTTP_OK, ctype)
302 302 return self.makeindex(req, tmpl, subdir)
303 303
304 304 # prefixes not found
305 305 req.respond(HTTP_NOT_FOUND, ctype)
306 306 return tmpl("notfound", repo=virtual)
307 307
308 308 except ErrorResponse as err:
309 309 req.respond(err, ctype)
310 310 return tmpl('error', error=err.message or '')
311 311 finally:
312 312 tmpl = None
313 313
314 314 def makeindex(self, req, tmpl, subdir=""):
315 315
316 316 def archivelist(ui, nodeid, url):
317 317 allowed = ui.configlist("web", "allow_archive", untrusted=True)
318 318 archives = []
319 319 for typ, spec in hgweb_mod.archivespecs.iteritems():
320 320 if typ in allowed or ui.configbool("web", "allow" + typ,
321 321 untrusted=True):
322 322 archives.append({"type" : typ, "extension": spec[2],
323 323 "node": nodeid, "url": url})
324 324 return archives
325 325
326 326 def rawentries(subdir="", **map):
327 327
328 328 descend = self.ui.configbool('web', 'descend')
329 329 collapse = self.ui.configbool('web', 'collapse')
330 330 seenrepos = set()
331 331 seendirs = set()
332 332 for name, path in self.repos:
333 333
334 334 if not name.startswith(subdir):
335 335 continue
336 336 name = name[len(subdir):]
337 337 directory = False
338 338
339 339 if '/' in name:
340 340 if not descend:
341 341 continue
342 342
343 343 nameparts = name.split('/')
344 344 rootname = nameparts[0]
345 345
346 346 if not collapse:
347 347 pass
348 348 elif rootname in seendirs:
349 349 continue
350 350 elif rootname in seenrepos:
351 351 pass
352 352 else:
353 353 directory = True
354 354 name = rootname
355 355
356 356 # redefine the path to refer to the directory
357 357 discarded = '/'.join(nameparts[1:])
358 358
359 359 # remove name parts plus accompanying slash
360 360 path = path[:-len(discarded) - 1]
361 361
362 362 try:
363 363 r = hg.repository(self.ui, path)
364 364 directory = False
365 365 except (IOError, error.RepoError):
366 366 pass
367 367
368 368 parts = [name]
369 369 parts.insert(0, '/' + subdir.rstrip('/'))
370 370 if req.env['SCRIPT_NAME']:
371 371 parts.insert(0, req.env['SCRIPT_NAME'])
372 372 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
373 373
374 374 # show either a directory entry or a repository
375 375 if directory:
376 376 # get the directory's time information
377 377 try:
378 378 d = (get_mtime(path), util.makedate()[1])
379 379 except OSError:
380 380 continue
381 381
382 382 # add '/' to the name to make it obvious that
383 383 # the entry is a directory, not a regular repository
384 384 row = {'contact': "",
385 385 'contact_sort': "",
386 386 'name': name + '/',
387 387 'name_sort': name,
388 388 'url': url,
389 389 'description': "",
390 390 'description_sort': "",
391 391 'lastchange': d,
392 392 'lastchange_sort': d[1]-d[0],
393 393 'archives': [],
394 394 'isdirectory': True,
395 395 'labels': [],
396 396 }
397 397
398 398 seendirs.add(name)
399 399 yield row
400 400 continue
401 401
402 402 u = self.ui.copy()
403 403 try:
404 404 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
405 405 except Exception as e:
406 406 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
407 407 continue
408 408 def get(section, name, default=uimod._unset):
409 409 return u.config(section, name, default, untrusted=True)
410 410
411 411 if u.configbool("web", "hidden", untrusted=True):
412 412 continue
413 413
414 414 if not self.read_allowed(u, req):
415 415 continue
416 416
417 417 # update time with local timezone
418 418 try:
419 419 r = hg.repository(self.ui, path)
420 420 except IOError:
421 421 u.warn(_('error accessing repository at %s\n') % path)
422 422 continue
423 423 except error.RepoError:
424 424 u.warn(_('error accessing repository at %s\n') % path)
425 425 continue
426 426 try:
427 427 d = (get_mtime(r.spath), util.makedate()[1])
428 428 except OSError:
429 429 continue
430 430
431 431 contact = get_contact(get)
432 432 description = get("web", "description")
433 433 seenrepos.add(name)
434 434 name = get("web", "name", name)
435 435 row = {'contact': contact or "unknown",
436 436 'contact_sort': contact.upper() or "unknown",
437 437 'name': name,
438 438 'name_sort': name,
439 439 'url': url,
440 440 'description': description or "unknown",
441 441 'description_sort': description.upper() or "unknown",
442 442 'lastchange': d,
443 443 'lastchange_sort': d[1]-d[0],
444 444 'archives': archivelist(u, "tip", url),
445 445 'isdirectory': None,
446 446 'labels': u.configlist('web', 'labels', untrusted=True),
447 447 }
448 448
449 449 yield row
450 450
451 451 sortdefault = None, False
452 452 def entries(sortcolumn="", descending=False, subdir="", **map):
453 453 rows = rawentries(subdir=subdir, **map)
454 454
455 455 if sortcolumn and sortdefault != (sortcolumn, descending):
456 456 sortkey = '%s_sort' % sortcolumn
457 457 rows = sorted(rows, key=lambda x: x[sortkey],
458 458 reverse=descending)
459 459 for row, parity in zip(rows, paritygen(self.stripecount)):
460 460 row['parity'] = parity
461 461 yield row
462 462
463 463 self.refresh()
464 464 sortable = ["name", "description", "contact", "lastchange"]
465 465 sortcolumn, descending = sortdefault
466 466 if 'sort' in req.form:
467 467 sortcolumn = req.form['sort'][0]
468 468 descending = sortcolumn.startswith('-')
469 469 if descending:
470 470 sortcolumn = sortcolumn[1:]
471 471 if sortcolumn not in sortable:
472 472 sortcolumn = ""
473 473
474 474 sort = [("sort_%s" % column,
475 475 "%s%s" % ((not descending and column == sortcolumn)
476 476 and "-" or "", column))
477 477 for column in sortable]
478 478
479 479 self.refresh()
480 480 self.updatereqenv(req.env)
481 481
482 482 return tmpl("index", entries=entries, subdir=subdir,
483 483 pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
484 484 sortcolumn=sortcolumn, descending=descending,
485 485 **dict(sort))
486 486
487 487 def templater(self, req, nonce):
488 488
489 489 def motd(**map):
490 490 if self.motd is not None:
491 491 yield self.motd
492 492 else:
493 493 yield config('web', 'motd', '')
494 494
495 495 def config(section, name, default=uimod._unset, untrusted=True):
496 496 return self.ui.config(section, name, default, untrusted)
497 497
498 498 self.updatereqenv(req.env)
499 499
500 500 url = req.env.get('SCRIPT_NAME', '')
501 501 if not url.endswith('/'):
502 502 url += '/'
503 503
504 504 vars = {}
505 505 styles = (
506 506 req.form.get('style', [None])[0],
507 507 config('web', 'style'),
508 508 'paper'
509 509 )
510 510 style, mapfile = templater.stylemap(styles, self.templatepath)
511 511 if style == styles[0]:
512 512 vars['style'] = style
513 513
514 514 start = url[-1] == '?' and '&' or '?'
515 515 sessionvars = webutil.sessionvars(vars, start)
516 516 logourl = config('web', 'logourl', 'https://mercurial-scm.org/')
517 517 logoimg = config('web', 'logoimg', 'hglogo.png')
518 518 staticurl = config('web', 'staticurl') or url + 'static/'
519 519 if not staticurl.endswith('/'):
520 520 staticurl += '/'
521 521
522 522 defaults = {
523 523 "encoding": encoding.encoding,
524 524 "motd": motd,
525 525 "url": url,
526 526 "logourl": logourl,
527 527 "logoimg": logoimg,
528 528 "staticurl": staticurl,
529 529 "sessionvars": sessionvars,
530 530 "style": style,
531 531 "nonce": nonce,
532 532 }
533 533 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
534 534 return tmpl
535 535
536 536 def updatereqenv(self, env):
537 537 if self._baseurl is not None:
538 538 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
539 539 env['SERVER_NAME'] = name
540 540 env['SERVER_PORT'] = port
541 541 env['SCRIPT_NAME'] = path
General Comments 0
You need to be logged in to leave comments. Login now