##// END OF EJS Templates
hgweb: drop unused import
Matt Mackall -
r26226:efebefe1 default
parent child Browse files
Show More
@@ -1,437 +1,437 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 contextlib
9 import contextlib
10 import os
10 import os
11 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
12 from mercurial.templatefilters import websub
12 from mercurial.templatefilters import websub
13 from common import get_stat, ErrorResponse, permhooks, caching
13 from common import 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 def makebreadcrumb(url, prefix=''):
29 def makebreadcrumb(url, prefix=''):
30 '''Return a 'URL breadcrumb' list
30 '''Return a 'URL breadcrumb' list
31
31
32 A 'URL breadcrumb' is a list of URL-name pairs,
32 A 'URL breadcrumb' is a list of URL-name pairs,
33 corresponding to each of the path items on a URL.
33 corresponding to each of the path items on a URL.
34 This can be used to create path navigation entries.
34 This can be used to create path navigation entries.
35 '''
35 '''
36 if url.endswith('/'):
36 if url.endswith('/'):
37 url = url[:-1]
37 url = url[:-1]
38 if prefix:
38 if prefix:
39 url = '/' + prefix + url
39 url = '/' + prefix + url
40 relpath = url
40 relpath = url
41 if relpath.startswith('/'):
41 if relpath.startswith('/'):
42 relpath = relpath[1:]
42 relpath = relpath[1:]
43
43
44 breadcrumb = []
44 breadcrumb = []
45 urlel = url
45 urlel = url
46 pathitems = [''] + relpath.split('/')
46 pathitems = [''] + relpath.split('/')
47 for pathel in reversed(pathitems):
47 for pathel in reversed(pathitems):
48 if not pathel or not urlel:
48 if not pathel or not urlel:
49 break
49 break
50 breadcrumb.append({'url': urlel, 'name': pathel})
50 breadcrumb.append({'url': urlel, 'name': pathel})
51 urlel = os.path.dirname(urlel)
51 urlel = os.path.dirname(urlel)
52 return reversed(breadcrumb)
52 return reversed(breadcrumb)
53
53
54 class requestcontext(object):
54 class requestcontext(object):
55 """Holds state/context for an individual request.
55 """Holds state/context for an individual request.
56
56
57 Servers can be multi-threaded. Holding state on the WSGI application
57 Servers can be multi-threaded. Holding state on the WSGI application
58 is prone to race conditions. Instances of this class exist to hold
58 is prone to race conditions. Instances of this class exist to hold
59 mutable and race-free state for requests.
59 mutable and race-free state for requests.
60 """
60 """
61 def __init__(self, app, repo):
61 def __init__(self, app, repo):
62 self.repo = repo
62 self.repo = repo
63 self.reponame = app.reponame
63 self.reponame = app.reponame
64
64
65 self.archives = ('zip', 'gz', 'bz2')
65 self.archives = ('zip', 'gz', 'bz2')
66
66
67 self.maxchanges = self.configint('web', 'maxchanges', 10)
67 self.maxchanges = self.configint('web', 'maxchanges', 10)
68 self.stripecount = self.configint('web', 'stripes', 1)
68 self.stripecount = self.configint('web', 'stripes', 1)
69 self.maxshortchanges = self.configint('web', 'maxshortchanges', 60)
69 self.maxshortchanges = self.configint('web', 'maxshortchanges', 60)
70 self.maxfiles = self.configint('web', 'maxfiles', 10)
70 self.maxfiles = self.configint('web', 'maxfiles', 10)
71 self.allowpull = self.configbool('web', 'allowpull', True)
71 self.allowpull = self.configbool('web', 'allowpull', True)
72
72
73 # we use untrusted=False to prevent a repo owner from using
73 # we use untrusted=False to prevent a repo owner from using
74 # web.templates in .hg/hgrc to get access to any file readable
74 # web.templates in .hg/hgrc to get access to any file readable
75 # by the user running the CGI script
75 # by the user running the CGI script
76 self.templatepath = self.config('web', 'templates', untrusted=False)
76 self.templatepath = self.config('web', 'templates', untrusted=False)
77
77
78 # This object is more expensive to build than simple config values.
78 # This object is more expensive to build than simple config values.
79 # It is shared across requests. The app will replace the object
79 # It is shared across requests. The app will replace the object
80 # if it is updated. Since this is a reference and nothing should
80 # if it is updated. Since this is a reference and nothing should
81 # modify the underlying object, it should be constant for the lifetime
81 # modify the underlying object, it should be constant for the lifetime
82 # of the request.
82 # of the request.
83 self.websubtable = app.websubtable
83 self.websubtable = app.websubtable
84
84
85 # Trust the settings from the .hg/hgrc files by default.
85 # Trust the settings from the .hg/hgrc files by default.
86 def config(self, section, name, default=None, untrusted=True):
86 def config(self, section, name, default=None, untrusted=True):
87 return self.repo.ui.config(section, name, default,
87 return self.repo.ui.config(section, name, default,
88 untrusted=untrusted)
88 untrusted=untrusted)
89
89
90 def configbool(self, section, name, default=False, untrusted=True):
90 def configbool(self, section, name, default=False, untrusted=True):
91 return self.repo.ui.configbool(section, name, default,
91 return self.repo.ui.configbool(section, name, default,
92 untrusted=untrusted)
92 untrusted=untrusted)
93
93
94 def configint(self, section, name, default=None, untrusted=True):
94 def configint(self, section, name, default=None, untrusted=True):
95 return self.repo.ui.configint(section, name, default,
95 return self.repo.ui.configint(section, name, default,
96 untrusted=untrusted)
96 untrusted=untrusted)
97
97
98 def configlist(self, section, name, default=None, untrusted=True):
98 def configlist(self, section, name, default=None, untrusted=True):
99 return self.repo.ui.configlist(section, name, default,
99 return self.repo.ui.configlist(section, name, default,
100 untrusted=untrusted)
100 untrusted=untrusted)
101
101
102 archivespecs = {
102 archivespecs = {
103 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
103 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
104 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
104 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
105 'zip': ('application/zip', 'zip', '.zip', None),
105 'zip': ('application/zip', 'zip', '.zip', None),
106 }
106 }
107
107
108 def archivelist(self, nodeid):
108 def archivelist(self, nodeid):
109 allowed = self.configlist('web', 'allow_archive')
109 allowed = self.configlist('web', 'allow_archive')
110 for typ, spec in self.archivespecs.iteritems():
110 for typ, spec in self.archivespecs.iteritems():
111 if typ in allowed or self.configbool('web', 'allow%s' % typ):
111 if typ in allowed or self.configbool('web', 'allow%s' % typ):
112 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
112 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
113
113
114 def templater(self, req):
114 def templater(self, req):
115 # determine scheme, port and server name
115 # determine scheme, port and server name
116 # this is needed to create absolute urls
116 # this is needed to create absolute urls
117
117
118 proto = req.env.get('wsgi.url_scheme')
118 proto = req.env.get('wsgi.url_scheme')
119 if proto == 'https':
119 if proto == 'https':
120 proto = 'https'
120 proto = 'https'
121 default_port = '443'
121 default_port = '443'
122 else:
122 else:
123 proto = 'http'
123 proto = 'http'
124 default_port = '80'
124 default_port = '80'
125
125
126 port = req.env['SERVER_PORT']
126 port = req.env['SERVER_PORT']
127 port = port != default_port and (':' + port) or ''
127 port = port != default_port and (':' + port) or ''
128 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
128 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
129 logourl = self.config('web', 'logourl', 'http://mercurial.selenic.com/')
129 logourl = self.config('web', 'logourl', 'http://mercurial.selenic.com/')
130 logoimg = self.config('web', 'logoimg', 'hglogo.png')
130 logoimg = self.config('web', 'logoimg', 'hglogo.png')
131 staticurl = self.config('web', 'staticurl') or req.url + 'static/'
131 staticurl = self.config('web', 'staticurl') or req.url + 'static/'
132 if not staticurl.endswith('/'):
132 if not staticurl.endswith('/'):
133 staticurl += '/'
133 staticurl += '/'
134
134
135 # some functions for the templater
135 # some functions for the templater
136
136
137 def motd(**map):
137 def motd(**map):
138 yield self.config('web', 'motd', '')
138 yield self.config('web', 'motd', '')
139
139
140 # figure out which style to use
140 # figure out which style to use
141
141
142 vars = {}
142 vars = {}
143 styles = (
143 styles = (
144 req.form.get('style', [None])[0],
144 req.form.get('style', [None])[0],
145 self.config('web', 'style'),
145 self.config('web', 'style'),
146 'paper',
146 'paper',
147 )
147 )
148 style, mapfile = templater.stylemap(styles, self.templatepath)
148 style, mapfile = templater.stylemap(styles, self.templatepath)
149 if style == styles[0]:
149 if style == styles[0]:
150 vars['style'] = style
150 vars['style'] = style
151
151
152 start = req.url[-1] == '?' and '&' or '?'
152 start = req.url[-1] == '?' and '&' or '?'
153 sessionvars = webutil.sessionvars(vars, start)
153 sessionvars = webutil.sessionvars(vars, start)
154
154
155 if not self.reponame:
155 if not self.reponame:
156 self.reponame = (self.config('web', 'name')
156 self.reponame = (self.config('web', 'name')
157 or req.env.get('REPO_NAME')
157 or req.env.get('REPO_NAME')
158 or req.url.strip('/') or self.repo.root)
158 or req.url.strip('/') or self.repo.root)
159
159
160 def websubfilter(text):
160 def websubfilter(text):
161 return websub(text, self.websubtable)
161 return websub(text, self.websubtable)
162
162
163 # create the templater
163 # create the templater
164
164
165 tmpl = templater.templater(mapfile,
165 tmpl = templater.templater(mapfile,
166 filters={'websub': websubfilter},
166 filters={'websub': websubfilter},
167 defaults={'url': req.url,
167 defaults={'url': req.url,
168 'logourl': logourl,
168 'logourl': logourl,
169 'logoimg': logoimg,
169 'logoimg': logoimg,
170 'staticurl': staticurl,
170 'staticurl': staticurl,
171 'urlbase': urlbase,
171 'urlbase': urlbase,
172 'repo': self.reponame,
172 'repo': self.reponame,
173 'encoding': encoding.encoding,
173 'encoding': encoding.encoding,
174 'motd': motd,
174 'motd': motd,
175 'sessionvars': sessionvars,
175 'sessionvars': sessionvars,
176 'pathdef': makebreadcrumb(req.url),
176 'pathdef': makebreadcrumb(req.url),
177 'style': style,
177 'style': style,
178 })
178 })
179 return tmpl
179 return tmpl
180
180
181
181
182 class hgweb(object):
182 class hgweb(object):
183 """HTTP server for individual repositories.
183 """HTTP server for individual repositories.
184
184
185 Instances of this class serve HTTP responses for a particular
185 Instances of this class serve HTTP responses for a particular
186 repository.
186 repository.
187
187
188 Instances are typically used as WSGI applications.
188 Instances are typically used as WSGI applications.
189
189
190 Some servers are multi-threaded. On these servers, there may
190 Some servers are multi-threaded. On these servers, there may
191 be multiple active threads inside __call__.
191 be multiple active threads inside __call__.
192 """
192 """
193 def __init__(self, repo, name=None, baseui=None):
193 def __init__(self, repo, name=None, baseui=None):
194 if isinstance(repo, str):
194 if isinstance(repo, str):
195 if baseui:
195 if baseui:
196 u = baseui.copy()
196 u = baseui.copy()
197 else:
197 else:
198 u = ui.ui()
198 u = ui.ui()
199 r = hg.repository(u, repo)
199 r = hg.repository(u, repo)
200 else:
200 else:
201 # we trust caller to give us a private copy
201 # we trust caller to give us a private copy
202 r = repo
202 r = repo
203
203
204 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
204 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
205 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
205 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
206 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
206 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
207 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
207 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
208 # displaying bundling progress bar while serving feel wrong and may
208 # displaying bundling progress bar while serving feel wrong and may
209 # break some wsgi implementation.
209 # break some wsgi implementation.
210 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
210 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
211 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
211 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
212 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
212 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
213 self._lastrepo = self._repos[0]
213 self._lastrepo = self._repos[0]
214 hook.redirect(True)
214 hook.redirect(True)
215 self.reponame = name
215 self.reponame = name
216
216
217 def _webifyrepo(self, repo):
217 def _webifyrepo(self, repo):
218 repo = getwebview(repo)
218 repo = getwebview(repo)
219 self.websubtable = webutil.getwebsubs(repo)
219 self.websubtable = webutil.getwebsubs(repo)
220 return repo
220 return repo
221
221
222 @contextlib.contextmanager
222 @contextlib.contextmanager
223 def _obtainrepo(self):
223 def _obtainrepo(self):
224 """Obtain a repo unique to the caller.
224 """Obtain a repo unique to the caller.
225
225
226 Internally we maintain a stack of cachedlocalrepo instances
226 Internally we maintain a stack of cachedlocalrepo instances
227 to be handed out. If one is available, we pop it and return it,
227 to be handed out. If one is available, we pop it and return it,
228 ensuring it is up to date in the process. If one is not available,
228 ensuring it is up to date in the process. If one is not available,
229 we clone the most recently used repo instance and return it.
229 we clone the most recently used repo instance and return it.
230
230
231 It is currently possible for the stack to grow without bounds
231 It is currently possible for the stack to grow without bounds
232 if the server allows infinite threads. However, servers should
232 if the server allows infinite threads. However, servers should
233 have a thread limit, thus establishing our limit.
233 have a thread limit, thus establishing our limit.
234 """
234 """
235 if self._repos:
235 if self._repos:
236 cached = self._repos.pop()
236 cached = self._repos.pop()
237 r, created = cached.fetch()
237 r, created = cached.fetch()
238 if created:
238 if created:
239 r = self._webifyrepo(r)
239 r = self._webifyrepo(r)
240 else:
240 else:
241 cached = self._lastrepo.copy()
241 cached = self._lastrepo.copy()
242 r, created = cached.fetch()
242 r, created = cached.fetch()
243
243
244 self._lastrepo = cached
244 self._lastrepo = cached
245 self.mtime = cached.mtime
245 self.mtime = cached.mtime
246 try:
246 try:
247 yield r
247 yield r
248 finally:
248 finally:
249 self._repos.append(cached)
249 self._repos.append(cached)
250
250
251 def run(self):
251 def run(self):
252 """Start a server from CGI environment.
252 """Start a server from CGI environment.
253
253
254 Modern servers should be using WSGI and should avoid this
254 Modern servers should be using WSGI and should avoid this
255 method, if possible.
255 method, if possible.
256 """
256 """
257 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
257 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
258 raise RuntimeError("This function is only intended to be "
258 raise RuntimeError("This function is only intended to be "
259 "called while running as a CGI script.")
259 "called while running as a CGI script.")
260 import mercurial.hgweb.wsgicgi as wsgicgi
260 import mercurial.hgweb.wsgicgi as wsgicgi
261 wsgicgi.launch(self)
261 wsgicgi.launch(self)
262
262
263 def __call__(self, env, respond):
263 def __call__(self, env, respond):
264 """Run the WSGI application.
264 """Run the WSGI application.
265
265
266 This may be called by multiple threads.
266 This may be called by multiple threads.
267 """
267 """
268 req = wsgirequest(env, respond)
268 req = wsgirequest(env, respond)
269 return self.run_wsgi(req)
269 return self.run_wsgi(req)
270
270
271 def run_wsgi(self, req):
271 def run_wsgi(self, req):
272 """Internal method to run the WSGI application.
272 """Internal method to run the WSGI application.
273
273
274 This is typically only called by Mercurial. External consumers
274 This is typically only called by Mercurial. External consumers
275 should be using instances of this class as the WSGI application.
275 should be using instances of this class as the WSGI application.
276 """
276 """
277 with self._obtainrepo() as repo:
277 with self._obtainrepo() as repo:
278 return self._runwsgi(req, repo)
278 return self._runwsgi(req, repo)
279
279
280 def _runwsgi(self, req, repo):
280 def _runwsgi(self, req, repo):
281 rctx = requestcontext(self, repo)
281 rctx = requestcontext(self, repo)
282
282
283 # This state is global across all threads.
283 # This state is global across all threads.
284 encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
284 encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
285 rctx.repo.ui.environ = req.env
285 rctx.repo.ui.environ = req.env
286
286
287 # work with CGI variables to create coherent structure
287 # work with CGI variables to create coherent structure
288 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
288 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
289
289
290 req.url = req.env['SCRIPT_NAME']
290 req.url = req.env['SCRIPT_NAME']
291 if not req.url.endswith('/'):
291 if not req.url.endswith('/'):
292 req.url += '/'
292 req.url += '/'
293 if 'REPO_NAME' in req.env:
293 if 'REPO_NAME' in req.env:
294 req.url += req.env['REPO_NAME'] + '/'
294 req.url += req.env['REPO_NAME'] + '/'
295
295
296 if 'PATH_INFO' in req.env:
296 if 'PATH_INFO' in req.env:
297 parts = req.env['PATH_INFO'].strip('/').split('/')
297 parts = req.env['PATH_INFO'].strip('/').split('/')
298 repo_parts = req.env.get('REPO_NAME', '').split('/')
298 repo_parts = req.env.get('REPO_NAME', '').split('/')
299 if parts[:len(repo_parts)] == repo_parts:
299 if parts[:len(repo_parts)] == repo_parts:
300 parts = parts[len(repo_parts):]
300 parts = parts[len(repo_parts):]
301 query = '/'.join(parts)
301 query = '/'.join(parts)
302 else:
302 else:
303 query = req.env['QUERY_STRING'].split('&', 1)[0]
303 query = req.env['QUERY_STRING'].split('&', 1)[0]
304 query = query.split(';', 1)[0]
304 query = query.split(';', 1)[0]
305
305
306 # process this if it's a protocol request
306 # process this if it's a protocol request
307 # protocol bits don't need to create any URLs
307 # protocol bits don't need to create any URLs
308 # and the clients always use the old URL structure
308 # and the clients always use the old URL structure
309
309
310 cmd = req.form.get('cmd', [''])[0]
310 cmd = req.form.get('cmd', [''])[0]
311 if protocol.iscmd(cmd):
311 if protocol.iscmd(cmd):
312 try:
312 try:
313 if query:
313 if query:
314 raise ErrorResponse(HTTP_NOT_FOUND)
314 raise ErrorResponse(HTTP_NOT_FOUND)
315 if cmd in perms:
315 if cmd in perms:
316 self.check_perm(rctx, req, perms[cmd])
316 self.check_perm(rctx, req, perms[cmd])
317 return protocol.call(rctx.repo, req, cmd)
317 return protocol.call(rctx.repo, req, cmd)
318 except ErrorResponse as inst:
318 except ErrorResponse as inst:
319 # A client that sends unbundle without 100-continue will
319 # A client that sends unbundle without 100-continue will
320 # break if we respond early.
320 # break if we respond early.
321 if (cmd == 'unbundle' and
321 if (cmd == 'unbundle' and
322 (req.env.get('HTTP_EXPECT',
322 (req.env.get('HTTP_EXPECT',
323 '').lower() != '100-continue') or
323 '').lower() != '100-continue') or
324 req.env.get('X-HgHttp2', '')):
324 req.env.get('X-HgHttp2', '')):
325 req.drain()
325 req.drain()
326 else:
326 else:
327 req.headers.append(('Connection', 'Close'))
327 req.headers.append(('Connection', 'Close'))
328 req.respond(inst, protocol.HGTYPE,
328 req.respond(inst, protocol.HGTYPE,
329 body='0\n%s\n' % inst)
329 body='0\n%s\n' % inst)
330 return ''
330 return ''
331
331
332 # translate user-visible url structure to internal structure
332 # translate user-visible url structure to internal structure
333
333
334 args = query.split('/', 2)
334 args = query.split('/', 2)
335 if 'cmd' not in req.form and args and args[0]:
335 if 'cmd' not in req.form and args and args[0]:
336
336
337 cmd = args.pop(0)
337 cmd = args.pop(0)
338 style = cmd.rfind('-')
338 style = cmd.rfind('-')
339 if style != -1:
339 if style != -1:
340 req.form['style'] = [cmd[:style]]
340 req.form['style'] = [cmd[:style]]
341 cmd = cmd[style + 1:]
341 cmd = cmd[style + 1:]
342
342
343 # avoid accepting e.g. style parameter as command
343 # avoid accepting e.g. style parameter as command
344 if util.safehasattr(webcommands, cmd):
344 if util.safehasattr(webcommands, cmd):
345 req.form['cmd'] = [cmd]
345 req.form['cmd'] = [cmd]
346
346
347 if cmd == 'static':
347 if cmd == 'static':
348 req.form['file'] = ['/'.join(args)]
348 req.form['file'] = ['/'.join(args)]
349 else:
349 else:
350 if args and args[0]:
350 if args and args[0]:
351 node = args.pop(0).replace('%2F', '/')
351 node = args.pop(0).replace('%2F', '/')
352 req.form['node'] = [node]
352 req.form['node'] = [node]
353 if args:
353 if args:
354 req.form['file'] = args
354 req.form['file'] = args
355
355
356 ua = req.env.get('HTTP_USER_AGENT', '')
356 ua = req.env.get('HTTP_USER_AGENT', '')
357 if cmd == 'rev' and 'mercurial' in ua:
357 if cmd == 'rev' and 'mercurial' in ua:
358 req.form['style'] = ['raw']
358 req.form['style'] = ['raw']
359
359
360 if cmd == 'archive':
360 if cmd == 'archive':
361 fn = req.form['node'][0]
361 fn = req.form['node'][0]
362 for type_, spec in rctx.archivespecs.iteritems():
362 for type_, spec in rctx.archivespecs.iteritems():
363 ext = spec[2]
363 ext = spec[2]
364 if fn.endswith(ext):
364 if fn.endswith(ext):
365 req.form['node'] = [fn[:-len(ext)]]
365 req.form['node'] = [fn[:-len(ext)]]
366 req.form['type'] = [type_]
366 req.form['type'] = [type_]
367
367
368 # process the web interface request
368 # process the web interface request
369
369
370 try:
370 try:
371 tmpl = rctx.templater(req)
371 tmpl = rctx.templater(req)
372 ctype = tmpl('mimetype', encoding=encoding.encoding)
372 ctype = tmpl('mimetype', encoding=encoding.encoding)
373 ctype = templater.stringify(ctype)
373 ctype = templater.stringify(ctype)
374
374
375 # check read permissions non-static content
375 # check read permissions non-static content
376 if cmd != 'static':
376 if cmd != 'static':
377 self.check_perm(rctx, req, None)
377 self.check_perm(rctx, req, None)
378
378
379 if cmd == '':
379 if cmd == '':
380 req.form['cmd'] = [tmpl.cache['default']]
380 req.form['cmd'] = [tmpl.cache['default']]
381 cmd = req.form['cmd'][0]
381 cmd = req.form['cmd'][0]
382
382
383 if rctx.configbool('web', 'cache', True):
383 if rctx.configbool('web', 'cache', True):
384 caching(self, req) # sets ETag header or raises NOT_MODIFIED
384 caching(self, req) # sets ETag header or raises NOT_MODIFIED
385 if cmd not in webcommands.__all__:
385 if cmd not in webcommands.__all__:
386 msg = 'no such method: %s' % cmd
386 msg = 'no such method: %s' % cmd
387 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
387 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
388 elif cmd == 'file' and 'raw' in req.form.get('style', []):
388 elif cmd == 'file' and 'raw' in req.form.get('style', []):
389 rctx.ctype = ctype
389 rctx.ctype = ctype
390 content = webcommands.rawfile(rctx, req, tmpl)
390 content = webcommands.rawfile(rctx, req, tmpl)
391 else:
391 else:
392 content = getattr(webcommands, cmd)(rctx, req, tmpl)
392 content = getattr(webcommands, cmd)(rctx, req, tmpl)
393 req.respond(HTTP_OK, ctype)
393 req.respond(HTTP_OK, ctype)
394
394
395 return content
395 return content
396
396
397 except (error.LookupError, error.RepoLookupError) as err:
397 except (error.LookupError, error.RepoLookupError) as err:
398 req.respond(HTTP_NOT_FOUND, ctype)
398 req.respond(HTTP_NOT_FOUND, ctype)
399 msg = str(err)
399 msg = str(err)
400 if (util.safehasattr(err, 'name') and
400 if (util.safehasattr(err, 'name') and
401 not isinstance(err, error.ManifestLookupError)):
401 not isinstance(err, error.ManifestLookupError)):
402 msg = 'revision not found: %s' % err.name
402 msg = 'revision not found: %s' % err.name
403 return tmpl('error', error=msg)
403 return tmpl('error', error=msg)
404 except (error.RepoError, error.RevlogError) as inst:
404 except (error.RepoError, error.RevlogError) as inst:
405 req.respond(HTTP_SERVER_ERROR, ctype)
405 req.respond(HTTP_SERVER_ERROR, ctype)
406 return tmpl('error', error=str(inst))
406 return tmpl('error', error=str(inst))
407 except ErrorResponse as inst:
407 except ErrorResponse as inst:
408 req.respond(inst, ctype)
408 req.respond(inst, ctype)
409 if inst.code == HTTP_NOT_MODIFIED:
409 if inst.code == HTTP_NOT_MODIFIED:
410 # Not allowed to return a body on a 304
410 # Not allowed to return a body on a 304
411 return ['']
411 return ['']
412 return tmpl('error', error=str(inst))
412 return tmpl('error', error=str(inst))
413
413
414 def check_perm(self, rctx, req, op):
414 def check_perm(self, rctx, req, op):
415 for permhook in permhooks:
415 for permhook in permhooks:
416 permhook(rctx, req, op)
416 permhook(rctx, req, op)
417
417
418 def getwebview(repo):
418 def getwebview(repo):
419 """The 'web.view' config controls changeset filter to hgweb. Possible
419 """The 'web.view' config controls changeset filter to hgweb. Possible
420 values are ``served``, ``visible`` and ``all``. Default is ``served``.
420 values are ``served``, ``visible`` and ``all``. Default is ``served``.
421 The ``served`` filter only shows changesets that can be pulled from the
421 The ``served`` filter only shows changesets that can be pulled from the
422 hgweb instance. The``visible`` filter includes secret changesets but
422 hgweb instance. The``visible`` filter includes secret changesets but
423 still excludes "hidden" one.
423 still excludes "hidden" one.
424
424
425 See the repoview module for details.
425 See the repoview module for details.
426
426
427 The option has been around undocumented since Mercurial 2.5, but no
427 The option has been around undocumented since Mercurial 2.5, but no
428 user ever asked about it. So we better keep it undocumented for now."""
428 user ever asked about it. So we better keep it undocumented for now."""
429 viewconfig = repo.ui.config('web', 'view', 'served',
429 viewconfig = repo.ui.config('web', 'view', 'served',
430 untrusted=True)
430 untrusted=True)
431 if viewconfig == 'all':
431 if viewconfig == 'all':
432 return repo.unfiltered()
432 return repo.unfiltered()
433 elif viewconfig in repoview.filtertable:
433 elif viewconfig in repoview.filtertable:
434 return repo.filtered(viewconfig)
434 return repo.filtered(viewconfig)
435 else:
435 else:
436 return repo.filtered('served')
436 return repo.filtered('served')
437
437
General Comments 0
You need to be logged in to leave comments. Login now