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