comparison mercurial/hgweb/hgwebdir_mod.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents d8e55c0c642c
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
48 webutil, 48 webutil,
49 wsgicgi, 49 wsgicgi,
50 ) 50 )
51 from ..utils import dateutil 51 from ..utils import dateutil
52 52
53
53 def cleannames(items): 54 def cleannames(items):
54 return [(util.pconvert(name).strip('/'), path) for name, path in items] 55 return [(util.pconvert(name).strip('/'), path) for name, path in items]
56
55 57
56 def findrepos(paths): 58 def findrepos(paths):
57 repos = [] 59 repos = []
58 for prefix, root in cleannames(paths): 60 for prefix, root in cleannames(paths):
59 roothead, roottail = os.path.split(root) 61 roothead, roottail = os.path.split(root)
69 roothead = os.path.normpath(os.path.abspath(roothead)) 71 roothead = os.path.normpath(os.path.abspath(roothead))
70 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse) 72 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
71 repos.extend(urlrepos(prefix, roothead, paths)) 73 repos.extend(urlrepos(prefix, roothead, paths))
72 return repos 74 return repos
73 75
76
74 def urlrepos(prefix, roothead, paths): 77 def urlrepos(prefix, roothead, paths):
75 """yield url paths and filesystem paths from a list of repo paths 78 """yield url paths and filesystem paths from a list of repo paths
76 79
77 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq] 80 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
78 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt'])) 81 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
80 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt'])) 83 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
81 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')] 84 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
82 """ 85 """
83 for path in paths: 86 for path in paths:
84 path = os.path.normpath(path) 87 path = os.path.normpath(path)
85 yield (prefix + '/' + 88 yield (
86 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path 89 prefix + '/' + util.pconvert(path[len(roothead) :]).lstrip('/')
90 ).strip('/'), path
91
87 92
88 def readallowed(ui, req): 93 def readallowed(ui, req):
89 """Check allow_read and deny_read config options of a repo's ui object 94 """Check allow_read and deny_read config options of a repo's ui object
90 to determine user permissions. By default, with neither option set (or 95 to determine user permissions. By default, with neither option set (or
91 both empty), allow all users to read the repo. There are two ways a 96 both empty), allow all users to read the repo. There are two ways a
105 if not allow_read or ismember(ui, user, allow_read): 110 if not allow_read or ismember(ui, user, allow_read):
106 return True 111 return True
107 112
108 return False 113 return False
109 114
115
110 def rawindexentries(ui, repos, req, subdir=''): 116 def rawindexentries(ui, repos, req, subdir=''):
111 descend = ui.configbool('web', 'descend') 117 descend = ui.configbool('web', 'descend')
112 collapse = ui.configbool('web', 'collapse') 118 collapse = ui.configbool('web', 'collapse')
113 seenrepos = set() 119 seenrepos = set()
114 seendirs = set() 120 seendirs = set()
115 for name, path in repos: 121 for name, path in repos:
116 122
117 if not name.startswith(subdir): 123 if not name.startswith(subdir):
118 continue 124 continue
119 name = name[len(subdir):] 125 name = name[len(subdir) :]
120 directory = False 126 directory = False
121 127
122 if '/' in name: 128 if '/' in name:
123 if not descend: 129 if not descend:
124 continue 130 continue
138 144
139 # redefine the path to refer to the directory 145 # redefine the path to refer to the directory
140 discarded = '/'.join(nameparts[1:]) 146 discarded = '/'.join(nameparts[1:])
141 147
142 # remove name parts plus accompanying slash 148 # remove name parts plus accompanying slash
143 path = path[:-len(discarded) - 1] 149 path = path[: -len(discarded) - 1]
144 150
145 try: 151 try:
146 hg.repository(ui, path) 152 hg.repository(ui, path)
147 directory = False 153 directory = False
148 except (IOError, error.RepoError): 154 except (IOError, error.RepoError):
163 except OSError: 169 except OSError:
164 continue 170 continue
165 171
166 # add '/' to the name to make it obvious that 172 # add '/' to the name to make it obvious that
167 # the entry is a directory, not a regular repository 173 # the entry is a directory, not a regular repository
168 row = {'contact': "", 174 row = {
169 'contact_sort': "", 175 'contact': "",
170 'name': name + '/', 176 'contact_sort': "",
171 'name_sort': name, 177 'name': name + '/',
172 'url': url, 178 'name_sort': name,
173 'description': "", 179 'url': url,
174 'description_sort': "", 180 'description': "",
175 'lastchange': d, 181 'description_sort': "",
176 'lastchange_sort': d[1] - d[0], 182 'lastchange': d,
177 'archives': templateutil.mappinglist([]), 183 'lastchange_sort': d[1] - d[0],
178 'isdirectory': True, 184 'archives': templateutil.mappinglist([]),
179 'labels': templateutil.hybridlist([], name='label'), 185 'isdirectory': True,
180 } 186 'labels': templateutil.hybridlist([], name='label'),
187 }
181 188
182 seendirs.add(name) 189 seendirs.add(name)
183 yield row 190 yield row
184 continue 191 continue
185 192
216 contact = get_contact(get) 223 contact = get_contact(get)
217 description = get("web", "description") 224 description = get("web", "description")
218 seenrepos.add(name) 225 seenrepos.add(name)
219 name = get("web", "name", name) 226 name = get("web", "name", name)
220 labels = u.configlist('web', 'labels', untrusted=True) 227 labels = u.configlist('web', 'labels', untrusted=True)
221 row = {'contact': contact or "unknown", 228 row = {
222 'contact_sort': contact.upper() or "unknown", 229 'contact': contact or "unknown",
223 'name': name, 230 'contact_sort': contact.upper() or "unknown",
224 'name_sort': name, 231 'name': name,
225 'url': url, 232 'name_sort': name,
226 'description': description or "unknown", 233 'url': url,
227 'description_sort': description.upper() or "unknown", 234 'description': description or "unknown",
228 'lastchange': d, 235 'description_sort': description.upper() or "unknown",
229 'lastchange_sort': d[1] - d[0], 236 'lastchange': d,
230 'archives': webutil.archivelist(u, "tip", url), 237 'lastchange_sort': d[1] - d[0],
231 'isdirectory': None, 238 'archives': webutil.archivelist(u, "tip", url),
232 'labels': templateutil.hybridlist(labels, name='label'), 239 'isdirectory': None,
233 } 240 'labels': templateutil.hybridlist(labels, name='label'),
241 }
234 242
235 yield row 243 yield row
236 244
237 def _indexentriesgen(context, ui, repos, req, stripecount, sortcolumn, 245
238 descending, subdir): 246 def _indexentriesgen(
247 context, ui, repos, req, stripecount, sortcolumn, descending, subdir
248 ):
239 rows = rawindexentries(ui, repos, req, subdir=subdir) 249 rows = rawindexentries(ui, repos, req, subdir=subdir)
240 250
241 sortdefault = None, False 251 sortdefault = None, False
242 252
243 if sortcolumn and sortdefault != (sortcolumn, descending): 253 if sortcolumn and sortdefault != (sortcolumn, descending):
244 sortkey = '%s_sort' % sortcolumn 254 sortkey = '%s_sort' % sortcolumn
245 rows = sorted(rows, key=lambda x: x[sortkey], 255 rows = sorted(rows, key=lambda x: x[sortkey], reverse=descending)
246 reverse=descending)
247 256
248 for row, parity in zip(rows, paritygen(stripecount)): 257 for row, parity in zip(rows, paritygen(stripecount)):
249 row['parity'] = parity 258 row['parity'] = parity
250 yield row 259 yield row
251 260
252 def indexentries(ui, repos, req, stripecount, sortcolumn='', 261
253 descending=False, subdir=''): 262 def indexentries(
263 ui, repos, req, stripecount, sortcolumn='', descending=False, subdir=''
264 ):
254 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir) 265 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir)
255 return templateutil.mappinggenerator(_indexentriesgen, args=args) 266 return templateutil.mappinggenerator(_indexentriesgen, args=args)
256 267
268
257 class hgwebdir(object): 269 class hgwebdir(object):
258 """HTTP server for multiple repositories. 270 """HTTP server for multiple repositories.
259 271
260 Given a configuration, different repositories will be served depending 272 Given a configuration, different repositories will be served depending
261 on the request path. 273 on the request path.
262 274
263 Instances are typically used as WSGI applications. 275 Instances are typically used as WSGI applications.
264 """ 276 """
277
265 def __init__(self, conf, baseui=None): 278 def __init__(self, conf, baseui=None):
266 self.conf = conf 279 self.conf = conf
267 self.baseui = baseui 280 self.baseui = baseui
268 self.ui = None 281 self.ui = None
269 self.lastrefresh = 0 282 self.lastrefresh = 0
280 else: 293 else:
281 item = configitems.coreitems['web']['refreshinterval'] 294 item = configitems.coreitems['web']['refreshinterval']
282 refreshinterval = item.default 295 refreshinterval = item.default
283 296
284 # refreshinterval <= 0 means to always refresh. 297 # refreshinterval <= 0 means to always refresh.
285 if (refreshinterval > 0 and 298 if (
286 self.lastrefresh + refreshinterval > time.time()): 299 refreshinterval > 0
300 and self.lastrefresh + refreshinterval > time.time()
301 ):
287 return 302 return
288 303
289 if self.baseui: 304 if self.baseui:
290 u = self.baseui.copy() 305 u = self.baseui.copy()
291 else: 306 else:
316 prefix = util.pconvert(prefix) 331 prefix = util.pconvert(prefix)
317 for path in scmutil.walkrepos(root, followsym=True): 332 for path in scmutil.walkrepos(root, followsym=True):
318 repo = os.path.normpath(path) 333 repo = os.path.normpath(path)
319 name = util.pconvert(repo) 334 name = util.pconvert(repo)
320 if name.startswith(prefix): 335 if name.startswith(prefix):
321 name = name[len(prefix):] 336 name = name[len(prefix) :]
322 repos.append((name.lstrip('/'), repo)) 337 repos.append((name.lstrip('/'), repo))
323 338
324 self.repos = repos 339 self.repos = repos
325 self.ui = u 340 self.ui = u
326 encoding.encoding = self.ui.config('web', 'encoding') 341 encoding.encoding = self.ui.config('web', 'encoding')
336 prefix = prefix[:-1] 351 prefix = prefix[:-1]
337 self.prefix = prefix 352 self.prefix = prefix
338 self.lastrefresh = time.time() 353 self.lastrefresh = time.time()
339 354
340 def run(self): 355 def run(self):
341 if not encoding.environ.get('GATEWAY_INTERFACE', 356 if not encoding.environ.get('GATEWAY_INTERFACE', '').startswith(
342 '').startswith("CGI/1."): 357 "CGI/1."
343 raise RuntimeError("This function is only intended to be " 358 ):
344 "called while running as a CGI script.") 359 raise RuntimeError(
360 "This function is only intended to be "
361 "called while running as a CGI script."
362 )
345 wsgicgi.launch(self) 363 wsgicgi.launch(self)
346 364
347 def __call__(self, env, respond): 365 def __call__(self, env, respond):
348 baseurl = self.ui.config('web', 'baseurl') 366 baseurl = self.ui.config('web', 'baseurl')
349 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl) 367 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl)
407 return self.makeindex(req, res, tmpl) 425 return self.makeindex(req, res, tmpl)
408 426
409 # nested indexes and hgwebs 427 # nested indexes and hgwebs
410 428
411 if virtual.endswith('/index') and virtual not in repos: 429 if virtual.endswith('/index') and virtual not in repos:
412 subdir = virtual[:-len('index')] 430 subdir = virtual[: -len('index')]
413 if any(r.startswith(subdir) for r in repos): 431 if any(r.startswith(subdir) for r in repos):
414 return self.makeindex(req, res, tmpl, subdir) 432 return self.makeindex(req, res, tmpl, subdir)
415 433
416 def _virtualdirs(): 434 def _virtualdirs():
417 # Check the full virtual path, and each parent 435 # Check the full virtual path, and each parent
424 if real: 442 if real:
425 # Re-parse the WSGI environment to take into account our 443 # Re-parse the WSGI environment to take into account our
426 # repository path component. 444 # repository path component.
427 uenv = req.rawenv 445 uenv = req.rawenv
428 if pycompat.ispy3: 446 if pycompat.ispy3:
429 uenv = {k.decode('latin1'): v for k, v in 447 uenv = {
430 uenv.iteritems()} 448 k.decode('latin1'): v for k, v in uenv.iteritems()
449 }
431 req = requestmod.parserequestfromenv( 450 req = requestmod.parserequestfromenv(
432 uenv, reponame=virtualrepo, 451 uenv,
452 reponame=virtualrepo,
433 altbaseurl=self.ui.config('web', 'baseurl'), 453 altbaseurl=self.ui.config('web', 'baseurl'),
434 # Reuse wrapped body file object otherwise state 454 # Reuse wrapped body file object otherwise state
435 # tracking can get confused. 455 # tracking can get confused.
436 bodyfh=req.bodyfh) 456 bodyfh=req.bodyfh,
457 )
437 try: 458 try:
438 # ensure caller gets private copy of ui 459 # ensure caller gets private copy of ui
439 repo = hg.repository(self.ui.copy(), real) 460 repo = hg.repository(self.ui.copy(), real)
440 return hgweb_mod.hgweb(repo).run_wsgi(req, res) 461 return hgweb_mod.hgweb(repo).run_wsgi(req, res)
441 except IOError as inst: 462 except IOError as inst:
471 if descending: 492 if descending:
472 sortcolumn = sortcolumn[1:] 493 sortcolumn = sortcolumn[1:]
473 if sortcolumn not in sortable: 494 if sortcolumn not in sortable:
474 sortcolumn = "" 495 sortcolumn = ""
475 496
476 sort = [("sort_%s" % column, 497 sort = [
477 "%s%s" % ((not descending and column == sortcolumn) 498 (
478 and "-" or "", column)) 499 "sort_%s" % column,
479 for column in sortable] 500 "%s%s"
501 % (
502 (not descending and column == sortcolumn) and "-" or "",
503 column,
504 ),
505 )
506 for column in sortable
507 ]
480 508
481 self.refresh() 509 self.refresh()
482 510
483 entries = indexentries(self.ui, self.repos, req, 511 entries = indexentries(
484 self.stripecount, sortcolumn=sortcolumn, 512 self.ui,
485 descending=descending, subdir=subdir) 513 self.repos,
514 req,
515 self.stripecount,
516 sortcolumn=sortcolumn,
517 descending=descending,
518 subdir=subdir,
519 )
486 520
487 mapping = { 521 mapping = {
488 'entries': entries, 522 'entries': entries,
489 'subdir': subdir, 523 'subdir': subdir,
490 'pathdef': hgweb_mod.makebreadcrumb('/' + subdir, self.prefix), 524 'pathdef': hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
494 mapping.update(sort) 528 mapping.update(sort)
495 res.setbodygen(tmpl.generate('index', mapping)) 529 res.setbodygen(tmpl.generate('index', mapping))
496 return res.sendresponse() 530 return res.sendresponse()
497 531
498 def templater(self, req, nonce): 532 def templater(self, req, nonce):
499
500 def config(section, name, default=uimod._unset, untrusted=True): 533 def config(section, name, default=uimod._unset, untrusted=True):
501 return self.ui.config(section, name, default, untrusted) 534 return self.ui.config(section, name, default, untrusted)
502 535
503 vars = {} 536 vars = {}
504 styles, (style, mapfile) = hgweb_mod.getstyle(req, config, 537 styles, (style, mapfile) = hgweb_mod.getstyle(
505 self.templatepath) 538 req, config, self.templatepath
539 )
506 if style == styles[0]: 540 if style == styles[0]:
507 vars['style'] = style 541 vars['style'] = style
508 542
509 sessionvars = webutil.sessionvars(vars, '?') 543 sessionvars = webutil.sessionvars(vars, '?')
510 logourl = config('web', 'logourl') 544 logourl = config('web', 'logourl')
511 logoimg = config('web', 'logoimg') 545 logoimg = config('web', 'logoimg')
512 staticurl = (config('web', 'staticurl') 546 staticurl = (
513 or req.apppath.rstrip('/') + '/static/') 547 config('web', 'staticurl') or req.apppath.rstrip('/') + '/static/'
548 )
514 if not staticurl.endswith('/'): 549 if not staticurl.endswith('/'):
515 staticurl += '/' 550 staticurl += '/'
516 551
517 defaults = { 552 defaults = {
518 "encoding": encoding.encoding, 553 "encoding": encoding.encoding,
523 "sessionvars": sessionvars, 558 "sessionvars": sessionvars,
524 "style": style, 559 "style": style,
525 "nonce": nonce, 560 "nonce": nonce,
526 } 561 }
527 templatekeyword = registrar.templatekeyword(defaults) 562 templatekeyword = registrar.templatekeyword(defaults)
563
528 @templatekeyword('motd', requires=()) 564 @templatekeyword('motd', requires=())
529 def motd(context, mapping): 565 def motd(context, mapping):
530 if self.motd is not None: 566 if self.motd is not None:
531 yield self.motd 567 yield self.motd
532 else: 568 else: