##// END OF EJS Templates
hgweb: use latest mtime for caching tag (issue4814)...
Matt Mackall -
r26150:ee31ede3 stable
parent child Browse files
Show More
@@ -1,425 +1,427
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 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 os, re
9 import os, re
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 from mercurial.templatefilters import websub
11 from mercurial.templatefilters import websub
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from common import get_stat, ErrorResponse, permhooks, caching
13 from common import get_stat, ErrorResponse, permhooks, caching
14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol, webutil
17 import webcommands, protocol, webutil
18
18
19 perms = {
19 perms = {
20 'changegroup': 'pull',
20 'changegroup': 'pull',
21 'changegroupsubset': 'pull',
21 'changegroupsubset': 'pull',
22 'getbundle': 'pull',
22 'getbundle': 'pull',
23 'stream_out': 'pull',
23 'stream_out': 'pull',
24 'listkeys': 'pull',
24 'listkeys': 'pull',
25 'unbundle': 'push',
25 'unbundle': 'push',
26 'pushkey': 'push',
26 'pushkey': 'push',
27 }
27 }
28
28
29 ## Files of interest
29 ## Files of interest
30 # Used to check if the repository has changed looking at mtime and size of
30 # Used to check if the repository has changed looking at mtime and size of
31 # theses files. This should probably be relocated a bit higher in core.
31 # theses files. This should probably be relocated a bit higher in core.
32 foi = [('spath', '00changelog.i'),
32 foi = [('spath', '00changelog.i'),
33 ('spath', 'phaseroots'), # ! phase can change content at the same size
33 ('spath', 'phaseroots'), # ! phase can change content at the same size
34 ('spath', 'obsstore'),
34 ('spath', 'obsstore'),
35 ('path', 'bookmarks'), # ! bookmark can change content at the same size
35 ('path', 'bookmarks'), # ! bookmark can change content at the same size
36 ]
36 ]
37
37
38 def makebreadcrumb(url, prefix=''):
38 def makebreadcrumb(url, prefix=''):
39 '''Return a 'URL breadcrumb' list
39 '''Return a 'URL breadcrumb' list
40
40
41 A 'URL breadcrumb' is a list of URL-name pairs,
41 A 'URL breadcrumb' is a list of URL-name pairs,
42 corresponding to each of the path items on a URL.
42 corresponding to each of the path items on a URL.
43 This can be used to create path navigation entries.
43 This can be used to create path navigation entries.
44 '''
44 '''
45 if url.endswith('/'):
45 if url.endswith('/'):
46 url = url[:-1]
46 url = url[:-1]
47 if prefix:
47 if prefix:
48 url = '/' + prefix + url
48 url = '/' + prefix + url
49 relpath = url
49 relpath = url
50 if relpath.startswith('/'):
50 if relpath.startswith('/'):
51 relpath = relpath[1:]
51 relpath = relpath[1:]
52
52
53 breadcrumb = []
53 breadcrumb = []
54 urlel = url
54 urlel = url
55 pathitems = [''] + relpath.split('/')
55 pathitems = [''] + relpath.split('/')
56 for pathel in reversed(pathitems):
56 for pathel in reversed(pathitems):
57 if not pathel or not urlel:
57 if not pathel or not urlel:
58 break
58 break
59 breadcrumb.append({'url': urlel, 'name': pathel})
59 breadcrumb.append({'url': urlel, 'name': pathel})
60 urlel = os.path.dirname(urlel)
60 urlel = os.path.dirname(urlel)
61 return reversed(breadcrumb)
61 return reversed(breadcrumb)
62
62
63
63
64 class hgweb(object):
64 class hgweb(object):
65 def __init__(self, repo, name=None, baseui=None):
65 def __init__(self, repo, name=None, baseui=None):
66 if isinstance(repo, str):
66 if isinstance(repo, str):
67 if baseui:
67 if baseui:
68 u = baseui.copy()
68 u = baseui.copy()
69 else:
69 else:
70 u = ui.ui()
70 u = ui.ui()
71 r = hg.repository(u, repo)
71 r = hg.repository(u, repo)
72 else:
72 else:
73 # we trust caller to give us a private copy
73 # we trust caller to give us a private copy
74 r = repo
74 r = repo
75
75
76 r = self._getview(r)
76 r = self._getview(r)
77 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
77 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
78 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
78 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
79 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
79 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
80 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
80 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
81 # displaying bundling progress bar while serving feel wrong and may
81 # displaying bundling progress bar while serving feel wrong and may
82 # break some wsgi implementation.
82 # break some wsgi implementation.
83 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
83 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
84 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
84 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
85 self.repo = r
85 self.repo = r
86 hook.redirect(True)
86 hook.redirect(True)
87 self.repostate = ((-1, -1), (-1, -1))
87 self.repostate = ((-1, -1), (-1, -1))
88 self.mtime = -1
88 self.mtime = -1
89 self.reponame = name
89 self.reponame = name
90 self.archives = 'zip', 'gz', 'bz2'
90 self.archives = 'zip', 'gz', 'bz2'
91 self.stripecount = 1
91 self.stripecount = 1
92 # we use untrusted=False to prevent a repo owner from using
92 # we use untrusted=False to prevent a repo owner from using
93 # web.templates in .hg/hgrc to get access to any file readable
93 # web.templates in .hg/hgrc to get access to any file readable
94 # by the user running the CGI script
94 # by the user running the CGI script
95 self.templatepath = self.config('web', 'templates', untrusted=False)
95 self.templatepath = self.config('web', 'templates', untrusted=False)
96 self.websubtable = self.loadwebsub()
96 self.websubtable = self.loadwebsub()
97
97
98 # The CGI scripts are often run by a user different from the repo owner.
98 # The CGI scripts are often run by a user different from the repo owner.
99 # Trust the settings from the .hg/hgrc files by default.
99 # Trust the settings from the .hg/hgrc files by default.
100 def config(self, section, name, default=None, untrusted=True):
100 def config(self, section, name, default=None, untrusted=True):
101 return self.repo.ui.config(section, name, default,
101 return self.repo.ui.config(section, name, default,
102 untrusted=untrusted)
102 untrusted=untrusted)
103
103
104 def configbool(self, section, name, default=False, untrusted=True):
104 def configbool(self, section, name, default=False, untrusted=True):
105 return self.repo.ui.configbool(section, name, default,
105 return self.repo.ui.configbool(section, name, default,
106 untrusted=untrusted)
106 untrusted=untrusted)
107
107
108 def configlist(self, section, name, default=None, untrusted=True):
108 def configlist(self, section, name, default=None, untrusted=True):
109 return self.repo.ui.configlist(section, name, default,
109 return self.repo.ui.configlist(section, name, default,
110 untrusted=untrusted)
110 untrusted=untrusted)
111
111
112 def _getview(self, repo):
112 def _getview(self, repo):
113 """The 'web.view' config controls changeset filter to hgweb. Possible
113 """The 'web.view' config controls changeset filter to hgweb. Possible
114 values are ``served``, ``visible`` and ``all``. Default is ``served``.
114 values are ``served``, ``visible`` and ``all``. Default is ``served``.
115 The ``served`` filter only shows changesets that can be pulled from the
115 The ``served`` filter only shows changesets that can be pulled from the
116 hgweb instance. The``visible`` filter includes secret changesets but
116 hgweb instance. The``visible`` filter includes secret changesets but
117 still excludes "hidden" one.
117 still excludes "hidden" one.
118
118
119 See the repoview module for details.
119 See the repoview module for details.
120
120
121 The option has been around undocumented since Mercurial 2.5, but no
121 The option has been around undocumented since Mercurial 2.5, but no
122 user ever asked about it. So we better keep it undocumented for now."""
122 user ever asked about it. So we better keep it undocumented for now."""
123 viewconfig = repo.ui.config('web', 'view', 'served',
123 viewconfig = repo.ui.config('web', 'view', 'served',
124 untrusted=True)
124 untrusted=True)
125 if viewconfig == 'all':
125 if viewconfig == 'all':
126 return repo.unfiltered()
126 return repo.unfiltered()
127 elif viewconfig in repoview.filtertable:
127 elif viewconfig in repoview.filtertable:
128 return repo.filtered(viewconfig)
128 return repo.filtered(viewconfig)
129 else:
129 else:
130 return repo.filtered('served')
130 return repo.filtered('served')
131
131
132 def refresh(self, request=None):
132 def refresh(self, request=None):
133 repostate = []
133 repostate = []
134 mtime = 0
134 # file of interrests mtime and size
135 # file of interrests mtime and size
135 for meth, fname in foi:
136 for meth, fname in foi:
136 prefix = getattr(self.repo, meth)
137 prefix = getattr(self.repo, meth)
137 st = get_stat(prefix, fname)
138 st = get_stat(prefix, fname)
138 repostate.append((st.st_mtime, st.st_size))
139 repostate.append((st.st_mtime, st.st_size))
140 mtime = max(mtime, st.st_mtime)
139 repostate = tuple(repostate)
141 repostate = tuple(repostate)
140 # we need to compare file size in addition to mtime to catch
142 # we need to compare file size in addition to mtime to catch
141 # changes made less than a second ago
143 # changes made less than a second ago
142 if repostate != self.repostate:
144 if repostate != self.repostate:
143 r = hg.repository(self.repo.baseui, self.repo.url())
145 r = hg.repository(self.repo.baseui, self.repo.url())
144 self.repo = self._getview(r)
146 self.repo = self._getview(r)
145 self.maxchanges = int(self.config("web", "maxchanges", 10))
147 self.maxchanges = int(self.config("web", "maxchanges", 10))
146 self.stripecount = int(self.config("web", "stripes", 1))
148 self.stripecount = int(self.config("web", "stripes", 1))
147 self.maxshortchanges = int(self.config("web", "maxshortchanges",
149 self.maxshortchanges = int(self.config("web", "maxshortchanges",
148 60))
150 60))
149 self.maxfiles = int(self.config("web", "maxfiles", 10))
151 self.maxfiles = int(self.config("web", "maxfiles", 10))
150 self.allowpull = self.configbool("web", "allowpull", True)
152 self.allowpull = self.configbool("web", "allowpull", True)
151 encoding.encoding = self.config("web", "encoding",
153 encoding.encoding = self.config("web", "encoding",
152 encoding.encoding)
154 encoding.encoding)
153 # update these last to avoid threads seeing empty settings
155 # update these last to avoid threads seeing empty settings
154 self.repostate = repostate
156 self.repostate = repostate
155 # mtime is needed for ETag
157 # mtime is needed for ETag
156 self.mtime = st.st_mtime
158 self.mtime = mtime
157 if request:
159 if request:
158 self.repo.ui.environ = request.env
160 self.repo.ui.environ = request.env
159
161
160 def run(self):
162 def run(self):
161 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
163 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
162 raise RuntimeError("This function is only intended to be "
164 raise RuntimeError("This function is only intended to be "
163 "called while running as a CGI script.")
165 "called while running as a CGI script.")
164 import mercurial.hgweb.wsgicgi as wsgicgi
166 import mercurial.hgweb.wsgicgi as wsgicgi
165 wsgicgi.launch(self)
167 wsgicgi.launch(self)
166
168
167 def __call__(self, env, respond):
169 def __call__(self, env, respond):
168 req = wsgirequest(env, respond)
170 req = wsgirequest(env, respond)
169 return self.run_wsgi(req)
171 return self.run_wsgi(req)
170
172
171 def run_wsgi(self, req):
173 def run_wsgi(self, req):
172
174
173 self.refresh(req)
175 self.refresh(req)
174
176
175 # work with CGI variables to create coherent structure
177 # work with CGI variables to create coherent structure
176 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
178 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
177
179
178 req.url = req.env['SCRIPT_NAME']
180 req.url = req.env['SCRIPT_NAME']
179 if not req.url.endswith('/'):
181 if not req.url.endswith('/'):
180 req.url += '/'
182 req.url += '/'
181 if 'REPO_NAME' in req.env:
183 if 'REPO_NAME' in req.env:
182 req.url += req.env['REPO_NAME'] + '/'
184 req.url += req.env['REPO_NAME'] + '/'
183
185
184 if 'PATH_INFO' in req.env:
186 if 'PATH_INFO' in req.env:
185 parts = req.env['PATH_INFO'].strip('/').split('/')
187 parts = req.env['PATH_INFO'].strip('/').split('/')
186 repo_parts = req.env.get('REPO_NAME', '').split('/')
188 repo_parts = req.env.get('REPO_NAME', '').split('/')
187 if parts[:len(repo_parts)] == repo_parts:
189 if parts[:len(repo_parts)] == repo_parts:
188 parts = parts[len(repo_parts):]
190 parts = parts[len(repo_parts):]
189 query = '/'.join(parts)
191 query = '/'.join(parts)
190 else:
192 else:
191 query = req.env['QUERY_STRING'].split('&', 1)[0]
193 query = req.env['QUERY_STRING'].split('&', 1)[0]
192 query = query.split(';', 1)[0]
194 query = query.split(';', 1)[0]
193
195
194 # process this if it's a protocol request
196 # process this if it's a protocol request
195 # protocol bits don't need to create any URLs
197 # protocol bits don't need to create any URLs
196 # and the clients always use the old URL structure
198 # and the clients always use the old URL structure
197
199
198 cmd = req.form.get('cmd', [''])[0]
200 cmd = req.form.get('cmd', [''])[0]
199 if protocol.iscmd(cmd):
201 if protocol.iscmd(cmd):
200 try:
202 try:
201 if query:
203 if query:
202 raise ErrorResponse(HTTP_NOT_FOUND)
204 raise ErrorResponse(HTTP_NOT_FOUND)
203 if cmd in perms:
205 if cmd in perms:
204 self.check_perm(req, perms[cmd])
206 self.check_perm(req, perms[cmd])
205 return protocol.call(self.repo, req, cmd)
207 return protocol.call(self.repo, req, cmd)
206 except ErrorResponse as inst:
208 except ErrorResponse as inst:
207 # A client that sends unbundle without 100-continue will
209 # A client that sends unbundle without 100-continue will
208 # break if we respond early.
210 # break if we respond early.
209 if (cmd == 'unbundle' and
211 if (cmd == 'unbundle' and
210 (req.env.get('HTTP_EXPECT',
212 (req.env.get('HTTP_EXPECT',
211 '').lower() != '100-continue') or
213 '').lower() != '100-continue') or
212 req.env.get('X-HgHttp2', '')):
214 req.env.get('X-HgHttp2', '')):
213 req.drain()
215 req.drain()
214 else:
216 else:
215 req.headers.append(('Connection', 'Close'))
217 req.headers.append(('Connection', 'Close'))
216 req.respond(inst, protocol.HGTYPE,
218 req.respond(inst, protocol.HGTYPE,
217 body='0\n%s\n' % inst.message)
219 body='0\n%s\n' % inst.message)
218 return ''
220 return ''
219
221
220 # translate user-visible url structure to internal structure
222 # translate user-visible url structure to internal structure
221
223
222 args = query.split('/', 2)
224 args = query.split('/', 2)
223 if 'cmd' not in req.form and args and args[0]:
225 if 'cmd' not in req.form and args and args[0]:
224
226
225 cmd = args.pop(0)
227 cmd = args.pop(0)
226 style = cmd.rfind('-')
228 style = cmd.rfind('-')
227 if style != -1:
229 if style != -1:
228 req.form['style'] = [cmd[:style]]
230 req.form['style'] = [cmd[:style]]
229 cmd = cmd[style + 1:]
231 cmd = cmd[style + 1:]
230
232
231 # avoid accepting e.g. style parameter as command
233 # avoid accepting e.g. style parameter as command
232 if util.safehasattr(webcommands, cmd):
234 if util.safehasattr(webcommands, cmd):
233 req.form['cmd'] = [cmd]
235 req.form['cmd'] = [cmd]
234
236
235 if cmd == 'static':
237 if cmd == 'static':
236 req.form['file'] = ['/'.join(args)]
238 req.form['file'] = ['/'.join(args)]
237 else:
239 else:
238 if args and args[0]:
240 if args and args[0]:
239 node = args.pop(0).replace('%2F', '/')
241 node = args.pop(0).replace('%2F', '/')
240 req.form['node'] = [node]
242 req.form['node'] = [node]
241 if args:
243 if args:
242 req.form['file'] = args
244 req.form['file'] = args
243
245
244 ua = req.env.get('HTTP_USER_AGENT', '')
246 ua = req.env.get('HTTP_USER_AGENT', '')
245 if cmd == 'rev' and 'mercurial' in ua:
247 if cmd == 'rev' and 'mercurial' in ua:
246 req.form['style'] = ['raw']
248 req.form['style'] = ['raw']
247
249
248 if cmd == 'archive':
250 if cmd == 'archive':
249 fn = req.form['node'][0]
251 fn = req.form['node'][0]
250 for type_, spec in self.archive_specs.iteritems():
252 for type_, spec in self.archive_specs.iteritems():
251 ext = spec[2]
253 ext = spec[2]
252 if fn.endswith(ext):
254 if fn.endswith(ext):
253 req.form['node'] = [fn[:-len(ext)]]
255 req.form['node'] = [fn[:-len(ext)]]
254 req.form['type'] = [type_]
256 req.form['type'] = [type_]
255
257
256 # process the web interface request
258 # process the web interface request
257
259
258 try:
260 try:
259 tmpl = self.templater(req)
261 tmpl = self.templater(req)
260 ctype = tmpl('mimetype', encoding=encoding.encoding)
262 ctype = tmpl('mimetype', encoding=encoding.encoding)
261 ctype = templater.stringify(ctype)
263 ctype = templater.stringify(ctype)
262
264
263 # check read permissions non-static content
265 # check read permissions non-static content
264 if cmd != 'static':
266 if cmd != 'static':
265 self.check_perm(req, None)
267 self.check_perm(req, None)
266
268
267 if cmd == '':
269 if cmd == '':
268 req.form['cmd'] = [tmpl.cache['default']]
270 req.form['cmd'] = [tmpl.cache['default']]
269 cmd = req.form['cmd'][0]
271 cmd = req.form['cmd'][0]
270
272
271 if self.configbool('web', 'cache', True):
273 if self.configbool('web', 'cache', True):
272 caching(self, req) # sets ETag header or raises NOT_MODIFIED
274 caching(self, req) # sets ETag header or raises NOT_MODIFIED
273 if cmd not in webcommands.__all__:
275 if cmd not in webcommands.__all__:
274 msg = 'no such method: %s' % cmd
276 msg = 'no such method: %s' % cmd
275 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
277 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
276 elif cmd == 'file' and 'raw' in req.form.get('style', []):
278 elif cmd == 'file' and 'raw' in req.form.get('style', []):
277 self.ctype = ctype
279 self.ctype = ctype
278 content = webcommands.rawfile(self, req, tmpl)
280 content = webcommands.rawfile(self, req, tmpl)
279 else:
281 else:
280 content = getattr(webcommands, cmd)(self, req, tmpl)
282 content = getattr(webcommands, cmd)(self, req, tmpl)
281 req.respond(HTTP_OK, ctype)
283 req.respond(HTTP_OK, ctype)
282
284
283 return content
285 return content
284
286
285 except (error.LookupError, error.RepoLookupError) as err:
287 except (error.LookupError, error.RepoLookupError) as err:
286 req.respond(HTTP_NOT_FOUND, ctype)
288 req.respond(HTTP_NOT_FOUND, ctype)
287 msg = str(err)
289 msg = str(err)
288 if (util.safehasattr(err, 'name') and
290 if (util.safehasattr(err, 'name') and
289 not isinstance(err, error.ManifestLookupError)):
291 not isinstance(err, error.ManifestLookupError)):
290 msg = 'revision not found: %s' % err.name
292 msg = 'revision not found: %s' % err.name
291 return tmpl('error', error=msg)
293 return tmpl('error', error=msg)
292 except (error.RepoError, error.RevlogError) as inst:
294 except (error.RepoError, error.RevlogError) as inst:
293 req.respond(HTTP_SERVER_ERROR, ctype)
295 req.respond(HTTP_SERVER_ERROR, ctype)
294 return tmpl('error', error=str(inst))
296 return tmpl('error', error=str(inst))
295 except ErrorResponse as inst:
297 except ErrorResponse as inst:
296 req.respond(inst, ctype)
298 req.respond(inst, ctype)
297 if inst.code == HTTP_NOT_MODIFIED:
299 if inst.code == HTTP_NOT_MODIFIED:
298 # Not allowed to return a body on a 304
300 # Not allowed to return a body on a 304
299 return ['']
301 return ['']
300 return tmpl('error', error=inst.message)
302 return tmpl('error', error=inst.message)
301
303
302 def loadwebsub(self):
304 def loadwebsub(self):
303 websubtable = []
305 websubtable = []
304 websubdefs = self.repo.ui.configitems('websub')
306 websubdefs = self.repo.ui.configitems('websub')
305 # we must maintain interhg backwards compatibility
307 # we must maintain interhg backwards compatibility
306 websubdefs += self.repo.ui.configitems('interhg')
308 websubdefs += self.repo.ui.configitems('interhg')
307 for key, pattern in websubdefs:
309 for key, pattern in websubdefs:
308 # grab the delimiter from the character after the "s"
310 # grab the delimiter from the character after the "s"
309 unesc = pattern[1]
311 unesc = pattern[1]
310 delim = re.escape(unesc)
312 delim = re.escape(unesc)
311
313
312 # identify portions of the pattern, taking care to avoid escaped
314 # identify portions of the pattern, taking care to avoid escaped
313 # delimiters. the replace format and flags are optional, but
315 # delimiters. the replace format and flags are optional, but
314 # delimiters are required.
316 # delimiters are required.
315 match = re.match(
317 match = re.match(
316 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
318 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
317 % (delim, delim, delim), pattern)
319 % (delim, delim, delim), pattern)
318 if not match:
320 if not match:
319 self.repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
321 self.repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
320 % (key, pattern))
322 % (key, pattern))
321 continue
323 continue
322
324
323 # we need to unescape the delimiter for regexp and format
325 # we need to unescape the delimiter for regexp and format
324 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
326 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
325 regexp = delim_re.sub(unesc, match.group(1))
327 regexp = delim_re.sub(unesc, match.group(1))
326 format = delim_re.sub(unesc, match.group(2))
328 format = delim_re.sub(unesc, match.group(2))
327
329
328 # the pattern allows for 6 regexp flags, so set them if necessary
330 # the pattern allows for 6 regexp flags, so set them if necessary
329 flagin = match.group(3)
331 flagin = match.group(3)
330 flags = 0
332 flags = 0
331 if flagin:
333 if flagin:
332 for flag in flagin.upper():
334 for flag in flagin.upper():
333 flags |= re.__dict__[flag]
335 flags |= re.__dict__[flag]
334
336
335 try:
337 try:
336 regexp = re.compile(regexp, flags)
338 regexp = re.compile(regexp, flags)
337 websubtable.append((regexp, format))
339 websubtable.append((regexp, format))
338 except re.error:
340 except re.error:
339 self.repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
341 self.repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
340 % (key, regexp))
342 % (key, regexp))
341 return websubtable
343 return websubtable
342
344
343 def templater(self, req):
345 def templater(self, req):
344
346
345 # determine scheme, port and server name
347 # determine scheme, port and server name
346 # this is needed to create absolute urls
348 # this is needed to create absolute urls
347
349
348 proto = req.env.get('wsgi.url_scheme')
350 proto = req.env.get('wsgi.url_scheme')
349 if proto == 'https':
351 if proto == 'https':
350 proto = 'https'
352 proto = 'https'
351 default_port = "443"
353 default_port = "443"
352 else:
354 else:
353 proto = 'http'
355 proto = 'http'
354 default_port = "80"
356 default_port = "80"
355
357
356 port = req.env["SERVER_PORT"]
358 port = req.env["SERVER_PORT"]
357 port = port != default_port and (":" + port) or ""
359 port = port != default_port and (":" + port) or ""
358 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
360 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
359 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
361 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
360 logoimg = self.config("web", "logoimg", "hglogo.png")
362 logoimg = self.config("web", "logoimg", "hglogo.png")
361 staticurl = self.config("web", "staticurl") or req.url + 'static/'
363 staticurl = self.config("web", "staticurl") or req.url + 'static/'
362 if not staticurl.endswith('/'):
364 if not staticurl.endswith('/'):
363 staticurl += '/'
365 staticurl += '/'
364
366
365 # some functions for the templater
367 # some functions for the templater
366
368
367 def motd(**map):
369 def motd(**map):
368 yield self.config("web", "motd", "")
370 yield self.config("web", "motd", "")
369
371
370 # figure out which style to use
372 # figure out which style to use
371
373
372 vars = {}
374 vars = {}
373 styles = (
375 styles = (
374 req.form.get('style', [None])[0],
376 req.form.get('style', [None])[0],
375 self.config('web', 'style'),
377 self.config('web', 'style'),
376 'paper',
378 'paper',
377 )
379 )
378 style, mapfile = templater.stylemap(styles, self.templatepath)
380 style, mapfile = templater.stylemap(styles, self.templatepath)
379 if style == styles[0]:
381 if style == styles[0]:
380 vars['style'] = style
382 vars['style'] = style
381
383
382 start = req.url[-1] == '?' and '&' or '?'
384 start = req.url[-1] == '?' and '&' or '?'
383 sessionvars = webutil.sessionvars(vars, start)
385 sessionvars = webutil.sessionvars(vars, start)
384
386
385 if not self.reponame:
387 if not self.reponame:
386 self.reponame = (self.config("web", "name")
388 self.reponame = (self.config("web", "name")
387 or req.env.get('REPO_NAME')
389 or req.env.get('REPO_NAME')
388 or req.url.strip('/') or self.repo.root)
390 or req.url.strip('/') or self.repo.root)
389
391
390 def websubfilter(text):
392 def websubfilter(text):
391 return websub(text, self.websubtable)
393 return websub(text, self.websubtable)
392
394
393 # create the templater
395 # create the templater
394
396
395 tmpl = templater.templater(mapfile,
397 tmpl = templater.templater(mapfile,
396 filters={"websub": websubfilter},
398 filters={"websub": websubfilter},
397 defaults={"url": req.url,
399 defaults={"url": req.url,
398 "logourl": logourl,
400 "logourl": logourl,
399 "logoimg": logoimg,
401 "logoimg": logoimg,
400 "staticurl": staticurl,
402 "staticurl": staticurl,
401 "urlbase": urlbase,
403 "urlbase": urlbase,
402 "repo": self.reponame,
404 "repo": self.reponame,
403 "encoding": encoding.encoding,
405 "encoding": encoding.encoding,
404 "motd": motd,
406 "motd": motd,
405 "sessionvars": sessionvars,
407 "sessionvars": sessionvars,
406 "pathdef": makebreadcrumb(req.url),
408 "pathdef": makebreadcrumb(req.url),
407 "style": style,
409 "style": style,
408 })
410 })
409 return tmpl
411 return tmpl
410
412
411 def archivelist(self, nodeid):
413 def archivelist(self, nodeid):
412 allowed = self.configlist("web", "allow_archive")
414 allowed = self.configlist("web", "allow_archive")
413 for i, spec in self.archive_specs.iteritems():
415 for i, spec in self.archive_specs.iteritems():
414 if i in allowed or self.configbool("web", "allow" + i):
416 if i in allowed or self.configbool("web", "allow" + i):
415 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
417 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
416
418
417 archive_specs = {
419 archive_specs = {
418 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
420 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
419 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
421 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
420 'zip': ('application/zip', 'zip', '.zip', None),
422 'zip': ('application/zip', 'zip', '.zip', None),
421 }
423 }
422
424
423 def check_perm(self, req, op):
425 def check_perm(self, req, op):
424 for permhook in permhooks:
426 for permhook in permhooks:
425 permhook(self, req, op)
427 permhook(self, req, op)
General Comments 0
You need to be logged in to leave comments. Login now