134 |
133 |
135 if mt.startswith('text/'): |
134 if mt.startswith('text/'): |
136 mt += '; charset="%s"' % encoding.encoding |
135 mt += '; charset="%s"' % encoding.encoding |
137 |
136 |
138 web.res.headers['Content-Type'] = mt |
137 web.res.headers['Content-Type'] = mt |
139 filename = (path.rpartition('/')[-1] |
138 filename = ( |
140 .replace('\\', '\\\\').replace('"', '\\"')) |
139 path.rpartition('/')[-1].replace('\\', '\\\\').replace('"', '\\"') |
|
140 ) |
141 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename |
141 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename |
142 web.res.setbodybytes(text) |
142 web.res.setbodybytes(text) |
143 return web.res.sendresponse() |
143 return web.res.sendresponse() |
|
144 |
144 |
145 |
145 def _filerevision(web, fctx): |
146 def _filerevision(web, fctx): |
146 f = fctx.path() |
147 f = fctx.path() |
147 text = fctx.data() |
148 text = fctx.data() |
148 parity = paritygen(web.stripecount) |
149 parity = paritygen(web.stripecount) |
149 ishead = fctx.filenode() in fctx.filelog().heads() |
150 ishead = fctx.filenode() in fctx.filelog().heads() |
150 |
151 |
151 if stringutil.binary(text): |
152 if stringutil.binary(text): |
152 mt = pycompat.sysbytes( |
153 mt = pycompat.sysbytes( |
153 mimetypes.guess_type(pycompat.fsdecode(f))[0] |
154 mimetypes.guess_type(pycompat.fsdecode(f))[0] |
154 or r'application/octet-stream') |
155 or r'application/octet-stream' |
|
156 ) |
155 text = '(binary:%s)' % mt |
157 text = '(binary:%s)' % mt |
156 |
158 |
157 def lines(context): |
159 def lines(context): |
158 for lineno, t in enumerate(text.splitlines(True)): |
160 for lineno, t in enumerate(text.splitlines(True)): |
159 yield {"line": t, |
161 yield { |
160 "lineid": "l%d" % (lineno + 1), |
162 "line": t, |
161 "linenumber": "% 6d" % (lineno + 1), |
163 "lineid": "l%d" % (lineno + 1), |
162 "parity": next(parity)} |
164 "linenumber": "% 6d" % (lineno + 1), |
|
165 "parity": next(parity), |
|
166 } |
163 |
167 |
164 return web.sendtemplate( |
168 return web.sendtemplate( |
165 'filerevision', |
169 'filerevision', |
166 file=f, |
170 file=f, |
167 path=webutil.up(f), |
171 path=webutil.up(f), |
168 text=templateutil.mappinggenerator(lines), |
172 text=templateutil.mappinggenerator(lines), |
169 symrev=webutil.symrevorshortnode(web.req, fctx), |
173 symrev=webutil.symrevorshortnode(web.req, fctx), |
170 rename=webutil.renamelink(fctx), |
174 rename=webutil.renamelink(fctx), |
171 permissions=fctx.manifest().flags(f), |
175 permissions=fctx.manifest().flags(f), |
172 ishead=int(ishead), |
176 ishead=int(ishead), |
173 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))) |
177 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) |
|
178 ) |
|
179 |
174 |
180 |
175 @webcommand('file') |
181 @webcommand('file') |
176 def file(web): |
182 def file(web): |
177 """ |
183 """ |
178 /file/{revision}[/{path}] |
184 /file/{revision}[/{path}] |
271 |
280 |
272 if revsetlang.depth(tree) <= 2: |
281 if revsetlang.depth(tree) <= 2: |
273 # no revset syntax used |
282 # no revset syntax used |
274 return MODE_KEYWORD, query |
283 return MODE_KEYWORD, query |
275 |
284 |
276 if any((token, (value or '')[:3]) == ('string', 're:') |
285 if any( |
277 for token, value, pos in revsetlang.tokenize(revdef)): |
286 (token, (value or '')[:3]) == ('string', 're:') |
|
287 for token, value, pos in revsetlang.tokenize(revdef) |
|
288 ): |
278 return MODE_KEYWORD, query |
289 return MODE_KEYWORD, query |
279 |
290 |
280 funcsused = revsetlang.funcsused(tree) |
291 funcsused = revsetlang.funcsused(tree) |
281 if not funcsused.issubset(revset.safesymbols): |
292 if not funcsused.issubset(revset.safesymbols): |
282 return MODE_KEYWORD, query |
293 return MODE_KEYWORD, query |
283 |
294 |
284 try: |
295 try: |
285 mfunc = revset.match(web.repo.ui, revdef, |
296 mfunc = revset.match( |
286 lookup=revset.lookupfn(web.repo)) |
297 web.repo.ui, revdef, lookup=revset.lookupfn(web.repo) |
|
298 ) |
287 revs = mfunc(web.repo) |
299 revs = mfunc(web.repo) |
288 return MODE_REVSET, revs |
300 return MODE_REVSET, revs |
289 # ParseError: wrongly placed tokens, wrongs arguments, etc |
301 # ParseError: wrongly placed tokens, wrongs arguments, etc |
290 # RepoLookupError: no such revision, e.g. in 'revision:' |
302 # RepoLookupError: no such revision, e.g. in 'revision:' |
291 # Abort: bookmark/tag not exists |
303 # Abort: bookmark/tag not exists |
292 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo |
304 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo |
293 except (error.ParseError, error.RepoLookupError, error.Abort, |
305 except ( |
294 LookupError): |
306 error.ParseError, |
|
307 error.RepoLookupError, |
|
308 error.Abort, |
|
309 LookupError, |
|
310 ): |
295 return MODE_KEYWORD, query |
311 return MODE_KEYWORD, query |
296 |
312 |
297 def changelist(context): |
313 def changelist(context): |
298 count = 0 |
314 count = 0 |
299 |
315 |
302 n = scmutil.binnode(ctx) |
318 n = scmutil.binnode(ctx) |
303 showtags = webutil.showtag(web.repo, 'changelogtag', n) |
319 showtags = webutil.showtag(web.repo, 'changelogtag', n) |
304 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles) |
320 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles) |
305 |
321 |
306 lm = webutil.commonentry(web.repo, ctx) |
322 lm = webutil.commonentry(web.repo, ctx) |
307 lm.update({ |
323 lm.update( |
308 'parity': next(parity), |
324 { |
309 'changelogtag': showtags, |
325 'parity': next(parity), |
310 'files': files, |
326 'changelogtag': showtags, |
311 }) |
327 'files': files, |
|
328 } |
|
329 ) |
312 yield lm |
330 yield lm |
313 |
331 |
314 if count >= revcount: |
332 if count >= revcount: |
315 break |
333 break |
316 |
334 |
359 archives=web.archivelist('tip'), |
377 archives=web.archivelist('tip'), |
360 morevars=morevars, |
378 morevars=morevars, |
361 lessvars=lessvars, |
379 lessvars=lessvars, |
362 modedesc=searchfunc[1], |
380 modedesc=searchfunc[1], |
363 showforcekw=showforcekw, |
381 showforcekw=showforcekw, |
364 showunforcekw=showunforcekw) |
382 showunforcekw=showunforcekw, |
|
383 ) |
|
384 |
365 |
385 |
366 @webcommand('changelog') |
386 @webcommand('changelog') |
367 def changelog(web, shortlog=False): |
387 def changelog(web, shortlog=False): |
368 """ |
388 """ |
369 /changelog[/{revision}] |
389 /changelog[/{revision}] |
485 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many |
508 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many |
486 templates related to diffs may all be used to produce the output. |
509 templates related to diffs may all be used to produce the output. |
487 """ |
510 """ |
488 ctx = webutil.changectx(web.repo, web.req) |
511 ctx = webutil.changectx(web.repo, web.req) |
489 |
512 |
490 return web.sendtemplate( |
513 return web.sendtemplate('changeset', **webutil.changesetentry(web, ctx)) |
491 'changeset', |
514 |
492 **webutil.changesetentry(web, ctx)) |
|
493 |
515 |
494 rev = webcommand('rev')(changeset) |
516 rev = webcommand('rev')(changeset) |
|
517 |
495 |
518 |
496 def decodepath(path): |
519 def decodepath(path): |
497 """Hook for mapping a path in the repository to a path in the |
520 """Hook for mapping a path in the repository to a path in the |
498 working copy. |
521 working copy. |
499 |
522 |
500 Extensions (e.g., largefiles) can override this to remap files in |
523 Extensions (e.g., largefiles) can override this to remap files in |
501 the virtual file system presented by the manifest command below.""" |
524 the virtual file system presented by the manifest command below.""" |
502 return path |
525 return path |
|
526 |
503 |
527 |
504 @webcommand('manifest') |
528 @webcommand('manifest') |
505 def manifest(web): |
529 def manifest(web): |
506 """ |
530 """ |
507 /manifest[/{revision}[/{path}]] |
531 /manifest[/{revision}[/{path}]] |
547 remain = f[l:] |
571 remain = f[l:] |
548 elements = remain.split('/') |
572 elements = remain.split('/') |
549 if len(elements) == 1: |
573 if len(elements) == 1: |
550 files[remain] = full |
574 files[remain] = full |
551 else: |
575 else: |
552 h = dirs # need to retain ref to dirs (root) |
576 h = dirs # need to retain ref to dirs (root) |
553 for elem in elements[0:-1]: |
577 for elem in elements[0:-1]: |
554 if elem not in h: |
578 if elem not in h: |
555 h[elem] = {} |
579 h[elem] = {} |
556 h = h[elem] |
580 h = h[elem] |
557 if len(h) > 1: |
581 if len(h) > 1: |
558 break |
582 break |
559 h[None] = None # denotes files present |
583 h[None] = None # denotes files present |
560 |
584 |
561 if mf and not files and not dirs: |
585 if mf and not files and not dirs: |
562 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) |
586 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) |
563 |
587 |
564 def filelist(context): |
588 def filelist(context): |
565 for f in sorted(files): |
589 for f in sorted(files): |
566 full = files[f] |
590 full = files[f] |
567 |
591 |
568 fctx = ctx.filectx(full) |
592 fctx = ctx.filectx(full) |
569 yield {"file": full, |
593 yield { |
570 "parity": next(parity), |
594 "file": full, |
571 "basename": f, |
595 "parity": next(parity), |
572 "date": fctx.date(), |
596 "basename": f, |
573 "size": fctx.size(), |
597 "date": fctx.date(), |
574 "permissions": mf.flags(full)} |
598 "size": fctx.size(), |
|
599 "permissions": mf.flags(full), |
|
600 } |
575 |
601 |
576 def dirlist(context): |
602 def dirlist(context): |
577 for d in sorted(dirs): |
603 for d in sorted(dirs): |
578 |
604 |
579 emptydirs = [] |
605 emptydirs = [] |
583 if v: |
609 if v: |
584 emptydirs.append(k) |
610 emptydirs.append(k) |
585 h = v |
611 h = v |
586 |
612 |
587 path = "%s%s" % (abspath, d) |
613 path = "%s%s" % (abspath, d) |
588 yield {"parity": next(parity), |
614 yield { |
589 "path": path, |
615 "parity": next(parity), |
590 "emptydirs": "/".join(emptydirs), |
616 "path": path, |
591 "basename": d} |
617 "emptydirs": "/".join(emptydirs), |
|
618 "basename": d, |
|
619 } |
592 |
620 |
593 return web.sendtemplate( |
621 return web.sendtemplate( |
594 'manifest', |
622 'manifest', |
595 symrev=symrev, |
623 symrev=symrev, |
596 path=abspath, |
624 path=abspath, |
597 up=webutil.up(abspath), |
625 up=webutil.up(abspath), |
598 upparity=next(parity), |
626 upparity=next(parity), |
599 fentries=templateutil.mappinggenerator(filelist), |
627 fentries=templateutil.mappinggenerator(filelist), |
600 dentries=templateutil.mappinggenerator(dirlist), |
628 dentries=templateutil.mappinggenerator(dirlist), |
601 archives=web.archivelist(hex(node)), |
629 archives=web.archivelist(hex(node)), |
602 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))) |
630 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) |
|
631 ) |
|
632 |
603 |
633 |
604 @webcommand('tags') |
634 @webcommand('tags') |
605 def tags(web): |
635 def tags(web): |
606 """ |
636 """ |
607 /tags |
637 /tags |
621 if notip: |
651 if notip: |
622 t = [(k, n) for k, n in i if k != "tip"] |
652 t = [(k, n) for k, n in i if k != "tip"] |
623 if latestonly: |
653 if latestonly: |
624 t = t[:1] |
654 t = t[:1] |
625 for k, n in t: |
655 for k, n in t: |
626 yield {"parity": next(parity), |
656 yield { |
627 "tag": k, |
657 "parity": next(parity), |
628 "date": web.repo[n].date(), |
658 "tag": k, |
629 "node": hex(n)} |
659 "date": web.repo[n].date(), |
|
660 "node": hex(n), |
|
661 } |
630 |
662 |
631 return web.sendtemplate( |
663 return web.sendtemplate( |
632 'tags', |
664 'tags', |
633 node=hex(web.repo.changelog.tip()), |
665 node=hex(web.repo.changelog.tip()), |
634 entries=templateutil.mappinggenerator(entries, args=(False, False)), |
666 entries=templateutil.mappinggenerator(entries, args=(False, False)), |
635 entriesnotip=templateutil.mappinggenerator(entries, |
667 entriesnotip=templateutil.mappinggenerator(entries, args=(True, False)), |
636 args=(True, False)), |
668 latestentry=templateutil.mappinggenerator(entries, args=(True, True)), |
637 latestentry=templateutil.mappinggenerator(entries, args=(True, True))) |
669 ) |
|
670 |
638 |
671 |
639 @webcommand('bookmarks') |
672 @webcommand('bookmarks') |
640 def bookmarks(web): |
673 def bookmarks(web): |
641 """ |
674 """ |
642 /bookmarks |
675 /bookmarks |
672 return web.sendtemplate( |
707 return web.sendtemplate( |
673 'bookmarks', |
708 'bookmarks', |
674 node=hex(web.repo.changelog.tip()), |
709 node=hex(web.repo.changelog.tip()), |
675 lastchange=templateutil.mappinglist([{'date': lastdate}]), |
710 lastchange=templateutil.mappinglist([{'date': lastdate}]), |
676 entries=templateutil.mappinggenerator(entries, args=(False,)), |
711 entries=templateutil.mappinggenerator(entries, args=(False,)), |
677 latestentry=templateutil.mappinggenerator(entries, args=(True,))) |
712 latestentry=templateutil.mappinggenerator(entries, args=(True,)), |
|
713 ) |
|
714 |
678 |
715 |
679 @webcommand('branches') |
716 @webcommand('branches') |
680 def branches(web): |
717 def branches(web): |
681 """ |
718 """ |
682 /branches |
719 /branches |
736 parity = paritygen(web.stripecount) |
775 parity = paritygen(web.stripecount) |
737 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] |
776 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] |
738 sortkey = lambda b: (web.repo[b[1]].rev(), b[0]) |
777 sortkey = lambda b: (web.repo[b[1]].rev(), b[0]) |
739 marks = sorted(marks, key=sortkey, reverse=True) |
778 marks = sorted(marks, key=sortkey, reverse=True) |
740 for k, n in marks[:10]: # limit to 10 bookmarks |
779 for k, n in marks[:10]: # limit to 10 bookmarks |
741 yield {'parity': next(parity), |
780 yield { |
742 'bookmark': k, |
781 'parity': next(parity), |
743 'date': web.repo[n].date(), |
782 'bookmark': k, |
744 'node': hex(n)} |
783 'date': web.repo[n].date(), |
|
784 'node': hex(n), |
|
785 } |
745 |
786 |
746 def changelist(context): |
787 def changelist(context): |
747 parity = paritygen(web.stripecount, offset=start - end) |
788 parity = paritygen(web.stripecount, offset=start - end) |
748 l = [] # build a list in forward order for efficiency |
789 l = [] # build a list in forward order for efficiency |
749 revs = [] |
790 revs = [] |
750 if start < end: |
791 if start < end: |
751 revs = web.repo.changelog.revs(start, end - 1) |
792 revs = web.repo.changelog.revs(start, end - 1) |
752 for i in revs: |
793 for i in revs: |
753 ctx = web.repo[i] |
794 ctx = web.repo[i] |
774 owner=get_contact(web.config) or 'unknown', |
815 owner=get_contact(web.config) or 'unknown', |
775 lastchange=tip.date(), |
816 lastchange=tip.date(), |
776 tags=templateutil.mappinggenerator(tagentries, name='tagentry'), |
817 tags=templateutil.mappinggenerator(tagentries, name='tagentry'), |
777 bookmarks=templateutil.mappinggenerator(bookmarks), |
818 bookmarks=templateutil.mappinggenerator(bookmarks), |
778 branches=webutil.branchentries(web.repo, web.stripecount, 10), |
819 branches=webutil.branchentries(web.repo, web.stripecount, 10), |
779 shortlog=templateutil.mappinggenerator(changelist, |
820 shortlog=templateutil.mappinggenerator( |
780 name='shortlogentry'), |
821 changelist, name='shortlogentry' |
|
822 ), |
781 node=tip.hex(), |
823 node=tip.hex(), |
782 symrev='tip', |
824 symrev='tip', |
783 archives=web.archivelist('tip'), |
825 archives=web.archivelist('tip'), |
784 labels=templateutil.hybridlist(labels, name='label')) |
826 labels=templateutil.hybridlist(labels, name='label'), |
|
827 ) |
|
828 |
785 |
829 |
786 @webcommand('filediff') |
830 @webcommand('filediff') |
787 def filediff(web): |
831 def filediff(web): |
788 """ |
832 """ |
789 /diff/{revision}/{path} |
833 /diff/{revision}/{path} |
903 leftrev=leftrev, |
951 leftrev=leftrev, |
904 leftnode=hex(leftnode), |
952 leftnode=hex(leftnode), |
905 rightrev=rightrev, |
953 rightrev=rightrev, |
906 rightnode=hex(rightnode), |
954 rightnode=hex(rightnode), |
907 comparison=comparison, |
955 comparison=comparison, |
908 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))) |
956 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) |
|
957 ) |
|
958 |
909 |
959 |
910 @webcommand('annotate') |
960 @webcommand('annotate') |
911 def annotate(web): |
961 def annotate(web): |
912 """ |
962 """ |
913 /annotate/{revision}/{path} |
963 /annotate/{revision}/{path} |
932 # parents() is called once per line and several lines likely belong to |
982 # parents() is called once per line and several lines likely belong to |
933 # same revision. So it is worth caching. |
983 # same revision. So it is worth caching. |
934 # TODO there are still redundant operations within basefilectx.parents() |
984 # TODO there are still redundant operations within basefilectx.parents() |
935 # and from the fctx.annotate() call itself that could be cached. |
985 # and from the fctx.annotate() call itself that could be cached. |
936 parentscache = {} |
986 parentscache = {} |
|
987 |
937 def parents(context, f): |
988 def parents(context, f): |
938 rev = f.rev() |
989 rev = f.rev() |
939 if rev not in parentscache: |
990 if rev not in parentscache: |
940 parentscache[rev] = [] |
991 parentscache[rev] = [] |
941 for p in f.parents(): |
992 for p in f.parents(): |
950 |
1001 |
951 def annotate(context): |
1002 def annotate(context): |
952 if fctx.isbinary(): |
1003 if fctx.isbinary(): |
953 mt = pycompat.sysbytes( |
1004 mt = pycompat.sysbytes( |
954 mimetypes.guess_type(pycompat.fsdecode(fctx.path()))[0] |
1005 mimetypes.guess_type(pycompat.fsdecode(fctx.path()))[0] |
955 or r'application/octet-stream') |
1006 or r'application/octet-stream' |
956 lines = [dagop.annotateline(fctx=fctx.filectx(fctx.filerev()), |
1007 ) |
957 lineno=1, text='(binary:%s)' % mt)] |
1008 lines = [ |
|
1009 dagop.annotateline( |
|
1010 fctx=fctx.filectx(fctx.filerev()), |
|
1011 lineno=1, |
|
1012 text='(binary:%s)' % mt, |
|
1013 ) |
|
1014 ] |
958 else: |
1015 else: |
959 lines = webutil.annotate(web.req, fctx, web.repo.ui) |
1016 lines = webutil.annotate(web.req, fctx, web.repo.ui) |
960 |
1017 |
961 previousrev = None |
1018 previousrev = None |
962 blockparitygen = paritygen(1) |
1019 blockparitygen = paritygen(1) |
967 blockhead = True |
1024 blockhead = True |
968 blockparity = next(blockparitygen) |
1025 blockparity = next(blockparitygen) |
969 else: |
1026 else: |
970 blockhead = None |
1027 blockhead = None |
971 previousrev = rev |
1028 previousrev = rev |
972 yield {"parity": next(parity), |
1029 yield { |
973 "node": f.hex(), |
1030 "parity": next(parity), |
974 "rev": rev, |
1031 "node": f.hex(), |
975 "author": f.user(), |
1032 "rev": rev, |
976 "parents": templateutil.mappinggenerator(parents, args=(f,)), |
1033 "author": f.user(), |
977 "desc": f.description(), |
1034 "parents": templateutil.mappinggenerator(parents, args=(f,)), |
978 "extra": f.extra(), |
1035 "desc": f.description(), |
979 "file": f.path(), |
1036 "extra": f.extra(), |
980 "blockhead": blockhead, |
1037 "file": f.path(), |
981 "blockparity": blockparity, |
1038 "blockhead": blockhead, |
982 "targetline": aline.lineno, |
1039 "blockparity": blockparity, |
983 "line": aline.text, |
1040 "targetline": aline.lineno, |
984 "lineno": lineno + 1, |
1041 "line": aline.text, |
985 "lineid": "l%d" % (lineno + 1), |
1042 "lineno": lineno + 1, |
986 "linenumber": "% 6d" % (lineno + 1), |
1043 "lineid": "l%d" % (lineno + 1), |
987 "revdate": f.date()} |
1044 "linenumber": "% 6d" % (lineno + 1), |
|
1045 "revdate": f.date(), |
|
1046 } |
988 |
1047 |
989 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, 'annotate') |
1048 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, 'annotate') |
990 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults} |
1049 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults} |
991 |
1050 |
992 return web.sendtemplate( |
1051 return web.sendtemplate( |
997 symrev=webutil.symrevorshortnode(web.req, fctx), |
1056 symrev=webutil.symrevorshortnode(web.req, fctx), |
998 rename=webutil.renamelink(fctx), |
1057 rename=webutil.renamelink(fctx), |
999 permissions=fctx.manifest().flags(f), |
1058 permissions=fctx.manifest().flags(f), |
1000 ishead=int(ishead), |
1059 ishead=int(ishead), |
1001 diffopts=templateutil.hybriddict(diffopts), |
1060 diffopts=templateutil.hybriddict(diffopts), |
1002 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))) |
1061 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) |
|
1062 ) |
|
1063 |
1003 |
1064 |
1004 @webcommand('filelog') |
1065 @webcommand('filelog') |
1005 def filelog(web): |
1066 def filelog(web): |
1006 """ |
1067 """ |
1007 /filelog/{revision}/{path} |
1068 /filelog/{revision}/{path} |
1021 fl = fctx.filelog() |
1082 fl = fctx.filelog() |
1022 except error.LookupError: |
1083 except error.LookupError: |
1023 f = webutil.cleanpath(web.repo, web.req.qsparams['file']) |
1084 f = webutil.cleanpath(web.repo, web.req.qsparams['file']) |
1024 fl = web.repo.file(f) |
1085 fl = web.repo.file(f) |
1025 numrevs = len(fl) |
1086 numrevs = len(fl) |
1026 if not numrevs: # file doesn't exist at all |
1087 if not numrevs: # file doesn't exist at all |
1027 raise |
1088 raise |
1028 rev = webutil.changectx(web.repo, web.req).rev() |
1089 rev = webutil.changectx(web.repo, web.req).rev() |
1029 first = fl.linkrev(0) |
1090 first = fl.linkrev(0) |
1030 if rev < first: # current rev is from before file existed |
1091 if rev < first: # current rev is from before file existed |
1031 raise |
1092 raise |
1032 frev = numrevs - 1 |
1093 frev = numrevs - 1 |
1033 while fl.linkrev(frev) > rev: |
1094 while fl.linkrev(frev) > rev: |
1034 frev -= 1 |
1095 frev -= 1 |
1035 fctx = web.repo.filectx(f, fl.linkrev(frev)) |
1096 fctx = web.repo.filectx(f, fl.linkrev(frev)) |
1056 descend = 'descend' in web.req.qsparams |
1117 descend = 'descend' in web.req.qsparams |
1057 if descend: |
1118 if descend: |
1058 lessvars['descend'] = morevars['descend'] = web.req.qsparams['descend'] |
1119 lessvars['descend'] = morevars['descend'] = web.req.qsparams['descend'] |
1059 |
1120 |
1060 count = fctx.filerev() + 1 |
1121 count = fctx.filerev() + 1 |
1061 start = max(0, count - revcount) # first rev on this page |
1122 start = max(0, count - revcount) # first rev on this page |
1062 end = min(count, start + revcount) # last rev on this page |
1123 end = min(count, start + revcount) # last rev on this page |
1063 parity = paritygen(web.stripecount, offset=start - end) |
1124 parity = paritygen(web.stripecount, offset=start - end) |
1064 |
1125 |
1065 repo = web.repo |
1126 repo = web.repo |
1066 filelog = fctx.filelog() |
1127 filelog = fctx.filelog() |
1067 revs = [filerev for filerev in filelog.revs(start, end - 1) |
1128 revs = [ |
1068 if filelog.linkrev(filerev) in repo] |
1129 filerev |
|
1130 for filerev in filelog.revs(start, end - 1) |
|
1131 if filelog.linkrev(filerev) in repo |
|
1132 ] |
1069 entries = [] |
1133 entries = [] |
1070 |
1134 |
1071 diffstyle = web.config('web', 'style') |
1135 diffstyle = web.config('web', 'style') |
1072 if 'style' in web.req.qsparams: |
1136 if 'style' in web.req.qsparams: |
1073 diffstyle = web.req.qsparams['style'] |
1137 diffstyle = web.req.qsparams['style'] |
1074 |
1138 |
1075 def diff(fctx, linerange=None): |
1139 def diff(fctx, linerange=None): |
1076 ctx = fctx.changectx() |
1140 ctx = fctx.changectx() |
1077 basectx = ctx.p1() |
1141 basectx = ctx.p1() |
1078 path = fctx.path() |
1142 path = fctx.path() |
1079 return webutil.diffs(web, ctx, basectx, [path], diffstyle, |
1143 return webutil.diffs( |
1080 linerange=linerange, |
1144 web, |
1081 lineidprefix='%s-' % ctx.hex()[:12]) |
1145 ctx, |
|
1146 basectx, |
|
1147 [path], |
|
1148 diffstyle, |
|
1149 linerange=linerange, |
|
1150 lineidprefix='%s-' % ctx.hex()[:12], |
|
1151 ) |
1082 |
1152 |
1083 linerange = None |
1153 linerange = None |
1084 if lrange is not None: |
1154 if lrange is not None: |
1085 linerange = webutil.formatlinerange(*lrange) |
1155 linerange = webutil.formatlinerange(*lrange) |
1086 # deactivate numeric nav links when linerange is specified as this |
1156 # deactivate numeric nav links when linerange is specified as this |
1095 if patch: |
1165 if patch: |
1096 diffs = diff(c, linerange=lr) |
1166 diffs = diff(c, linerange=lr) |
1097 # follow renames accross filtered (not in range) revisions |
1167 # follow renames accross filtered (not in range) revisions |
1098 path = c.path() |
1168 path = c.path() |
1099 lm = webutil.commonentry(repo, c) |
1169 lm = webutil.commonentry(repo, c) |
1100 lm.update({ |
1170 lm.update( |
1101 'parity': next(parity), |
1171 { |
1102 'filerev': c.rev(), |
1172 'parity': next(parity), |
1103 'file': path, |
1173 'filerev': c.rev(), |
1104 'diff': diffs, |
1174 'file': path, |
1105 'linerange': webutil.formatlinerange(*lr), |
1175 'diff': diffs, |
1106 'rename': templateutil.mappinglist([]), |
1176 'linerange': webutil.formatlinerange(*lr), |
1107 }) |
1177 'rename': templateutil.mappinglist([]), |
|
1178 } |
|
1179 ) |
1108 entries.append(lm) |
1180 entries.append(lm) |
1109 if i == revcount: |
1181 if i == revcount: |
1110 break |
1182 break |
1111 lessvars['linerange'] = webutil.formatlinerange(*lrange) |
1183 lessvars['linerange'] = webutil.formatlinerange(*lrange) |
1112 morevars['linerange'] = lessvars['linerange'] |
1184 morevars['linerange'] = lessvars['linerange'] |
1115 iterfctx = fctx.filectx(i) |
1187 iterfctx = fctx.filectx(i) |
1116 diffs = None |
1188 diffs = None |
1117 if patch: |
1189 if patch: |
1118 diffs = diff(iterfctx) |
1190 diffs = diff(iterfctx) |
1119 lm = webutil.commonentry(repo, iterfctx) |
1191 lm = webutil.commonentry(repo, iterfctx) |
1120 lm.update({ |
1192 lm.update( |
1121 'parity': next(parity), |
1193 { |
1122 'filerev': i, |
1194 'parity': next(parity), |
1123 'file': f, |
1195 'filerev': i, |
1124 'diff': diffs, |
1196 'file': f, |
1125 'rename': webutil.renamelink(iterfctx), |
1197 'diff': diffs, |
1126 }) |
1198 'rename': webutil.renamelink(iterfctx), |
|
1199 } |
|
1200 ) |
1127 entries.append(lm) |
1201 entries.append(lm) |
1128 entries.reverse() |
1202 entries.reverse() |
1129 revnav = webutil.filerevnav(web.repo, fctx.path()) |
1203 revnav = webutil.filerevnav(web.repo, fctx.path()) |
1130 nav = revnav.gen(end - 1, revcount, count) |
1204 nav = revnav.gen(end - 1, revcount, count) |
1131 |
1205 |
1173 |
1249 |
1174 if type_ not in webutil.archivespecs: |
1250 if type_ not in webutil.archivespecs: |
1175 msg = 'Unsupported archive type: %s' % stringutil.pprint(type_) |
1251 msg = 'Unsupported archive type: %s' % stringutil.pprint(type_) |
1176 raise ErrorResponse(HTTP_NOT_FOUND, msg) |
1252 raise ErrorResponse(HTTP_NOT_FOUND, msg) |
1177 |
1253 |
1178 if not ((type_ in allowed or |
1254 if not ((type_ in allowed or web.configbool("web", "allow" + type_))): |
1179 web.configbool("web", "allow" + type_))): |
|
1180 msg = 'Archive type not allowed: %s' % type_ |
1255 msg = 'Archive type not allowed: %s' % type_ |
1181 raise ErrorResponse(HTTP_FORBIDDEN, msg) |
1256 raise ErrorResponse(HTTP_FORBIDDEN, msg) |
1182 |
1257 |
1183 reponame = re.sub(br"\W+", "-", os.path.basename(web.reponame)) |
1258 reponame = re.sub(br"\W+", "-", os.path.basename(web.reponame)) |
1184 cnode = web.repo.lookup(key) |
1259 cnode = web.repo.lookup(key) |
1195 pats = ['path:' + file] |
1270 pats = ['path:' + file] |
1196 match = scmutil.match(ctx, pats, default='path') |
1271 match = scmutil.match(ctx, pats, default='path') |
1197 if pats: |
1272 if pats: |
1198 files = [f for f in ctx.manifest().keys() if match(f)] |
1273 files = [f for f in ctx.manifest().keys() if match(f)] |
1199 if not files: |
1274 if not files: |
1200 raise ErrorResponse(HTTP_NOT_FOUND, |
1275 raise ErrorResponse( |
1201 'file(s) not found: %s' % file) |
1276 HTTP_NOT_FOUND, 'file(s) not found: %s' % file |
|
1277 ) |
1202 |
1278 |
1203 mimetype, artype, extension, encoding = webutil.archivespecs[type_] |
1279 mimetype, artype, extension, encoding = webutil.archivespecs[type_] |
1204 |
1280 |
1205 web.res.headers['Content-Type'] = mimetype |
1281 web.res.headers['Content-Type'] = mimetype |
1206 web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % ( |
1282 web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % ( |
1207 name, extension) |
1283 name, |
|
1284 extension, |
|
1285 ) |
1208 |
1286 |
1209 if encoding: |
1287 if encoding: |
1210 web.res.headers['Content-Encoding'] = encoding |
1288 web.res.headers['Content-Encoding'] = encoding |
1211 |
1289 |
1212 web.res.setbodywillwrite() |
1290 web.res.setbodywillwrite() |
1213 if list(web.res.sendresponse()): |
1291 if list(web.res.sendresponse()): |
1214 raise error.ProgrammingError('sendresponse() should not emit data ' |
1292 raise error.ProgrammingError( |
1215 'if writing later') |
1293 'sendresponse() should not emit data ' 'if writing later' |
|
1294 ) |
1216 |
1295 |
1217 bodyfh = web.res.getbodyfile() |
1296 bodyfh = web.res.getbodyfile() |
1218 |
1297 |
1219 archival.archive(web.repo, bodyfh, cnode, artype, prefix=name, match=match, |
1298 archival.archive( |
1220 subrepos=web.configbool("web", "archivesubrepos")) |
1299 web.repo, |
|
1300 bodyfh, |
|
1301 cnode, |
|
1302 artype, |
|
1303 prefix=name, |
|
1304 match=match, |
|
1305 subrepos=web.configbool("web", "archivesubrepos"), |
|
1306 ) |
1221 |
1307 |
1222 return [] |
1308 return [] |
|
1309 |
1223 |
1310 |
1224 @webcommand('static') |
1311 @webcommand('static') |
1225 def static(web): |
1312 def static(web): |
1226 fname = web.req.qsparams['file'] |
1313 fname = web.req.qsparams['file'] |
1227 # a repo owner may set web.static in .hg/hgrc to get any file |
1314 # a repo owner may set web.static in .hg/hgrc to get any file |
1314 # We have to feed a baseset to dagwalker as it is expecting smartset |
1402 # We have to feed a baseset to dagwalker as it is expecting smartset |
1315 # object. This does not have a big impact on hgweb performance itself |
1403 # object. This does not have a big impact on hgweb performance itself |
1316 # since hgweb graphing code is not itself lazy yet. |
1404 # since hgweb graphing code is not itself lazy yet. |
1317 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) |
1405 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) |
1318 # As we said one line above... not lazy. |
1406 # As we said one line above... not lazy. |
1319 tree = list(item for item in graphmod.colored(dag, web.repo) |
1407 tree = list( |
1320 if item[1] == graphmod.CHANGESET) |
1408 item |
|
1409 for item in graphmod.colored(dag, web.repo) |
|
1410 if item[1] == graphmod.CHANGESET |
|
1411 ) |
1321 |
1412 |
1322 def fulltree(): |
1413 def fulltree(): |
1323 pos = web.repo[graphtop].rev() |
1414 pos = web.repo[graphtop].rev() |
1324 tree = [] |
1415 tree = [] |
1325 if pos != -1: |
1416 if pos != -1: |
1326 revs = web.repo.changelog.revs(pos, lastrev) |
1417 revs = web.repo.changelog.revs(pos, lastrev) |
1327 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) |
1418 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) |
1328 tree = list(item for item in graphmod.colored(dag, web.repo) |
1419 tree = list( |
1329 if item[1] == graphmod.CHANGESET) |
1420 item |
|
1421 for item in graphmod.colored(dag, web.repo) |
|
1422 if item[1] == graphmod.CHANGESET |
|
1423 ) |
1330 return tree |
1424 return tree |
1331 |
1425 |
1332 def jsdata(context): |
1426 def jsdata(context): |
1333 for (id, type, ctx, vtx, edges) in fulltree(): |
1427 for (id, type, ctx, vtx, edges) in fulltree(): |
1334 yield {'node': pycompat.bytestr(ctx), |
1428 yield { |
1335 'graphnode': webutil.getgraphnode(web.repo, ctx), |
1429 'node': pycompat.bytestr(ctx), |
1336 'vertex': vtx, |
1430 'graphnode': webutil.getgraphnode(web.repo, ctx), |
1337 'edges': edges} |
1431 'vertex': vtx, |
|
1432 'edges': edges, |
|
1433 } |
1338 |
1434 |
1339 def nodes(context): |
1435 def nodes(context): |
1340 parity = paritygen(web.stripecount) |
1436 parity = paritygen(web.stripecount) |
1341 for row, (id, type, ctx, vtx, edges) in enumerate(tree): |
1437 for row, (id, type, ctx, vtx, edges) in enumerate(tree): |
1342 entry = webutil.commonentry(web.repo, ctx) |
1438 entry = webutil.commonentry(web.repo, ctx) |
1343 edgedata = [{'col': edge[0], |
1439 edgedata = [ |
1344 'nextcol': edge[1], |
1440 { |
1345 'color': (edge[2] - 1) % 6 + 1, |
1441 'col': edge[0], |
1346 'width': edge[3], |
1442 'nextcol': edge[1], |
1347 'bcolor': edge[4]} |
1443 'color': (edge[2] - 1) % 6 + 1, |
1348 for edge in edges] |
1444 'width': edge[3], |
1349 |
1445 'bcolor': edge[4], |
1350 entry.update({'col': vtx[0], |
1446 } |
1351 'color': (vtx[1] - 1) % 6 + 1, |
1447 for edge in edges |
1352 'parity': next(parity), |
1448 ] |
1353 'edges': templateutil.mappinglist(edgedata), |
1449 |
1354 'row': row, |
1450 entry.update( |
1355 'nextrow': row + 1}) |
1451 { |
|
1452 'col': vtx[0], |
|
1453 'color': (vtx[1] - 1) % 6 + 1, |
|
1454 'parity': next(parity), |
|
1455 'edges': templateutil.mappinglist(edgedata), |
|
1456 'row': row, |
|
1457 'nextrow': row + 1, |
|
1458 } |
|
1459 ) |
1356 |
1460 |
1357 yield entry |
1461 yield entry |
1358 |
1462 |
1359 rows = len(tree) |
1463 rows = len(tree) |
1360 |
1464 |
1374 nextentry=templateutil.mappinglist(nextentry), |
1478 nextentry=templateutil.mappinglist(nextentry), |
1375 jsdata=templateutil.mappinggenerator(jsdata), |
1479 jsdata=templateutil.mappinggenerator(jsdata), |
1376 nodes=templateutil.mappinggenerator(nodes), |
1480 nodes=templateutil.mappinggenerator(nodes), |
1377 node=ctx.hex(), |
1481 node=ctx.hex(), |
1378 archives=web.archivelist('tip'), |
1482 archives=web.archivelist('tip'), |
1379 changenav=changenav) |
1483 changenav=changenav, |
|
1484 ) |
|
1485 |
1380 |
1486 |
1381 def _getdoc(e): |
1487 def _getdoc(e): |
1382 doc = e[0].__doc__ |
1488 doc = e[0].__doc__ |
1383 if doc: |
1489 if doc: |
1384 doc = _(doc).partition('\n')[0] |
1490 doc = _(doc).partition('\n')[0] |
1385 else: |
1491 else: |
1386 doc = _('(no help text available)') |
1492 doc = _('(no help text available)') |
1387 return doc |
1493 return doc |
1388 |
1494 |
|
1495 |
1389 @webcommand('help') |
1496 @webcommand('help') |
1390 def help(web): |
1497 def help(web): |
1391 """ |
1498 """ |
1392 /help[/{topic}] |
1499 /help[/{topic}] |
1393 --------------- |
1500 --------------- |
1403 """ |
1510 """ |
1404 from .. import commands, help as helpmod # avoid cycle |
1511 from .. import commands, help as helpmod # avoid cycle |
1405 |
1512 |
1406 topicname = web.req.qsparams.get('node') |
1513 topicname = web.req.qsparams.get('node') |
1407 if not topicname: |
1514 if not topicname: |
|
1515 |
1408 def topics(context): |
1516 def topics(context): |
1409 for h in helpmod.helptable: |
1517 for h in helpmod.helptable: |
1410 entries, summary, _doc = h[0:3] |
1518 entries, summary, _doc = h[0:3] |
1411 yield {'topic': entries[0], 'summary': summary} |
1519 yield {'topic': entries[0], 'summary': summary} |
1412 |
1520 |
1436 return web.sendtemplate( |
1544 return web.sendtemplate( |
1437 'helptopics', |
1545 'helptopics', |
1438 topics=templateutil.mappinggenerator(topics), |
1546 topics=templateutil.mappinggenerator(topics), |
1439 earlycommands=templateutil.mappinggenerator(earlycommands), |
1547 earlycommands=templateutil.mappinggenerator(earlycommands), |
1440 othercommands=templateutil.mappinggenerator(othercommands), |
1548 othercommands=templateutil.mappinggenerator(othercommands), |
1441 title='Index') |
1549 title='Index', |
|
1550 ) |
1442 |
1551 |
1443 # Render an index of sub-topics. |
1552 # Render an index of sub-topics. |
1444 if topicname in helpmod.subtopics: |
1553 if topicname in helpmod.subtopics: |
1445 topics = [] |
1554 topics = [] |
1446 for entries, summary, _doc in helpmod.subtopics[topicname]: |
1555 for entries, summary, _doc in helpmod.subtopics[topicname]: |
1447 topics.append({ |
1556 topics.append( |
1448 'topic': '%s.%s' % (topicname, entries[0]), |
1557 { |
1449 'basename': entries[0], |
1558 'topic': '%s.%s' % (topicname, entries[0]), |
1450 'summary': summary, |
1559 'basename': entries[0], |
1451 }) |
1560 'summary': summary, |
|
1561 } |
|
1562 ) |
1452 |
1563 |
1453 return web.sendtemplate( |
1564 return web.sendtemplate( |
1454 'helptopics', |
1565 'helptopics', |
1455 topics=templateutil.mappinglist(topics), |
1566 topics=templateutil.mappinglist(topics), |
1456 title=topicname, |
1567 title=topicname, |
1457 subindex=True) |
1568 subindex=True, |
|
1569 ) |
1458 |
1570 |
1459 u = webutil.wsgiui.load() |
1571 u = webutil.wsgiui.load() |
1460 u.verbose = True |
1572 u.verbose = True |
1461 |
1573 |
1462 # Render a page from a sub-topic. |
1574 # Render a page from a sub-topic. |