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