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