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 |
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 |
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) |
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, |