##// END OF EJS Templates
hgweb: discard Content-Type header for 304 responses (issue5844)...
Gregory Szorc -
r37846:11ee9bf2 stable
parent child Browse files
Show More
@@ -1,458 +1,464 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import contextlib
12 12 import os
13 13
14 14 from .common import (
15 15 ErrorResponse,
16 16 HTTP_BAD_REQUEST,
17 17 cspvalues,
18 18 permhooks,
19 19 statusmessage,
20 20 )
21 21
22 22 from .. import (
23 23 encoding,
24 24 error,
25 25 formatter,
26 26 hg,
27 27 hook,
28 28 profiling,
29 29 pycompat,
30 30 registrar,
31 31 repoview,
32 32 templatefilters,
33 33 templater,
34 34 templateutil,
35 35 ui as uimod,
36 36 util,
37 37 wireprotoserver,
38 38 )
39 39
40 40 from . import (
41 41 request as requestmod,
42 42 webcommands,
43 43 webutil,
44 44 wsgicgi,
45 45 )
46 46
47 47 def getstyle(req, configfn, templatepath):
48 48 styles = (
49 49 req.qsparams.get('style', None),
50 50 configfn('web', 'style'),
51 51 'paper',
52 52 )
53 53 return styles, templater.stylemap(styles, templatepath)
54 54
55 55 def makebreadcrumb(url, prefix=''):
56 56 '''Return a 'URL breadcrumb' list
57 57
58 58 A 'URL breadcrumb' is a list of URL-name pairs,
59 59 corresponding to each of the path items on a URL.
60 60 This can be used to create path navigation entries.
61 61 '''
62 62 if url.endswith('/'):
63 63 url = url[:-1]
64 64 if prefix:
65 65 url = '/' + prefix + url
66 66 relpath = url
67 67 if relpath.startswith('/'):
68 68 relpath = relpath[1:]
69 69
70 70 breadcrumb = []
71 71 urlel = url
72 72 pathitems = [''] + relpath.split('/')
73 73 for pathel in reversed(pathitems):
74 74 if not pathel or not urlel:
75 75 break
76 76 breadcrumb.append({'url': urlel, 'name': pathel})
77 77 urlel = os.path.dirname(urlel)
78 78 return templateutil.mappinglist(reversed(breadcrumb))
79 79
80 80 class requestcontext(object):
81 81 """Holds state/context for an individual request.
82 82
83 83 Servers can be multi-threaded. Holding state on the WSGI application
84 84 is prone to race conditions. Instances of this class exist to hold
85 85 mutable and race-free state for requests.
86 86 """
87 87 def __init__(self, app, repo, req, res):
88 88 self.repo = repo
89 89 self.reponame = app.reponame
90 90 self.req = req
91 91 self.res = res
92 92
93 93 self.maxchanges = self.configint('web', 'maxchanges')
94 94 self.stripecount = self.configint('web', 'stripes')
95 95 self.maxshortchanges = self.configint('web', 'maxshortchanges')
96 96 self.maxfiles = self.configint('web', 'maxfiles')
97 97 self.allowpull = self.configbool('web', 'allow-pull')
98 98
99 99 # we use untrusted=False to prevent a repo owner from using
100 100 # web.templates in .hg/hgrc to get access to any file readable
101 101 # by the user running the CGI script
102 102 self.templatepath = self.config('web', 'templates', untrusted=False)
103 103
104 104 # This object is more expensive to build than simple config values.
105 105 # It is shared across requests. The app will replace the object
106 106 # if it is updated. Since this is a reference and nothing should
107 107 # modify the underlying object, it should be constant for the lifetime
108 108 # of the request.
109 109 self.websubtable = app.websubtable
110 110
111 111 self.csp, self.nonce = cspvalues(self.repo.ui)
112 112
113 113 # Trust the settings from the .hg/hgrc files by default.
114 114 def config(self, section, name, default=uimod._unset, untrusted=True):
115 115 return self.repo.ui.config(section, name, default,
116 116 untrusted=untrusted)
117 117
118 118 def configbool(self, section, name, default=uimod._unset, untrusted=True):
119 119 return self.repo.ui.configbool(section, name, default,
120 120 untrusted=untrusted)
121 121
122 122 def configint(self, section, name, default=uimod._unset, untrusted=True):
123 123 return self.repo.ui.configint(section, name, default,
124 124 untrusted=untrusted)
125 125
126 126 def configlist(self, section, name, default=uimod._unset, untrusted=True):
127 127 return self.repo.ui.configlist(section, name, default,
128 128 untrusted=untrusted)
129 129
130 130 def archivelist(self, nodeid):
131 131 return webutil.archivelist(self.repo.ui, nodeid)
132 132
133 133 def templater(self, req):
134 134 # determine scheme, port and server name
135 135 # this is needed to create absolute urls
136 136 logourl = self.config('web', 'logourl')
137 137 logoimg = self.config('web', 'logoimg')
138 138 staticurl = (self.config('web', 'staticurl')
139 139 or req.apppath + '/static/')
140 140 if not staticurl.endswith('/'):
141 141 staticurl += '/'
142 142
143 143 # some functions for the templater
144 144
145 145 def motd(**map):
146 146 yield self.config('web', 'motd')
147 147
148 148 # figure out which style to use
149 149
150 150 vars = {}
151 151 styles, (style, mapfile) = getstyle(req, self.config,
152 152 self.templatepath)
153 153 if style == styles[0]:
154 154 vars['style'] = style
155 155
156 156 sessionvars = webutil.sessionvars(vars, '?')
157 157
158 158 if not self.reponame:
159 159 self.reponame = (self.config('web', 'name', '')
160 160 or req.reponame
161 161 or req.apppath
162 162 or self.repo.root)
163 163
164 164 filters = {}
165 165 templatefilter = registrar.templatefilter(filters)
166 166 @templatefilter('websub', intype=bytes)
167 167 def websubfilter(text):
168 168 return templatefilters.websub(text, self.websubtable)
169 169
170 170 # create the templater
171 171 # TODO: export all keywords: defaults = templatekw.keywords.copy()
172 172 defaults = {
173 173 'url': req.apppath + '/',
174 174 'logourl': logourl,
175 175 'logoimg': logoimg,
176 176 'staticurl': staticurl,
177 177 'urlbase': req.advertisedbaseurl,
178 178 'repo': self.reponame,
179 179 'encoding': encoding.encoding,
180 180 'motd': motd,
181 181 'sessionvars': sessionvars,
182 182 'pathdef': makebreadcrumb(req.apppath),
183 183 'style': style,
184 184 'nonce': self.nonce,
185 185 }
186 186 tres = formatter.templateresources(self.repo.ui, self.repo)
187 187 tmpl = templater.templater.frommapfile(mapfile,
188 188 filters=filters,
189 189 defaults=defaults,
190 190 resources=tres)
191 191 return tmpl
192 192
193 193 def sendtemplate(self, name, **kwargs):
194 194 """Helper function to send a response generated from a template."""
195 195 kwargs = pycompat.byteskwargs(kwargs)
196 196 self.res.setbodygen(self.tmpl.generate(name, kwargs))
197 197 return self.res.sendresponse()
198 198
199 199 class hgweb(object):
200 200 """HTTP server for individual repositories.
201 201
202 202 Instances of this class serve HTTP responses for a particular
203 203 repository.
204 204
205 205 Instances are typically used as WSGI applications.
206 206
207 207 Some servers are multi-threaded. On these servers, there may
208 208 be multiple active threads inside __call__.
209 209 """
210 210 def __init__(self, repo, name=None, baseui=None):
211 211 if isinstance(repo, bytes):
212 212 if baseui:
213 213 u = baseui.copy()
214 214 else:
215 215 u = uimod.ui.load()
216 216 r = hg.repository(u, repo)
217 217 else:
218 218 # we trust caller to give us a private copy
219 219 r = repo
220 220
221 221 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
222 222 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
223 223 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
224 224 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
225 225 # resolve file patterns relative to repo root
226 226 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
227 227 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
228 228 # displaying bundling progress bar while serving feel wrong and may
229 229 # break some wsgi implementation.
230 230 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
231 231 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
232 232 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
233 233 self._lastrepo = self._repos[0]
234 234 hook.redirect(True)
235 235 self.reponame = name
236 236
237 237 def _webifyrepo(self, repo):
238 238 repo = getwebview(repo)
239 239 self.websubtable = webutil.getwebsubs(repo)
240 240 return repo
241 241
242 242 @contextlib.contextmanager
243 243 def _obtainrepo(self):
244 244 """Obtain a repo unique to the caller.
245 245
246 246 Internally we maintain a stack of cachedlocalrepo instances
247 247 to be handed out. If one is available, we pop it and return it,
248 248 ensuring it is up to date in the process. If one is not available,
249 249 we clone the most recently used repo instance and return it.
250 250
251 251 It is currently possible for the stack to grow without bounds
252 252 if the server allows infinite threads. However, servers should
253 253 have a thread limit, thus establishing our limit.
254 254 """
255 255 if self._repos:
256 256 cached = self._repos.pop()
257 257 r, created = cached.fetch()
258 258 else:
259 259 cached = self._lastrepo.copy()
260 260 r, created = cached.fetch()
261 261 if created:
262 262 r = self._webifyrepo(r)
263 263
264 264 self._lastrepo = cached
265 265 self.mtime = cached.mtime
266 266 try:
267 267 yield r
268 268 finally:
269 269 self._repos.append(cached)
270 270
271 271 def run(self):
272 272 """Start a server from CGI environment.
273 273
274 274 Modern servers should be using WSGI and should avoid this
275 275 method, if possible.
276 276 """
277 277 if not encoding.environ.get('GATEWAY_INTERFACE',
278 278 '').startswith("CGI/1."):
279 279 raise RuntimeError("This function is only intended to be "
280 280 "called while running as a CGI script.")
281 281 wsgicgi.launch(self)
282 282
283 283 def __call__(self, env, respond):
284 284 """Run the WSGI application.
285 285
286 286 This may be called by multiple threads.
287 287 """
288 288 req = requestmod.parserequestfromenv(env)
289 289 res = requestmod.wsgiresponse(req, respond)
290 290
291 291 return self.run_wsgi(req, res)
292 292
293 293 def run_wsgi(self, req, res):
294 294 """Internal method to run the WSGI application.
295 295
296 296 This is typically only called by Mercurial. External consumers
297 297 should be using instances of this class as the WSGI application.
298 298 """
299 299 with self._obtainrepo() as repo:
300 300 profile = repo.ui.configbool('profiling', 'enabled')
301 301 with profiling.profile(repo.ui, enabled=profile):
302 302 for r in self._runwsgi(req, res, repo):
303 303 yield r
304 304
305 305 def _runwsgi(self, req, res, repo):
306 306 rctx = requestcontext(self, repo, req, res)
307 307
308 308 # This state is global across all threads.
309 309 encoding.encoding = rctx.config('web', 'encoding')
310 310 rctx.repo.ui.environ = req.rawenv
311 311
312 312 if rctx.csp:
313 313 # hgwebdir may have added CSP header. Since we generate our own,
314 314 # replace it.
315 315 res.headers['Content-Security-Policy'] = rctx.csp
316 316
317 317 # /api/* is reserved for various API implementations. Dispatch
318 318 # accordingly. But URL paths can conflict with subrepos and virtual
319 319 # repos in hgwebdir. So until we have a workaround for this, only
320 320 # expose the URLs if the feature is enabled.
321 321 apienabled = rctx.repo.ui.configbool('experimental', 'web.apiserver')
322 322 if apienabled and req.dispatchparts and req.dispatchparts[0] == b'api':
323 323 wireprotoserver.handlewsgiapirequest(rctx, req, res,
324 324 self.check_perm)
325 325 return res.sendresponse()
326 326
327 327 handled = wireprotoserver.handlewsgirequest(
328 328 rctx, req, res, self.check_perm)
329 329 if handled:
330 330 return res.sendresponse()
331 331
332 332 # Old implementations of hgweb supported dispatching the request via
333 333 # the initial query string parameter instead of using PATH_INFO.
334 334 # If PATH_INFO is present (signaled by ``req.dispatchpath`` having
335 335 # a value), we use it. Otherwise fall back to the query string.
336 336 if req.dispatchpath is not None:
337 337 query = req.dispatchpath
338 338 else:
339 339 query = req.querystring.partition('&')[0].partition(';')[0]
340 340
341 341 # translate user-visible url structure to internal structure
342 342
343 343 args = query.split('/', 2)
344 344 if 'cmd' not in req.qsparams and args and args[0]:
345 345 cmd = args.pop(0)
346 346 style = cmd.rfind('-')
347 347 if style != -1:
348 348 req.qsparams['style'] = cmd[:style]
349 349 cmd = cmd[style + 1:]
350 350
351 351 # avoid accepting e.g. style parameter as command
352 352 if util.safehasattr(webcommands, cmd):
353 353 req.qsparams['cmd'] = cmd
354 354
355 355 if cmd == 'static':
356 356 req.qsparams['file'] = '/'.join(args)
357 357 else:
358 358 if args and args[0]:
359 359 node = args.pop(0).replace('%2F', '/')
360 360 req.qsparams['node'] = node
361 361 if args:
362 362 if 'file' in req.qsparams:
363 363 del req.qsparams['file']
364 364 for a in args:
365 365 req.qsparams.add('file', a)
366 366
367 367 ua = req.headers.get('User-Agent', '')
368 368 if cmd == 'rev' and 'mercurial' in ua:
369 369 req.qsparams['style'] = 'raw'
370 370
371 371 if cmd == 'archive':
372 372 fn = req.qsparams['node']
373 373 for type_, spec in webutil.archivespecs.iteritems():
374 374 ext = spec[2]
375 375 if fn.endswith(ext):
376 376 req.qsparams['node'] = fn[:-len(ext)]
377 377 req.qsparams['type'] = type_
378 378 else:
379 379 cmd = req.qsparams.get('cmd', '')
380 380
381 381 # process the web interface request
382 382
383 383 try:
384 384 rctx.tmpl = rctx.templater(req)
385 385 ctype = rctx.tmpl.render('mimetype',
386 386 {'encoding': encoding.encoding})
387 387
388 388 # check read permissions non-static content
389 389 if cmd != 'static':
390 390 self.check_perm(rctx, req, None)
391 391
392 392 if cmd == '':
393 393 req.qsparams['cmd'] = rctx.tmpl.render('default', {})
394 394 cmd = req.qsparams['cmd']
395 395
396 396 # Don't enable caching if using a CSP nonce because then it wouldn't
397 397 # be a nonce.
398 398 if rctx.configbool('web', 'cache') and not rctx.nonce:
399 399 tag = 'W/"%d"' % self.mtime
400 400 if req.headers.get('If-None-Match') == tag:
401 401 res.status = '304 Not Modified'
402 # Content-Type may be defined globally. It isn't valid on a
403 # 304, so discard it.
404 try:
405 del res.headers[b'Content-Type']
406 except KeyError:
407 pass
402 408 # Response body not allowed on 304.
403 409 res.setbodybytes('')
404 410 return res.sendresponse()
405 411
406 412 res.headers['ETag'] = tag
407 413
408 414 if cmd not in webcommands.__all__:
409 415 msg = 'no such method: %s' % cmd
410 416 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
411 417 else:
412 418 # Set some globals appropriate for web handlers. Commands can
413 419 # override easily enough.
414 420 res.status = '200 Script output follows'
415 421 res.headers['Content-Type'] = ctype
416 422 return getattr(webcommands, cmd)(rctx)
417 423
418 424 except (error.LookupError, error.RepoLookupError) as err:
419 425 msg = pycompat.bytestr(err)
420 426 if (util.safehasattr(err, 'name') and
421 427 not isinstance(err, error.ManifestLookupError)):
422 428 msg = 'revision not found: %s' % err.name
423 429
424 430 res.status = '404 Not Found'
425 431 res.headers['Content-Type'] = ctype
426 432 return rctx.sendtemplate('error', error=msg)
427 433 except (error.RepoError, error.RevlogError) as e:
428 434 res.status = '500 Internal Server Error'
429 435 res.headers['Content-Type'] = ctype
430 436 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
431 437 except ErrorResponse as e:
432 438 res.status = statusmessage(e.code, pycompat.bytestr(e))
433 439 res.headers['Content-Type'] = ctype
434 440 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
435 441
436 442 def check_perm(self, rctx, req, op):
437 443 for permhook in permhooks:
438 444 permhook(rctx, req, op)
439 445
440 446 def getwebview(repo):
441 447 """The 'web.view' config controls changeset filter to hgweb. Possible
442 448 values are ``served``, ``visible`` and ``all``. Default is ``served``.
443 449 The ``served`` filter only shows changesets that can be pulled from the
444 450 hgweb instance. The``visible`` filter includes secret changesets but
445 451 still excludes "hidden" one.
446 452
447 453 See the repoview module for details.
448 454
449 455 The option has been around undocumented since Mercurial 2.5, but no
450 456 user ever asked about it. So we better keep it undocumented for now."""
451 457 # experimental config: web.view
452 458 viewconfig = repo.ui.config('web', 'view', untrusted=True)
453 459 if viewconfig == 'all':
454 460 return repo.unfiltered()
455 461 elif viewconfig in repoview.filtertable:
456 462 return repo.filtered(viewconfig)
457 463 else:
458 464 return repo.filtered('served')
@@ -1,899 +1,897 b''
1 1 #require serve
2 2
3 3 Some tests for hgweb. Tests static files, plain files and different 404's.
4 4
5 5 $ hg init test
6 6 $ cd test
7 7 $ mkdir da
8 8 $ echo foo > da/foo
9 9 $ echo foo > foo
10 10 $ hg ci -Ambase
11 11 adding da/foo
12 12 adding foo
13 13 $ hg bookmark -r0 '@'
14 14 $ hg bookmark -r0 'a b c'
15 15 $ hg bookmark -r0 'd/e/f'
16 16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
17 17 $ cat hg.pid >> $DAEMON_PIDS
18 18
19 19 manifest
20 20
21 21 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw')
22 22 200 Script output follows
23 23
24 24
25 25 drwxr-xr-x da
26 26 -rw-r--r-- 4 foo
27 27
28 28
29 29 $ (get-with-headers.py localhost:$HGPORT 'file/tip/da?style=raw')
30 30 200 Script output follows
31 31
32 32
33 33 -rw-r--r-- 4 foo
34 34
35 35
36 36
37 37 plain file
38 38
39 39 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?style=raw'
40 40 200 Script output follows
41 41
42 42 foo
43 43
44 44 should give a 404 - static file that does not exist
45 45
46 46 $ get-with-headers.py localhost:$HGPORT 'static/bogus'
47 47 404 Not Found
48 48
49 49 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
50 50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
51 51 <head>
52 52 <link rel="icon" href="/static/hgicon.png" type="image/png" />
53 53 <meta name="robots" content="index, nofollow" />
54 54 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
55 55 <script type="text/javascript" src="/static/mercurial.js"></script>
56 56
57 57 <title>test: error</title>
58 58 </head>
59 59 <body>
60 60
61 61 <div class="container">
62 62 <div class="menu">
63 63 <div class="logo">
64 64 <a href="https://mercurial-scm.org/">
65 65 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
66 66 </div>
67 67 <ul>
68 68 <li><a href="/shortlog">log</a></li>
69 69 <li><a href="/graph">graph</a></li>
70 70 <li><a href="/tags">tags</a></li>
71 71 <li><a href="/bookmarks">bookmarks</a></li>
72 72 <li><a href="/branches">branches</a></li>
73 73 </ul>
74 74 <ul>
75 75 <li><a href="/help">help</a></li>
76 76 </ul>
77 77 </div>
78 78
79 79 <div class="main">
80 80
81 81 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
82 82 <h3>error</h3>
83 83
84 84
85 85 <form class="search" action="/log">
86 86
87 87 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
88 88 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
89 89 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
90 90 </form>
91 91
92 92 <div class="description">
93 93 <p>
94 94 An error occurred while processing your request:
95 95 </p>
96 96 <p>
97 97 Not Found
98 98 </p>
99 99 </div>
100 100 </div>
101 101 </div>
102 102
103 103
104 104
105 105 </body>
106 106 </html>
107 107
108 108 [1]
109 109
110 110 should give a 404 - bad revision
111 111
112 112 $ get-with-headers.py localhost:$HGPORT 'file/spam/foo?style=raw'
113 113 404 Not Found
114 114
115 115
116 116 error: revision not found: spam
117 117 [1]
118 118
119 119 should give a 400 - bad command
120 120
121 121 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
122 122 400* (glob)
123 123
124 124
125 125 error: no such method: spam
126 126 [1]
127 127
128 128 $ get-with-headers.py --headeronly localhost:$HGPORT '?cmd=spam'
129 129 400 no such method: spam
130 130 [1]
131 131
132 132 should give a 400 - bad command as a part of url path (issue4071)
133 133
134 134 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam'
135 135 400 no such method: spam
136 136 [1]
137 137
138 138 $ get-with-headers.py --headeronly localhost:$HGPORT 'raw-spam'
139 139 400 no such method: spam
140 140 [1]
141 141
142 142 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam/tip/foo'
143 143 400 no such method: spam
144 144 [1]
145 145
146 146 should give a 404 - file does not exist
147 147
148 148 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork?style=raw'
149 149 404 Not Found
150 150
151 151
152 152 error: bork@2ef0ac749a14: not found in manifest
153 153 [1]
154 154 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork'
155 155 404 Not Found
156 156
157 157 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
158 158 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
159 159 <head>
160 160 <link rel="icon" href="/static/hgicon.png" type="image/png" />
161 161 <meta name="robots" content="index, nofollow" />
162 162 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
163 163 <script type="text/javascript" src="/static/mercurial.js"></script>
164 164
165 165 <title>test: error</title>
166 166 </head>
167 167 <body>
168 168
169 169 <div class="container">
170 170 <div class="menu">
171 171 <div class="logo">
172 172 <a href="https://mercurial-scm.org/">
173 173 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
174 174 </div>
175 175 <ul>
176 176 <li><a href="/shortlog">log</a></li>
177 177 <li><a href="/graph">graph</a></li>
178 178 <li><a href="/tags">tags</a></li>
179 179 <li><a href="/bookmarks">bookmarks</a></li>
180 180 <li><a href="/branches">branches</a></li>
181 181 </ul>
182 182 <ul>
183 183 <li><a href="/help">help</a></li>
184 184 </ul>
185 185 </div>
186 186
187 187 <div class="main">
188 188
189 189 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
190 190 <h3>error</h3>
191 191
192 192
193 193 <form class="search" action="/log">
194 194
195 195 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
196 196 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
197 197 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
198 198 </form>
199 199
200 200 <div class="description">
201 201 <p>
202 202 An error occurred while processing your request:
203 203 </p>
204 204 <p>
205 205 bork@2ef0ac749a14: not found in manifest
206 206 </p>
207 207 </div>
208 208 </div>
209 209 </div>
210 210
211 211
212 212
213 213 </body>
214 214 </html>
215 215
216 216 [1]
217 217 $ get-with-headers.py localhost:$HGPORT 'diff/tip/bork?style=raw'
218 218 404 Not Found
219 219
220 220
221 221 error: bork@2ef0ac749a14: not found in manifest
222 222 [1]
223 223
224 224 try bad style
225 225
226 226 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=foobar')
227 227 200 Script output follows
228 228
229 229 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
230 230 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
231 231 <head>
232 232 <link rel="icon" href="/static/hgicon.png" type="image/png" />
233 233 <meta name="robots" content="index, nofollow" />
234 234 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
235 235 <script type="text/javascript" src="/static/mercurial.js"></script>
236 236
237 237 <title>test: 2ef0ac749a14 /</title>
238 238 </head>
239 239 <body>
240 240
241 241 <div class="container">
242 242 <div class="menu">
243 243 <div class="logo">
244 244 <a href="https://mercurial-scm.org/">
245 245 <img src="/static/hglogo.png" alt="mercurial" /></a>
246 246 </div>
247 247 <ul>
248 248 <li><a href="/shortlog/tip">log</a></li>
249 249 <li><a href="/graph/tip">graph</a></li>
250 250 <li><a href="/tags">tags</a></li>
251 251 <li><a href="/bookmarks">bookmarks</a></li>
252 252 <li><a href="/branches">branches</a></li>
253 253 </ul>
254 254 <ul>
255 255 <li><a href="/rev/tip">changeset</a></li>
256 256 <li class="active">browse</li>
257 257 </ul>
258 258 <ul>
259 259
260 260 </ul>
261 261 <ul>
262 262 <li><a href="/help">help</a></li>
263 263 </ul>
264 264 </div>
265 265
266 266 <div class="main">
267 267 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
268 268 <h3>
269 269 directory / @ 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
270 270 <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> <span class="tag">@</span> <span class="tag">a b c</span> <span class="tag">d/e/f</span>
271 271 </h3>
272 272
273 273
274 274 <form class="search" action="/log">
275 275
276 276 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
277 277 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
278 278 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
279 279 </form>
280 280
281 281 <table class="bigtable">
282 282 <thead>
283 283 <tr>
284 284 <th class="name">name</th>
285 285 <th class="size">size</th>
286 286 <th class="permissions">permissions</th>
287 287 </tr>
288 288 </thead>
289 289 <tbody class="stripes2">
290 290 <tr class="fileline">
291 291 <td class="name"><a href="/file/tip/">[up]</a></td>
292 292 <td class="size"></td>
293 293 <td class="permissions">drwxr-xr-x</td>
294 294 </tr>
295 295
296 296 <tr class="fileline">
297 297 <td class="name">
298 298 <a href="/file/tip/da">
299 299 <img src="/static/coal-folder.png" alt="dir."/> da/
300 300 </a>
301 301 <a href="/file/tip/da/">
302 302
303 303 </a>
304 304 </td>
305 305 <td class="size"></td>
306 306 <td class="permissions">drwxr-xr-x</td>
307 307 </tr>
308 308
309 309 <tr class="fileline">
310 310 <td class="filename">
311 311 <a href="/file/tip/foo">
312 312 <img src="/static/coal-file.png" alt="file"/> foo
313 313 </a>
314 314 </td>
315 315 <td class="size">4</td>
316 316 <td class="permissions">-rw-r--r--</td>
317 317 </tr>
318 318 </tbody>
319 319 </table>
320 320 </div>
321 321 </div>
322 322
323 323
324 324 </body>
325 325 </html>
326 326
327 327
328 328 stop and restart
329 329
330 330 $ killdaemons.py
331 331 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
332 332 $ cat hg.pid >> $DAEMON_PIDS
333 333
334 334 Test the access/error files are opened in append mode
335 335
336 336 $ $PYTHON -c "print len(open('access.log', 'rb').readlines()), 'log lines written'"
337 337 14 log lines written
338 338
339 339 static file
340 340
341 341 $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
342 342 200 Script output follows
343 343 content-length: 9059
344 344 content-type: text/css
345 345
346 346 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
347 347 a { color:#0000cc; }
348 348 a:hover, a:visited, a:active { color:#880000; }
349 349 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
350 350 div.page_header a:visited { color:#0000cc; }
351 351 div.page_header a:hover { color:#880000; }
352 352 div.page_nav {
353 353 padding:8px;
354 354 display: flex;
355 355 justify-content: space-between;
356 356 align-items: center;
357 357 }
358 358 div.page_nav a:visited { color:#0000cc; }
359 359 div.extra_nav {
360 360 padding: 8px;
361 361 }
362 362 div.extra_nav a:visited {
363 363 color: #0000cc;
364 364 }
365 365 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
366 366 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
367 367 div.page_footer_text { float:left; color:#555555; font-style:italic; }
368 368 div.page_body { padding:8px; }
369 369 div.title, a.title {
370 370 display:block; padding:6px 8px;
371 371 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
372 372 }
373 373 a.title:hover { background-color: #d9d8d1; }
374 374 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
375 375 div.log_body { padding:8px 8px 8px 150px; }
376 376 .age { white-space:nowrap; }
377 377 a.title span.age { position:relative; float:left; width:142px; font-style:italic; }
378 378 div.log_link {
379 379 padding:0px 8px;
380 380 font-size:10px; font-family:sans-serif; font-style:normal;
381 381 position:relative; float:left; width:136px;
382 382 }
383 383 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
384 384 a.list { text-decoration:none; color:#000000; }
385 385 a.list:hover { text-decoration:underline; color:#880000; }
386 386 table { padding:8px 4px; }
387 387 th { padding:2px 5px; font-size:12px; text-align:left; }
388 388 .parity0 { background-color:#ffffff; }
389 389 tr.dark, .parity1, pre.sourcelines.stripes > :nth-child(4n+4) { background-color:#f6f6f0; }
390 390 tr.light:hover, .parity0:hover, tr.dark:hover, .parity1:hover,
391 391 pre.sourcelines.stripes > :nth-child(4n+2):hover,
392 392 pre.sourcelines.stripes > :nth-child(4n+4):hover,
393 393 pre.sourcelines.stripes > :nth-child(4n+1):hover + :nth-child(4n+2),
394 394 pre.sourcelines.stripes > :nth-child(4n+3):hover + :nth-child(4n+4) { background-color:#edece6; }
395 395 td { padding:2px 5px; font-size:12px; vertical-align:top; }
396 396 td.closed { background-color: #99f; }
397 397 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
398 398 td.indexlinks { white-space: nowrap; }
399 399 td.indexlinks a {
400 400 padding: 2px 5px; line-height: 10px;
401 401 border: 1px solid;
402 402 color: #ffffff; background-color: #7777bb;
403 403 border-color: #aaaadd #333366 #333366 #aaaadd;
404 404 font-weight: bold; text-align: center; text-decoration: none;
405 405 font-size: 10px;
406 406 }
407 407 td.indexlinks a:hover { background-color: #6666aa; }
408 408 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
409 409
410 410 .search {
411 411 margin-right: 8px;
412 412 }
413 413
414 414 div#hint {
415 415 position: absolute;
416 416 display: none;
417 417 width: 250px;
418 418 padding: 5px;
419 419 background: #ffc;
420 420 border: 1px solid yellow;
421 421 border-radius: 5px;
422 422 }
423 423
424 424 #searchform:hover div#hint { display: block; }
425 425
426 426 tr.thisrev a { color:#999999; text-decoration: none; }
427 427 tr.thisrev pre { color:#009900; }
428 428 td.annotate {
429 429 white-space: nowrap;
430 430 }
431 431 div.annotate-info {
432 432 z-index: 5;
433 433 display: none;
434 434 position: absolute;
435 435 background-color: #FFFFFF;
436 436 border: 1px solid #d9d8d1;
437 437 text-align: left;
438 438 color: #000000;
439 439 padding: 5px;
440 440 }
441 441 div.annotate-info a { color: #0000FF; text-decoration: underline; }
442 442 td.annotate:hover div.annotate-info { display: inline; }
443 443
444 444 #diffopts-form {
445 445 padding-left: 8px;
446 446 display: none;
447 447 }
448 448
449 449 .linenr { color:#999999; text-decoration:none }
450 450 div.rss_logo { float: right; white-space: nowrap; }
451 451 div.rss_logo a {
452 452 padding:3px 6px; line-height:10px;
453 453 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
454 454 color:#ffffff; background-color:#ff6600;
455 455 font-weight:bold; font-family:sans-serif; font-size:10px;
456 456 text-align:center; text-decoration:none;
457 457 }
458 458 div.rss_logo a:hover { background-color:#ee5500; }
459 459 pre { margin: 0; }
460 460 span.logtags span {
461 461 padding: 0px 4px;
462 462 font-size: 10px;
463 463 font-weight: normal;
464 464 border: 1px solid;
465 465 background-color: #ffaaff;
466 466 border-color: #ffccff #ff00ee #ff00ee #ffccff;
467 467 }
468 468 span.logtags span.phasetag {
469 469 background-color: #dfafff;
470 470 border-color: #e2b8ff #ce48ff #ce48ff #e2b8ff;
471 471 }
472 472 span.logtags span.obsoletetag {
473 473 background-color: #dddddd;
474 474 border-color: #e4e4e4 #a3a3a3 #a3a3a3 #e4e4e4;
475 475 }
476 476 span.logtags span.instabilitytag {
477 477 background-color: #ffb1c0;
478 478 border-color: #ffbbc8 #ff4476 #ff4476 #ffbbc8;
479 479 }
480 480 span.logtags span.tagtag {
481 481 background-color: #ffffaa;
482 482 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
483 483 }
484 484 span.logtags span.branchtag {
485 485 background-color: #aaffaa;
486 486 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
487 487 }
488 488 span.logtags span.inbranchtag {
489 489 background-color: #d5dde6;
490 490 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
491 491 }
492 492 span.logtags span.bookmarktag {
493 493 background-color: #afdffa;
494 494 border-color: #ccecff #46ace6 #46ace6 #ccecff;
495 495 }
496 496 span.difflineplus { color:#008800; }
497 497 span.difflineminus { color:#cc0000; }
498 498 span.difflineat { color:#990099; }
499 499 div.diffblocks { counter-reset: lineno; }
500 500 div.diffblock { counter-increment: lineno; }
501 501 pre.sourcelines { position: relative; counter-reset: lineno; }
502 502 pre.sourcelines > span {
503 503 display: inline-block;
504 504 box-sizing: border-box;
505 505 width: 100%;
506 506 padding: 0 0 0 5em;
507 507 counter-increment: lineno;
508 508 vertical-align: top;
509 509 }
510 510 pre.sourcelines > span:before {
511 511 -moz-user-select: -moz-none;
512 512 -khtml-user-select: none;
513 513 -webkit-user-select: none;
514 514 -ms-user-select: none;
515 515 user-select: none;
516 516 display: inline-block;
517 517 margin-left: -6em;
518 518 width: 4em;
519 519 color: #999;
520 520 text-align: right;
521 521 content: counters(lineno,".");
522 522 float: left;
523 523 }
524 524 pre.sourcelines > a {
525 525 display: inline-block;
526 526 position: absolute;
527 527 left: 0px;
528 528 width: 4em;
529 529 height: 1em;
530 530 }
531 531 tr:target td,
532 532 pre.sourcelines > span:target,
533 533 pre.sourcelines.stripes > span:target {
534 534 background-color: #bfdfff;
535 535 }
536 536
537 537 .description {
538 538 font-family: monospace;
539 539 white-space: pre;
540 540 }
541 541
542 542 /* Followlines */
543 543 tbody.sourcelines > tr.followlines-selected,
544 544 pre.sourcelines > span.followlines-selected {
545 545 background-color: #99C7E9 !important;
546 546 }
547 547
548 548 div#followlines {
549 549 background-color: #FFF;
550 550 border: 1px solid #d9d8d1;
551 551 padding: 5px;
552 552 position: fixed;
553 553 }
554 554
555 555 div.followlines-cancel {
556 556 text-align: right;
557 557 }
558 558
559 559 div.followlines-cancel > button {
560 560 line-height: 80%;
561 561 padding: 0;
562 562 border: 0;
563 563 border-radius: 2px;
564 564 background-color: inherit;
565 565 font-weight: bold;
566 566 }
567 567
568 568 div.followlines-cancel > button:hover {
569 569 color: #FFFFFF;
570 570 background-color: #CF1F1F;
571 571 }
572 572
573 573 div.followlines-link {
574 574 margin: 2px;
575 575 margin-top: 4px;
576 576 font-family: sans-serif;
577 577 }
578 578
579 579 .btn-followlines {
580 580 position: absolute;
581 581 display: none;
582 582 cursor: pointer;
583 583 box-sizing: content-box;
584 584 font-size: 11px;
585 585 width: 13px;
586 586 height: 13px;
587 587 border-radius: 3px;
588 588 margin: 0px;
589 589 margin-top: -2px;
590 590 padding: 0px;
591 591 background-color: #E5FDE5;
592 592 border: 1px solid #9BC19B;
593 593 font-family: monospace;
594 594 text-align: center;
595 595 line-height: 5px;
596 596 }
597 597
598 598 span.followlines-select .btn-followlines {
599 599 margin-left: -1.6em;
600 600 }
601 601
602 602 .btn-followlines:hover {
603 603 transform: scale(1.1, 1.1);
604 604 }
605 605
606 606 .btn-followlines .followlines-plus {
607 607 color: green;
608 608 }
609 609
610 610 .btn-followlines .followlines-minus {
611 611 color: red;
612 612 }
613 613
614 614 .btn-followlines-end {
615 615 background-color: #ffdcdc;
616 616 }
617 617
618 618 .sourcelines tr:hover .btn-followlines,
619 619 .sourcelines span.followlines-select:hover > .btn-followlines {
620 620 display: inline;
621 621 }
622 622
623 623 .btn-followlines-hidden,
624 624 .sourcelines tr:hover .btn-followlines-hidden {
625 625 display: none;
626 626 }
627 627
628 628 /* Graph */
629 629 div#wrapper {
630 630 position: relative;
631 631 margin: 0;
632 632 padding: 0;
633 633 margin-top: 3px;
634 634 }
635 635
636 636 canvas {
637 637 position: absolute;
638 638 z-index: 5;
639 639 top: -0.9em;
640 640 margin: 0;
641 641 }
642 642
643 643 ul#graphnodes {
644 644 list-style: none inside none;
645 645 padding: 0;
646 646 margin: 0;
647 647 }
648 648
649 649 ul#graphnodes li {
650 650 position: relative;
651 651 height: 37px;
652 652 overflow: visible;
653 653 padding-top: 2px;
654 654 }
655 655
656 656 ul#graphnodes li .fg {
657 657 position: absolute;
658 658 z-index: 10;
659 659 }
660 660
661 661 ul#graphnodes li .info {
662 662 font-size: 100%;
663 663 font-style: italic;
664 664 }
665 665
666 666 /* Comparison */
667 667 .legend {
668 668 padding: 1.5% 0 1.5% 0;
669 669 }
670 670
671 671 .legendinfo {
672 672 border: 1px solid #d9d8d1;
673 673 font-size: 80%;
674 674 text-align: center;
675 675 padding: 0.5%;
676 676 }
677 677
678 678 .equal {
679 679 background-color: #ffffff;
680 680 }
681 681
682 682 .delete {
683 683 background-color: #faa;
684 684 color: #333;
685 685 }
686 686
687 687 .insert {
688 688 background-color: #ffa;
689 689 }
690 690
691 691 .replace {
692 692 background-color: #e8e8e8;
693 693 }
694 694
695 695 .comparison {
696 696 overflow-x: auto;
697 697 }
698 698
699 699 .header th {
700 700 text-align: center;
701 701 }
702 702
703 703 .block {
704 704 border-top: 1px solid #d9d8d1;
705 705 }
706 706
707 707 .scroll-loading {
708 708 -webkit-animation: change_color 1s linear 0s infinite alternate;
709 709 -moz-animation: change_color 1s linear 0s infinite alternate;
710 710 -o-animation: change_color 1s linear 0s infinite alternate;
711 711 animation: change_color 1s linear 0s infinite alternate;
712 712 }
713 713
714 714 @-webkit-keyframes change_color {
715 715 from { background-color: #A0CEFF; } to { }
716 716 }
717 717 @-moz-keyframes change_color {
718 718 from { background-color: #A0CEFF; } to { }
719 719 }
720 720 @-o-keyframes change_color {
721 721 from { background-color: #A0CEFF; } to { }
722 722 }
723 723 @keyframes change_color {
724 724 from { background-color: #A0CEFF; } to { }
725 725 }
726 726
727 727 .scroll-loading-error {
728 728 background-color: #FFCCCC !important;
729 729 }
730 730
731 731 #doc {
732 732 margin: 0 8px;
733 733 }
734 734 304 Not Modified
735 735
736 736
737 737 phase changes are refreshed (issue4061)
738 738
739 739 $ echo bar >> foo
740 740 $ hg ci -msecret --secret
741 741 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
742 742 200 Script output follows
743 743
744 744
745 745 # HG changelog
746 746 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
747 747
748 748 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
749 749 revision: 0
750 750 user: test
751 751 date: Thu, 01 Jan 1970 00:00:00 +0000
752 752 summary: base
753 753 branch: default
754 754 tag: tip
755 755 bookmark: @
756 756 bookmark: a b c
757 757 bookmark: d/e/f
758 758
759 759
760 760 $ hg phase --draft tip
761 761 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
762 762 200 Script output follows
763 763
764 764
765 765 # HG changelog
766 766 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
767 767
768 768 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
769 769 revision: 1
770 770 user: test
771 771 date: Thu, 01 Jan 1970 00:00:00 +0000
772 772 summary: secret
773 773 branch: default
774 774 tag: tip
775 775
776 776 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
777 777 revision: 0
778 778 user: test
779 779 date: Thu, 01 Jan 1970 00:00:00 +0000
780 780 summary: base
781 781 bookmark: @
782 782 bookmark: a b c
783 783 bookmark: d/e/f
784 784
785 785
786 786
787 787 access bookmarks
788 788
789 789 $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:'
790 790 200 Script output follows
791 791 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
792 792
793 793 $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:'
794 794 200 Script output follows
795 795 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
796 796
797 797 $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:'
798 798 200 Script output follows
799 799 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
800 800
801 801 $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:'
802 802 200 Script output follows
803 803 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
804 804
805 805 no style can be loaded from directories other than the specified paths
806 806
807 807 $ mkdir -p x/templates/fallback
808 808 $ cat <<EOF > x/templates/fallback/map
809 809 > default = 'shortlog'
810 810 > shortlog = 'fall back to default\n'
811 811 > mimetype = 'text/plain'
812 812 > EOF
813 813 $ cat <<EOF > x/map
814 814 > default = 'shortlog'
815 815 > shortlog = 'access to outside of templates directory\n'
816 816 > mimetype = 'text/plain'
817 817 > EOF
818 818
819 819 $ killdaemons.py
820 820 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \
821 821 > --config web.style=fallback --config web.templates=x/templates
822 822 $ cat hg.pid >> $DAEMON_PIDS
823 823
824 824 $ get-with-headers.py localhost:$HGPORT "?style=`pwd`/x"
825 825 200 Script output follows
826 826
827 827 fall back to default
828 828
829 829 $ get-with-headers.py localhost:$HGPORT '?style=..'
830 830 200 Script output follows
831 831
832 832 fall back to default
833 833
834 834 $ get-with-headers.py localhost:$HGPORT '?style=./..'
835 835 200 Script output follows
836 836
837 837 fall back to default
838 838
839 839 $ get-with-headers.py localhost:$HGPORT '?style=.../.../'
840 840 200 Script output follows
841 841
842 842 fall back to default
843 843
844 844 errors
845 845
846 846 $ cat errors.log
847 847
848 848 Uncaught exceptions result in a logged error and canned HTTP response
849 849
850 850 $ killdaemons.py
851 851 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
852 852 $ cat hg.pid >> $DAEMON_PIDS
853 853
854 854 $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
855 855 500 Internal Server Error
856 856 transfer-encoding: chunked
857 857
858 858 Internal Server Error (no-eol)
859 859 [1]
860 860
861 861 $ killdaemons.py
862 862 $ head -1 errors.log
863 863 .* Exception happened during processing request '/raiseerror': (re)
864 864
865 865 Uncaught exception after partial content sent
866 866
867 867 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
868 868 $ cat hg.pid >> $DAEMON_PIDS
869 869 $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
870 870 200 Script output follows
871 871 transfer-encoding: chunked
872 872 content-type: text/plain
873 873
874 874 partial content
875 875 Internal Server Error (no-eol)
876 876
877 877 $ killdaemons.py
878 878
879 879 HTTP 304 works with hgwebdir (issue5844)
880 880
881 881 $ cat > hgweb.conf << EOF
882 882 > [paths]
883 883 > /repo = $TESTTMP/test
884 884 > EOF
885 885
886 886 $ hg serve --web-conf hgweb.conf -p $HGPORT -d --pid-file hg.pid -E error.log
887 887 $ cat hg.pid >> $DAEMON_PIDS
888 888
889 889 $ get-with-headers.py --twice --headeronly localhost:$HGPORT 'repo/static/style.css' - date etag server
890 890 200 Script output follows
891 891 content-length: 2677
892 892 content-type: text/css
893 500 Internal Server Error
894 transfer-encoding: chunked
895 [1]
893 304 Not Modified
896 894
897 895 $ killdaemons.py
898 896
899 897 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now