##// END OF EJS Templates
hgweb: separate out utility functions
Dirkjan Ochtman -
r6392:2540521d default
parent child Browse files
Show More
@@ -0,0 +1,94 b''
1 # hgweb/webutil.py - utility library for the web interface.
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
8
9 from mercurial.node import hex, nullid
10 from mercurial.repo import RepoError
11 from mercurial import util
12
13 def siblings(siblings=[], hiderev=None, **args):
14 siblings = [s for s in siblings if s.node() != nullid]
15 if len(siblings) == 1 and siblings[0].rev() == hiderev:
16 return
17 for s in siblings:
18 d = {'node': hex(s.node()), 'rev': s.rev()}
19 if hasattr(s, 'path'):
20 d['file'] = s.path()
21 d.update(args)
22 yield d
23
24 def renamelink(fl, node):
25 r = fl.renamed(node)
26 if r:
27 return [dict(file=r[0], node=hex(r[1]))]
28 return []
29
30 def nodetagsdict(repo, node):
31 return [{"name": i} for i in repo.nodetags(node)]
32
33 def nodebranchdict(repo, ctx):
34 branches = []
35 branch = ctx.branch()
36 # If this is an empty repo, ctx.node() == nullid,
37 # ctx.branch() == 'default', but branchtags() is
38 # an empty dict. Using dict.get avoids a traceback.
39 if repo.branchtags().get(branch) == ctx.node():
40 branches.append({"name": branch})
41 return branches
42
43 def nodeinbranch(repo, ctx):
44 branches = []
45 branch = ctx.branch()
46 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
47 branches.append({"name": branch})
48 return branches
49
50 def nodebranchnodefault(ctx):
51 branches = []
52 branch = ctx.branch()
53 if branch != 'default':
54 branches.append({"name": branch})
55 return branches
56
57 def showtag(repo, tmpl, t1, node=nullid, **args):
58 for t in repo.nodetags(node):
59 yield tmpl(t1, tag=t, **args)
60
61 def cleanpath(repo, path):
62 path = path.lstrip('/')
63 return util.canonpath(repo.root, '', path)
64
65 def changectx(repo, req):
66 if 'node' in req.form:
67 changeid = req.form['node'][0]
68 elif 'manifest' in req.form:
69 changeid = req.form['manifest'][0]
70 else:
71 changeid = self.repo.changelog.count() - 1
72
73 try:
74 ctx = repo.changectx(changeid)
75 except RepoError:
76 man = repo.manifest
77 mn = man.lookup(changeid)
78 ctx = repo.changectx(man.linkrev(mn))
79
80 return ctx
81
82 def filectx(repo, req):
83 path = cleanpath(repo, req.form['file'][0])
84 if 'node' in req.form:
85 changeid = req.form['node'][0]
86 else:
87 changeid = req.form['filenode'][0]
88 try:
89 ctx = repo.changectx(changeid)
90 fctx = ctx.filectx(path)
91 except RepoError:
92 fctx = repo.filectx(path, fileid=changeid)
93
94 return fctx
@@ -1,978 +1,892 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
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-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes, re
9 import os, mimetypes, re
10 from mercurial.node import hex, nullid, short
10 from mercurial.node import hex, nullid, short
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
13 from mercurial import revlog, templater, templatefilters, changegroup
13 from mercurial import revlog, templater, templatefilters, changegroup
14 from common import get_mtime, style_map, paritygen, countgen, get_contact
14 from common import get_mtime, style_map, paritygen, countgen, get_contact
15 from common import ErrorResponse
15 from common import ErrorResponse
16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
17 from request import wsgirequest
17 from request import wsgirequest
18 import webcommands, protocol
18 import webcommands, protocol, webutil
19
19
20 shortcuts = {
20 shortcuts = {
21 'cl': [('cmd', ['changelog']), ('rev', None)],
21 'cl': [('cmd', ['changelog']), ('rev', None)],
22 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 'sl': [('cmd', ['shortlog']), ('rev', None)],
23 'cs': [('cmd', ['changeset']), ('node', None)],
23 'cs': [('cmd', ['changeset']), ('node', None)],
24 'f': [('cmd', ['file']), ('filenode', None)],
24 'f': [('cmd', ['file']), ('filenode', None)],
25 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 'fl': [('cmd', ['filelog']), ('filenode', None)],
26 'fd': [('cmd', ['filediff']), ('node', None)],
26 'fd': [('cmd', ['filediff']), ('node', None)],
27 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 'fa': [('cmd', ['annotate']), ('filenode', None)],
28 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 'mf': [('cmd', ['manifest']), ('manifest', None)],
29 'ca': [('cmd', ['archive']), ('node', None)],
29 'ca': [('cmd', ['archive']), ('node', None)],
30 'tags': [('cmd', ['tags'])],
30 'tags': [('cmd', ['tags'])],
31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
32 'static': [('cmd', ['static']), ('file', None)]
32 'static': [('cmd', ['static']), ('file', None)]
33 }
33 }
34
34
35 def _up(p):
35 def _up(p):
36 if p[0] != "/":
36 if p[0] != "/":
37 p = "/" + p
37 p = "/" + p
38 if p[-1] == "/":
38 if p[-1] == "/":
39 p = p[:-1]
39 p = p[:-1]
40 up = os.path.dirname(p)
40 up = os.path.dirname(p)
41 if up == "/":
41 if up == "/":
42 return "/"
42 return "/"
43 return up + "/"
43 return up + "/"
44
44
45 def revnavgen(pos, pagelen, limit, nodefunc):
45 def revnavgen(pos, pagelen, limit, nodefunc):
46 def seq(factor, limit=None):
46 def seq(factor, limit=None):
47 if limit:
47 if limit:
48 yield limit
48 yield limit
49 if limit >= 20 and limit <= 40:
49 if limit >= 20 and limit <= 40:
50 yield 50
50 yield 50
51 else:
51 else:
52 yield 1 * factor
52 yield 1 * factor
53 yield 3 * factor
53 yield 3 * factor
54 for f in seq(factor * 10):
54 for f in seq(factor * 10):
55 yield f
55 yield f
56
56
57 def nav(**map):
57 def nav(**map):
58 l = []
58 l = []
59 last = 0
59 last = 0
60 for f in seq(1, pagelen):
60 for f in seq(1, pagelen):
61 if f < pagelen or f <= last:
61 if f < pagelen or f <= last:
62 continue
62 continue
63 if f > limit:
63 if f > limit:
64 break
64 break
65 last = f
65 last = f
66 if pos + f < limit:
66 if pos + f < limit:
67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
68 if pos - f >= 0:
68 if pos - f >= 0:
69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
70
70
71 try:
71 try:
72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
73
73
74 for label, node in l:
74 for label, node in l:
75 yield {"label": label, "node": node}
75 yield {"label": label, "node": node}
76
76
77 yield {"label": "tip", "node": "tip"}
77 yield {"label": "tip", "node": "tip"}
78 except RepoError:
78 except RepoError:
79 pass
79 pass
80
80
81 return nav
81 return nav
82
82
83 class hgweb(object):
83 class hgweb(object):
84 def __init__(self, repo, name=None):
84 def __init__(self, repo, name=None):
85 if isinstance(repo, str):
85 if isinstance(repo, str):
86 parentui = ui.ui(report_untrusted=False, interactive=False)
86 parentui = ui.ui(report_untrusted=False, interactive=False)
87 self.repo = hg.repository(parentui, repo)
87 self.repo = hg.repository(parentui, repo)
88 else:
88 else:
89 self.repo = repo
89 self.repo = repo
90
90
91 hook.redirect(True)
91 hook.redirect(True)
92 self.mtime = -1
92 self.mtime = -1
93 self.reponame = name
93 self.reponame = name
94 self.archives = 'zip', 'gz', 'bz2'
94 self.archives = 'zip', 'gz', 'bz2'
95 self.stripecount = 1
95 self.stripecount = 1
96 self._capabilities = None
96 self._capabilities = None
97 # a repo owner may set web.templates in .hg/hgrc to get any file
97 # a repo owner may set web.templates in .hg/hgrc to get any file
98 # readable by the user running the CGI script
98 # readable by the user running the CGI script
99 self.templatepath = self.config("web", "templates",
99 self.templatepath = self.config("web", "templates",
100 templater.templatepath(),
100 templater.templatepath(),
101 untrusted=False)
101 untrusted=False)
102
102
103 # The CGI scripts are often run by a user different from the repo owner.
103 # The CGI scripts are often run by a user different from the repo owner.
104 # Trust the settings from the .hg/hgrc files by default.
104 # Trust the settings from the .hg/hgrc files by default.
105 def config(self, section, name, default=None, untrusted=True):
105 def config(self, section, name, default=None, untrusted=True):
106 return self.repo.ui.config(section, name, default,
106 return self.repo.ui.config(section, name, default,
107 untrusted=untrusted)
107 untrusted=untrusted)
108
108
109 def configbool(self, section, name, default=False, untrusted=True):
109 def configbool(self, section, name, default=False, untrusted=True):
110 return self.repo.ui.configbool(section, name, default,
110 return self.repo.ui.configbool(section, name, default,
111 untrusted=untrusted)
111 untrusted=untrusted)
112
112
113 def configlist(self, section, name, default=None, untrusted=True):
113 def configlist(self, section, name, default=None, untrusted=True):
114 return self.repo.ui.configlist(section, name, default,
114 return self.repo.ui.configlist(section, name, default,
115 untrusted=untrusted)
115 untrusted=untrusted)
116
116
117 def refresh(self):
117 def refresh(self):
118 mtime = get_mtime(self.repo.root)
118 mtime = get_mtime(self.repo.root)
119 if mtime != self.mtime:
119 if mtime != self.mtime:
120 self.mtime = mtime
120 self.mtime = mtime
121 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 self.repo = hg.repository(self.repo.ui, self.repo.root)
122 self.maxchanges = int(self.config("web", "maxchanges", 10))
122 self.maxchanges = int(self.config("web", "maxchanges", 10))
123 self.stripecount = int(self.config("web", "stripes", 1))
123 self.stripecount = int(self.config("web", "stripes", 1))
124 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
124 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
125 self.maxfiles = int(self.config("web", "maxfiles", 10))
125 self.maxfiles = int(self.config("web", "maxfiles", 10))
126 self.allowpull = self.configbool("web", "allowpull", True)
126 self.allowpull = self.configbool("web", "allowpull", True)
127 self.encoding = self.config("web", "encoding", util._encoding)
127 self.encoding = self.config("web", "encoding", util._encoding)
128 self._capabilities = None
128 self._capabilities = None
129
129
130 def capabilities(self):
130 def capabilities(self):
131 if self._capabilities is not None:
131 if self._capabilities is not None:
132 return self._capabilities
132 return self._capabilities
133 caps = ['lookup', 'changegroupsubset']
133 caps = ['lookup', 'changegroupsubset']
134 if self.configbool('server', 'uncompressed'):
134 if self.configbool('server', 'uncompressed'):
135 caps.append('stream=%d' % self.repo.changelog.version)
135 caps.append('stream=%d' % self.repo.changelog.version)
136 if changegroup.bundlepriority:
136 if changegroup.bundlepriority:
137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
138 self._capabilities = caps
138 self._capabilities = caps
139 return caps
139 return caps
140
140
141 def run(self):
141 def run(self):
142 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
142 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
143 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
143 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
144 import mercurial.hgweb.wsgicgi as wsgicgi
144 import mercurial.hgweb.wsgicgi as wsgicgi
145 wsgicgi.launch(self)
145 wsgicgi.launch(self)
146
146
147 def __call__(self, env, respond):
147 def __call__(self, env, respond):
148 req = wsgirequest(env, respond)
148 req = wsgirequest(env, respond)
149 self.run_wsgi(req)
149 self.run_wsgi(req)
150 return req
150 return req
151
151
152 def run_wsgi(self, req):
152 def run_wsgi(self, req):
153
153
154 self.refresh()
154 self.refresh()
155
155
156 # expand form shortcuts
156 # expand form shortcuts
157
157
158 for k in shortcuts.iterkeys():
158 for k in shortcuts.iterkeys():
159 if k in req.form:
159 if k in req.form:
160 for name, value in shortcuts[k]:
160 for name, value in shortcuts[k]:
161 if value is None:
161 if value is None:
162 value = req.form[k]
162 value = req.form[k]
163 req.form[name] = value
163 req.form[name] = value
164 del req.form[k]
164 del req.form[k]
165
165
166 # work with CGI variables to create coherent structure
166 # work with CGI variables to create coherent structure
167 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
167 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
168
168
169 req.url = req.env['SCRIPT_NAME']
169 req.url = req.env['SCRIPT_NAME']
170 if not req.url.endswith('/'):
170 if not req.url.endswith('/'):
171 req.url += '/'
171 req.url += '/'
172 if 'REPO_NAME' in req.env:
172 if 'REPO_NAME' in req.env:
173 req.url += req.env['REPO_NAME'] + '/'
173 req.url += req.env['REPO_NAME'] + '/'
174
174
175 if req.env.get('PATH_INFO'):
175 if req.env.get('PATH_INFO'):
176 parts = req.env.get('PATH_INFO').strip('/').split('/')
176 parts = req.env.get('PATH_INFO').strip('/').split('/')
177 repo_parts = req.env.get('REPO_NAME', '').split('/')
177 repo_parts = req.env.get('REPO_NAME', '').split('/')
178 if parts[:len(repo_parts)] == repo_parts:
178 if parts[:len(repo_parts)] == repo_parts:
179 parts = parts[len(repo_parts):]
179 parts = parts[len(repo_parts):]
180 query = '/'.join(parts)
180 query = '/'.join(parts)
181 else:
181 else:
182 query = req.env['QUERY_STRING'].split('&', 1)[0]
182 query = req.env['QUERY_STRING'].split('&', 1)[0]
183 query = query.split(';', 1)[0]
183 query = query.split(';', 1)[0]
184
184
185 # translate user-visible url structure to internal structure
185 # translate user-visible url structure to internal structure
186
186
187 args = query.split('/', 2)
187 args = query.split('/', 2)
188 if 'cmd' not in req.form and args and args[0]:
188 if 'cmd' not in req.form and args and args[0]:
189
189
190 cmd = args.pop(0)
190 cmd = args.pop(0)
191 style = cmd.rfind('-')
191 style = cmd.rfind('-')
192 if style != -1:
192 if style != -1:
193 req.form['style'] = [cmd[:style]]
193 req.form['style'] = [cmd[:style]]
194 cmd = cmd[style+1:]
194 cmd = cmd[style+1:]
195
195
196 # avoid accepting e.g. style parameter as command
196 # avoid accepting e.g. style parameter as command
197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
198 req.form['cmd'] = [cmd]
198 req.form['cmd'] = [cmd]
199
199
200 if args and args[0]:
200 if args and args[0]:
201 node = args.pop(0)
201 node = args.pop(0)
202 req.form['node'] = [node]
202 req.form['node'] = [node]
203 if args:
203 if args:
204 req.form['file'] = args
204 req.form['file'] = args
205
205
206 if cmd == 'static':
206 if cmd == 'static':
207 req.form['file'] = req.form['node']
207 req.form['file'] = req.form['node']
208 elif cmd == 'archive':
208 elif cmd == 'archive':
209 fn = req.form['node'][0]
209 fn = req.form['node'][0]
210 for type_, spec in self.archive_specs.iteritems():
210 for type_, spec in self.archive_specs.iteritems():
211 ext = spec[2]
211 ext = spec[2]
212 if fn.endswith(ext):
212 if fn.endswith(ext):
213 req.form['node'] = [fn[:-len(ext)]]
213 req.form['node'] = [fn[:-len(ext)]]
214 req.form['type'] = [type_]
214 req.form['type'] = [type_]
215
215
216 # process this if it's a protocol request
216 # process this if it's a protocol request
217
217
218 cmd = req.form.get('cmd', [''])[0]
218 cmd = req.form.get('cmd', [''])[0]
219 if cmd in protocol.__all__:
219 if cmd in protocol.__all__:
220 method = getattr(protocol, cmd)
220 method = getattr(protocol, cmd)
221 method(self, req)
221 method(self, req)
222 return
222 return
223
223
224 # process the web interface request
224 # process the web interface request
225
225
226 try:
226 try:
227
227
228 tmpl = self.templater(req)
228 tmpl = self.templater(req)
229 ctype = tmpl('mimetype', encoding=self.encoding)
229 ctype = tmpl('mimetype', encoding=self.encoding)
230 ctype = templater.stringify(ctype)
230 ctype = templater.stringify(ctype)
231
231
232 if cmd == '':
232 if cmd == '':
233 req.form['cmd'] = [tmpl.cache['default']]
233 req.form['cmd'] = [tmpl.cache['default']]
234 cmd = req.form['cmd'][0]
234 cmd = req.form['cmd'][0]
235
235
236 if cmd not in webcommands.__all__:
236 if cmd not in webcommands.__all__:
237 msg = 'no such method: %s' % cmd
237 msg = 'no such method: %s' % cmd
238 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
238 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
239 elif cmd == 'file' and 'raw' in req.form.get('style', []):
239 elif cmd == 'file' and 'raw' in req.form.get('style', []):
240 self.ctype = ctype
240 self.ctype = ctype
241 content = webcommands.rawfile(self, req, tmpl)
241 content = webcommands.rawfile(self, req, tmpl)
242 else:
242 else:
243 content = getattr(webcommands, cmd)(self, req, tmpl)
243 content = getattr(webcommands, cmd)(self, req, tmpl)
244 req.respond(HTTP_OK, ctype)
244 req.respond(HTTP_OK, ctype)
245
245
246 req.write(content)
246 req.write(content)
247 del tmpl
247 del tmpl
248
248
249 except revlog.LookupError, err:
249 except revlog.LookupError, err:
250 req.respond(HTTP_NOT_FOUND, ctype)
250 req.respond(HTTP_NOT_FOUND, ctype)
251 msg = str(err)
251 msg = str(err)
252 if 'manifest' not in msg:
252 if 'manifest' not in msg:
253 msg = 'revision not found: %s' % err.name
253 msg = 'revision not found: %s' % err.name
254 req.write(tmpl('error', error=msg))
254 req.write(tmpl('error', error=msg))
255 except (RepoError, revlog.RevlogError), inst:
255 except (RepoError, revlog.RevlogError), inst:
256 req.respond(HTTP_SERVER_ERROR, ctype)
256 req.respond(HTTP_SERVER_ERROR, ctype)
257 req.write(tmpl('error', error=str(inst)))
257 req.write(tmpl('error', error=str(inst)))
258 except ErrorResponse, inst:
258 except ErrorResponse, inst:
259 req.respond(inst.code, ctype)
259 req.respond(inst.code, ctype)
260 req.write(tmpl('error', error=inst.message))
260 req.write(tmpl('error', error=inst.message))
261
261
262 def templater(self, req):
262 def templater(self, req):
263
263
264 # determine scheme, port and server name
264 # determine scheme, port and server name
265 # this is needed to create absolute urls
265 # this is needed to create absolute urls
266
266
267 proto = req.env.get('wsgi.url_scheme')
267 proto = req.env.get('wsgi.url_scheme')
268 if proto == 'https':
268 if proto == 'https':
269 proto = 'https'
269 proto = 'https'
270 default_port = "443"
270 default_port = "443"
271 else:
271 else:
272 proto = 'http'
272 proto = 'http'
273 default_port = "80"
273 default_port = "80"
274
274
275 port = req.env["SERVER_PORT"]
275 port = req.env["SERVER_PORT"]
276 port = port != default_port and (":" + port) or ""
276 port = port != default_port and (":" + port) or ""
277 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
277 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
278 staticurl = self.config("web", "staticurl") or req.url + 'static/'
278 staticurl = self.config("web", "staticurl") or req.url + 'static/'
279 if not staticurl.endswith('/'):
279 if not staticurl.endswith('/'):
280 staticurl += '/'
280 staticurl += '/'
281
281
282 # some functions for the templater
282 # some functions for the templater
283
283
284 def header(**map):
284 def header(**map):
285 yield tmpl('header', encoding=self.encoding, **map)
285 yield tmpl('header', encoding=self.encoding, **map)
286
286
287 def footer(**map):
287 def footer(**map):
288 yield tmpl("footer", **map)
288 yield tmpl("footer", **map)
289
289
290 def motd(**map):
290 def motd(**map):
291 yield self.config("web", "motd", "")
291 yield self.config("web", "motd", "")
292
292
293 def sessionvars(**map):
293 def sessionvars(**map):
294 fields = []
294 fields = []
295 if 'style' in req.form:
295 if 'style' in req.form:
296 style = req.form['style'][0]
296 style = req.form['style'][0]
297 if style != self.config('web', 'style', ''):
297 if style != self.config('web', 'style', ''):
298 fields.append(('style', style))
298 fields.append(('style', style))
299
299
300 separator = req.url[-1] == '?' and ';' or '?'
300 separator = req.url[-1] == '?' and ';' or '?'
301 for name, value in fields:
301 for name, value in fields:
302 yield dict(name=name, value=value, separator=separator)
302 yield dict(name=name, value=value, separator=separator)
303 separator = ';'
303 separator = ';'
304
304
305 # figure out which style to use
305 # figure out which style to use
306
306
307 style = self.config("web", "style", "")
307 style = self.config("web", "style", "")
308 if 'style' in req.form:
308 if 'style' in req.form:
309 style = req.form['style'][0]
309 style = req.form['style'][0]
310 mapfile = style_map(self.templatepath, style)
310 mapfile = style_map(self.templatepath, style)
311
311
312 if not self.reponame:
312 if not self.reponame:
313 self.reponame = (self.config("web", "name")
313 self.reponame = (self.config("web", "name")
314 or req.env.get('REPO_NAME')
314 or req.env.get('REPO_NAME')
315 or req.url.strip('/') or self.repo.root)
315 or req.url.strip('/') or self.repo.root)
316
316
317 # create the templater
317 # create the templater
318
318
319 tmpl = templater.templater(mapfile, templatefilters.filters,
319 tmpl = templater.templater(mapfile, templatefilters.filters,
320 defaults={"url": req.url,
320 defaults={"url": req.url,
321 "staticurl": staticurl,
321 "staticurl": staticurl,
322 "urlbase": urlbase,
322 "urlbase": urlbase,
323 "repo": self.reponame,
323 "repo": self.reponame,
324 "header": header,
324 "header": header,
325 "footer": footer,
325 "footer": footer,
326 "motd": motd,
326 "motd": motd,
327 "sessionvars": sessionvars
327 "sessionvars": sessionvars
328 })
328 })
329 return tmpl
329 return tmpl
330
330
331 def archivelist(self, nodeid):
331 def archivelist(self, nodeid):
332 allowed = self.configlist("web", "allow_archive")
332 allowed = self.configlist("web", "allow_archive")
333 for i, spec in self.archive_specs.iteritems():
333 for i, spec in self.archive_specs.iteritems():
334 if i in allowed or self.configbool("web", "allow" + i):
334 if i in allowed or self.configbool("web", "allow" + i):
335 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
335 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
336
336
337 def listfilediffs(self, tmpl, files, changeset):
337 def listfilediffs(self, tmpl, files, changeset):
338 for f in files[:self.maxfiles]:
338 for f in files[:self.maxfiles]:
339 yield tmpl("filedifflink", node=hex(changeset), file=f)
339 yield tmpl("filedifflink", node=hex(changeset), file=f)
340 if len(files) > self.maxfiles:
340 if len(files) > self.maxfiles:
341 yield tmpl("fileellipses")
341 yield tmpl("fileellipses")
342
342
343 def siblings(self, siblings=[], hiderev=None, **args):
344 siblings = [s for s in siblings if s.node() != nullid]
345 if len(siblings) == 1 and siblings[0].rev() == hiderev:
346 return
347 for s in siblings:
348 d = {'node': hex(s.node()), 'rev': s.rev()}
349 if hasattr(s, 'path'):
350 d['file'] = s.path()
351 d.update(args)
352 yield d
353
354 def renamelink(self, fl, node):
355 r = fl.renamed(node)
356 if r:
357 return [dict(file=r[0], node=hex(r[1]))]
358 return []
359
360 def nodetagsdict(self, node):
361 return [{"name": i} for i in self.repo.nodetags(node)]
362
363 def nodebranchdict(self, ctx):
364 branches = []
365 branch = ctx.branch()
366 # If this is an empty repo, ctx.node() == nullid,
367 # ctx.branch() == 'default', but branchtags() is
368 # an empty dict. Using dict.get avoids a traceback.
369 if self.repo.branchtags().get(branch) == ctx.node():
370 branches.append({"name": branch})
371 return branches
372
373 def nodeinbranch(self, ctx):
374 branches = []
375 branch = ctx.branch()
376 if branch != 'default' and self.repo.branchtags().get(branch) != ctx.node():
377 branches.append({"name": branch})
378 return branches
379
380 def nodebranchnodefault(self, ctx):
381 branches = []
382 branch = ctx.branch()
383 if branch != 'default':
384 branches.append({"name": branch})
385 return branches
386
387 def showtag(self, tmpl, t1, node=nullid, **args):
388 for t in self.repo.nodetags(node):
389 yield tmpl(t1, tag=t, **args)
390
391 def diff(self, tmpl, node1, node2, files):
343 def diff(self, tmpl, node1, node2, files):
392 def filterfiles(filters, files):
344 def filterfiles(filters, files):
393 l = [x for x in files if x in filters]
345 l = [x for x in files if x in filters]
394
346
395 for t in filters:
347 for t in filters:
396 if t and t[-1] != os.sep:
348 if t and t[-1] != os.sep:
397 t += os.sep
349 t += os.sep
398 l += [x for x in files if x.startswith(t)]
350 l += [x for x in files if x.startswith(t)]
399 return l
351 return l
400
352
401 parity = paritygen(self.stripecount)
353 parity = paritygen(self.stripecount)
402 def diffblock(diff, f, fn):
354 def diffblock(diff, f, fn):
403 yield tmpl("diffblock",
355 yield tmpl("diffblock",
404 lines=prettyprintlines(diff),
356 lines=prettyprintlines(diff),
405 parity=parity.next(),
357 parity=parity.next(),
406 file=f,
358 file=f,
407 filenode=hex(fn or nullid))
359 filenode=hex(fn or nullid))
408
360
409 blockcount = countgen()
361 blockcount = countgen()
410 def prettyprintlines(diff):
362 def prettyprintlines(diff):
411 blockno = blockcount.next()
363 blockno = blockcount.next()
412 for lineno, l in enumerate(diff.splitlines(1)):
364 for lineno, l in enumerate(diff.splitlines(1)):
413 if blockno == 0:
365 if blockno == 0:
414 lineno = lineno + 1
366 lineno = lineno + 1
415 else:
367 else:
416 lineno = "%d.%d" % (blockno, lineno + 1)
368 lineno = "%d.%d" % (blockno, lineno + 1)
417 if l.startswith('+'):
369 if l.startswith('+'):
418 ltype = "difflineplus"
370 ltype = "difflineplus"
419 elif l.startswith('-'):
371 elif l.startswith('-'):
420 ltype = "difflineminus"
372 ltype = "difflineminus"
421 elif l.startswith('@'):
373 elif l.startswith('@'):
422 ltype = "difflineat"
374 ltype = "difflineat"
423 else:
375 else:
424 ltype = "diffline"
376 ltype = "diffline"
425 yield tmpl(ltype,
377 yield tmpl(ltype,
426 line=l,
378 line=l,
427 lineid="l%s" % lineno,
379 lineid="l%s" % lineno,
428 linenumber="% 8s" % lineno)
380 linenumber="% 8s" % lineno)
429
381
430 r = self.repo
382 r = self.repo
431 c1 = r.changectx(node1)
383 c1 = r.changectx(node1)
432 c2 = r.changectx(node2)
384 c2 = r.changectx(node2)
433 date1 = util.datestr(c1.date())
385 date1 = util.datestr(c1.date())
434 date2 = util.datestr(c2.date())
386 date2 = util.datestr(c2.date())
435
387
436 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
388 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
437 if files:
389 if files:
438 modified, added, removed = map(lambda x: filterfiles(files, x),
390 modified, added, removed = map(lambda x: filterfiles(files, x),
439 (modified, added, removed))
391 (modified, added, removed))
440
392
441 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
393 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
442 for f in modified:
394 for f in modified:
443 to = c1.filectx(f).data()
395 to = c1.filectx(f).data()
444 tn = c2.filectx(f).data()
396 tn = c2.filectx(f).data()
445 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
397 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
446 opts=diffopts), f, tn)
398 opts=diffopts), f, tn)
447 for f in added:
399 for f in added:
448 to = None
400 to = None
449 tn = c2.filectx(f).data()
401 tn = c2.filectx(f).data()
450 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
402 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
451 opts=diffopts), f, tn)
403 opts=diffopts), f, tn)
452 for f in removed:
404 for f in removed:
453 to = c1.filectx(f).data()
405 to = c1.filectx(f).data()
454 tn = None
406 tn = None
455 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
407 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
456 opts=diffopts), f, tn)
408 opts=diffopts), f, tn)
457
409
458 def changelog(self, tmpl, ctx, shortlog=False):
410 def changelog(self, tmpl, ctx, shortlog=False):
459 def changelist(limit=0,**map):
411 def changelist(limit=0,**map):
460 cl = self.repo.changelog
412 cl = self.repo.changelog
461 l = [] # build a list in forward order for efficiency
413 l = [] # build a list in forward order for efficiency
462 for i in xrange(start, end):
414 for i in xrange(start, end):
463 ctx = self.repo.changectx(i)
415 ctx = self.repo.changectx(i)
464 n = ctx.node()
416 n = ctx.node()
465 showtags = self.showtag(tmpl, 'changelogtag', n)
417 showtags = webutil.showtag(self.repo, tmpl, 'changelogtag', n)
466
418
467 l.insert(0, {"parity": parity.next(),
419 l.insert(0, {"parity": parity.next(),
468 "author": ctx.user(),
420 "author": ctx.user(),
469 "parent": self.siblings(ctx.parents(), i - 1),
421 "parent": webutil.siblings(ctx.parents(), i - 1),
470 "child": self.siblings(ctx.children(), i + 1),
422 "child": webutil.siblings(ctx.children(), i + 1),
471 "changelogtag": showtags,
423 "changelogtag": showtags,
472 "desc": ctx.description(),
424 "desc": ctx.description(),
473 "date": ctx.date(),
425 "date": ctx.date(),
474 "files": self.listfilediffs(tmpl, ctx.files(), n),
426 "files": self.listfilediffs(tmpl, ctx.files(), n),
475 "rev": i,
427 "rev": i,
476 "node": hex(n),
428 "node": hex(n),
477 "tags": self.nodetagsdict(n),
429 "tags": webutil.nodetagsdict(self.repo, n),
478 "inbranch": self.nodeinbranch(ctx),
430 "inbranch": webutil.nodeinbranch(self.repo, ctx),
479 "branches": self.nodebranchdict(ctx)})
431 "branches": webutil.nodebranchdict(self.repo, ctx)
432 })
480
433
481 if limit > 0:
434 if limit > 0:
482 l = l[:limit]
435 l = l[:limit]
483
436
484 for e in l:
437 for e in l:
485 yield e
438 yield e
486
439
487 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
440 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
488 cl = self.repo.changelog
441 cl = self.repo.changelog
489 count = cl.count()
442 count = cl.count()
490 pos = ctx.rev()
443 pos = ctx.rev()
491 start = max(0, pos - maxchanges + 1)
444 start = max(0, pos - maxchanges + 1)
492 end = min(count, start + maxchanges)
445 end = min(count, start + maxchanges)
493 pos = end - 1
446 pos = end - 1
494 parity = paritygen(self.stripecount, offset=start-end)
447 parity = paritygen(self.stripecount, offset=start-end)
495
448
496 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
449 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
497
450
498 return tmpl(shortlog and 'shortlog' or 'changelog',
451 return tmpl(shortlog and 'shortlog' or 'changelog',
499 changenav=changenav,
452 changenav=changenav,
500 node=hex(cl.tip()),
453 node=hex(cl.tip()),
501 rev=pos, changesets=count,
454 rev=pos, changesets=count,
502 entries=lambda **x: changelist(limit=0,**x),
455 entries=lambda **x: changelist(limit=0,**x),
503 latestentry=lambda **x: changelist(limit=1,**x),
456 latestentry=lambda **x: changelist(limit=1,**x),
504 archives=self.archivelist("tip"))
457 archives=self.archivelist("tip"))
505
458
506 def search(self, tmpl, query):
459 def search(self, tmpl, query):
507
460
508 def changelist(**map):
461 def changelist(**map):
509 cl = self.repo.changelog
462 cl = self.repo.changelog
510 count = 0
463 count = 0
511 qw = query.lower().split()
464 qw = query.lower().split()
512
465
513 def revgen():
466 def revgen():
514 for i in xrange(cl.count() - 1, 0, -100):
467 for i in xrange(cl.count() - 1, 0, -100):
515 l = []
468 l = []
516 for j in xrange(max(0, i - 100), i + 1):
469 for j in xrange(max(0, i - 100), i + 1):
517 ctx = self.repo.changectx(j)
470 ctx = self.repo.changectx(j)
518 l.append(ctx)
471 l.append(ctx)
519 l.reverse()
472 l.reverse()
520 for e in l:
473 for e in l:
521 yield e
474 yield e
522
475
523 for ctx in revgen():
476 for ctx in revgen():
524 miss = 0
477 miss = 0
525 for q in qw:
478 for q in qw:
526 if not (q in ctx.user().lower() or
479 if not (q in ctx.user().lower() or
527 q in ctx.description().lower() or
480 q in ctx.description().lower() or
528 q in " ".join(ctx.files()).lower()):
481 q in " ".join(ctx.files()).lower()):
529 miss = 1
482 miss = 1
530 break
483 break
531 if miss:
484 if miss:
532 continue
485 continue
533
486
534 count += 1
487 count += 1
535 n = ctx.node()
488 n = ctx.node()
536 showtags = self.showtag(tmpl, 'changelogtag', n)
489 showtags = webutil.showtag(self.repo, tmpl, 'changelogtag', n)
537
490
538 yield tmpl('searchentry',
491 yield tmpl('searchentry',
539 parity=parity.next(),
492 parity=parity.next(),
540 author=ctx.user(),
493 author=ctx.user(),
541 parent=self.siblings(ctx.parents()),
494 parent=webutil.siblings(ctx.parents()),
542 child=self.siblings(ctx.children()),
495 child=webutil.siblings(ctx.children()),
543 changelogtag=showtags,
496 changelogtag=showtags,
544 desc=ctx.description(),
497 desc=ctx.description(),
545 date=ctx.date(),
498 date=ctx.date(),
546 files=self.listfilediffs(tmpl, ctx.files(), n),
499 files=self.listfilediffs(tmpl, ctx.files(), n),
547 rev=ctx.rev(),
500 rev=ctx.rev(),
548 node=hex(n),
501 node=hex(n),
549 tags=self.nodetagsdict(n),
502 tags=webutil.nodetagsdict(self.repo, n),
550 inbranch=self.nodeinbranch(ctx),
503 inbranch=webutil.nodeinbranch(self.repo, ctx),
551 branches=self.nodebranchdict(ctx))
504 branches=webutil.nodebranchdict(self.repo, ctx))
552
505
553 if count >= self.maxchanges:
506 if count >= self.maxchanges:
554 break
507 break
555
508
556 cl = self.repo.changelog
509 cl = self.repo.changelog
557 parity = paritygen(self.stripecount)
510 parity = paritygen(self.stripecount)
558
511
559 return tmpl('search',
512 return tmpl('search',
560 query=query,
513 query=query,
561 node=hex(cl.tip()),
514 node=hex(cl.tip()),
562 entries=changelist,
515 entries=changelist,
563 archives=self.archivelist("tip"))
516 archives=self.archivelist("tip"))
564
517
565 def changeset(self, tmpl, ctx):
518 def changeset(self, tmpl, ctx):
566 n = ctx.node()
519 n = ctx.node()
567 showtags = self.showtag(tmpl, 'changesettag', n)
520 showtags = webutil.showtag(self.repo, tmpl, 'changesettag', n)
568 parents = ctx.parents()
521 parents = ctx.parents()
569 p1 = parents[0].node()
522 p1 = parents[0].node()
570
523
571 files = []
524 files = []
572 parity = paritygen(self.stripecount)
525 parity = paritygen(self.stripecount)
573 for f in ctx.files():
526 for f in ctx.files():
574 files.append(tmpl("filenodelink",
527 files.append(tmpl("filenodelink",
575 node=hex(n), file=f,
528 node=hex(n), file=f,
576 parity=parity.next()))
529 parity=parity.next()))
577
530
578 def diff(**map):
531 def diff(**map):
579 yield self.diff(tmpl, p1, n, None)
532 yield self.diff(tmpl, p1, n, None)
580
533
581 return tmpl('changeset',
534 return tmpl('changeset',
582 diff=diff,
535 diff=diff,
583 rev=ctx.rev(),
536 rev=ctx.rev(),
584 node=hex(n),
537 node=hex(n),
585 parent=self.siblings(parents),
538 parent=webutil.siblings(parents),
586 child=self.siblings(ctx.children()),
539 child=webutil.siblings(ctx.children()),
587 changesettag=showtags,
540 changesettag=showtags,
588 author=ctx.user(),
541 author=ctx.user(),
589 desc=ctx.description(),
542 desc=ctx.description(),
590 date=ctx.date(),
543 date=ctx.date(),
591 files=files,
544 files=files,
592 archives=self.archivelist(hex(n)),
545 archives=self.archivelist(hex(n)),
593 tags=self.nodetagsdict(n),
546 tags=webutil.nodetagsdict(self.repo, n),
594 branch=self.nodebranchnodefault(ctx),
547 branch=webutil.nodebranchnodefault(ctx),
595 inbranch=self.nodeinbranch(ctx),
548 inbranch=webutil.nodeinbranch(self.repo, ctx),
596 branches=self.nodebranchdict(ctx))
549 branches=webutil.nodebranchdict(self.repo, ctx))
597
550
598 def filelog(self, tmpl, fctx):
551 def filelog(self, tmpl, fctx):
599 f = fctx.path()
552 f = fctx.path()
600 fl = fctx.filelog()
553 fl = fctx.filelog()
601 count = fl.count()
554 count = fl.count()
602 pagelen = self.maxshortchanges
555 pagelen = self.maxshortchanges
603 pos = fctx.filerev()
556 pos = fctx.filerev()
604 start = max(0, pos - pagelen + 1)
557 start = max(0, pos - pagelen + 1)
605 end = min(count, start + pagelen)
558 end = min(count, start + pagelen)
606 pos = end - 1
559 pos = end - 1
607 parity = paritygen(self.stripecount, offset=start-end)
560 parity = paritygen(self.stripecount, offset=start-end)
608
561
609 def entries(limit=0, **map):
562 def entries(limit=0, **map):
610 l = []
563 l = []
611
564
612 for i in xrange(start, end):
565 for i in xrange(start, end):
613 ctx = fctx.filectx(i)
566 ctx = fctx.filectx(i)
614 n = fl.node(i)
567 n = fl.node(i)
615
568
616 l.insert(0, {"parity": parity.next(),
569 l.insert(0, {"parity": parity.next(),
617 "filerev": i,
570 "filerev": i,
618 "file": f,
571 "file": f,
619 "node": hex(ctx.node()),
572 "node": hex(ctx.node()),
620 "author": ctx.user(),
573 "author": ctx.user(),
621 "date": ctx.date(),
574 "date": ctx.date(),
622 "rename": self.renamelink(fl, n),
575 "rename": webutil.renamelink(fl, n),
623 "parent": self.siblings(fctx.parents()),
576 "parent": webutil.siblings(fctx.parents()),
624 "child": self.siblings(fctx.children()),
577 "child": webutil.siblings(fctx.children()),
625 "desc": ctx.description()})
578 "desc": ctx.description()})
626
579
627 if limit > 0:
580 if limit > 0:
628 l = l[:limit]
581 l = l[:limit]
629
582
630 for e in l:
583 for e in l:
631 yield e
584 yield e
632
585
633 nodefunc = lambda x: fctx.filectx(fileid=x)
586 nodefunc = lambda x: fctx.filectx(fileid=x)
634 nav = revnavgen(pos, pagelen, count, nodefunc)
587 nav = revnavgen(pos, pagelen, count, nodefunc)
635 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
588 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
636 entries=lambda **x: entries(limit=0, **x),
589 entries=lambda **x: entries(limit=0, **x),
637 latestentry=lambda **x: entries(limit=1, **x))
590 latestentry=lambda **x: entries(limit=1, **x))
638
591
639 def filerevision(self, tmpl, fctx):
592 def filerevision(self, tmpl, fctx):
640 f = fctx.path()
593 f = fctx.path()
641 text = fctx.data()
594 text = fctx.data()
642 fl = fctx.filelog()
595 fl = fctx.filelog()
643 n = fctx.filenode()
596 n = fctx.filenode()
644 parity = paritygen(self.stripecount)
597 parity = paritygen(self.stripecount)
645
598
646 if util.binary(text):
599 if util.binary(text):
647 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
600 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
648 text = '(binary:%s)' % mt
601 text = '(binary:%s)' % mt
649
602
650 def lines():
603 def lines():
651 for lineno, t in enumerate(text.splitlines(1)):
604 for lineno, t in enumerate(text.splitlines(1)):
652 yield {"line": t,
605 yield {"line": t,
653 "lineid": "l%d" % (lineno + 1),
606 "lineid": "l%d" % (lineno + 1),
654 "linenumber": "% 6d" % (lineno + 1),
607 "linenumber": "% 6d" % (lineno + 1),
655 "parity": parity.next()}
608 "parity": parity.next()}
656
609
657 return tmpl("filerevision",
610 return tmpl("filerevision",
658 file=f,
611 file=f,
659 path=_up(f),
612 path=_up(f),
660 text=lines(),
613 text=lines(),
661 rev=fctx.rev(),
614 rev=fctx.rev(),
662 node=hex(fctx.node()),
615 node=hex(fctx.node()),
663 author=fctx.user(),
616 author=fctx.user(),
664 date=fctx.date(),
617 date=fctx.date(),
665 desc=fctx.description(),
618 desc=fctx.description(),
666 branch=self.nodebranchnodefault(fctx),
619 branch=webutil.nodebranchnodefault(fctx),
667 parent=self.siblings(fctx.parents()),
620 parent=webutil.siblings(fctx.parents()),
668 child=self.siblings(fctx.children()),
621 child=webutil.siblings(fctx.children()),
669 rename=self.renamelink(fl, n),
622 rename=webutil.renamelink(fl, n),
670 permissions=fctx.manifest().flags(f))
623 permissions=fctx.manifest().flags(f))
671
624
672 def fileannotate(self, tmpl, fctx):
625 def fileannotate(self, tmpl, fctx):
673 f = fctx.path()
626 f = fctx.path()
674 n = fctx.filenode()
627 n = fctx.filenode()
675 fl = fctx.filelog()
628 fl = fctx.filelog()
676 parity = paritygen(self.stripecount)
629 parity = paritygen(self.stripecount)
677
630
678 def annotate(**map):
631 def annotate(**map):
679 last = None
632 last = None
680 if util.binary(fctx.data()):
633 if util.binary(fctx.data()):
681 mt = (mimetypes.guess_type(fctx.path())[0]
634 mt = (mimetypes.guess_type(fctx.path())[0]
682 or 'application/octet-stream')
635 or 'application/octet-stream')
683 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
636 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
684 '(binary:%s)' % mt)])
637 '(binary:%s)' % mt)])
685 else:
638 else:
686 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
639 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
687 for lineno, ((f, targetline), l) in lines:
640 for lineno, ((f, targetline), l) in lines:
688 fnode = f.filenode()
641 fnode = f.filenode()
689 name = self.repo.ui.shortuser(f.user())
642 name = self.repo.ui.shortuser(f.user())
690
643
691 if last != fnode:
644 if last != fnode:
692 last = fnode
645 last = fnode
693
646
694 yield {"parity": parity.next(),
647 yield {"parity": parity.next(),
695 "node": hex(f.node()),
648 "node": hex(f.node()),
696 "rev": f.rev(),
649 "rev": f.rev(),
697 "author": name,
650 "author": name,
698 "file": f.path(),
651 "file": f.path(),
699 "targetline": targetline,
652 "targetline": targetline,
700 "line": l,
653 "line": l,
701 "lineid": "l%d" % (lineno + 1),
654 "lineid": "l%d" % (lineno + 1),
702 "linenumber": "% 6d" % (lineno + 1)}
655 "linenumber": "% 6d" % (lineno + 1)}
703
656
704 return tmpl("fileannotate",
657 return tmpl("fileannotate",
705 file=f,
658 file=f,
706 annotate=annotate,
659 annotate=annotate,
707 path=_up(f),
660 path=_up(f),
708 rev=fctx.rev(),
661 rev=fctx.rev(),
709 node=hex(fctx.node()),
662 node=hex(fctx.node()),
710 author=fctx.user(),
663 author=fctx.user(),
711 date=fctx.date(),
664 date=fctx.date(),
712 desc=fctx.description(),
665 desc=fctx.description(),
713 rename=self.renamelink(fl, n),
666 rename=webutil.renamelink(fl, n),
714 branch=self.nodebranchnodefault(fctx),
667 branch=webutil.nodebranchnodefault(fctx),
715 parent=self.siblings(fctx.parents()),
668 parent=webutil.siblings(fctx.parents()),
716 child=self.siblings(fctx.children()),
669 child=webutil.siblings(fctx.children()),
717 permissions=fctx.manifest().flags(f))
670 permissions=fctx.manifest().flags(f))
718
671
719 def manifest(self, tmpl, ctx, path):
672 def manifest(self, tmpl, ctx, path):
720 mf = ctx.manifest()
673 mf = ctx.manifest()
721 node = ctx.node()
674 node = ctx.node()
722
675
723 files = {}
676 files = {}
724 parity = paritygen(self.stripecount)
677 parity = paritygen(self.stripecount)
725
678
726 if path and path[-1] != "/":
679 if path and path[-1] != "/":
727 path += "/"
680 path += "/"
728 l = len(path)
681 l = len(path)
729 abspath = "/" + path
682 abspath = "/" + path
730
683
731 for f, n in mf.items():
684 for f, n in mf.items():
732 if f[:l] != path:
685 if f[:l] != path:
733 continue
686 continue
734 remain = f[l:]
687 remain = f[l:]
735 if "/" in remain:
688 if "/" in remain:
736 short = remain[:remain.index("/") + 1] # bleah
689 short = remain[:remain.index("/") + 1] # bleah
737 files[short] = (f, None)
690 files[short] = (f, None)
738 else:
691 else:
739 short = os.path.basename(remain)
692 short = os.path.basename(remain)
740 files[short] = (f, n)
693 files[short] = (f, n)
741
694
742 if not files:
695 if not files:
743 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
696 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
744
697
745 def filelist(**map):
698 def filelist(**map):
746 fl = files.keys()
699 fl = files.keys()
747 fl.sort()
700 fl.sort()
748 for f in fl:
701 for f in fl:
749 full, fnode = files[f]
702 full, fnode = files[f]
750 if not fnode:
703 if not fnode:
751 continue
704 continue
752
705
753 fctx = ctx.filectx(full)
706 fctx = ctx.filectx(full)
754 yield {"file": full,
707 yield {"file": full,
755 "parity": parity.next(),
708 "parity": parity.next(),
756 "basename": f,
709 "basename": f,
757 "date": fctx.changectx().date(),
710 "date": fctx.changectx().date(),
758 "size": fctx.size(),
711 "size": fctx.size(),
759 "permissions": mf.flags(full)}
712 "permissions": mf.flags(full)}
760
713
761 def dirlist(**map):
714 def dirlist(**map):
762 fl = files.keys()
715 fl = files.keys()
763 fl.sort()
716 fl.sort()
764 for f in fl:
717 for f in fl:
765 full, fnode = files[f]
718 full, fnode = files[f]
766 if fnode:
719 if fnode:
767 continue
720 continue
768
721
769 yield {"parity": parity.next(),
722 yield {"parity": parity.next(),
770 "path": "%s%s" % (abspath, f),
723 "path": "%s%s" % (abspath, f),
771 "basename": f[:-1]}
724 "basename": f[:-1]}
772
725
773 return tmpl("manifest",
726 return tmpl("manifest",
774 rev=ctx.rev(),
727 rev=ctx.rev(),
775 node=hex(node),
728 node=hex(node),
776 path=abspath,
729 path=abspath,
777 up=_up(abspath),
730 up=_up(abspath),
778 upparity=parity.next(),
731 upparity=parity.next(),
779 fentries=filelist,
732 fentries=filelist,
780 dentries=dirlist,
733 dentries=dirlist,
781 archives=self.archivelist(hex(node)),
734 archives=self.archivelist(hex(node)),
782 tags=self.nodetagsdict(node),
735 tags=webutil.nodetagsdict(self.repo, node),
783 inbranch=self.nodeinbranch(ctx),
736 inbranch=webutil.nodeinbranch(self.repo, ctx),
784 branches=self.nodebranchdict(ctx))
737 branches=webutil.nodebranchdict(self.repo, ctx))
785
738
786 def tags(self, tmpl):
739 def tags(self, tmpl):
787 i = self.repo.tagslist()
740 i = self.repo.tagslist()
788 i.reverse()
741 i.reverse()
789 parity = paritygen(self.stripecount)
742 parity = paritygen(self.stripecount)
790
743
791 def entries(notip=False,limit=0, **map):
744 def entries(notip=False,limit=0, **map):
792 count = 0
745 count = 0
793 for k, n in i:
746 for k, n in i:
794 if notip and k == "tip":
747 if notip and k == "tip":
795 continue
748 continue
796 if limit > 0 and count >= limit:
749 if limit > 0 and count >= limit:
797 continue
750 continue
798 count = count + 1
751 count = count + 1
799 yield {"parity": parity.next(),
752 yield {"parity": parity.next(),
800 "tag": k,
753 "tag": k,
801 "date": self.repo.changectx(n).date(),
754 "date": self.repo.changectx(n).date(),
802 "node": hex(n)}
755 "node": hex(n)}
803
756
804 return tmpl("tags",
757 return tmpl("tags",
805 node=hex(self.repo.changelog.tip()),
758 node=hex(self.repo.changelog.tip()),
806 entries=lambda **x: entries(False,0, **x),
759 entries=lambda **x: entries(False,0, **x),
807 entriesnotip=lambda **x: entries(True,0, **x),
760 entriesnotip=lambda **x: entries(True,0, **x),
808 latestentry=lambda **x: entries(True,1, **x))
761 latestentry=lambda **x: entries(True,1, **x))
809
762
810 def summary(self, tmpl):
763 def summary(self, tmpl):
811 i = self.repo.tagslist()
764 i = self.repo.tagslist()
812 i.reverse()
765 i.reverse()
813
766
814 def tagentries(**map):
767 def tagentries(**map):
815 parity = paritygen(self.stripecount)
768 parity = paritygen(self.stripecount)
816 count = 0
769 count = 0
817 for k, n in i:
770 for k, n in i:
818 if k == "tip": # skip tip
771 if k == "tip": # skip tip
819 continue;
772 continue;
820
773
821 count += 1
774 count += 1
822 if count > 10: # limit to 10 tags
775 if count > 10: # limit to 10 tags
823 break;
776 break;
824
777
825 yield tmpl("tagentry",
778 yield tmpl("tagentry",
826 parity=parity.next(),
779 parity=parity.next(),
827 tag=k,
780 tag=k,
828 node=hex(n),
781 node=hex(n),
829 date=self.repo.changectx(n).date())
782 date=self.repo.changectx(n).date())
830
783
831
784
832 def branches(**map):
785 def branches(**map):
833 parity = paritygen(self.stripecount)
786 parity = paritygen(self.stripecount)
834
787
835 b = self.repo.branchtags()
788 b = self.repo.branchtags()
836 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
789 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
837 l.sort()
790 l.sort()
838
791
839 for r,n,t in l:
792 for r,n,t in l:
840 ctx = self.repo.changectx(n)
793 ctx = self.repo.changectx(n)
841
794
842 yield {'parity': parity.next(),
795 yield {'parity': parity.next(),
843 'branch': t,
796 'branch': t,
844 'node': hex(n),
797 'node': hex(n),
845 'date': ctx.date()}
798 'date': ctx.date()}
846
799
847 def changelist(**map):
800 def changelist(**map):
848 parity = paritygen(self.stripecount, offset=start-end)
801 parity = paritygen(self.stripecount, offset=start-end)
849 l = [] # build a list in forward order for efficiency
802 l = [] # build a list in forward order for efficiency
850 for i in xrange(start, end):
803 for i in xrange(start, end):
851 ctx = self.repo.changectx(i)
804 ctx = self.repo.changectx(i)
852 n = ctx.node()
805 n = ctx.node()
853 hn = hex(n)
806 hn = hex(n)
854
807
855 l.insert(0, tmpl(
808 l.insert(0, tmpl(
856 'shortlogentry',
809 'shortlogentry',
857 parity=parity.next(),
810 parity=parity.next(),
858 author=ctx.user(),
811 author=ctx.user(),
859 desc=ctx.description(),
812 desc=ctx.description(),
860 date=ctx.date(),
813 date=ctx.date(),
861 rev=i,
814 rev=i,
862 node=hn,
815 node=hn,
863 tags=self.nodetagsdict(n),
816 tags=webutil.nodetagsdict(self.repo, n),
864 inbranch=self.nodeinbranch(ctx),
817 inbranch=webutil.nodeinbranch(self.repo, ctx),
865 branches=self.nodebranchdict(ctx)))
818 branches=webutil.nodebranchdict(self.repo, ctx)))
866
819
867 yield l
820 yield l
868
821
869 cl = self.repo.changelog
822 cl = self.repo.changelog
870 count = cl.count()
823 count = cl.count()
871 start = max(0, count - self.maxchanges)
824 start = max(0, count - self.maxchanges)
872 end = min(count, start + self.maxchanges)
825 end = min(count, start + self.maxchanges)
873
826
874 return tmpl("summary",
827 return tmpl("summary",
875 desc=self.config("web", "description", "unknown"),
828 desc=self.config("web", "description", "unknown"),
876 owner=get_contact(self.config) or "unknown",
829 owner=get_contact(self.config) or "unknown",
877 lastchange=cl.read(cl.tip())[2],
830 lastchange=cl.read(cl.tip())[2],
878 tags=tagentries,
831 tags=tagentries,
879 branches=branches,
832 branches=branches,
880 shortlog=changelist,
833 shortlog=changelist,
881 node=hex(cl.tip()),
834 node=hex(cl.tip()),
882 archives=self.archivelist("tip"))
835 archives=self.archivelist("tip"))
883
836
884 def filediff(self, tmpl, fctx):
837 def filediff(self, tmpl, fctx):
885 n = fctx.node()
838 n = fctx.node()
886 path = fctx.path()
839 path = fctx.path()
887 parents = fctx.parents()
840 parents = fctx.parents()
888 p1 = parents and parents[0].node() or nullid
841 p1 = parents and parents[0].node() or nullid
889
842
890 def diff(**map):
843 def diff(**map):
891 yield self.diff(tmpl, p1, n, [path])
844 yield self.diff(tmpl, p1, n, [path])
892
845
893 return tmpl("filediff",
846 return tmpl("filediff",
894 file=path,
847 file=path,
895 node=hex(n),
848 node=hex(n),
896 rev=fctx.rev(),
849 rev=fctx.rev(),
897 branch=self.nodebranchnodefault(fctx),
850 branch=webutil.nodebranchnodefault(fctx),
898 parent=self.siblings(parents),
851 parent=webutil.siblings(parents),
899 child=self.siblings(fctx.children()),
852 child=webutil.siblings(fctx.children()),
900 diff=diff)
853 diff=diff)
901
854
902 archive_specs = {
855 archive_specs = {
903 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
856 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
904 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
857 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
905 'zip': ('application/zip', 'zip', '.zip', None),
858 'zip': ('application/zip', 'zip', '.zip', None),
906 }
859 }
907
860
908 def archive(self, tmpl, req, key, type_):
861 def archive(self, tmpl, req, key, type_):
909 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
862 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
910 cnode = self.repo.lookup(key)
863 cnode = self.repo.lookup(key)
911 arch_version = key
864 arch_version = key
912 if cnode == key or key == 'tip':
865 if cnode == key or key == 'tip':
913 arch_version = short(cnode)
866 arch_version = short(cnode)
914 name = "%s-%s" % (reponame, arch_version)
867 name = "%s-%s" % (reponame, arch_version)
915 mimetype, artype, extension, encoding = self.archive_specs[type_]
868 mimetype, artype, extension, encoding = self.archive_specs[type_]
916 headers = [
869 headers = [
917 ('Content-Type', mimetype),
870 ('Content-Type', mimetype),
918 ('Content-Disposition', 'attachment; filename=%s%s' %
871 ('Content-Disposition', 'attachment; filename=%s%s' %
919 (name, extension))
872 (name, extension))
920 ]
873 ]
921 if encoding:
874 if encoding:
922 headers.append(('Content-Encoding', encoding))
875 headers.append(('Content-Encoding', encoding))
923 req.header(headers)
876 req.header(headers)
924 req.respond(HTTP_OK)
877 req.respond(HTTP_OK)
925 archival.archive(self.repo, req, cnode, artype, prefix=name)
878 archival.archive(self.repo, req, cnode, artype, prefix=name)
926
879
927 # add tags to things
928 # tags -> list of changesets corresponding to tags
929 # find tag, changeset, file
930
931 def cleanpath(self, path):
932 path = path.lstrip('/')
933 return util.canonpath(self.repo.root, '', path)
934
935 def changectx(self, req):
936 if 'node' in req.form:
937 changeid = req.form['node'][0]
938 elif 'manifest' in req.form:
939 changeid = req.form['manifest'][0]
940 else:
941 changeid = self.repo.changelog.count() - 1
942
943 try:
944 ctx = self.repo.changectx(changeid)
945 except RepoError:
946 man = self.repo.manifest
947 mn = man.lookup(changeid)
948 ctx = self.repo.changectx(man.linkrev(mn))
949
950 return ctx
951
952 def filectx(self, req):
953 path = self.cleanpath(req.form['file'][0])
954 if 'node' in req.form:
955 changeid = req.form['node'][0]
956 else:
957 changeid = req.form['filenode'][0]
958 try:
959 ctx = self.repo.changectx(changeid)
960 fctx = ctx.filectx(path)
961 except RepoError:
962 fctx = self.repo.filectx(path, fileid=changeid)
963
964 return fctx
965
966 def check_perm(self, req, op, default):
880 def check_perm(self, req, op, default):
967 '''check permission for operation based on user auth.
881 '''check permission for operation based on user auth.
968 return true if op allowed, else false.
882 return true if op allowed, else false.
969 default is policy to use if no config given.'''
883 default is policy to use if no config given.'''
970
884
971 user = req.env.get('REMOTE_USER')
885 user = req.env.get('REMOTE_USER')
972
886
973 deny = self.configlist('web', 'deny_' + op)
887 deny = self.configlist('web', 'deny_' + op)
974 if deny and (not user or deny == ['*'] or user in deny):
888 if deny and (not user or deny == ['*'] or user in deny):
975 return False
889 return False
976
890
977 allow = self.configlist('web', 'allow_' + op)
891 allow = self.configlist('web', 'allow_' + op)
978 return (allow and (allow == ['*'] or user in allow)) or default
892 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,127 +1,129 b''
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, mimetypes
8 import os, mimetypes
9 from mercurial import revlog, util
9 import webutil
10 from mercurial import revlog
11 from mercurial.util import binary
10 from mercurial.repo import RepoError
12 from mercurial.repo import RepoError
11 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
13 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
12
14
13 # __all__ is populated with the allowed commands. Be sure to add to it if
15 # __all__ is populated with the allowed commands. Be sure to add to it if
14 # you're adding a new command, or the new command won't work.
16 # you're adding a new command, or the new command won't work.
15
17
16 __all__ = [
18 __all__ = [
17 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
19 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
18 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
20 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
19 'archive', 'static',
21 'archive', 'static',
20 ]
22 ]
21
23
22 def log(web, req, tmpl):
24 def log(web, req, tmpl):
23 if 'file' in req.form and req.form['file'][0]:
25 if 'file' in req.form and req.form['file'][0]:
24 return filelog(web, req, tmpl)
26 return filelog(web, req, tmpl)
25 else:
27 else:
26 return changelog(web, req, tmpl)
28 return changelog(web, req, tmpl)
27
29
28 def rawfile(web, req, tmpl):
30 def rawfile(web, req, tmpl):
29 path = web.cleanpath(req.form.get('file', [''])[0])
31 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
30 if not path:
32 if not path:
31 content = web.manifest(tmpl, web.changectx(req), path)
33 content = web.manifest(tmpl, webutil.changectx(web.repo, req), path)
32 req.respond(HTTP_OK, web.ctype)
34 req.respond(HTTP_OK, web.ctype)
33 return content
35 return content
34
36
35 try:
37 try:
36 fctx = web.filectx(req)
38 fctx = webutil.filectx(web.repo, req)
37 except revlog.LookupError, inst:
39 except revlog.LookupError, inst:
38 try:
40 try:
39 content = web.manifest(tmpl, web.changectx(req), path)
41 content = web.manifest(tmpl, webutil.changectx(web.repo, req), path)
40 req.respond(HTTP_OK, web.ctype)
42 req.respond(HTTP_OK, web.ctype)
41 return content
43 return content
42 except ErrorResponse:
44 except ErrorResponse:
43 raise inst
45 raise inst
44
46
45 path = fctx.path()
47 path = fctx.path()
46 text = fctx.data()
48 text = fctx.data()
47 mt = mimetypes.guess_type(path)[0]
49 mt = mimetypes.guess_type(path)[0]
48 if mt is None or util.binary(text):
50 if mt is None or binary(text):
49 mt = mt or 'application/octet-stream'
51 mt = mt or 'application/octet-stream'
50
52
51 req.respond(HTTP_OK, mt, path, len(text))
53 req.respond(HTTP_OK, mt, path, len(text))
52 return [text]
54 return [text]
53
55
54 def file(web, req, tmpl):
56 def file(web, req, tmpl):
55 path = web.cleanpath(req.form.get('file', [''])[0])
57 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
56 if path:
58 if path:
57 try:
59 try:
58 return web.filerevision(tmpl, web.filectx(req))
60 return web.filerevision(tmpl, webutil.filectx(web.repo, req))
59 except revlog.LookupError, inst:
61 except revlog.LookupError, inst:
60 pass
62 pass
61
63
62 try:
64 try:
63 return web.manifest(tmpl, web.changectx(req), path)
65 return web.manifest(tmpl, webutil.changectx(web.repo, req), path)
64 except ErrorResponse:
66 except ErrorResponse:
65 raise inst
67 raise inst
66
68
67 def changelog(web, req, tmpl, shortlog = False):
69 def changelog(web, req, tmpl, shortlog = False):
68 if 'node' in req.form:
70 if 'node' in req.form:
69 ctx = web.changectx(req)
71 ctx = webutil.changectx(web.repo, req)
70 else:
72 else:
71 if 'rev' in req.form:
73 if 'rev' in req.form:
72 hi = req.form['rev'][0]
74 hi = req.form['rev'][0]
73 else:
75 else:
74 hi = web.repo.changelog.count() - 1
76 hi = web.repo.changelog.count() - 1
75 try:
77 try:
76 ctx = web.repo.changectx(hi)
78 ctx = web.repo.changectx(hi)
77 except RepoError:
79 except RepoError:
78 return web.search(tmpl, hi) # XXX redirect to 404 page?
80 return web.search(tmpl, hi) # XXX redirect to 404 page?
79
81
80 return web.changelog(tmpl, ctx, shortlog = shortlog)
82 return web.changelog(tmpl, ctx, shortlog = shortlog)
81
83
82 def shortlog(web, req, tmpl):
84 def shortlog(web, req, tmpl):
83 return changelog(web, req, tmpl, shortlog = True)
85 return changelog(web, req, tmpl, shortlog = True)
84
86
85 def changeset(web, req, tmpl):
87 def changeset(web, req, tmpl):
86 return web.changeset(tmpl, web.changectx(req))
88 return web.changeset(tmpl, webutil.changectx(web.repo, req))
87
89
88 rev = changeset
90 rev = changeset
89
91
90 def manifest(web, req, tmpl):
92 def manifest(web, req, tmpl):
91 return web.manifest(tmpl, web.changectx(req),
93 return web.manifest(tmpl, webutil.changectx(web.repo, req),
92 web.cleanpath(req.form['path'][0]))
94 webutil.cleanpath(web.repo, req.form['path'][0]))
93
95
94 def tags(web, req, tmpl):
96 def tags(web, req, tmpl):
95 return web.tags(tmpl)
97 return web.tags(tmpl)
96
98
97 def summary(web, req, tmpl):
99 def summary(web, req, tmpl):
98 return web.summary(tmpl)
100 return web.summary(tmpl)
99
101
100 def filediff(web, req, tmpl):
102 def filediff(web, req, tmpl):
101 return web.filediff(tmpl, web.filectx(req))
103 return web.filediff(tmpl, webutil.filectx(web.repo, req))
102
104
103 diff = filediff
105 diff = filediff
104
106
105 def annotate(web, req, tmpl):
107 def annotate(web, req, tmpl):
106 return web.fileannotate(tmpl, web.filectx(req))
108 return web.fileannotate(tmpl, webutil.filectx(web.repo, req))
107
109
108 def filelog(web, req, tmpl):
110 def filelog(web, req, tmpl):
109 return web.filelog(tmpl, web.filectx(req))
111 return web.filelog(tmpl, webutil.filectx(web.repo, req))
110
112
111 def archive(web, req, tmpl):
113 def archive(web, req, tmpl):
112 type_ = req.form['type'][0]
114 type_ = req.form['type'][0]
113 allowed = web.configlist("web", "allow_archive")
115 allowed = web.configlist("web", "allow_archive")
114 if (type_ in web.archives and (type_ in allowed or
116 if (type_ in web.archives and (type_ in allowed or
115 web.configbool("web", "allow" + type_, False))):
117 web.configbool("web", "allow" + type_, False))):
116 web.archive(tmpl, req, req.form['node'][0], type_)
118 web.archive(tmpl, req, req.form['node'][0], type_)
117 return []
119 return []
118 raise ErrorResponse(HTTP_NOT_FOUND, 'unsupported archive type: %s' % type_)
120 raise ErrorResponse(HTTP_NOT_FOUND, 'unsupported archive type: %s' % type_)
119
121
120 def static(web, req, tmpl):
122 def static(web, req, tmpl):
121 fname = req.form['file'][0]
123 fname = req.form['file'][0]
122 # a repo owner may set web.static in .hg/hgrc to get any file
124 # a repo owner may set web.static in .hg/hgrc to get any file
123 # readable by the user running the CGI script
125 # readable by the user running the CGI script
124 static = web.config("web", "static",
126 static = web.config("web", "static",
125 os.path.join(web.templatepath, "static"),
127 os.path.join(web.templatepath, "static"),
126 untrusted=False)
128 untrusted=False)
127 return [staticfile(static, fname, req)]
129 return [staticfile(static, fname, req)]
General Comments 0
You need to be logged in to leave comments. Login now