##// END OF EJS Templates
hgweb: do not try to replace signal handlers while locking...
Yuya Nishihara -
r38158:5b831053 stable
parent child Browse files
Show More
@@ -1,464 +1,470 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 # it's unlikely that we can replace signal handlers in WSGI server,
229 # and mod_wsgi issues a big warning. a plain hgweb process (with no
230 # threading) could replace signal handlers, but we don't bother
231 # conditionally enabling it.
232 r.ui.setconfig('ui', 'signal-safe-lock', 'false', 'hgweb')
233 r.baseui.setconfig('ui', 'signal-safe-lock', 'false', 'hgweb')
228 234 # displaying bundling progress bar while serving feel wrong and may
229 235 # break some wsgi implementation.
230 236 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
231 237 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
232 238 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
233 239 self._lastrepo = self._repos[0]
234 240 hook.redirect(True)
235 241 self.reponame = name
236 242
237 243 def _webifyrepo(self, repo):
238 244 repo = getwebview(repo)
239 245 self.websubtable = webutil.getwebsubs(repo)
240 246 return repo
241 247
242 248 @contextlib.contextmanager
243 249 def _obtainrepo(self):
244 250 """Obtain a repo unique to the caller.
245 251
246 252 Internally we maintain a stack of cachedlocalrepo instances
247 253 to be handed out. If one is available, we pop it and return it,
248 254 ensuring it is up to date in the process. If one is not available,
249 255 we clone the most recently used repo instance and return it.
250 256
251 257 It is currently possible for the stack to grow without bounds
252 258 if the server allows infinite threads. However, servers should
253 259 have a thread limit, thus establishing our limit.
254 260 """
255 261 if self._repos:
256 262 cached = self._repos.pop()
257 263 r, created = cached.fetch()
258 264 else:
259 265 cached = self._lastrepo.copy()
260 266 r, created = cached.fetch()
261 267 if created:
262 268 r = self._webifyrepo(r)
263 269
264 270 self._lastrepo = cached
265 271 self.mtime = cached.mtime
266 272 try:
267 273 yield r
268 274 finally:
269 275 self._repos.append(cached)
270 276
271 277 def run(self):
272 278 """Start a server from CGI environment.
273 279
274 280 Modern servers should be using WSGI and should avoid this
275 281 method, if possible.
276 282 """
277 283 if not encoding.environ.get('GATEWAY_INTERFACE',
278 284 '').startswith("CGI/1."):
279 285 raise RuntimeError("This function is only intended to be "
280 286 "called while running as a CGI script.")
281 287 wsgicgi.launch(self)
282 288
283 289 def __call__(self, env, respond):
284 290 """Run the WSGI application.
285 291
286 292 This may be called by multiple threads.
287 293 """
288 294 req = requestmod.parserequestfromenv(env)
289 295 res = requestmod.wsgiresponse(req, respond)
290 296
291 297 return self.run_wsgi(req, res)
292 298
293 299 def run_wsgi(self, req, res):
294 300 """Internal method to run the WSGI application.
295 301
296 302 This is typically only called by Mercurial. External consumers
297 303 should be using instances of this class as the WSGI application.
298 304 """
299 305 with self._obtainrepo() as repo:
300 306 profile = repo.ui.configbool('profiling', 'enabled')
301 307 with profiling.profile(repo.ui, enabled=profile):
302 308 for r in self._runwsgi(req, res, repo):
303 309 yield r
304 310
305 311 def _runwsgi(self, req, res, repo):
306 312 rctx = requestcontext(self, repo, req, res)
307 313
308 314 # This state is global across all threads.
309 315 encoding.encoding = rctx.config('web', 'encoding')
310 316 rctx.repo.ui.environ = req.rawenv
311 317
312 318 if rctx.csp:
313 319 # hgwebdir may have added CSP header. Since we generate our own,
314 320 # replace it.
315 321 res.headers['Content-Security-Policy'] = rctx.csp
316 322
317 323 # /api/* is reserved for various API implementations. Dispatch
318 324 # accordingly. But URL paths can conflict with subrepos and virtual
319 325 # repos in hgwebdir. So until we have a workaround for this, only
320 326 # expose the URLs if the feature is enabled.
321 327 apienabled = rctx.repo.ui.configbool('experimental', 'web.apiserver')
322 328 if apienabled and req.dispatchparts and req.dispatchparts[0] == b'api':
323 329 wireprotoserver.handlewsgiapirequest(rctx, req, res,
324 330 self.check_perm)
325 331 return res.sendresponse()
326 332
327 333 handled = wireprotoserver.handlewsgirequest(
328 334 rctx, req, res, self.check_perm)
329 335 if handled:
330 336 return res.sendresponse()
331 337
332 338 # Old implementations of hgweb supported dispatching the request via
333 339 # the initial query string parameter instead of using PATH_INFO.
334 340 # If PATH_INFO is present (signaled by ``req.dispatchpath`` having
335 341 # a value), we use it. Otherwise fall back to the query string.
336 342 if req.dispatchpath is not None:
337 343 query = req.dispatchpath
338 344 else:
339 345 query = req.querystring.partition('&')[0].partition(';')[0]
340 346
341 347 # translate user-visible url structure to internal structure
342 348
343 349 args = query.split('/', 2)
344 350 if 'cmd' not in req.qsparams and args and args[0]:
345 351 cmd = args.pop(0)
346 352 style = cmd.rfind('-')
347 353 if style != -1:
348 354 req.qsparams['style'] = cmd[:style]
349 355 cmd = cmd[style + 1:]
350 356
351 357 # avoid accepting e.g. style parameter as command
352 358 if util.safehasattr(webcommands, cmd):
353 359 req.qsparams['cmd'] = cmd
354 360
355 361 if cmd == 'static':
356 362 req.qsparams['file'] = '/'.join(args)
357 363 else:
358 364 if args and args[0]:
359 365 node = args.pop(0).replace('%2F', '/')
360 366 req.qsparams['node'] = node
361 367 if args:
362 368 if 'file' in req.qsparams:
363 369 del req.qsparams['file']
364 370 for a in args:
365 371 req.qsparams.add('file', a)
366 372
367 373 ua = req.headers.get('User-Agent', '')
368 374 if cmd == 'rev' and 'mercurial' in ua:
369 375 req.qsparams['style'] = 'raw'
370 376
371 377 if cmd == 'archive':
372 378 fn = req.qsparams['node']
373 379 for type_, spec in webutil.archivespecs.iteritems():
374 380 ext = spec[2]
375 381 if fn.endswith(ext):
376 382 req.qsparams['node'] = fn[:-len(ext)]
377 383 req.qsparams['type'] = type_
378 384 else:
379 385 cmd = req.qsparams.get('cmd', '')
380 386
381 387 # process the web interface request
382 388
383 389 try:
384 390 rctx.tmpl = rctx.templater(req)
385 391 ctype = rctx.tmpl.render('mimetype',
386 392 {'encoding': encoding.encoding})
387 393
388 394 # check read permissions non-static content
389 395 if cmd != 'static':
390 396 self.check_perm(rctx, req, None)
391 397
392 398 if cmd == '':
393 399 req.qsparams['cmd'] = rctx.tmpl.render('default', {})
394 400 cmd = req.qsparams['cmd']
395 401
396 402 # Don't enable caching if using a CSP nonce because then it wouldn't
397 403 # be a nonce.
398 404 if rctx.configbool('web', 'cache') and not rctx.nonce:
399 405 tag = 'W/"%d"' % self.mtime
400 406 if req.headers.get('If-None-Match') == tag:
401 407 res.status = '304 Not Modified'
402 408 # Content-Type may be defined globally. It isn't valid on a
403 409 # 304, so discard it.
404 410 try:
405 411 del res.headers[b'Content-Type']
406 412 except KeyError:
407 413 pass
408 414 # Response body not allowed on 304.
409 415 res.setbodybytes('')
410 416 return res.sendresponse()
411 417
412 418 res.headers['ETag'] = tag
413 419
414 420 if cmd not in webcommands.__all__:
415 421 msg = 'no such method: %s' % cmd
416 422 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
417 423 else:
418 424 # Set some globals appropriate for web handlers. Commands can
419 425 # override easily enough.
420 426 res.status = '200 Script output follows'
421 427 res.headers['Content-Type'] = ctype
422 428 return getattr(webcommands, cmd)(rctx)
423 429
424 430 except (error.LookupError, error.RepoLookupError) as err:
425 431 msg = pycompat.bytestr(err)
426 432 if (util.safehasattr(err, 'name') and
427 433 not isinstance(err, error.ManifestLookupError)):
428 434 msg = 'revision not found: %s' % err.name
429 435
430 436 res.status = '404 Not Found'
431 437 res.headers['Content-Type'] = ctype
432 438 return rctx.sendtemplate('error', error=msg)
433 439 except (error.RepoError, error.RevlogError) as e:
434 440 res.status = '500 Internal Server Error'
435 441 res.headers['Content-Type'] = ctype
436 442 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
437 443 except ErrorResponse as e:
438 444 res.status = statusmessage(e.code, pycompat.bytestr(e))
439 445 res.headers['Content-Type'] = ctype
440 446 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
441 447
442 448 def check_perm(self, rctx, req, op):
443 449 for permhook in permhooks:
444 450 permhook(rctx, req, op)
445 451
446 452 def getwebview(repo):
447 453 """The 'web.view' config controls changeset filter to hgweb. Possible
448 454 values are ``served``, ``visible`` and ``all``. Default is ``served``.
449 455 The ``served`` filter only shows changesets that can be pulled from the
450 456 hgweb instance. The``visible`` filter includes secret changesets but
451 457 still excludes "hidden" one.
452 458
453 459 See the repoview module for details.
454 460
455 461 The option has been around undocumented since Mercurial 2.5, but no
456 462 user ever asked about it. So we better keep it undocumented for now."""
457 463 # experimental config: web.view
458 464 viewconfig = repo.ui.config('web', 'view', untrusted=True)
459 465 if viewconfig == 'all':
460 466 return repo.unfiltered()
461 467 elif viewconfig in repoview.filtertable:
462 468 return repo.filtered(viewconfig)
463 469 else:
464 470 return repo.filtered('served')
@@ -1,897 +1,943 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 $ killdaemons.py
845
846 Test signal-safe-lock in web and non-web processes
847
848 $ cat <<'EOF' > disablesig.py
849 > import signal
850 > from mercurial import error, extensions
851 > def disabledsig(orig, signalnum, handler):
852 > if signalnum == signal.SIGTERM:
853 > raise error.Abort(b'SIGTERM cannot be replaced')
854 > try:
855 > return orig(signalnum, handler)
856 > except ValueError:
857 > raise error.Abort(b'signal.signal() called in thread?')
858 > def uisetup(ui):
859 > extensions.wrapfunction(signal, b'signal', disabledsig)
860 > EOF
861
862 by default, signal interrupt should be disabled while making a lock file
863
864 $ hg debuglock -s --config extensions.disablesig=disablesig.py
865 abort: SIGTERM cannot be replaced
866 [255]
867
868 but in hgweb, it isn't disabled since some WSGI servers complains about
869 unsupported signal.signal() calls (see issue5889)
870
871 $ hg serve --config extensions.disablesig=disablesig.py \
872 > --config web.allow-push='*' --config web.push_ssl=False \
873 > -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
874 $ cat hg.pid >> $DAEMON_PIDS
875
876 $ hg clone -q http://localhost:$HGPORT/ repo
877 $ hg bookmark -R repo foo
878
879 push would fail if signal.signal() were called
880
881 $ hg push -R repo -B foo
882 pushing to http://localhost:$HGPORT/
883 searching for changes
884 no changes found
885 exporting bookmark foo
886 [1]
887
888 $ rm -R repo
889 $ killdaemons.py
890
844 891 errors
845 892
846 893 $ cat errors.log
847 894
848 895 Uncaught exceptions result in a logged error and canned HTTP response
849 896
850 $ killdaemons.py
851 897 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
852 898 $ cat hg.pid >> $DAEMON_PIDS
853 899
854 900 $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
855 901 500 Internal Server Error
856 902 transfer-encoding: chunked
857 903
858 904 Internal Server Error (no-eol)
859 905 [1]
860 906
861 907 $ killdaemons.py
862 908 $ head -1 errors.log
863 909 .* Exception happened during processing request '/raiseerror': (re)
864 910
865 911 Uncaught exception after partial content sent
866 912
867 913 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
868 914 $ cat hg.pid >> $DAEMON_PIDS
869 915 $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
870 916 200 Script output follows
871 917 transfer-encoding: chunked
872 918 content-type: text/plain
873 919
874 920 partial content
875 921 Internal Server Error (no-eol)
876 922
877 923 $ killdaemons.py
878 924
879 925 HTTP 304 works with hgwebdir (issue5844)
880 926
881 927 $ cat > hgweb.conf << EOF
882 928 > [paths]
883 929 > /repo = $TESTTMP/test
884 930 > EOF
885 931
886 932 $ hg serve --web-conf hgweb.conf -p $HGPORT -d --pid-file hg.pid -E error.log
887 933 $ cat hg.pid >> $DAEMON_PIDS
888 934
889 935 $ get-with-headers.py --twice --headeronly localhost:$HGPORT 'repo/static/style.css' - date etag server
890 936 200 Script output follows
891 937 content-length: 2677
892 938 content-type: text/css
893 939 304 Not Modified
894 940
895 941 $ killdaemons.py
896 942
897 943 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now