##// END OF EJS Templates
hgweb: simplify wsgirequest header handling...
Mads Kiilerich -
r18348:764a7587 default
parent child Browse files
Show More
@@ -1,144 +1,135
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import socket, cgi, errno
9 import socket, cgi, errno
10 from mercurial import util
10 from mercurial import util
11 from common import ErrorResponse, statusmessage, HTTP_NOT_MODIFIED
11 from common import ErrorResponse, statusmessage, HTTP_NOT_MODIFIED
12
12
13 shortcuts = {
13 shortcuts = {
14 'cl': [('cmd', ['changelog']), ('rev', None)],
14 'cl': [('cmd', ['changelog']), ('rev', None)],
15 'sl': [('cmd', ['shortlog']), ('rev', None)],
15 'sl': [('cmd', ['shortlog']), ('rev', None)],
16 'cs': [('cmd', ['changeset']), ('node', None)],
16 'cs': [('cmd', ['changeset']), ('node', None)],
17 'f': [('cmd', ['file']), ('filenode', None)],
17 'f': [('cmd', ['file']), ('filenode', None)],
18 'fl': [('cmd', ['filelog']), ('filenode', None)],
18 'fl': [('cmd', ['filelog']), ('filenode', None)],
19 'fd': [('cmd', ['filediff']), ('node', None)],
19 'fd': [('cmd', ['filediff']), ('node', None)],
20 'fa': [('cmd', ['annotate']), ('filenode', None)],
20 'fa': [('cmd', ['annotate']), ('filenode', None)],
21 'mf': [('cmd', ['manifest']), ('manifest', None)],
21 'mf': [('cmd', ['manifest']), ('manifest', None)],
22 'ca': [('cmd', ['archive']), ('node', None)],
22 'ca': [('cmd', ['archive']), ('node', None)],
23 'tags': [('cmd', ['tags'])],
23 'tags': [('cmd', ['tags'])],
24 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
24 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
25 'static': [('cmd', ['static']), ('file', None)]
25 'static': [('cmd', ['static']), ('file', None)]
26 }
26 }
27
27
28 def normalize(form):
28 def normalize(form):
29 # first expand the shortcuts
29 # first expand the shortcuts
30 for k in shortcuts.iterkeys():
30 for k in shortcuts.iterkeys():
31 if k in form:
31 if k in form:
32 for name, value in shortcuts[k]:
32 for name, value in shortcuts[k]:
33 if value is None:
33 if value is None:
34 value = form[k]
34 value = form[k]
35 form[name] = value
35 form[name] = value
36 del form[k]
36 del form[k]
37 # And strip the values
37 # And strip the values
38 for k, v in form.iteritems():
38 for k, v in form.iteritems():
39 form[k] = [i.strip() for i in v]
39 form[k] = [i.strip() for i in v]
40 return form
40 return form
41
41
42 class wsgirequest(object):
42 class wsgirequest(object):
43 def __init__(self, wsgienv, start_response):
43 def __init__(self, wsgienv, start_response):
44 version = wsgienv['wsgi.version']
44 version = wsgienv['wsgi.version']
45 if (version < (1, 0)) or (version >= (2, 0)):
45 if (version < (1, 0)) or (version >= (2, 0)):
46 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
46 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
47 % version)
47 % version)
48 self.inp = wsgienv['wsgi.input']
48 self.inp = wsgienv['wsgi.input']
49 self.err = wsgienv['wsgi.errors']
49 self.err = wsgienv['wsgi.errors']
50 self.threaded = wsgienv['wsgi.multithread']
50 self.threaded = wsgienv['wsgi.multithread']
51 self.multiprocess = wsgienv['wsgi.multiprocess']
51 self.multiprocess = wsgienv['wsgi.multiprocess']
52 self.run_once = wsgienv['wsgi.run_once']
52 self.run_once = wsgienv['wsgi.run_once']
53 self.env = wsgienv
53 self.env = wsgienv
54 self.form = normalize(cgi.parse(self.inp,
54 self.form = normalize(cgi.parse(self.inp,
55 self.env,
55 self.env,
56 keep_blank_values=1))
56 keep_blank_values=1))
57 self._start_response = start_response
57 self._start_response = start_response
58 self.server_write = None
58 self.server_write = None
59 self.headers = []
59 self.headers = []
60
60
61 def __iter__(self):
61 def __iter__(self):
62 return iter([])
62 return iter([])
63
63
64 def read(self, count=-1):
64 def read(self, count=-1):
65 return self.inp.read(count)
65 return self.inp.read(count)
66
66
67 def drain(self):
67 def drain(self):
68 '''need to read all data from request, httplib is half-duplex'''
68 '''need to read all data from request, httplib is half-duplex'''
69 length = int(self.env.get('CONTENT_LENGTH') or 0)
69 length = int(self.env.get('CONTENT_LENGTH') or 0)
70 for s in util.filechunkiter(self.inp, limit=length):
70 for s in util.filechunkiter(self.inp, limit=length):
71 pass
71 pass
72
72
73 def respond(self, status, type, filename=None, length=None):
73 def respond(self, status, type, filename=None, length=None):
74 if self._start_response is not None:
74 if self._start_response is not None:
75
75 self.headers.append(('Content-Type', type))
76 self.httphdr(type, filename, length)
76 if filename:
77 filename = (filename.split('/')[-1]
78 .replace('\\', '\\\\').replace('"', '\\"'))
79 self.headers.append(('Content-Disposition',
80 'inline; filename="%s"' % filename))
81 if length is not None:
82 self.headers.append(('Content-Length', str(length)))
77
83
78 for k, v in self.headers:
84 for k, v in self.headers:
79 if not isinstance(v, str):
85 if not isinstance(v, str):
80 raise TypeError('header value must be string: %r' % v)
86 raise TypeError('header value must be string: %r' % (v,))
81
87
82 if isinstance(status, ErrorResponse):
88 if isinstance(status, ErrorResponse):
83 self.header(status.headers)
89 self.headers.extend(status.headers)
84 if status.code == HTTP_NOT_MODIFIED:
90 if status.code == HTTP_NOT_MODIFIED:
85 # RFC 2616 Section 10.3.5: 304 Not Modified has cases where
91 # RFC 2616 Section 10.3.5: 304 Not Modified has cases where
86 # it MUST NOT include any headers other than these and no
92 # it MUST NOT include any headers other than these and no
87 # body
93 # body
88 self.headers = [(k, v) for (k, v) in self.headers if
94 self.headers = [(k, v) for (k, v) in self.headers if
89 k in ('Date', 'ETag', 'Expires',
95 k in ('Date', 'ETag', 'Expires',
90 'Cache-Control', 'Vary')]
96 'Cache-Control', 'Vary')]
91 status = statusmessage(status.code, status.message)
97 status = statusmessage(status.code, status.message)
92 elif status == 200:
98 elif status == 200:
93 status = '200 Script output follows'
99 status = '200 Script output follows'
94 elif isinstance(status, int):
100 elif isinstance(status, int):
95 status = statusmessage(status)
101 status = statusmessage(status)
96
102
97 self.server_write = self._start_response(status, self.headers)
103 self.server_write = self._start_response(status, self.headers)
98 self._start_response = None
104 self._start_response = None
99 self.headers = []
105 self.headers = []
100
106
101 def write(self, thing):
107 def write(self, thing):
102 if util.safehasattr(thing, "__iter__"):
108 if util.safehasattr(thing, "__iter__"):
103 for part in thing:
109 for part in thing:
104 self.write(part)
110 self.write(part)
105 else:
111 else:
106 thing = str(thing)
112 thing = str(thing)
107 try:
113 try:
108 self.server_write(thing)
114 self.server_write(thing)
109 except socket.error, inst:
115 except socket.error, inst:
110 if inst[0] != errno.ECONNRESET:
116 if inst[0] != errno.ECONNRESET:
111 raise
117 raise
112
118
113 def writelines(self, lines):
119 def writelines(self, lines):
114 for line in lines:
120 for line in lines:
115 self.write(line)
121 self.write(line)
116
122
117 def flush(self):
123 def flush(self):
118 return None
124 return None
119
125
120 def close(self):
126 def close(self):
121 return None
127 return None
122
128
123 def header(self, headers=[('Content-Type','text/html')]):
124 self.headers.extend(headers)
125
126 def httphdr(self, type, filename=None, length=None, headers={}):
127 headers = headers.items()
128 headers.append(('Content-Type', type))
129 if filename:
130 filename = (filename.split('/')[-1]
131 .replace('\\', '\\\\').replace('"', '\\"'))
132 headers.append(('Content-Disposition',
133 'inline; filename="%s"' % filename))
134 if length is not None:
135 headers.append(('Content-Length', str(length)))
136 self.header(headers)
137
138 def wsgiapplication(app_maker):
129 def wsgiapplication(app_maker):
139 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
130 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
140 can and should now be used as a WSGI application.'''
131 can and should now be used as a WSGI application.'''
141 application = app_maker()
132 application = app_maker()
142 def run_wsgi(env, respond):
133 def run_wsgi(env, respond):
143 return application(env, respond)
134 return application(env, respond)
144 return run_wsgi
135 return run_wsgi
@@ -1,981 +1,981
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os, mimetypes, re, cgi, copy
8 import os, mimetypes, re, cgi, copy
9 import webutil
9 import webutil
10 from mercurial import error, encoding, archival, templater, templatefilters
10 from mercurial import error, encoding, archival, templater, templatefilters
11 from mercurial.node import short, hex, nullid
11 from mercurial.node import short, hex, nullid
12 from mercurial.util import binary
12 from mercurial.util import binary
13 from common import paritygen, staticfile, get_contact, ErrorResponse
13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 from mercurial import graphmod, patch
15 from mercurial import graphmod, patch
16 from mercurial import help as helpmod
16 from mercurial import help as helpmod
17 from mercurial import scmutil
17 from mercurial import scmutil
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 # __all__ is populated with the allowed commands. Be sure to add to it if
20 # __all__ is populated with the allowed commands. Be sure to add to it if
21 # you're adding a new command, or the new command won't work.
21 # you're adding a new command, or the new command won't work.
22
22
23 __all__ = [
23 __all__ = [
24 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
24 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
25 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
25 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
26 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
26 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
27 ]
27 ]
28
28
29 def log(web, req, tmpl):
29 def log(web, req, tmpl):
30 if 'file' in req.form and req.form['file'][0]:
30 if 'file' in req.form and req.form['file'][0]:
31 return filelog(web, req, tmpl)
31 return filelog(web, req, tmpl)
32 else:
32 else:
33 return changelog(web, req, tmpl)
33 return changelog(web, req, tmpl)
34
34
35 def rawfile(web, req, tmpl):
35 def rawfile(web, req, tmpl):
36 guessmime = web.configbool('web', 'guessmime', False)
36 guessmime = web.configbool('web', 'guessmime', False)
37
37
38 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
38 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
39 if not path:
39 if not path:
40 content = manifest(web, req, tmpl)
40 content = manifest(web, req, tmpl)
41 req.respond(HTTP_OK, web.ctype)
41 req.respond(HTTP_OK, web.ctype)
42 return content
42 return content
43
43
44 try:
44 try:
45 fctx = webutil.filectx(web.repo, req)
45 fctx = webutil.filectx(web.repo, req)
46 except error.LookupError, inst:
46 except error.LookupError, inst:
47 try:
47 try:
48 content = manifest(web, req, tmpl)
48 content = manifest(web, req, tmpl)
49 req.respond(HTTP_OK, web.ctype)
49 req.respond(HTTP_OK, web.ctype)
50 return content
50 return content
51 except ErrorResponse:
51 except ErrorResponse:
52 raise inst
52 raise inst
53
53
54 path = fctx.path()
54 path = fctx.path()
55 text = fctx.data()
55 text = fctx.data()
56 mt = 'application/binary'
56 mt = 'application/binary'
57 if guessmime:
57 if guessmime:
58 mt = mimetypes.guess_type(path)[0]
58 mt = mimetypes.guess_type(path)[0]
59 if mt is None:
59 if mt is None:
60 mt = binary(text) and 'application/binary' or 'text/plain'
60 mt = binary(text) and 'application/binary' or 'text/plain'
61 if mt.startswith('text/'):
61 if mt.startswith('text/'):
62 mt += '; charset="%s"' % encoding.encoding
62 mt += '; charset="%s"' % encoding.encoding
63
63
64 req.respond(HTTP_OK, mt, path, len(text))
64 req.respond(HTTP_OK, mt, path, len(text))
65 return [text]
65 return [text]
66
66
67 def _filerevision(web, tmpl, fctx):
67 def _filerevision(web, tmpl, fctx):
68 f = fctx.path()
68 f = fctx.path()
69 text = fctx.data()
69 text = fctx.data()
70 parity = paritygen(web.stripecount)
70 parity = paritygen(web.stripecount)
71
71
72 if binary(text):
72 if binary(text):
73 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
73 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
74 text = '(binary:%s)' % mt
74 text = '(binary:%s)' % mt
75
75
76 def lines():
76 def lines():
77 for lineno, t in enumerate(text.splitlines(True)):
77 for lineno, t in enumerate(text.splitlines(True)):
78 yield {"line": t,
78 yield {"line": t,
79 "lineid": "l%d" % (lineno + 1),
79 "lineid": "l%d" % (lineno + 1),
80 "linenumber": "% 6d" % (lineno + 1),
80 "linenumber": "% 6d" % (lineno + 1),
81 "parity": parity.next()}
81 "parity": parity.next()}
82
82
83 return tmpl("filerevision",
83 return tmpl("filerevision",
84 file=f,
84 file=f,
85 path=webutil.up(f),
85 path=webutil.up(f),
86 text=lines(),
86 text=lines(),
87 rev=fctx.rev(),
87 rev=fctx.rev(),
88 node=fctx.hex(),
88 node=fctx.hex(),
89 author=fctx.user(),
89 author=fctx.user(),
90 date=fctx.date(),
90 date=fctx.date(),
91 desc=fctx.description(),
91 desc=fctx.description(),
92 branch=webutil.nodebranchnodefault(fctx),
92 branch=webutil.nodebranchnodefault(fctx),
93 parent=webutil.parents(fctx),
93 parent=webutil.parents(fctx),
94 child=webutil.children(fctx),
94 child=webutil.children(fctx),
95 rename=webutil.renamelink(fctx),
95 rename=webutil.renamelink(fctx),
96 permissions=fctx.manifest().flags(f))
96 permissions=fctx.manifest().flags(f))
97
97
98 def file(web, req, tmpl):
98 def file(web, req, tmpl):
99 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
99 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
100 if not path:
100 if not path:
101 return manifest(web, req, tmpl)
101 return manifest(web, req, tmpl)
102 try:
102 try:
103 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
103 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
104 except error.LookupError, inst:
104 except error.LookupError, inst:
105 try:
105 try:
106 return manifest(web, req, tmpl)
106 return manifest(web, req, tmpl)
107 except ErrorResponse:
107 except ErrorResponse:
108 raise inst
108 raise inst
109
109
110 def _search(web, req, tmpl):
110 def _search(web, req, tmpl):
111
111
112 query = req.form['rev'][0]
112 query = req.form['rev'][0]
113 revcount = web.maxchanges
113 revcount = web.maxchanges
114 if 'revcount' in req.form:
114 if 'revcount' in req.form:
115 revcount = int(req.form.get('revcount', [revcount])[0])
115 revcount = int(req.form.get('revcount', [revcount])[0])
116 revcount = max(revcount, 1)
116 revcount = max(revcount, 1)
117 tmpl.defaults['sessionvars']['revcount'] = revcount
117 tmpl.defaults['sessionvars']['revcount'] = revcount
118
118
119 lessvars = copy.copy(tmpl.defaults['sessionvars'])
119 lessvars = copy.copy(tmpl.defaults['sessionvars'])
120 lessvars['revcount'] = max(revcount / 2, 1)
120 lessvars['revcount'] = max(revcount / 2, 1)
121 lessvars['rev'] = query
121 lessvars['rev'] = query
122 morevars = copy.copy(tmpl.defaults['sessionvars'])
122 morevars = copy.copy(tmpl.defaults['sessionvars'])
123 morevars['revcount'] = revcount * 2
123 morevars['revcount'] = revcount * 2
124 morevars['rev'] = query
124 morevars['rev'] = query
125
125
126 def changelist(**map):
126 def changelist(**map):
127 count = 0
127 count = 0
128 lower = encoding.lower
128 lower = encoding.lower
129 qw = lower(query).split()
129 qw = lower(query).split()
130
130
131 def revgen():
131 def revgen():
132 for i in xrange(len(web.repo) - 1, 0, -100):
132 for i in xrange(len(web.repo) - 1, 0, -100):
133 l = []
133 l = []
134 for j in xrange(max(0, i - 100), i + 1):
134 for j in xrange(max(0, i - 100), i + 1):
135 ctx = web.repo[j]
135 ctx = web.repo[j]
136 l.append(ctx)
136 l.append(ctx)
137 l.reverse()
137 l.reverse()
138 for e in l:
138 for e in l:
139 yield e
139 yield e
140
140
141 for ctx in revgen():
141 for ctx in revgen():
142 miss = 0
142 miss = 0
143 for q in qw:
143 for q in qw:
144 if not (q in lower(ctx.user()) or
144 if not (q in lower(ctx.user()) or
145 q in lower(ctx.description()) or
145 q in lower(ctx.description()) or
146 q in lower(" ".join(ctx.files()))):
146 q in lower(" ".join(ctx.files()))):
147 miss = 1
147 miss = 1
148 break
148 break
149 if miss:
149 if miss:
150 continue
150 continue
151
151
152 count += 1
152 count += 1
153 n = ctx.node()
153 n = ctx.node()
154 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
154 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
155 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
155 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
156
156
157 yield tmpl('searchentry',
157 yield tmpl('searchentry',
158 parity=parity.next(),
158 parity=parity.next(),
159 author=ctx.user(),
159 author=ctx.user(),
160 parent=webutil.parents(ctx),
160 parent=webutil.parents(ctx),
161 child=webutil.children(ctx),
161 child=webutil.children(ctx),
162 changelogtag=showtags,
162 changelogtag=showtags,
163 desc=ctx.description(),
163 desc=ctx.description(),
164 date=ctx.date(),
164 date=ctx.date(),
165 files=files,
165 files=files,
166 rev=ctx.rev(),
166 rev=ctx.rev(),
167 node=hex(n),
167 node=hex(n),
168 tags=webutil.nodetagsdict(web.repo, n),
168 tags=webutil.nodetagsdict(web.repo, n),
169 bookmarks=webutil.nodebookmarksdict(web.repo, n),
169 bookmarks=webutil.nodebookmarksdict(web.repo, n),
170 inbranch=webutil.nodeinbranch(web.repo, ctx),
170 inbranch=webutil.nodeinbranch(web.repo, ctx),
171 branches=webutil.nodebranchdict(web.repo, ctx))
171 branches=webutil.nodebranchdict(web.repo, ctx))
172
172
173 if count >= revcount:
173 if count >= revcount:
174 break
174 break
175
175
176 tip = web.repo['tip']
176 tip = web.repo['tip']
177 parity = paritygen(web.stripecount)
177 parity = paritygen(web.stripecount)
178
178
179 return tmpl('search', query=query, node=tip.hex(),
179 return tmpl('search', query=query, node=tip.hex(),
180 entries=changelist, archives=web.archivelist("tip"),
180 entries=changelist, archives=web.archivelist("tip"),
181 morevars=morevars, lessvars=lessvars)
181 morevars=morevars, lessvars=lessvars)
182
182
183 def changelog(web, req, tmpl, shortlog=False):
183 def changelog(web, req, tmpl, shortlog=False):
184
184
185 if 'node' in req.form:
185 if 'node' in req.form:
186 ctx = webutil.changectx(web.repo, req)
186 ctx = webutil.changectx(web.repo, req)
187 else:
187 else:
188 if 'rev' in req.form:
188 if 'rev' in req.form:
189 hi = req.form['rev'][0]
189 hi = req.form['rev'][0]
190 else:
190 else:
191 hi = len(web.repo) - 1
191 hi = len(web.repo) - 1
192 try:
192 try:
193 ctx = web.repo[hi]
193 ctx = web.repo[hi]
194 except error.RepoError:
194 except error.RepoError:
195 return _search(web, req, tmpl) # XXX redirect to 404 page?
195 return _search(web, req, tmpl) # XXX redirect to 404 page?
196
196
197 def changelist(limit=0, **map):
197 def changelist(limit=0, **map):
198 l = [] # build a list in forward order for efficiency
198 l = [] # build a list in forward order for efficiency
199 for i in xrange(start, end):
199 for i in xrange(start, end):
200 ctx = web.repo[i]
200 ctx = web.repo[i]
201 n = ctx.node()
201 n = ctx.node()
202 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
202 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
203 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
203 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
204
204
205 l.append({"parity": parity.next(),
205 l.append({"parity": parity.next(),
206 "author": ctx.user(),
206 "author": ctx.user(),
207 "parent": webutil.parents(ctx, i - 1),
207 "parent": webutil.parents(ctx, i - 1),
208 "child": webutil.children(ctx, i + 1),
208 "child": webutil.children(ctx, i + 1),
209 "changelogtag": showtags,
209 "changelogtag": showtags,
210 "desc": ctx.description(),
210 "desc": ctx.description(),
211 "date": ctx.date(),
211 "date": ctx.date(),
212 "files": files,
212 "files": files,
213 "rev": i,
213 "rev": i,
214 "node": hex(n),
214 "node": hex(n),
215 "tags": webutil.nodetagsdict(web.repo, n),
215 "tags": webutil.nodetagsdict(web.repo, n),
216 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
216 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
217 "inbranch": webutil.nodeinbranch(web.repo, ctx),
217 "inbranch": webutil.nodeinbranch(web.repo, ctx),
218 "branches": webutil.nodebranchdict(web.repo, ctx)
218 "branches": webutil.nodebranchdict(web.repo, ctx)
219 })
219 })
220 if limit > 0:
220 if limit > 0:
221 l = l[-limit:]
221 l = l[-limit:]
222
222
223 for e in reversed(l):
223 for e in reversed(l):
224 yield e
224 yield e
225
225
226 revcount = shortlog and web.maxshortchanges or web.maxchanges
226 revcount = shortlog and web.maxshortchanges or web.maxchanges
227 if 'revcount' in req.form:
227 if 'revcount' in req.form:
228 revcount = int(req.form.get('revcount', [revcount])[0])
228 revcount = int(req.form.get('revcount', [revcount])[0])
229 revcount = max(revcount, 1)
229 revcount = max(revcount, 1)
230 tmpl.defaults['sessionvars']['revcount'] = revcount
230 tmpl.defaults['sessionvars']['revcount'] = revcount
231
231
232 lessvars = copy.copy(tmpl.defaults['sessionvars'])
232 lessvars = copy.copy(tmpl.defaults['sessionvars'])
233 lessvars['revcount'] = max(revcount / 2, 1)
233 lessvars['revcount'] = max(revcount / 2, 1)
234 morevars = copy.copy(tmpl.defaults['sessionvars'])
234 morevars = copy.copy(tmpl.defaults['sessionvars'])
235 morevars['revcount'] = revcount * 2
235 morevars['revcount'] = revcount * 2
236
236
237 count = len(web.repo)
237 count = len(web.repo)
238 pos = ctx.rev()
238 pos = ctx.rev()
239 start = max(0, pos - revcount + 1)
239 start = max(0, pos - revcount + 1)
240 end = min(count, start + revcount)
240 end = min(count, start + revcount)
241 pos = end - 1
241 pos = end - 1
242 parity = paritygen(web.stripecount, offset=start - end)
242 parity = paritygen(web.stripecount, offset=start - end)
243
243
244 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
244 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
245
245
246 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
246 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
247 node=ctx.hex(), rev=pos, changesets=count,
247 node=ctx.hex(), rev=pos, changesets=count,
248 entries=lambda **x: changelist(limit=0,**x),
248 entries=lambda **x: changelist(limit=0,**x),
249 latestentry=lambda **x: changelist(limit=1,**x),
249 latestentry=lambda **x: changelist(limit=1,**x),
250 archives=web.archivelist("tip"), revcount=revcount,
250 archives=web.archivelist("tip"), revcount=revcount,
251 morevars=morevars, lessvars=lessvars)
251 morevars=morevars, lessvars=lessvars)
252
252
253 def shortlog(web, req, tmpl):
253 def shortlog(web, req, tmpl):
254 return changelog(web, req, tmpl, shortlog = True)
254 return changelog(web, req, tmpl, shortlog = True)
255
255
256 def changeset(web, req, tmpl):
256 def changeset(web, req, tmpl):
257 ctx = webutil.changectx(web.repo, req)
257 ctx = webutil.changectx(web.repo, req)
258 basectx = webutil.basechangectx(web.repo, req)
258 basectx = webutil.basechangectx(web.repo, req)
259 if basectx is None:
259 if basectx is None:
260 basectx = ctx.p1()
260 basectx = ctx.p1()
261 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
261 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
262 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
262 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
263 ctx.node())
263 ctx.node())
264 showbranch = webutil.nodebranchnodefault(ctx)
264 showbranch = webutil.nodebranchnodefault(ctx)
265
265
266 files = []
266 files = []
267 parity = paritygen(web.stripecount)
267 parity = paritygen(web.stripecount)
268 for blockno, f in enumerate(ctx.files()):
268 for blockno, f in enumerate(ctx.files()):
269 template = f in ctx and 'filenodelink' or 'filenolink'
269 template = f in ctx and 'filenodelink' or 'filenolink'
270 files.append(tmpl(template,
270 files.append(tmpl(template,
271 node=ctx.hex(), file=f, blockno=blockno + 1,
271 node=ctx.hex(), file=f, blockno=blockno + 1,
272 parity=parity.next()))
272 parity=parity.next()))
273
273
274 style = web.config('web', 'style', 'paper')
274 style = web.config('web', 'style', 'paper')
275 if 'style' in req.form:
275 if 'style' in req.form:
276 style = req.form['style'][0]
276 style = req.form['style'][0]
277
277
278 parity = paritygen(web.stripecount)
278 parity = paritygen(web.stripecount)
279 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
279 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
280
280
281 parity = paritygen(web.stripecount)
281 parity = paritygen(web.stripecount)
282 diffstatgen = webutil.diffstatgen(ctx, basectx)
282 diffstatgen = webutil.diffstatgen(ctx, basectx)
283 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
283 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
284
284
285 return tmpl('changeset',
285 return tmpl('changeset',
286 diff=diffs,
286 diff=diffs,
287 rev=ctx.rev(),
287 rev=ctx.rev(),
288 node=ctx.hex(),
288 node=ctx.hex(),
289 parent=webutil.parents(ctx),
289 parent=webutil.parents(ctx),
290 child=webutil.children(ctx),
290 child=webutil.children(ctx),
291 currentbaseline=basectx.hex(),
291 currentbaseline=basectx.hex(),
292 changesettag=showtags,
292 changesettag=showtags,
293 changesetbookmark=showbookmarks,
293 changesetbookmark=showbookmarks,
294 changesetbranch=showbranch,
294 changesetbranch=showbranch,
295 author=ctx.user(),
295 author=ctx.user(),
296 desc=ctx.description(),
296 desc=ctx.description(),
297 date=ctx.date(),
297 date=ctx.date(),
298 files=files,
298 files=files,
299 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
299 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
300 diffstat=diffstat,
300 diffstat=diffstat,
301 archives=web.archivelist(ctx.hex()),
301 archives=web.archivelist(ctx.hex()),
302 tags=webutil.nodetagsdict(web.repo, ctx.node()),
302 tags=webutil.nodetagsdict(web.repo, ctx.node()),
303 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
303 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
304 branch=webutil.nodebranchnodefault(ctx),
304 branch=webutil.nodebranchnodefault(ctx),
305 inbranch=webutil.nodeinbranch(web.repo, ctx),
305 inbranch=webutil.nodeinbranch(web.repo, ctx),
306 branches=webutil.nodebranchdict(web.repo, ctx))
306 branches=webutil.nodebranchdict(web.repo, ctx))
307
307
308 rev = changeset
308 rev = changeset
309
309
310 def decodepath(path):
310 def decodepath(path):
311 """Hook for mapping a path in the repository to a path in the
311 """Hook for mapping a path in the repository to a path in the
312 working copy.
312 working copy.
313
313
314 Extensions (e.g., largefiles) can override this to remap files in
314 Extensions (e.g., largefiles) can override this to remap files in
315 the virtual file system presented by the manifest command below."""
315 the virtual file system presented by the manifest command below."""
316 return path
316 return path
317
317
318 def manifest(web, req, tmpl):
318 def manifest(web, req, tmpl):
319 ctx = webutil.changectx(web.repo, req)
319 ctx = webutil.changectx(web.repo, req)
320 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
320 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
321 mf = ctx.manifest()
321 mf = ctx.manifest()
322 node = ctx.node()
322 node = ctx.node()
323
323
324 files = {}
324 files = {}
325 dirs = {}
325 dirs = {}
326 parity = paritygen(web.stripecount)
326 parity = paritygen(web.stripecount)
327
327
328 if path and path[-1] != "/":
328 if path and path[-1] != "/":
329 path += "/"
329 path += "/"
330 l = len(path)
330 l = len(path)
331 abspath = "/" + path
331 abspath = "/" + path
332
332
333 for full, n in mf.iteritems():
333 for full, n in mf.iteritems():
334 # the virtual path (working copy path) used for the full
334 # the virtual path (working copy path) used for the full
335 # (repository) path
335 # (repository) path
336 f = decodepath(full)
336 f = decodepath(full)
337
337
338 if f[:l] != path:
338 if f[:l] != path:
339 continue
339 continue
340 remain = f[l:]
340 remain = f[l:]
341 elements = remain.split('/')
341 elements = remain.split('/')
342 if len(elements) == 1:
342 if len(elements) == 1:
343 files[remain] = full
343 files[remain] = full
344 else:
344 else:
345 h = dirs # need to retain ref to dirs (root)
345 h = dirs # need to retain ref to dirs (root)
346 for elem in elements[0:-1]:
346 for elem in elements[0:-1]:
347 if elem not in h:
347 if elem not in h:
348 h[elem] = {}
348 h[elem] = {}
349 h = h[elem]
349 h = h[elem]
350 if len(h) > 1:
350 if len(h) > 1:
351 break
351 break
352 h[None] = None # denotes files present
352 h[None] = None # denotes files present
353
353
354 if mf and not files and not dirs:
354 if mf and not files and not dirs:
355 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
355 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
356
356
357 def filelist(**map):
357 def filelist(**map):
358 for f in sorted(files):
358 for f in sorted(files):
359 full = files[f]
359 full = files[f]
360
360
361 fctx = ctx.filectx(full)
361 fctx = ctx.filectx(full)
362 yield {"file": full,
362 yield {"file": full,
363 "parity": parity.next(),
363 "parity": parity.next(),
364 "basename": f,
364 "basename": f,
365 "date": fctx.date(),
365 "date": fctx.date(),
366 "size": fctx.size(),
366 "size": fctx.size(),
367 "permissions": mf.flags(full)}
367 "permissions": mf.flags(full)}
368
368
369 def dirlist(**map):
369 def dirlist(**map):
370 for d in sorted(dirs):
370 for d in sorted(dirs):
371
371
372 emptydirs = []
372 emptydirs = []
373 h = dirs[d]
373 h = dirs[d]
374 while isinstance(h, dict) and len(h) == 1:
374 while isinstance(h, dict) and len(h) == 1:
375 k, v = h.items()[0]
375 k, v = h.items()[0]
376 if v:
376 if v:
377 emptydirs.append(k)
377 emptydirs.append(k)
378 h = v
378 h = v
379
379
380 path = "%s%s" % (abspath, d)
380 path = "%s%s" % (abspath, d)
381 yield {"parity": parity.next(),
381 yield {"parity": parity.next(),
382 "path": path,
382 "path": path,
383 "emptydirs": "/".join(emptydirs),
383 "emptydirs": "/".join(emptydirs),
384 "basename": d}
384 "basename": d}
385
385
386 return tmpl("manifest",
386 return tmpl("manifest",
387 rev=ctx.rev(),
387 rev=ctx.rev(),
388 node=hex(node),
388 node=hex(node),
389 path=abspath,
389 path=abspath,
390 up=webutil.up(abspath),
390 up=webutil.up(abspath),
391 upparity=parity.next(),
391 upparity=parity.next(),
392 fentries=filelist,
392 fentries=filelist,
393 dentries=dirlist,
393 dentries=dirlist,
394 archives=web.archivelist(hex(node)),
394 archives=web.archivelist(hex(node)),
395 tags=webutil.nodetagsdict(web.repo, node),
395 tags=webutil.nodetagsdict(web.repo, node),
396 bookmarks=webutil.nodebookmarksdict(web.repo, node),
396 bookmarks=webutil.nodebookmarksdict(web.repo, node),
397 inbranch=webutil.nodeinbranch(web.repo, ctx),
397 inbranch=webutil.nodeinbranch(web.repo, ctx),
398 branches=webutil.nodebranchdict(web.repo, ctx))
398 branches=webutil.nodebranchdict(web.repo, ctx))
399
399
400 def tags(web, req, tmpl):
400 def tags(web, req, tmpl):
401 i = list(reversed(web.repo.tagslist()))
401 i = list(reversed(web.repo.tagslist()))
402 parity = paritygen(web.stripecount)
402 parity = paritygen(web.stripecount)
403
403
404 def entries(notip=False, limit=0, **map):
404 def entries(notip=False, limit=0, **map):
405 count = 0
405 count = 0
406 for k, n in i:
406 for k, n in i:
407 if notip and k == "tip":
407 if notip and k == "tip":
408 continue
408 continue
409 if limit > 0 and count >= limit:
409 if limit > 0 and count >= limit:
410 continue
410 continue
411 count = count + 1
411 count = count + 1
412 yield {"parity": parity.next(),
412 yield {"parity": parity.next(),
413 "tag": k,
413 "tag": k,
414 "date": web.repo[n].date(),
414 "date": web.repo[n].date(),
415 "node": hex(n)}
415 "node": hex(n)}
416
416
417 return tmpl("tags",
417 return tmpl("tags",
418 node=hex(web.repo.changelog.tip()),
418 node=hex(web.repo.changelog.tip()),
419 entries=lambda **x: entries(False, 0, **x),
419 entries=lambda **x: entries(False, 0, **x),
420 entriesnotip=lambda **x: entries(True, 0, **x),
420 entriesnotip=lambda **x: entries(True, 0, **x),
421 latestentry=lambda **x: entries(True, 1, **x))
421 latestentry=lambda **x: entries(True, 1, **x))
422
422
423 def bookmarks(web, req, tmpl):
423 def bookmarks(web, req, tmpl):
424 i = web.repo._bookmarks.items()
424 i = web.repo._bookmarks.items()
425 parity = paritygen(web.stripecount)
425 parity = paritygen(web.stripecount)
426
426
427 def entries(limit=0, **map):
427 def entries(limit=0, **map):
428 count = 0
428 count = 0
429 for k, n in sorted(i):
429 for k, n in sorted(i):
430 if limit > 0 and count >= limit:
430 if limit > 0 and count >= limit:
431 continue
431 continue
432 count = count + 1
432 count = count + 1
433 yield {"parity": parity.next(),
433 yield {"parity": parity.next(),
434 "bookmark": k,
434 "bookmark": k,
435 "date": web.repo[n].date(),
435 "date": web.repo[n].date(),
436 "node": hex(n)}
436 "node": hex(n)}
437
437
438 return tmpl("bookmarks",
438 return tmpl("bookmarks",
439 node=hex(web.repo.changelog.tip()),
439 node=hex(web.repo.changelog.tip()),
440 entries=lambda **x: entries(0, **x),
440 entries=lambda **x: entries(0, **x),
441 latestentry=lambda **x: entries(1, **x))
441 latestentry=lambda **x: entries(1, **x))
442
442
443 def branches(web, req, tmpl):
443 def branches(web, req, tmpl):
444 tips = []
444 tips = []
445 heads = web.repo.heads()
445 heads = web.repo.heads()
446 parity = paritygen(web.stripecount)
446 parity = paritygen(web.stripecount)
447 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
447 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
448
448
449 def entries(limit, **map):
449 def entries(limit, **map):
450 count = 0
450 count = 0
451 if not tips:
451 if not tips:
452 for t, n in web.repo.branchtags().iteritems():
452 for t, n in web.repo.branchtags().iteritems():
453 tips.append(web.repo[n])
453 tips.append(web.repo[n])
454 for ctx in sorted(tips, key=sortkey, reverse=True):
454 for ctx in sorted(tips, key=sortkey, reverse=True):
455 if limit > 0 and count >= limit:
455 if limit > 0 and count >= limit:
456 return
456 return
457 count += 1
457 count += 1
458 if not web.repo.branchheads(ctx.branch()):
458 if not web.repo.branchheads(ctx.branch()):
459 status = 'closed'
459 status = 'closed'
460 elif ctx.node() not in heads:
460 elif ctx.node() not in heads:
461 status = 'inactive'
461 status = 'inactive'
462 else:
462 else:
463 status = 'open'
463 status = 'open'
464 yield {'parity': parity.next(),
464 yield {'parity': parity.next(),
465 'branch': ctx.branch(),
465 'branch': ctx.branch(),
466 'status': status,
466 'status': status,
467 'node': ctx.hex(),
467 'node': ctx.hex(),
468 'date': ctx.date()}
468 'date': ctx.date()}
469
469
470 return tmpl('branches', node=hex(web.repo.changelog.tip()),
470 return tmpl('branches', node=hex(web.repo.changelog.tip()),
471 entries=lambda **x: entries(0, **x),
471 entries=lambda **x: entries(0, **x),
472 latestentry=lambda **x: entries(1, **x))
472 latestentry=lambda **x: entries(1, **x))
473
473
474 def summary(web, req, tmpl):
474 def summary(web, req, tmpl):
475 i = reversed(web.repo.tagslist())
475 i = reversed(web.repo.tagslist())
476
476
477 def tagentries(**map):
477 def tagentries(**map):
478 parity = paritygen(web.stripecount)
478 parity = paritygen(web.stripecount)
479 count = 0
479 count = 0
480 for k, n in i:
480 for k, n in i:
481 if k == "tip": # skip tip
481 if k == "tip": # skip tip
482 continue
482 continue
483
483
484 count += 1
484 count += 1
485 if count > 10: # limit to 10 tags
485 if count > 10: # limit to 10 tags
486 break
486 break
487
487
488 yield tmpl("tagentry",
488 yield tmpl("tagentry",
489 parity=parity.next(),
489 parity=parity.next(),
490 tag=k,
490 tag=k,
491 node=hex(n),
491 node=hex(n),
492 date=web.repo[n].date())
492 date=web.repo[n].date())
493
493
494 def bookmarks(**map):
494 def bookmarks(**map):
495 parity = paritygen(web.stripecount)
495 parity = paritygen(web.stripecount)
496 b = web.repo._bookmarks.items()
496 b = web.repo._bookmarks.items()
497 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
497 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
498 yield {'parity': parity.next(),
498 yield {'parity': parity.next(),
499 'bookmark': k,
499 'bookmark': k,
500 'date': web.repo[n].date(),
500 'date': web.repo[n].date(),
501 'node': hex(n)}
501 'node': hex(n)}
502
502
503 def branches(**map):
503 def branches(**map):
504 parity = paritygen(web.stripecount)
504 parity = paritygen(web.stripecount)
505
505
506 b = web.repo.branchtags()
506 b = web.repo.branchtags()
507 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
507 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
508 for r, n, t in sorted(l):
508 for r, n, t in sorted(l):
509 yield {'parity': parity.next(),
509 yield {'parity': parity.next(),
510 'branch': t,
510 'branch': t,
511 'node': hex(n),
511 'node': hex(n),
512 'date': web.repo[n].date()}
512 'date': web.repo[n].date()}
513
513
514 def changelist(**map):
514 def changelist(**map):
515 parity = paritygen(web.stripecount, offset=start - end)
515 parity = paritygen(web.stripecount, offset=start - end)
516 l = [] # build a list in forward order for efficiency
516 l = [] # build a list in forward order for efficiency
517 for i in xrange(start, end):
517 for i in xrange(start, end):
518 ctx = web.repo[i]
518 ctx = web.repo[i]
519 n = ctx.node()
519 n = ctx.node()
520 hn = hex(n)
520 hn = hex(n)
521
521
522 l.append(tmpl(
522 l.append(tmpl(
523 'shortlogentry',
523 'shortlogentry',
524 parity=parity.next(),
524 parity=parity.next(),
525 author=ctx.user(),
525 author=ctx.user(),
526 desc=ctx.description(),
526 desc=ctx.description(),
527 date=ctx.date(),
527 date=ctx.date(),
528 rev=i,
528 rev=i,
529 node=hn,
529 node=hn,
530 tags=webutil.nodetagsdict(web.repo, n),
530 tags=webutil.nodetagsdict(web.repo, n),
531 bookmarks=webutil.nodebookmarksdict(web.repo, n),
531 bookmarks=webutil.nodebookmarksdict(web.repo, n),
532 inbranch=webutil.nodeinbranch(web.repo, ctx),
532 inbranch=webutil.nodeinbranch(web.repo, ctx),
533 branches=webutil.nodebranchdict(web.repo, ctx)))
533 branches=webutil.nodebranchdict(web.repo, ctx)))
534
534
535 l.reverse()
535 l.reverse()
536 yield l
536 yield l
537
537
538 tip = web.repo['tip']
538 tip = web.repo['tip']
539 count = len(web.repo)
539 count = len(web.repo)
540 start = max(0, count - web.maxchanges)
540 start = max(0, count - web.maxchanges)
541 end = min(count, start + web.maxchanges)
541 end = min(count, start + web.maxchanges)
542
542
543 return tmpl("summary",
543 return tmpl("summary",
544 desc=web.config("web", "description", "unknown"),
544 desc=web.config("web", "description", "unknown"),
545 owner=get_contact(web.config) or "unknown",
545 owner=get_contact(web.config) or "unknown",
546 lastchange=tip.date(),
546 lastchange=tip.date(),
547 tags=tagentries,
547 tags=tagentries,
548 bookmarks=bookmarks,
548 bookmarks=bookmarks,
549 branches=branches,
549 branches=branches,
550 shortlog=changelist,
550 shortlog=changelist,
551 node=tip.hex(),
551 node=tip.hex(),
552 archives=web.archivelist("tip"))
552 archives=web.archivelist("tip"))
553
553
554 def filediff(web, req, tmpl):
554 def filediff(web, req, tmpl):
555 fctx, ctx = None, None
555 fctx, ctx = None, None
556 try:
556 try:
557 fctx = webutil.filectx(web.repo, req)
557 fctx = webutil.filectx(web.repo, req)
558 except LookupError:
558 except LookupError:
559 ctx = webutil.changectx(web.repo, req)
559 ctx = webutil.changectx(web.repo, req)
560 path = webutil.cleanpath(web.repo, req.form['file'][0])
560 path = webutil.cleanpath(web.repo, req.form['file'][0])
561 if path not in ctx.files():
561 if path not in ctx.files():
562 raise
562 raise
563
563
564 if fctx is not None:
564 if fctx is not None:
565 n = fctx.node()
565 n = fctx.node()
566 path = fctx.path()
566 path = fctx.path()
567 ctx = fctx.changectx()
567 ctx = fctx.changectx()
568 else:
568 else:
569 n = ctx.node()
569 n = ctx.node()
570 # path already defined in except clause
570 # path already defined in except clause
571
571
572 parity = paritygen(web.stripecount)
572 parity = paritygen(web.stripecount)
573 style = web.config('web', 'style', 'paper')
573 style = web.config('web', 'style', 'paper')
574 if 'style' in req.form:
574 if 'style' in req.form:
575 style = req.form['style'][0]
575 style = req.form['style'][0]
576
576
577 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
577 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
578 rename = fctx and webutil.renamelink(fctx) or []
578 rename = fctx and webutil.renamelink(fctx) or []
579 ctx = fctx and fctx or ctx
579 ctx = fctx and fctx or ctx
580 return tmpl("filediff",
580 return tmpl("filediff",
581 file=path,
581 file=path,
582 node=hex(n),
582 node=hex(n),
583 rev=ctx.rev(),
583 rev=ctx.rev(),
584 date=ctx.date(),
584 date=ctx.date(),
585 desc=ctx.description(),
585 desc=ctx.description(),
586 author=ctx.user(),
586 author=ctx.user(),
587 rename=rename,
587 rename=rename,
588 branch=webutil.nodebranchnodefault(ctx),
588 branch=webutil.nodebranchnodefault(ctx),
589 parent=webutil.parents(ctx),
589 parent=webutil.parents(ctx),
590 child=webutil.children(ctx),
590 child=webutil.children(ctx),
591 diff=diffs)
591 diff=diffs)
592
592
593 diff = filediff
593 diff = filediff
594
594
595 def comparison(web, req, tmpl):
595 def comparison(web, req, tmpl):
596 ctx = webutil.changectx(web.repo, req)
596 ctx = webutil.changectx(web.repo, req)
597 if 'file' not in req.form:
597 if 'file' not in req.form:
598 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
598 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
599 path = webutil.cleanpath(web.repo, req.form['file'][0])
599 path = webutil.cleanpath(web.repo, req.form['file'][0])
600 rename = path in ctx and webutil.renamelink(ctx[path]) or []
600 rename = path in ctx and webutil.renamelink(ctx[path]) or []
601
601
602 parsecontext = lambda v: v == 'full' and -1 or int(v)
602 parsecontext = lambda v: v == 'full' and -1 or int(v)
603 if 'context' in req.form:
603 if 'context' in req.form:
604 context = parsecontext(req.form['context'][0])
604 context = parsecontext(req.form['context'][0])
605 else:
605 else:
606 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
606 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
607
607
608 def filelines(f):
608 def filelines(f):
609 if binary(f.data()):
609 if binary(f.data()):
610 mt = mimetypes.guess_type(f.path())[0]
610 mt = mimetypes.guess_type(f.path())[0]
611 if not mt:
611 if not mt:
612 mt = 'application/octet-stream'
612 mt = 'application/octet-stream'
613 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
613 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
614 return f.data().splitlines()
614 return f.data().splitlines()
615
615
616 if path in ctx:
616 if path in ctx:
617 fctx = ctx[path]
617 fctx = ctx[path]
618 rightrev = fctx.filerev()
618 rightrev = fctx.filerev()
619 rightnode = fctx.filenode()
619 rightnode = fctx.filenode()
620 rightlines = filelines(fctx)
620 rightlines = filelines(fctx)
621 parents = fctx.parents()
621 parents = fctx.parents()
622 if not parents:
622 if not parents:
623 leftrev = -1
623 leftrev = -1
624 leftnode = nullid
624 leftnode = nullid
625 leftlines = ()
625 leftlines = ()
626 else:
626 else:
627 pfctx = parents[0]
627 pfctx = parents[0]
628 leftrev = pfctx.filerev()
628 leftrev = pfctx.filerev()
629 leftnode = pfctx.filenode()
629 leftnode = pfctx.filenode()
630 leftlines = filelines(pfctx)
630 leftlines = filelines(pfctx)
631 else:
631 else:
632 rightrev = -1
632 rightrev = -1
633 rightnode = nullid
633 rightnode = nullid
634 rightlines = ()
634 rightlines = ()
635 fctx = ctx.parents()[0][path]
635 fctx = ctx.parents()[0][path]
636 leftrev = fctx.filerev()
636 leftrev = fctx.filerev()
637 leftnode = fctx.filenode()
637 leftnode = fctx.filenode()
638 leftlines = filelines(fctx)
638 leftlines = filelines(fctx)
639
639
640 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
640 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
641 return tmpl('filecomparison',
641 return tmpl('filecomparison',
642 file=path,
642 file=path,
643 node=hex(ctx.node()),
643 node=hex(ctx.node()),
644 rev=ctx.rev(),
644 rev=ctx.rev(),
645 date=ctx.date(),
645 date=ctx.date(),
646 desc=ctx.description(),
646 desc=ctx.description(),
647 author=ctx.user(),
647 author=ctx.user(),
648 rename=rename,
648 rename=rename,
649 branch=webutil.nodebranchnodefault(ctx),
649 branch=webutil.nodebranchnodefault(ctx),
650 parent=webutil.parents(fctx),
650 parent=webutil.parents(fctx),
651 child=webutil.children(fctx),
651 child=webutil.children(fctx),
652 leftrev=leftrev,
652 leftrev=leftrev,
653 leftnode=hex(leftnode),
653 leftnode=hex(leftnode),
654 rightrev=rightrev,
654 rightrev=rightrev,
655 rightnode=hex(rightnode),
655 rightnode=hex(rightnode),
656 comparison=comparison)
656 comparison=comparison)
657
657
658 def annotate(web, req, tmpl):
658 def annotate(web, req, tmpl):
659 fctx = webutil.filectx(web.repo, req)
659 fctx = webutil.filectx(web.repo, req)
660 f = fctx.path()
660 f = fctx.path()
661 parity = paritygen(web.stripecount)
661 parity = paritygen(web.stripecount)
662 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
662 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
663
663
664 def annotate(**map):
664 def annotate(**map):
665 last = None
665 last = None
666 if binary(fctx.data()):
666 if binary(fctx.data()):
667 mt = (mimetypes.guess_type(fctx.path())[0]
667 mt = (mimetypes.guess_type(fctx.path())[0]
668 or 'application/octet-stream')
668 or 'application/octet-stream')
669 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
669 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
670 '(binary:%s)' % mt)])
670 '(binary:%s)' % mt)])
671 else:
671 else:
672 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
672 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
673 diffopts=diffopts))
673 diffopts=diffopts))
674 for lineno, ((f, targetline), l) in lines:
674 for lineno, ((f, targetline), l) in lines:
675 fnode = f.filenode()
675 fnode = f.filenode()
676
676
677 if last != fnode:
677 if last != fnode:
678 last = fnode
678 last = fnode
679
679
680 yield {"parity": parity.next(),
680 yield {"parity": parity.next(),
681 "node": f.hex(),
681 "node": f.hex(),
682 "rev": f.rev(),
682 "rev": f.rev(),
683 "author": f.user(),
683 "author": f.user(),
684 "desc": f.description(),
684 "desc": f.description(),
685 "file": f.path(),
685 "file": f.path(),
686 "targetline": targetline,
686 "targetline": targetline,
687 "line": l,
687 "line": l,
688 "lineid": "l%d" % (lineno + 1),
688 "lineid": "l%d" % (lineno + 1),
689 "linenumber": "% 6d" % (lineno + 1),
689 "linenumber": "% 6d" % (lineno + 1),
690 "revdate": f.date()}
690 "revdate": f.date()}
691
691
692 return tmpl("fileannotate",
692 return tmpl("fileannotate",
693 file=f,
693 file=f,
694 annotate=annotate,
694 annotate=annotate,
695 path=webutil.up(f),
695 path=webutil.up(f),
696 rev=fctx.rev(),
696 rev=fctx.rev(),
697 node=fctx.hex(),
697 node=fctx.hex(),
698 author=fctx.user(),
698 author=fctx.user(),
699 date=fctx.date(),
699 date=fctx.date(),
700 desc=fctx.description(),
700 desc=fctx.description(),
701 rename=webutil.renamelink(fctx),
701 rename=webutil.renamelink(fctx),
702 branch=webutil.nodebranchnodefault(fctx),
702 branch=webutil.nodebranchnodefault(fctx),
703 parent=webutil.parents(fctx),
703 parent=webutil.parents(fctx),
704 child=webutil.children(fctx),
704 child=webutil.children(fctx),
705 permissions=fctx.manifest().flags(f))
705 permissions=fctx.manifest().flags(f))
706
706
707 def filelog(web, req, tmpl):
707 def filelog(web, req, tmpl):
708
708
709 try:
709 try:
710 fctx = webutil.filectx(web.repo, req)
710 fctx = webutil.filectx(web.repo, req)
711 f = fctx.path()
711 f = fctx.path()
712 fl = fctx.filelog()
712 fl = fctx.filelog()
713 except error.LookupError:
713 except error.LookupError:
714 f = webutil.cleanpath(web.repo, req.form['file'][0])
714 f = webutil.cleanpath(web.repo, req.form['file'][0])
715 fl = web.repo.file(f)
715 fl = web.repo.file(f)
716 numrevs = len(fl)
716 numrevs = len(fl)
717 if not numrevs: # file doesn't exist at all
717 if not numrevs: # file doesn't exist at all
718 raise
718 raise
719 rev = webutil.changectx(web.repo, req).rev()
719 rev = webutil.changectx(web.repo, req).rev()
720 first = fl.linkrev(0)
720 first = fl.linkrev(0)
721 if rev < first: # current rev is from before file existed
721 if rev < first: # current rev is from before file existed
722 raise
722 raise
723 frev = numrevs - 1
723 frev = numrevs - 1
724 while fl.linkrev(frev) > rev:
724 while fl.linkrev(frev) > rev:
725 frev -= 1
725 frev -= 1
726 fctx = web.repo.filectx(f, fl.linkrev(frev))
726 fctx = web.repo.filectx(f, fl.linkrev(frev))
727
727
728 revcount = web.maxshortchanges
728 revcount = web.maxshortchanges
729 if 'revcount' in req.form:
729 if 'revcount' in req.form:
730 revcount = int(req.form.get('revcount', [revcount])[0])
730 revcount = int(req.form.get('revcount', [revcount])[0])
731 revcount = max(revcount, 1)
731 revcount = max(revcount, 1)
732 tmpl.defaults['sessionvars']['revcount'] = revcount
732 tmpl.defaults['sessionvars']['revcount'] = revcount
733
733
734 lessvars = copy.copy(tmpl.defaults['sessionvars'])
734 lessvars = copy.copy(tmpl.defaults['sessionvars'])
735 lessvars['revcount'] = max(revcount / 2, 1)
735 lessvars['revcount'] = max(revcount / 2, 1)
736 morevars = copy.copy(tmpl.defaults['sessionvars'])
736 morevars = copy.copy(tmpl.defaults['sessionvars'])
737 morevars['revcount'] = revcount * 2
737 morevars['revcount'] = revcount * 2
738
738
739 count = fctx.filerev() + 1
739 count = fctx.filerev() + 1
740 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
740 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
741 end = min(count, start + revcount) # last rev on this page
741 end = min(count, start + revcount) # last rev on this page
742 parity = paritygen(web.stripecount, offset=start - end)
742 parity = paritygen(web.stripecount, offset=start - end)
743
743
744 def entries(limit=0, **map):
744 def entries(limit=0, **map):
745 l = []
745 l = []
746
746
747 repo = web.repo
747 repo = web.repo
748 for i in xrange(start, end):
748 for i in xrange(start, end):
749 iterfctx = fctx.filectx(i)
749 iterfctx = fctx.filectx(i)
750
750
751 l.append({"parity": parity.next(),
751 l.append({"parity": parity.next(),
752 "filerev": i,
752 "filerev": i,
753 "file": f,
753 "file": f,
754 "node": iterfctx.hex(),
754 "node": iterfctx.hex(),
755 "author": iterfctx.user(),
755 "author": iterfctx.user(),
756 "date": iterfctx.date(),
756 "date": iterfctx.date(),
757 "rename": webutil.renamelink(iterfctx),
757 "rename": webutil.renamelink(iterfctx),
758 "parent": webutil.parents(iterfctx),
758 "parent": webutil.parents(iterfctx),
759 "child": webutil.children(iterfctx),
759 "child": webutil.children(iterfctx),
760 "desc": iterfctx.description(),
760 "desc": iterfctx.description(),
761 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
761 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
762 "bookmarks": webutil.nodebookmarksdict(
762 "bookmarks": webutil.nodebookmarksdict(
763 repo, iterfctx.node()),
763 repo, iterfctx.node()),
764 "branch": webutil.nodebranchnodefault(iterfctx),
764 "branch": webutil.nodebranchnodefault(iterfctx),
765 "inbranch": webutil.nodeinbranch(repo, iterfctx),
765 "inbranch": webutil.nodeinbranch(repo, iterfctx),
766 "branches": webutil.nodebranchdict(repo, iterfctx)})
766 "branches": webutil.nodebranchdict(repo, iterfctx)})
767
767
768 if limit > 0:
768 if limit > 0:
769 l = l[-limit:]
769 l = l[-limit:]
770
770
771 for e in reversed(l):
771 for e in reversed(l):
772 yield e
772 yield e
773
773
774 nodefunc = lambda x: fctx.filectx(fileid=x)
774 nodefunc = lambda x: fctx.filectx(fileid=x)
775 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
775 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
776 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
776 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
777 entries=lambda **x: entries(limit=0, **x),
777 entries=lambda **x: entries(limit=0, **x),
778 latestentry=lambda **x: entries(limit=1, **x),
778 latestentry=lambda **x: entries(limit=1, **x),
779 revcount=revcount, morevars=morevars, lessvars=lessvars)
779 revcount=revcount, morevars=morevars, lessvars=lessvars)
780
780
781 def archive(web, req, tmpl):
781 def archive(web, req, tmpl):
782 type_ = req.form.get('type', [None])[0]
782 type_ = req.form.get('type', [None])[0]
783 allowed = web.configlist("web", "allow_archive")
783 allowed = web.configlist("web", "allow_archive")
784 key = req.form['node'][0]
784 key = req.form['node'][0]
785
785
786 if type_ not in web.archives:
786 if type_ not in web.archives:
787 msg = 'Unsupported archive type: %s' % type_
787 msg = 'Unsupported archive type: %s' % type_
788 raise ErrorResponse(HTTP_NOT_FOUND, msg)
788 raise ErrorResponse(HTTP_NOT_FOUND, msg)
789
789
790 if not ((type_ in allowed or
790 if not ((type_ in allowed or
791 web.configbool("web", "allow" + type_, False))):
791 web.configbool("web", "allow" + type_, False))):
792 msg = 'Archive type not allowed: %s' % type_
792 msg = 'Archive type not allowed: %s' % type_
793 raise ErrorResponse(HTTP_FORBIDDEN, msg)
793 raise ErrorResponse(HTTP_FORBIDDEN, msg)
794
794
795 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
795 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
796 cnode = web.repo.lookup(key)
796 cnode = web.repo.lookup(key)
797 arch_version = key
797 arch_version = key
798 if cnode == key or key == 'tip':
798 if cnode == key or key == 'tip':
799 arch_version = short(cnode)
799 arch_version = short(cnode)
800 name = "%s-%s" % (reponame, arch_version)
800 name = "%s-%s" % (reponame, arch_version)
801 mimetype, artype, extension, encoding = web.archive_specs[type_]
801 mimetype, artype, extension, encoding = web.archive_specs[type_]
802 headers = [
802 headers = [
803 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
803 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
804 ]
804 ]
805 if encoding:
805 if encoding:
806 headers.append(('Content-Encoding', encoding))
806 headers.append(('Content-Encoding', encoding))
807 req.header(headers)
807 req.headers.extend(headers)
808 req.respond(HTTP_OK, mimetype)
808 req.respond(HTTP_OK, mimetype)
809
809
810 ctx = webutil.changectx(web.repo, req)
810 ctx = webutil.changectx(web.repo, req)
811 archival.archive(web.repo, req, cnode, artype, prefix=name,
811 archival.archive(web.repo, req, cnode, artype, prefix=name,
812 matchfn=scmutil.match(ctx, []),
812 matchfn=scmutil.match(ctx, []),
813 subrepos=web.configbool("web", "archivesubrepos"))
813 subrepos=web.configbool("web", "archivesubrepos"))
814 return []
814 return []
815
815
816
816
817 def static(web, req, tmpl):
817 def static(web, req, tmpl):
818 fname = req.form['file'][0]
818 fname = req.form['file'][0]
819 # a repo owner may set web.static in .hg/hgrc to get any file
819 # a repo owner may set web.static in .hg/hgrc to get any file
820 # readable by the user running the CGI script
820 # readable by the user running the CGI script
821 static = web.config("web", "static", None, untrusted=False)
821 static = web.config("web", "static", None, untrusted=False)
822 if not static:
822 if not static:
823 tp = web.templatepath or templater.templatepath()
823 tp = web.templatepath or templater.templatepath()
824 if isinstance(tp, str):
824 if isinstance(tp, str):
825 tp = [tp]
825 tp = [tp]
826 static = [os.path.join(p, 'static') for p in tp]
826 static = [os.path.join(p, 'static') for p in tp]
827 return [staticfile(static, fname, req)]
827 return [staticfile(static, fname, req)]
828
828
829 def graph(web, req, tmpl):
829 def graph(web, req, tmpl):
830
830
831 ctx = webutil.changectx(web.repo, req)
831 ctx = webutil.changectx(web.repo, req)
832 rev = ctx.rev()
832 rev = ctx.rev()
833
833
834 bg_height = 39
834 bg_height = 39
835 revcount = web.maxshortchanges
835 revcount = web.maxshortchanges
836 if 'revcount' in req.form:
836 if 'revcount' in req.form:
837 revcount = int(req.form.get('revcount', [revcount])[0])
837 revcount = int(req.form.get('revcount', [revcount])[0])
838 revcount = max(revcount, 1)
838 revcount = max(revcount, 1)
839 tmpl.defaults['sessionvars']['revcount'] = revcount
839 tmpl.defaults['sessionvars']['revcount'] = revcount
840
840
841 lessvars = copy.copy(tmpl.defaults['sessionvars'])
841 lessvars = copy.copy(tmpl.defaults['sessionvars'])
842 lessvars['revcount'] = max(revcount / 2, 1)
842 lessvars['revcount'] = max(revcount / 2, 1)
843 morevars = copy.copy(tmpl.defaults['sessionvars'])
843 morevars = copy.copy(tmpl.defaults['sessionvars'])
844 morevars['revcount'] = revcount * 2
844 morevars['revcount'] = revcount * 2
845
845
846 count = len(web.repo)
846 count = len(web.repo)
847 pos = rev
847 pos = rev
848 start = max(0, pos - revcount + 1)
848 start = max(0, pos - revcount + 1)
849 end = min(count, start + revcount)
849 end = min(count, start + revcount)
850 pos = end - 1
850 pos = end - 1
851
851
852 uprev = min(max(0, count - 1), rev + revcount)
852 uprev = min(max(0, count - 1), rev + revcount)
853 downrev = max(0, rev - revcount)
853 downrev = max(0, rev - revcount)
854 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
854 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
855
855
856 dag = graphmod.dagwalker(web.repo, range(start, end)[::-1])
856 dag = graphmod.dagwalker(web.repo, range(start, end)[::-1])
857 tree = list(graphmod.colored(dag, web.repo))
857 tree = list(graphmod.colored(dag, web.repo))
858
858
859 def getcolumns(tree):
859 def getcolumns(tree):
860 cols = 0
860 cols = 0
861 for (id, type, ctx, vtx, edges) in tree:
861 for (id, type, ctx, vtx, edges) in tree:
862 if type != graphmod.CHANGESET:
862 if type != graphmod.CHANGESET:
863 continue
863 continue
864 cols = max(cols, max([edge[0] for edge in edges] or [0]),
864 cols = max(cols, max([edge[0] for edge in edges] or [0]),
865 max([edge[1] for edge in edges] or [0]))
865 max([edge[1] for edge in edges] or [0]))
866 return cols
866 return cols
867
867
868 def graphdata(usetuples, **map):
868 def graphdata(usetuples, **map):
869 data = []
869 data = []
870
870
871 row = 0
871 row = 0
872 for (id, type, ctx, vtx, edges) in tree:
872 for (id, type, ctx, vtx, edges) in tree:
873 if type != graphmod.CHANGESET:
873 if type != graphmod.CHANGESET:
874 continue
874 continue
875 node = str(ctx)
875 node = str(ctx)
876 age = templatefilters.age(ctx.date())
876 age = templatefilters.age(ctx.date())
877 desc = templatefilters.firstline(ctx.description())
877 desc = templatefilters.firstline(ctx.description())
878 desc = cgi.escape(templatefilters.nonempty(desc))
878 desc = cgi.escape(templatefilters.nonempty(desc))
879 user = cgi.escape(templatefilters.person(ctx.user()))
879 user = cgi.escape(templatefilters.person(ctx.user()))
880 branch = ctx.branch()
880 branch = ctx.branch()
881 try:
881 try:
882 branchnode = web.repo.branchtip(branch)
882 branchnode = web.repo.branchtip(branch)
883 except error.RepoLookupError:
883 except error.RepoLookupError:
884 branchnode = None
884 branchnode = None
885 branch = branch, branchnode == ctx.node()
885 branch = branch, branchnode == ctx.node()
886
886
887 if usetuples:
887 if usetuples:
888 data.append((node, vtx, edges, desc, user, age, branch,
888 data.append((node, vtx, edges, desc, user, age, branch,
889 ctx.tags(), ctx.bookmarks()))
889 ctx.tags(), ctx.bookmarks()))
890 else:
890 else:
891 edgedata = [dict(col=edge[0], nextcol=edge[1],
891 edgedata = [dict(col=edge[0], nextcol=edge[1],
892 color=(edge[2] - 1) % 6 + 1,
892 color=(edge[2] - 1) % 6 + 1,
893 width=edge[3], bcolor=edge[4])
893 width=edge[3], bcolor=edge[4])
894 for edge in edges]
894 for edge in edges]
895
895
896 data.append(
896 data.append(
897 dict(node=node,
897 dict(node=node,
898 col=vtx[0],
898 col=vtx[0],
899 color=(vtx[1] - 1) % 6 + 1,
899 color=(vtx[1] - 1) % 6 + 1,
900 edges=edgedata,
900 edges=edgedata,
901 row=row,
901 row=row,
902 nextrow=row + 1,
902 nextrow=row + 1,
903 desc=desc,
903 desc=desc,
904 user=user,
904 user=user,
905 age=age,
905 age=age,
906 bookmarks=webutil.nodebookmarksdict(
906 bookmarks=webutil.nodebookmarksdict(
907 web.repo, ctx.node()),
907 web.repo, ctx.node()),
908 branches=webutil.nodebranchdict(web.repo, ctx),
908 branches=webutil.nodebranchdict(web.repo, ctx),
909 inbranch=webutil.nodeinbranch(web.repo, ctx),
909 inbranch=webutil.nodeinbranch(web.repo, ctx),
910 tags=webutil.nodetagsdict(web.repo, ctx.node())))
910 tags=webutil.nodetagsdict(web.repo, ctx.node())))
911
911
912 row += 1
912 row += 1
913
913
914 return data
914 return data
915
915
916 cols = getcolumns(tree)
916 cols = getcolumns(tree)
917 rows = len(tree)
917 rows = len(tree)
918 canvasheight = (rows + 1) * bg_height - 27
918 canvasheight = (rows + 1) * bg_height - 27
919
919
920 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
920 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
921 lessvars=lessvars, morevars=morevars, downrev=downrev,
921 lessvars=lessvars, morevars=morevars, downrev=downrev,
922 cols=cols, rows=rows,
922 cols=cols, rows=rows,
923 canvaswidth=(cols + 1) * bg_height,
923 canvaswidth=(cols + 1) * bg_height,
924 truecanvasheight=rows * bg_height,
924 truecanvasheight=rows * bg_height,
925 canvasheight=canvasheight, bg_height=bg_height,
925 canvasheight=canvasheight, bg_height=bg_height,
926 jsdata=lambda **x: graphdata(True, **x),
926 jsdata=lambda **x: graphdata(True, **x),
927 nodes=lambda **x: graphdata(False, **x),
927 nodes=lambda **x: graphdata(False, **x),
928 node=ctx.hex(), changenav=changenav)
928 node=ctx.hex(), changenav=changenav)
929
929
930 def _getdoc(e):
930 def _getdoc(e):
931 doc = e[0].__doc__
931 doc = e[0].__doc__
932 if doc:
932 if doc:
933 doc = _(doc).split('\n')[0]
933 doc = _(doc).split('\n')[0]
934 else:
934 else:
935 doc = _('(no help text available)')
935 doc = _('(no help text available)')
936 return doc
936 return doc
937
937
938 def help(web, req, tmpl):
938 def help(web, req, tmpl):
939 from mercurial import commands # avoid cycle
939 from mercurial import commands # avoid cycle
940
940
941 topicname = req.form.get('node', [None])[0]
941 topicname = req.form.get('node', [None])[0]
942 if not topicname:
942 if not topicname:
943 def topics(**map):
943 def topics(**map):
944 for entries, summary, _ in helpmod.helptable:
944 for entries, summary, _ in helpmod.helptable:
945 yield {'topic': entries[0], 'summary': summary}
945 yield {'topic': entries[0], 'summary': summary}
946
946
947 early, other = [], []
947 early, other = [], []
948 primary = lambda s: s.split('|')[0]
948 primary = lambda s: s.split('|')[0]
949 for c, e in commands.table.iteritems():
949 for c, e in commands.table.iteritems():
950 doc = _getdoc(e)
950 doc = _getdoc(e)
951 if 'DEPRECATED' in doc or c.startswith('debug'):
951 if 'DEPRECATED' in doc or c.startswith('debug'):
952 continue
952 continue
953 cmd = primary(c)
953 cmd = primary(c)
954 if cmd.startswith('^'):
954 if cmd.startswith('^'):
955 early.append((cmd[1:], doc))
955 early.append((cmd[1:], doc))
956 else:
956 else:
957 other.append((cmd, doc))
957 other.append((cmd, doc))
958
958
959 early.sort()
959 early.sort()
960 other.sort()
960 other.sort()
961
961
962 def earlycommands(**map):
962 def earlycommands(**map):
963 for c, doc in early:
963 for c, doc in early:
964 yield {'topic': c, 'summary': doc}
964 yield {'topic': c, 'summary': doc}
965
965
966 def othercommands(**map):
966 def othercommands(**map):
967 for c, doc in other:
967 for c, doc in other:
968 yield {'topic': c, 'summary': doc}
968 yield {'topic': c, 'summary': doc}
969
969
970 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
970 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
971 othercommands=othercommands, title='Index')
971 othercommands=othercommands, title='Index')
972
972
973 u = webutil.wsgiui()
973 u = webutil.wsgiui()
974 u.pushbuffer()
974 u.pushbuffer()
975 u.verbose = True
975 u.verbose = True
976 try:
976 try:
977 commands.help_(u, topicname)
977 commands.help_(u, topicname)
978 except error.UnknownCommand:
978 except error.UnknownCommand:
979 raise ErrorResponse(HTTP_NOT_FOUND)
979 raise ErrorResponse(HTTP_NOT_FOUND)
980 doc = u.popbuffer()
980 doc = u.popbuffer()
981 return tmpl('help', topic=topicname, doc=doc)
981 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now