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