##// END OF EJS Templates
server: skip logging of ECONNRESET...
Augie Fackler -
r42005:6bbb12cb default
parent child Browse files
Show More
@@ -1,383 +1,385
1 # hgweb/server.py - The standalone hg web server.
1 # hgweb/server.py - The standalone hg web server.
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 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import os
12 import os
13 import socket
13 import socket
14 import sys
14 import sys
15 import traceback
15 import traceback
16 import wsgiref.validate
16 import wsgiref.validate
17
17
18 from ..i18n import _
18 from ..i18n import _
19
19
20 from .. import (
20 from .. import (
21 encoding,
21 encoding,
22 error,
22 error,
23 pycompat,
23 pycompat,
24 util,
24 util,
25 )
25 )
26
26
27 httpservermod = util.httpserver
27 httpservermod = util.httpserver
28 socketserver = util.socketserver
28 socketserver = util.socketserver
29 urlerr = util.urlerr
29 urlerr = util.urlerr
30 urlreq = util.urlreq
30 urlreq = util.urlreq
31
31
32 from . import (
32 from . import (
33 common,
33 common,
34 )
34 )
35
35
36 def _splitURI(uri):
36 def _splitURI(uri):
37 """Return path and query that has been split from uri
37 """Return path and query that has been split from uri
38
38
39 Just like CGI environment, the path is unquoted, the query is
39 Just like CGI environment, the path is unquoted, the query is
40 not.
40 not.
41 """
41 """
42 if r'?' in uri:
42 if r'?' in uri:
43 path, query = uri.split(r'?', 1)
43 path, query = uri.split(r'?', 1)
44 else:
44 else:
45 path, query = uri, r''
45 path, query = uri, r''
46 return urlreq.unquote(path), query
46 return urlreq.unquote(path), query
47
47
48 class _error_logger(object):
48 class _error_logger(object):
49 def __init__(self, handler):
49 def __init__(self, handler):
50 self.handler = handler
50 self.handler = handler
51 def flush(self):
51 def flush(self):
52 pass
52 pass
53 def write(self, str):
53 def write(self, str):
54 self.writelines(str.split('\n'))
54 self.writelines(str.split('\n'))
55 def writelines(self, seq):
55 def writelines(self, seq):
56 for msg in seq:
56 for msg in seq:
57 self.handler.log_error(r"HG error: %s", encoding.strfromlocal(msg))
57 self.handler.log_error(r"HG error: %s", encoding.strfromlocal(msg))
58
58
59 class _httprequesthandler(httpservermod.basehttprequesthandler):
59 class _httprequesthandler(httpservermod.basehttprequesthandler):
60
60
61 url_scheme = 'http'
61 url_scheme = 'http'
62
62
63 @staticmethod
63 @staticmethod
64 def preparehttpserver(httpserver, ui):
64 def preparehttpserver(httpserver, ui):
65 """Prepare .socket of new HTTPServer instance"""
65 """Prepare .socket of new HTTPServer instance"""
66
66
67 def __init__(self, *args, **kargs):
67 def __init__(self, *args, **kargs):
68 self.protocol_version = r'HTTP/1.1'
68 self.protocol_version = r'HTTP/1.1'
69 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
69 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
70
70
71 def _log_any(self, fp, format, *args):
71 def _log_any(self, fp, format, *args):
72 fp.write(pycompat.sysbytes(
72 fp.write(pycompat.sysbytes(
73 r"%s - - [%s] %s" % (self.client_address[0],
73 r"%s - - [%s] %s" % (self.client_address[0],
74 self.log_date_time_string(),
74 self.log_date_time_string(),
75 format % args)) + '\n')
75 format % args)) + '\n')
76 fp.flush()
76 fp.flush()
77
77
78 def log_error(self, format, *args):
78 def log_error(self, format, *args):
79 self._log_any(self.server.errorlog, format, *args)
79 self._log_any(self.server.errorlog, format, *args)
80
80
81 def log_message(self, format, *args):
81 def log_message(self, format, *args):
82 self._log_any(self.server.accesslog, format, *args)
82 self._log_any(self.server.accesslog, format, *args)
83
83
84 def log_request(self, code=r'-', size=r'-'):
84 def log_request(self, code=r'-', size=r'-'):
85 xheaders = []
85 xheaders = []
86 if util.safehasattr(self, 'headers'):
86 if util.safehasattr(self, 'headers'):
87 xheaders = [h for h in self.headers.items()
87 xheaders = [h for h in self.headers.items()
88 if h[0].startswith(r'x-')]
88 if h[0].startswith(r'x-')]
89 self.log_message(r'"%s" %s %s%s',
89 self.log_message(r'"%s" %s %s%s',
90 self.requestline, str(code), str(size),
90 self.requestline, str(code), str(size),
91 r''.join([r' %s:%s' % h for h in sorted(xheaders)]))
91 r''.join([r' %s:%s' % h for h in sorted(xheaders)]))
92
92
93 def do_write(self):
93 def do_write(self):
94 try:
94 try:
95 self.do_hgweb()
95 self.do_hgweb()
96 except socket.error as inst:
96 except socket.error as inst:
97 if inst.errno != errno.EPIPE:
97 if inst.errno != errno.EPIPE:
98 raise
98 raise
99
99
100 def do_POST(self):
100 def do_POST(self):
101 try:
101 try:
102 self.do_write()
102 self.do_write()
103 except Exception:
103 except Exception as e:
104 # I/O below could raise another exception. So log the original
104 # I/O below could raise another exception. So log the original
105 # exception first to ensure it is recorded.
105 # exception first to ensure it is recorded.
106 if not (isinstance(e, (OSError, socket.error))
107 and e.errno == errno.ECONNRESET):
106 tb = r"".join(traceback.format_exception(*sys.exc_info()))
108 tb = r"".join(traceback.format_exception(*sys.exc_info()))
107 # We need a native-string newline to poke in the log
109 # We need a native-string newline to poke in the log
108 # message, because we won't get a newline when using an
110 # message, because we won't get a newline when using an
109 # r-string. This is the easy way out.
111 # r-string. This is the easy way out.
110 newline = chr(10)
112 newline = chr(10)
111 self.log_error(r"Exception happened during processing "
113 self.log_error(r"Exception happened during processing "
112 r"request '%s':%s%s", self.path, newline, tb)
114 r"request '%s':%s%s", self.path, newline, tb)
113
115
114 self._start_response(r"500 Internal Server Error", [])
116 self._start_response(r"500 Internal Server Error", [])
115 self._write(b"Internal Server Error")
117 self._write(b"Internal Server Error")
116 self._done()
118 self._done()
117
119
118 def do_PUT(self):
120 def do_PUT(self):
119 self.do_POST()
121 self.do_POST()
120
122
121 def do_GET(self):
123 def do_GET(self):
122 self.do_POST()
124 self.do_POST()
123
125
124 def do_hgweb(self):
126 def do_hgweb(self):
125 self.sent_headers = False
127 self.sent_headers = False
126 path, query = _splitURI(self.path)
128 path, query = _splitURI(self.path)
127
129
128 # Ensure the slicing of path below is valid
130 # Ensure the slicing of path below is valid
129 if (path != self.server.prefix
131 if (path != self.server.prefix
130 and not path.startswith(self.server.prefix + b'/')):
132 and not path.startswith(self.server.prefix + b'/')):
131 self._start_response(pycompat.strurl(common.statusmessage(404)),
133 self._start_response(pycompat.strurl(common.statusmessage(404)),
132 [])
134 [])
133 if self.command == 'POST':
135 if self.command == 'POST':
134 # Paranoia: tell the client we're going to close the
136 # Paranoia: tell the client we're going to close the
135 # socket so they don't try and reuse a socket that
137 # socket so they don't try and reuse a socket that
136 # might have a POST body waiting to confuse us. We do
138 # might have a POST body waiting to confuse us. We do
137 # this by directly munging self.saved_headers because
139 # this by directly munging self.saved_headers because
138 # self._start_response ignores Connection headers.
140 # self._start_response ignores Connection headers.
139 self.saved_headers = [(r'Connection', r'Close')]
141 self.saved_headers = [(r'Connection', r'Close')]
140 self._write(b"Not Found")
142 self._write(b"Not Found")
141 self._done()
143 self._done()
142 return
144 return
143
145
144 env = {}
146 env = {}
145 env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
147 env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
146 env[r'REQUEST_METHOD'] = self.command
148 env[r'REQUEST_METHOD'] = self.command
147 env[r'SERVER_NAME'] = self.server.server_name
149 env[r'SERVER_NAME'] = self.server.server_name
148 env[r'SERVER_PORT'] = str(self.server.server_port)
150 env[r'SERVER_PORT'] = str(self.server.server_port)
149 env[r'REQUEST_URI'] = self.path
151 env[r'REQUEST_URI'] = self.path
150 env[r'SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
152 env[r'SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
151 env[r'PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix):])
153 env[r'PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix):])
152 env[r'REMOTE_HOST'] = self.client_address[0]
154 env[r'REMOTE_HOST'] = self.client_address[0]
153 env[r'REMOTE_ADDR'] = self.client_address[0]
155 env[r'REMOTE_ADDR'] = self.client_address[0]
154 env[r'QUERY_STRING'] = query or r''
156 env[r'QUERY_STRING'] = query or r''
155
157
156 if pycompat.ispy3:
158 if pycompat.ispy3:
157 if self.headers.get_content_type() is None:
159 if self.headers.get_content_type() is None:
158 env[r'CONTENT_TYPE'] = self.headers.get_default_type()
160 env[r'CONTENT_TYPE'] = self.headers.get_default_type()
159 else:
161 else:
160 env[r'CONTENT_TYPE'] = self.headers.get_content_type()
162 env[r'CONTENT_TYPE'] = self.headers.get_content_type()
161 length = self.headers.get(r'content-length')
163 length = self.headers.get(r'content-length')
162 else:
164 else:
163 if self.headers.typeheader is None:
165 if self.headers.typeheader is None:
164 env[r'CONTENT_TYPE'] = self.headers.type
166 env[r'CONTENT_TYPE'] = self.headers.type
165 else:
167 else:
166 env[r'CONTENT_TYPE'] = self.headers.typeheader
168 env[r'CONTENT_TYPE'] = self.headers.typeheader
167 length = self.headers.getheader(r'content-length')
169 length = self.headers.getheader(r'content-length')
168 if length:
170 if length:
169 env[r'CONTENT_LENGTH'] = length
171 env[r'CONTENT_LENGTH'] = length
170 for header in [h for h in self.headers.keys()
172 for header in [h for h in self.headers.keys()
171 if h.lower() not in (r'content-type', r'content-length')]:
173 if h.lower() not in (r'content-type', r'content-length')]:
172 hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
174 hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
173 hval = self.headers.get(header)
175 hval = self.headers.get(header)
174 hval = hval.replace(r'\n', r'').strip()
176 hval = hval.replace(r'\n', r'').strip()
175 if hval:
177 if hval:
176 env[hkey] = hval
178 env[hkey] = hval
177 env[r'SERVER_PROTOCOL'] = self.request_version
179 env[r'SERVER_PROTOCOL'] = self.request_version
178 env[r'wsgi.version'] = (1, 0)
180 env[r'wsgi.version'] = (1, 0)
179 env[r'wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
181 env[r'wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
180 if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
182 if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
181 self.rfile = common.continuereader(self.rfile, self.wfile.write)
183 self.rfile = common.continuereader(self.rfile, self.wfile.write)
182
184
183 env[r'wsgi.input'] = self.rfile
185 env[r'wsgi.input'] = self.rfile
184 env[r'wsgi.errors'] = _error_logger(self)
186 env[r'wsgi.errors'] = _error_logger(self)
185 env[r'wsgi.multithread'] = isinstance(self.server,
187 env[r'wsgi.multithread'] = isinstance(self.server,
186 socketserver.ThreadingMixIn)
188 socketserver.ThreadingMixIn)
187 if util.safehasattr(socketserver, 'ForkingMixIn'):
189 if util.safehasattr(socketserver, 'ForkingMixIn'):
188 env[r'wsgi.multiprocess'] = isinstance(self.server,
190 env[r'wsgi.multiprocess'] = isinstance(self.server,
189 socketserver.ForkingMixIn)
191 socketserver.ForkingMixIn)
190 else:
192 else:
191 env[r'wsgi.multiprocess'] = False
193 env[r'wsgi.multiprocess'] = False
192
194
193 env[r'wsgi.run_once'] = 0
195 env[r'wsgi.run_once'] = 0
194
196
195 wsgiref.validate.check_environ(env)
197 wsgiref.validate.check_environ(env)
196
198
197 self.saved_status = None
199 self.saved_status = None
198 self.saved_headers = []
200 self.saved_headers = []
199 self.length = None
201 self.length = None
200 self._chunked = None
202 self._chunked = None
201 for chunk in self.server.application(env, self._start_response):
203 for chunk in self.server.application(env, self._start_response):
202 self._write(chunk)
204 self._write(chunk)
203 if not self.sent_headers:
205 if not self.sent_headers:
204 self.send_headers()
206 self.send_headers()
205 self._done()
207 self._done()
206
208
207 def send_headers(self):
209 def send_headers(self):
208 if not self.saved_status:
210 if not self.saved_status:
209 raise AssertionError("Sending headers before "
211 raise AssertionError("Sending headers before "
210 "start_response() called")
212 "start_response() called")
211 saved_status = self.saved_status.split(None, 1)
213 saved_status = self.saved_status.split(None, 1)
212 saved_status[0] = int(saved_status[0])
214 saved_status[0] = int(saved_status[0])
213 self.send_response(*saved_status)
215 self.send_response(*saved_status)
214 self.length = None
216 self.length = None
215 self._chunked = False
217 self._chunked = False
216 for h in self.saved_headers:
218 for h in self.saved_headers:
217 self.send_header(*h)
219 self.send_header(*h)
218 if h[0].lower() == r'content-length':
220 if h[0].lower() == r'content-length':
219 self.length = int(h[1])
221 self.length = int(h[1])
220 if (self.length is None and
222 if (self.length is None and
221 saved_status[0] != common.HTTP_NOT_MODIFIED):
223 saved_status[0] != common.HTTP_NOT_MODIFIED):
222 self._chunked = (not self.close_connection and
224 self._chunked = (not self.close_connection and
223 self.request_version == r'HTTP/1.1')
225 self.request_version == r'HTTP/1.1')
224 if self._chunked:
226 if self._chunked:
225 self.send_header(r'Transfer-Encoding', r'chunked')
227 self.send_header(r'Transfer-Encoding', r'chunked')
226 else:
228 else:
227 self.send_header(r'Connection', r'close')
229 self.send_header(r'Connection', r'close')
228 self.end_headers()
230 self.end_headers()
229 self.sent_headers = True
231 self.sent_headers = True
230
232
231 def _start_response(self, http_status, headers, exc_info=None):
233 def _start_response(self, http_status, headers, exc_info=None):
232 assert isinstance(http_status, str)
234 assert isinstance(http_status, str)
233 code, msg = http_status.split(None, 1)
235 code, msg = http_status.split(None, 1)
234 code = int(code)
236 code = int(code)
235 self.saved_status = http_status
237 self.saved_status = http_status
236 bad_headers = (r'connection', r'transfer-encoding')
238 bad_headers = (r'connection', r'transfer-encoding')
237 self.saved_headers = [h for h in headers
239 self.saved_headers = [h for h in headers
238 if h[0].lower() not in bad_headers]
240 if h[0].lower() not in bad_headers]
239 return self._write
241 return self._write
240
242
241 def _write(self, data):
243 def _write(self, data):
242 if not self.saved_status:
244 if not self.saved_status:
243 raise AssertionError("data written before start_response() called")
245 raise AssertionError("data written before start_response() called")
244 elif not self.sent_headers:
246 elif not self.sent_headers:
245 self.send_headers()
247 self.send_headers()
246 if self.length is not None:
248 if self.length is not None:
247 if len(data) > self.length:
249 if len(data) > self.length:
248 raise AssertionError("Content-length header sent, but more "
250 raise AssertionError("Content-length header sent, but more "
249 "bytes than specified are being written.")
251 "bytes than specified are being written.")
250 self.length = self.length - len(data)
252 self.length = self.length - len(data)
251 elif self._chunked and data:
253 elif self._chunked and data:
252 data = '%x\r\n%s\r\n' % (len(data), data)
254 data = '%x\r\n%s\r\n' % (len(data), data)
253 self.wfile.write(data)
255 self.wfile.write(data)
254 self.wfile.flush()
256 self.wfile.flush()
255
257
256 def _done(self):
258 def _done(self):
257 if self._chunked:
259 if self._chunked:
258 self.wfile.write('0\r\n\r\n')
260 self.wfile.write('0\r\n\r\n')
259 self.wfile.flush()
261 self.wfile.flush()
260
262
261 def version_string(self):
263 def version_string(self):
262 if self.server.serverheader:
264 if self.server.serverheader:
263 return encoding.strfromlocal(self.server.serverheader)
265 return encoding.strfromlocal(self.server.serverheader)
264 return httpservermod.basehttprequesthandler.version_string(self)
266 return httpservermod.basehttprequesthandler.version_string(self)
265
267
266 class _httprequesthandlerssl(_httprequesthandler):
268 class _httprequesthandlerssl(_httprequesthandler):
267 """HTTPS handler based on Python's ssl module"""
269 """HTTPS handler based on Python's ssl module"""
268
270
269 url_scheme = 'https'
271 url_scheme = 'https'
270
272
271 @staticmethod
273 @staticmethod
272 def preparehttpserver(httpserver, ui):
274 def preparehttpserver(httpserver, ui):
273 try:
275 try:
274 from .. import sslutil
276 from .. import sslutil
275 sslutil.modernssl
277 sslutil.modernssl
276 except ImportError:
278 except ImportError:
277 raise error.Abort(_("SSL support is unavailable"))
279 raise error.Abort(_("SSL support is unavailable"))
278
280
279 certfile = ui.config('web', 'certificate')
281 certfile = ui.config('web', 'certificate')
280
282
281 # These config options are currently only meant for testing. Use
283 # These config options are currently only meant for testing. Use
282 # at your own risk.
284 # at your own risk.
283 cafile = ui.config('devel', 'servercafile')
285 cafile = ui.config('devel', 'servercafile')
284 reqcert = ui.configbool('devel', 'serverrequirecert')
286 reqcert = ui.configbool('devel', 'serverrequirecert')
285
287
286 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
288 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
287 ui,
289 ui,
288 certfile=certfile,
290 certfile=certfile,
289 cafile=cafile,
291 cafile=cafile,
290 requireclientcert=reqcert)
292 requireclientcert=reqcert)
291
293
292 def setup(self):
294 def setup(self):
293 self.connection = self.request
295 self.connection = self.request
294 self.rfile = self.request.makefile(r"rb", self.rbufsize)
296 self.rfile = self.request.makefile(r"rb", self.rbufsize)
295 self.wfile = self.request.makefile(r"wb", self.wbufsize)
297 self.wfile = self.request.makefile(r"wb", self.wbufsize)
296
298
297 try:
299 try:
298 import threading
300 import threading
299 threading.activeCount() # silence pyflakes and bypass demandimport
301 threading.activeCount() # silence pyflakes and bypass demandimport
300 _mixin = socketserver.ThreadingMixIn
302 _mixin = socketserver.ThreadingMixIn
301 except ImportError:
303 except ImportError:
302 if util.safehasattr(os, "fork"):
304 if util.safehasattr(os, "fork"):
303 _mixin = socketserver.ForkingMixIn
305 _mixin = socketserver.ForkingMixIn
304 else:
306 else:
305 class _mixin(object):
307 class _mixin(object):
306 pass
308 pass
307
309
308 def openlog(opt, default):
310 def openlog(opt, default):
309 if opt and opt != '-':
311 if opt and opt != '-':
310 return open(opt, 'ab')
312 return open(opt, 'ab')
311 return default
313 return default
312
314
313 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
315 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
314
316
315 # SO_REUSEADDR has broken semantics on windows
317 # SO_REUSEADDR has broken semantics on windows
316 if pycompat.iswindows:
318 if pycompat.iswindows:
317 allow_reuse_address = 0
319 allow_reuse_address = 0
318
320
319 def __init__(self, ui, app, addr, handler, **kwargs):
321 def __init__(self, ui, app, addr, handler, **kwargs):
320 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
322 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
321 self.daemon_threads = True
323 self.daemon_threads = True
322 self.application = app
324 self.application = app
323
325
324 handler.preparehttpserver(self, ui)
326 handler.preparehttpserver(self, ui)
325
327
326 prefix = ui.config('web', 'prefix')
328 prefix = ui.config('web', 'prefix')
327 if prefix:
329 if prefix:
328 prefix = '/' + prefix.strip('/')
330 prefix = '/' + prefix.strip('/')
329 self.prefix = prefix
331 self.prefix = prefix
330
332
331 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
333 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
332 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
334 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
333 self.accesslog = alog
335 self.accesslog = alog
334 self.errorlog = elog
336 self.errorlog = elog
335
337
336 self.addr, self.port = self.socket.getsockname()[0:2]
338 self.addr, self.port = self.socket.getsockname()[0:2]
337 self.fqaddr = socket.getfqdn(addr[0])
339 self.fqaddr = socket.getfqdn(addr[0])
338
340
339 self.serverheader = ui.config('web', 'server-header')
341 self.serverheader = ui.config('web', 'server-header')
340
342
341 class IPv6HTTPServer(MercurialHTTPServer):
343 class IPv6HTTPServer(MercurialHTTPServer):
342 address_family = getattr(socket, 'AF_INET6', None)
344 address_family = getattr(socket, 'AF_INET6', None)
343 def __init__(self, *args, **kwargs):
345 def __init__(self, *args, **kwargs):
344 if self.address_family is None:
346 if self.address_family is None:
345 raise error.RepoError(_('IPv6 is not available on this system'))
347 raise error.RepoError(_('IPv6 is not available on this system'))
346 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
348 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
347
349
348 def create_server(ui, app):
350 def create_server(ui, app):
349
351
350 if ui.config('web', 'certificate'):
352 if ui.config('web', 'certificate'):
351 handler = _httprequesthandlerssl
353 handler = _httprequesthandlerssl
352 else:
354 else:
353 handler = _httprequesthandler
355 handler = _httprequesthandler
354
356
355 if ui.configbool('web', 'ipv6'):
357 if ui.configbool('web', 'ipv6'):
356 cls = IPv6HTTPServer
358 cls = IPv6HTTPServer
357 else:
359 else:
358 cls = MercurialHTTPServer
360 cls = MercurialHTTPServer
359
361
360 # ugly hack due to python issue5853 (for threaded use)
362 # ugly hack due to python issue5853 (for threaded use)
361 try:
363 try:
362 import mimetypes
364 import mimetypes
363 mimetypes.init()
365 mimetypes.init()
364 except UnicodeDecodeError:
366 except UnicodeDecodeError:
365 # Python 2.x's mimetypes module attempts to decode strings
367 # Python 2.x's mimetypes module attempts to decode strings
366 # from Windows' ANSI APIs as ascii (fail), then re-encode them
368 # from Windows' ANSI APIs as ascii (fail), then re-encode them
367 # as ascii (clown fail), because the default Python Unicode
369 # as ascii (clown fail), because the default Python Unicode
368 # codec is hardcoded as ascii.
370 # codec is hardcoded as ascii.
369
371
370 sys.argv # unwrap demand-loader so that reload() works
372 sys.argv # unwrap demand-loader so that reload() works
371 reload(sys) # resurrect sys.setdefaultencoding()
373 reload(sys) # resurrect sys.setdefaultencoding()
372 oldenc = sys.getdefaultencoding()
374 oldenc = sys.getdefaultencoding()
373 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
375 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
374 mimetypes.init()
376 mimetypes.init()
375 sys.setdefaultencoding(oldenc)
377 sys.setdefaultencoding(oldenc)
376
378
377 address = ui.config('web', 'address')
379 address = ui.config('web', 'address')
378 port = util.getport(ui.config('web', 'port'))
380 port = util.getport(ui.config('web', 'port'))
379 try:
381 try:
380 return cls(ui, app, (address, port), handler)
382 return cls(ui, app, (address, port), handler)
381 except socket.error as inst:
383 except socket.error as inst:
382 raise error.Abort(_("cannot start server at '%s:%d': %s")
384 raise error.Abort(_("cannot start server at '%s:%d': %s")
383 % (address, port, encoding.strtolocal(inst.args[1])))
385 % (address, port, encoding.strtolocal(inst.args[1])))
@@ -1,979 +1,967
1 #require serve
1 #require serve
2
2
3 Some tests for hgweb. Tests static files, plain files and different 404's.
3 Some tests for hgweb. Tests static files, plain files and different 404's.
4
4
5 $ hg init test
5 $ hg init test
6 $ cd test
6 $ cd test
7 $ mkdir da
7 $ mkdir da
8 $ echo foo > da/foo
8 $ echo foo > da/foo
9 $ echo foo > foo
9 $ echo foo > foo
10 $ hg ci -Ambase
10 $ hg ci -Ambase
11 adding da/foo
11 adding da/foo
12 adding foo
12 adding foo
13 $ hg bookmark -r0 '@'
13 $ hg bookmark -r0 '@'
14 $ hg bookmark -r0 'a b c'
14 $ hg bookmark -r0 'a b c'
15 $ hg bookmark -r0 'd/e/f'
15 $ hg bookmark -r0 'd/e/f'
16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
17 $ cat hg.pid >> $DAEMON_PIDS
17 $ cat hg.pid >> $DAEMON_PIDS
18
18
19 manifest
19 manifest
20
20
21 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw')
21 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw')
22 200 Script output follows
22 200 Script output follows
23
23
24
24
25 drwxr-xr-x da
25 drwxr-xr-x da
26 -rw-r--r-- 4 foo
26 -rw-r--r-- 4 foo
27
27
28
28
29 $ (get-with-headers.py localhost:$HGPORT 'file/tip/da?style=raw')
29 $ (get-with-headers.py localhost:$HGPORT 'file/tip/da?style=raw')
30 200 Script output follows
30 200 Script output follows
31
31
32
32
33 -rw-r--r-- 4 foo
33 -rw-r--r-- 4 foo
34
34
35
35
36
36
37 plain file
37 plain file
38
38
39 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?style=raw'
39 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?style=raw'
40 200 Script output follows
40 200 Script output follows
41
41
42 foo
42 foo
43
43
44 should give a 404 - static file that does not exist
44 should give a 404 - static file that does not exist
45
45
46 $ get-with-headers.py localhost:$HGPORT 'static/bogus'
46 $ get-with-headers.py localhost:$HGPORT 'static/bogus'
47 404 Not Found
47 404 Not Found
48
48
49 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
49 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
51 <head>
51 <head>
52 <link rel="icon" href="/static/hgicon.png" type="image/png" />
52 <link rel="icon" href="/static/hgicon.png" type="image/png" />
53 <meta name="robots" content="index, nofollow" />
53 <meta name="robots" content="index, nofollow" />
54 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
54 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
55 <script type="text/javascript" src="/static/mercurial.js"></script>
55 <script type="text/javascript" src="/static/mercurial.js"></script>
56
56
57 <title>test: error</title>
57 <title>test: error</title>
58 </head>
58 </head>
59 <body>
59 <body>
60
60
61 <div class="container">
61 <div class="container">
62 <div class="menu">
62 <div class="menu">
63 <div class="logo">
63 <div class="logo">
64 <a href="https://mercurial-scm.org/">
64 <a href="https://mercurial-scm.org/">
65 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
65 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
66 </div>
66 </div>
67 <ul>
67 <ul>
68 <li><a href="/shortlog">log</a></li>
68 <li><a href="/shortlog">log</a></li>
69 <li><a href="/graph">graph</a></li>
69 <li><a href="/graph">graph</a></li>
70 <li><a href="/tags">tags</a></li>
70 <li><a href="/tags">tags</a></li>
71 <li><a href="/bookmarks">bookmarks</a></li>
71 <li><a href="/bookmarks">bookmarks</a></li>
72 <li><a href="/branches">branches</a></li>
72 <li><a href="/branches">branches</a></li>
73 </ul>
73 </ul>
74 <ul>
74 <ul>
75 <li><a href="/help">help</a></li>
75 <li><a href="/help">help</a></li>
76 </ul>
76 </ul>
77 </div>
77 </div>
78
78
79 <div class="main">
79 <div class="main">
80
80
81 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
81 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
82 <h3>error</h3>
82 <h3>error</h3>
83
83
84
84
85 <form class="search" action="/log">
85 <form class="search" action="/log">
86
86
87 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
87 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
88 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
88 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
89 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
89 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
90 </form>
90 </form>
91
91
92 <div class="description">
92 <div class="description">
93 <p>
93 <p>
94 An error occurred while processing your request:
94 An error occurred while processing your request:
95 </p>
95 </p>
96 <p>
96 <p>
97 Not Found
97 Not Found
98 </p>
98 </p>
99 </div>
99 </div>
100 </div>
100 </div>
101 </div>
101 </div>
102
102
103
103
104
104
105 </body>
105 </body>
106 </html>
106 </html>
107
107
108 [1]
108 [1]
109
109
110 should give a 404 - bad revision
110 should give a 404 - bad revision
111
111
112 $ get-with-headers.py localhost:$HGPORT 'file/spam/foo?style=raw'
112 $ get-with-headers.py localhost:$HGPORT 'file/spam/foo?style=raw'
113 404 Not Found
113 404 Not Found
114
114
115
115
116 error: revision not found: spam
116 error: revision not found: spam
117 [1]
117 [1]
118
118
119 should give a 400 - bad command
119 should give a 400 - bad command
120
120
121 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
121 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
122 400* (glob)
122 400* (glob)
123
123
124
124
125 error: no such method: spam
125 error: no such method: spam
126 [1]
126 [1]
127
127
128 $ get-with-headers.py --headeronly localhost:$HGPORT '?cmd=spam'
128 $ get-with-headers.py --headeronly localhost:$HGPORT '?cmd=spam'
129 400 no such method: spam
129 400 no such method: spam
130 [1]
130 [1]
131
131
132 should give a 400 - bad command as a part of url path (issue4071)
132 should give a 400 - bad command as a part of url path (issue4071)
133
133
134 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam'
134 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam'
135 400 no such method: spam
135 400 no such method: spam
136 [1]
136 [1]
137
137
138 $ get-with-headers.py --headeronly localhost:$HGPORT 'raw-spam'
138 $ get-with-headers.py --headeronly localhost:$HGPORT 'raw-spam'
139 400 no such method: spam
139 400 no such method: spam
140 [1]
140 [1]
141
141
142 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam/tip/foo'
142 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam/tip/foo'
143 400 no such method: spam
143 400 no such method: spam
144 [1]
144 [1]
145
145
146 should give a 404 - file does not exist
146 should give a 404 - file does not exist
147
147
148 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork?style=raw'
148 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork?style=raw'
149 404 Not Found
149 404 Not Found
150
150
151
151
152 error: bork@2ef0ac749a14: not found in manifest
152 error: bork@2ef0ac749a14: not found in manifest
153 [1]
153 [1]
154 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork'
154 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork'
155 404 Not Found
155 404 Not Found
156
156
157 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
157 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
158 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
158 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
159 <head>
159 <head>
160 <link rel="icon" href="/static/hgicon.png" type="image/png" />
160 <link rel="icon" href="/static/hgicon.png" type="image/png" />
161 <meta name="robots" content="index, nofollow" />
161 <meta name="robots" content="index, nofollow" />
162 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
162 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
163 <script type="text/javascript" src="/static/mercurial.js"></script>
163 <script type="text/javascript" src="/static/mercurial.js"></script>
164
164
165 <title>test: error</title>
165 <title>test: error</title>
166 </head>
166 </head>
167 <body>
167 <body>
168
168
169 <div class="container">
169 <div class="container">
170 <div class="menu">
170 <div class="menu">
171 <div class="logo">
171 <div class="logo">
172 <a href="https://mercurial-scm.org/">
172 <a href="https://mercurial-scm.org/">
173 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
173 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
174 </div>
174 </div>
175 <ul>
175 <ul>
176 <li><a href="/shortlog">log</a></li>
176 <li><a href="/shortlog">log</a></li>
177 <li><a href="/graph">graph</a></li>
177 <li><a href="/graph">graph</a></li>
178 <li><a href="/tags">tags</a></li>
178 <li><a href="/tags">tags</a></li>
179 <li><a href="/bookmarks">bookmarks</a></li>
179 <li><a href="/bookmarks">bookmarks</a></li>
180 <li><a href="/branches">branches</a></li>
180 <li><a href="/branches">branches</a></li>
181 </ul>
181 </ul>
182 <ul>
182 <ul>
183 <li><a href="/help">help</a></li>
183 <li><a href="/help">help</a></li>
184 </ul>
184 </ul>
185 </div>
185 </div>
186
186
187 <div class="main">
187 <div class="main">
188
188
189 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
189 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
190 <h3>error</h3>
190 <h3>error</h3>
191
191
192
192
193 <form class="search" action="/log">
193 <form class="search" action="/log">
194
194
195 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
195 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
196 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
196 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
197 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
197 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
198 </form>
198 </form>
199
199
200 <div class="description">
200 <div class="description">
201 <p>
201 <p>
202 An error occurred while processing your request:
202 An error occurred while processing your request:
203 </p>
203 </p>
204 <p>
204 <p>
205 bork@2ef0ac749a14: not found in manifest
205 bork@2ef0ac749a14: not found in manifest
206 </p>
206 </p>
207 </div>
207 </div>
208 </div>
208 </div>
209 </div>
209 </div>
210
210
211
211
212
212
213 </body>
213 </body>
214 </html>
214 </html>
215
215
216 [1]
216 [1]
217 $ get-with-headers.py localhost:$HGPORT 'diff/tip/bork?style=raw'
217 $ get-with-headers.py localhost:$HGPORT 'diff/tip/bork?style=raw'
218 404 Not Found
218 404 Not Found
219
219
220
220
221 error: bork@2ef0ac749a14: not found in manifest
221 error: bork@2ef0ac749a14: not found in manifest
222 [1]
222 [1]
223
223
224 try bad style
224 try bad style
225
225
226 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=foobar')
226 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=foobar')
227 200 Script output follows
227 200 Script output follows
228
228
229 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
229 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
230 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
230 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
231 <head>
231 <head>
232 <link rel="icon" href="/static/hgicon.png" type="image/png" />
232 <link rel="icon" href="/static/hgicon.png" type="image/png" />
233 <meta name="robots" content="index, nofollow" />
233 <meta name="robots" content="index, nofollow" />
234 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
234 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
235 <script type="text/javascript" src="/static/mercurial.js"></script>
235 <script type="text/javascript" src="/static/mercurial.js"></script>
236
236
237 <title>test: 2ef0ac749a14 /</title>
237 <title>test: 2ef0ac749a14 /</title>
238 </head>
238 </head>
239 <body>
239 <body>
240
240
241 <div class="container">
241 <div class="container">
242 <div class="menu">
242 <div class="menu">
243 <div class="logo">
243 <div class="logo">
244 <a href="https://mercurial-scm.org/">
244 <a href="https://mercurial-scm.org/">
245 <img src="/static/hglogo.png" alt="mercurial" /></a>
245 <img src="/static/hglogo.png" alt="mercurial" /></a>
246 </div>
246 </div>
247 <ul>
247 <ul>
248 <li><a href="/shortlog/tip">log</a></li>
248 <li><a href="/shortlog/tip">log</a></li>
249 <li><a href="/graph/tip">graph</a></li>
249 <li><a href="/graph/tip">graph</a></li>
250 <li><a href="/tags">tags</a></li>
250 <li><a href="/tags">tags</a></li>
251 <li><a href="/bookmarks">bookmarks</a></li>
251 <li><a href="/bookmarks">bookmarks</a></li>
252 <li><a href="/branches">branches</a></li>
252 <li><a href="/branches">branches</a></li>
253 </ul>
253 </ul>
254 <ul>
254 <ul>
255 <li><a href="/rev/tip">changeset</a></li>
255 <li><a href="/rev/tip">changeset</a></li>
256 <li class="active">browse</li>
256 <li class="active">browse</li>
257 </ul>
257 </ul>
258 <ul>
258 <ul>
259
259
260 </ul>
260 </ul>
261 <ul>
261 <ul>
262 <li><a href="/help">help</a></li>
262 <li><a href="/help">help</a></li>
263 </ul>
263 </ul>
264 </div>
264 </div>
265
265
266 <div class="main">
266 <div class="main">
267 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
267 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
268 <h3>
268 <h3>
269 directory / @ 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
269 directory / @ 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
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>
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 </h3>
271 </h3>
272
272
273
273
274 <form class="search" action="/log">
274 <form class="search" action="/log">
275
275
276 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
276 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
277 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
277 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
278 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
278 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
279 </form>
279 </form>
280
280
281 <table class="bigtable">
281 <table class="bigtable">
282 <thead>
282 <thead>
283 <tr>
283 <tr>
284 <th class="name">name</th>
284 <th class="name">name</th>
285 <th class="size">size</th>
285 <th class="size">size</th>
286 <th class="permissions">permissions</th>
286 <th class="permissions">permissions</th>
287 </tr>
287 </tr>
288 </thead>
288 </thead>
289 <tbody class="stripes2">
289 <tbody class="stripes2">
290
290
291
291
292 <tr class="fileline">
292 <tr class="fileline">
293 <td class="name">
293 <td class="name">
294 <a href="/file/tip/da">
294 <a href="/file/tip/da">
295 <img src="/static/coal-folder.png" alt="dir."/> da/
295 <img src="/static/coal-folder.png" alt="dir."/> da/
296 </a>
296 </a>
297 <a href="/file/tip/da/">
297 <a href="/file/tip/da/">
298
298
299 </a>
299 </a>
300 </td>
300 </td>
301 <td class="size"></td>
301 <td class="size"></td>
302 <td class="permissions">drwxr-xr-x</td>
302 <td class="permissions">drwxr-xr-x</td>
303 </tr>
303 </tr>
304
304
305 <tr class="fileline">
305 <tr class="fileline">
306 <td class="filename">
306 <td class="filename">
307 <a href="/file/tip/foo">
307 <a href="/file/tip/foo">
308 <img src="/static/coal-file.png" alt="file"/> foo
308 <img src="/static/coal-file.png" alt="file"/> foo
309 </a>
309 </a>
310 </td>
310 </td>
311 <td class="size">4</td>
311 <td class="size">4</td>
312 <td class="permissions">-rw-r--r--</td>
312 <td class="permissions">-rw-r--r--</td>
313 </tr>
313 </tr>
314 </tbody>
314 </tbody>
315 </table>
315 </table>
316 </div>
316 </div>
317 </div>
317 </div>
318
318
319
319
320 </body>
320 </body>
321 </html>
321 </html>
322
322
323
323
324 stop and restart
324 stop and restart
325
325
326 $ killdaemons.py
326 $ killdaemons.py
327 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
327 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
328 $ cat hg.pid >> $DAEMON_PIDS
328 $ cat hg.pid >> $DAEMON_PIDS
329
329
330 Test the access/error files are opened in append mode
330 Test the access/error files are opened in append mode
331
331
332 $ "$PYTHON" -c "from __future__ import print_function; print(len(open('access.log', 'rb').readlines()), 'log lines written')"
332 $ "$PYTHON" -c "from __future__ import print_function; print(len(open('access.log', 'rb').readlines()), 'log lines written')"
333 14 log lines written
333 14 log lines written
334
334
335 static file
335 static file
336
336
337 $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
337 $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
338 200 Script output follows
338 200 Script output follows
339 content-length: 9074
339 content-length: 9074
340 content-type: text/css
340 content-type: text/css
341
341
342 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
342 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
343 a { color:#0000cc; }
343 a { color:#0000cc; }
344 a:hover, a:visited, a:active { color:#880000; }
344 a:hover, a:visited, a:active { color:#880000; }
345 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
345 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
346 div.page_header a:visited { color:#0000cc; }
346 div.page_header a:visited { color:#0000cc; }
347 div.page_header a:hover { color:#880000; }
347 div.page_header a:hover { color:#880000; }
348 div.page_nav {
348 div.page_nav {
349 padding:8px;
349 padding:8px;
350 display: flex;
350 display: flex;
351 justify-content: space-between;
351 justify-content: space-between;
352 align-items: center;
352 align-items: center;
353 }
353 }
354 div.page_nav a:visited { color:#0000cc; }
354 div.page_nav a:visited { color:#0000cc; }
355 div.extra_nav {
355 div.extra_nav {
356 padding: 8px;
356 padding: 8px;
357 }
357 }
358 div.extra_nav a:visited {
358 div.extra_nav a:visited {
359 color: #0000cc;
359 color: #0000cc;
360 }
360 }
361 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
361 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
362 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
362 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
363 div.page_footer_text { float:left; color:#555555; font-style:italic; }
363 div.page_footer_text { float:left; color:#555555; font-style:italic; }
364 div.page_body { padding:8px; }
364 div.page_body { padding:8px; }
365 div.title, a.title {
365 div.title, a.title {
366 display:block; padding:6px 8px;
366 display:block; padding:6px 8px;
367 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
367 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
368 }
368 }
369 a.title:hover { background-color: #d9d8d1; }
369 a.title:hover { background-color: #d9d8d1; }
370 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
370 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
371 div.log_body { padding:8px 8px 8px 150px; }
371 div.log_body { padding:8px 8px 8px 150px; }
372 .age { white-space:nowrap; }
372 .age { white-space:nowrap; }
373 a.title span.age { position:relative; float:left; width:142px; font-style:italic; }
373 a.title span.age { position:relative; float:left; width:142px; font-style:italic; }
374 div.log_link {
374 div.log_link {
375 padding:0px 8px;
375 padding:0px 8px;
376 font-size:10px; font-family:sans-serif; font-style:normal;
376 font-size:10px; font-family:sans-serif; font-style:normal;
377 position:relative; float:left; width:136px;
377 position:relative; float:left; width:136px;
378 }
378 }
379 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
379 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
380 a.list { text-decoration:none; color:#000000; }
380 a.list { text-decoration:none; color:#000000; }
381 a.list:hover { text-decoration:underline; color:#880000; }
381 a.list:hover { text-decoration:underline; color:#880000; }
382 table { padding:8px 4px; }
382 table { padding:8px 4px; }
383 th { padding:2px 5px; font-size:12px; text-align:left; }
383 th { padding:2px 5px; font-size:12px; text-align:left; }
384 .parity0 { background-color:#ffffff; }
384 .parity0 { background-color:#ffffff; }
385 tr.dark, .parity1, pre.sourcelines.stripes > :nth-child(4n+4) { background-color:#f6f6f0; }
385 tr.dark, .parity1, pre.sourcelines.stripes > :nth-child(4n+4) { background-color:#f6f6f0; }
386 tr.light:hover, .parity0:hover, tr.dark:hover, .parity1:hover,
386 tr.light:hover, .parity0:hover, tr.dark:hover, .parity1:hover,
387 pre.sourcelines.stripes > :nth-child(4n+2):hover,
387 pre.sourcelines.stripes > :nth-child(4n+2):hover,
388 pre.sourcelines.stripes > :nth-child(4n+4):hover,
388 pre.sourcelines.stripes > :nth-child(4n+4):hover,
389 pre.sourcelines.stripes > :nth-child(4n+1):hover + :nth-child(4n+2),
389 pre.sourcelines.stripes > :nth-child(4n+1):hover + :nth-child(4n+2),
390 pre.sourcelines.stripes > :nth-child(4n+3):hover + :nth-child(4n+4) { background-color:#edece6; }
390 pre.sourcelines.stripes > :nth-child(4n+3):hover + :nth-child(4n+4) { background-color:#edece6; }
391 td { padding:2px 5px; font-size:12px; vertical-align:top; }
391 td { padding:2px 5px; font-size:12px; vertical-align:top; }
392 td.closed { background-color: #99f; }
392 td.closed { background-color: #99f; }
393 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
393 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
394 td.indexlinks { white-space: nowrap; }
394 td.indexlinks { white-space: nowrap; }
395 td.indexlinks a {
395 td.indexlinks a {
396 padding: 2px 5px; line-height: 10px;
396 padding: 2px 5px; line-height: 10px;
397 border: 1px solid;
397 border: 1px solid;
398 color: #ffffff; background-color: #7777bb;
398 color: #ffffff; background-color: #7777bb;
399 border-color: #aaaadd #333366 #333366 #aaaadd;
399 border-color: #aaaadd #333366 #333366 #aaaadd;
400 font-weight: bold; text-align: center; text-decoration: none;
400 font-weight: bold; text-align: center; text-decoration: none;
401 font-size: 10px;
401 font-size: 10px;
402 }
402 }
403 td.indexlinks a:hover { background-color: #6666aa; }
403 td.indexlinks a:hover { background-color: #6666aa; }
404 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
404 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
405
405
406 .search {
406 .search {
407 margin-right: 8px;
407 margin-right: 8px;
408 }
408 }
409
409
410 div#hint {
410 div#hint {
411 position: absolute;
411 position: absolute;
412 display: none;
412 display: none;
413 width: 250px;
413 width: 250px;
414 padding: 5px;
414 padding: 5px;
415 background: #ffc;
415 background: #ffc;
416 border: 1px solid yellow;
416 border: 1px solid yellow;
417 border-radius: 5px;
417 border-radius: 5px;
418 z-index: 15;
418 z-index: 15;
419 }
419 }
420
420
421 #searchform:hover div#hint { display: block; }
421 #searchform:hover div#hint { display: block; }
422
422
423 tr.thisrev a { color:#999999; text-decoration: none; }
423 tr.thisrev a { color:#999999; text-decoration: none; }
424 tr.thisrev pre { color:#009900; }
424 tr.thisrev pre { color:#009900; }
425 td.annotate {
425 td.annotate {
426 white-space: nowrap;
426 white-space: nowrap;
427 }
427 }
428 div.annotate-info {
428 div.annotate-info {
429 z-index: 5;
429 z-index: 5;
430 display: none;
430 display: none;
431 position: absolute;
431 position: absolute;
432 background-color: #FFFFFF;
432 background-color: #FFFFFF;
433 border: 1px solid #d9d8d1;
433 border: 1px solid #d9d8d1;
434 text-align: left;
434 text-align: left;
435 color: #000000;
435 color: #000000;
436 padding: 5px;
436 padding: 5px;
437 }
437 }
438 div.annotate-info a { color: #0000FF; text-decoration: underline; }
438 div.annotate-info a { color: #0000FF; text-decoration: underline; }
439 td.annotate:hover div.annotate-info { display: inline; }
439 td.annotate:hover div.annotate-info { display: inline; }
440
440
441 #diffopts-form {
441 #diffopts-form {
442 padding-left: 8px;
442 padding-left: 8px;
443 display: none;
443 display: none;
444 }
444 }
445
445
446 .linenr { color:#999999; text-decoration:none }
446 .linenr { color:#999999; text-decoration:none }
447 div.rss_logo { float: right; white-space: nowrap; }
447 div.rss_logo { float: right; white-space: nowrap; }
448 div.rss_logo a {
448 div.rss_logo a {
449 padding:3px 6px; line-height:10px;
449 padding:3px 6px; line-height:10px;
450 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
450 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
451 color:#ffffff; background-color:#ff6600;
451 color:#ffffff; background-color:#ff6600;
452 font-weight:bold; font-family:sans-serif; font-size:10px;
452 font-weight:bold; font-family:sans-serif; font-size:10px;
453 text-align:center; text-decoration:none;
453 text-align:center; text-decoration:none;
454 }
454 }
455 div.rss_logo a:hover { background-color:#ee5500; }
455 div.rss_logo a:hover { background-color:#ee5500; }
456 pre { margin: 0; }
456 pre { margin: 0; }
457 span.logtags span {
457 span.logtags span {
458 padding: 0px 4px;
458 padding: 0px 4px;
459 font-size: 10px;
459 font-size: 10px;
460 font-weight: normal;
460 font-weight: normal;
461 border: 1px solid;
461 border: 1px solid;
462 background-color: #ffaaff;
462 background-color: #ffaaff;
463 border-color: #ffccff #ff00ee #ff00ee #ffccff;
463 border-color: #ffccff #ff00ee #ff00ee #ffccff;
464 }
464 }
465 span.logtags span.phasetag {
465 span.logtags span.phasetag {
466 background-color: #dfafff;
466 background-color: #dfafff;
467 border-color: #e2b8ff #ce48ff #ce48ff #e2b8ff;
467 border-color: #e2b8ff #ce48ff #ce48ff #e2b8ff;
468 }
468 }
469 span.logtags span.obsoletetag {
469 span.logtags span.obsoletetag {
470 background-color: #dddddd;
470 background-color: #dddddd;
471 border-color: #e4e4e4 #a3a3a3 #a3a3a3 #e4e4e4;
471 border-color: #e4e4e4 #a3a3a3 #a3a3a3 #e4e4e4;
472 }
472 }
473 span.logtags span.instabilitytag {
473 span.logtags span.instabilitytag {
474 background-color: #ffb1c0;
474 background-color: #ffb1c0;
475 border-color: #ffbbc8 #ff4476 #ff4476 #ffbbc8;
475 border-color: #ffbbc8 #ff4476 #ff4476 #ffbbc8;
476 }
476 }
477 span.logtags span.tagtag {
477 span.logtags span.tagtag {
478 background-color: #ffffaa;
478 background-color: #ffffaa;
479 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
479 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
480 }
480 }
481 span.logtags span.branchtag {
481 span.logtags span.branchtag {
482 background-color: #aaffaa;
482 background-color: #aaffaa;
483 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
483 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
484 }
484 }
485 span.logtags span.inbranchtag {
485 span.logtags span.inbranchtag {
486 background-color: #d5dde6;
486 background-color: #d5dde6;
487 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
487 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
488 }
488 }
489 span.logtags span.bookmarktag {
489 span.logtags span.bookmarktag {
490 background-color: #afdffa;
490 background-color: #afdffa;
491 border-color: #ccecff #46ace6 #46ace6 #ccecff;
491 border-color: #ccecff #46ace6 #46ace6 #ccecff;
492 }
492 }
493 span.difflineplus { color:#008800; }
493 span.difflineplus { color:#008800; }
494 span.difflineminus { color:#cc0000; }
494 span.difflineminus { color:#cc0000; }
495 span.difflineat { color:#990099; }
495 span.difflineat { color:#990099; }
496 div.diffblocks { counter-reset: lineno; }
496 div.diffblocks { counter-reset: lineno; }
497 div.diffblock { counter-increment: lineno; }
497 div.diffblock { counter-increment: lineno; }
498 pre.sourcelines { position: relative; counter-reset: lineno; }
498 pre.sourcelines { position: relative; counter-reset: lineno; }
499 pre.sourcelines > span {
499 pre.sourcelines > span {
500 display: inline-block;
500 display: inline-block;
501 box-sizing: border-box;
501 box-sizing: border-box;
502 width: 100%;
502 width: 100%;
503 padding: 0 0 0 5em;
503 padding: 0 0 0 5em;
504 counter-increment: lineno;
504 counter-increment: lineno;
505 vertical-align: top;
505 vertical-align: top;
506 }
506 }
507 pre.sourcelines > span:before {
507 pre.sourcelines > span:before {
508 -moz-user-select: -moz-none;
508 -moz-user-select: -moz-none;
509 -khtml-user-select: none;
509 -khtml-user-select: none;
510 -webkit-user-select: none;
510 -webkit-user-select: none;
511 -ms-user-select: none;
511 -ms-user-select: none;
512 user-select: none;
512 user-select: none;
513 display: inline-block;
513 display: inline-block;
514 margin-left: -6em;
514 margin-left: -6em;
515 width: 4em;
515 width: 4em;
516 color: #999;
516 color: #999;
517 text-align: right;
517 text-align: right;
518 content: counters(lineno,".");
518 content: counters(lineno,".");
519 float: left;
519 float: left;
520 }
520 }
521 pre.sourcelines > a {
521 pre.sourcelines > a {
522 display: inline-block;
522 display: inline-block;
523 position: absolute;
523 position: absolute;
524 left: 0px;
524 left: 0px;
525 width: 4em;
525 width: 4em;
526 height: 1em;
526 height: 1em;
527 }
527 }
528 tr:target td,
528 tr:target td,
529 pre.sourcelines > span:target,
529 pre.sourcelines > span:target,
530 pre.sourcelines.stripes > span:target {
530 pre.sourcelines.stripes > span:target {
531 background-color: #bfdfff;
531 background-color: #bfdfff;
532 }
532 }
533
533
534 .description {
534 .description {
535 font-family: monospace;
535 font-family: monospace;
536 white-space: pre;
536 white-space: pre;
537 }
537 }
538
538
539 /* Followlines */
539 /* Followlines */
540 tbody.sourcelines > tr.followlines-selected,
540 tbody.sourcelines > tr.followlines-selected,
541 pre.sourcelines > span.followlines-selected {
541 pre.sourcelines > span.followlines-selected {
542 background-color: #99C7E9 !important;
542 background-color: #99C7E9 !important;
543 }
543 }
544
544
545 div#followlines {
545 div#followlines {
546 background-color: #FFF;
546 background-color: #FFF;
547 border: 1px solid #d9d8d1;
547 border: 1px solid #d9d8d1;
548 padding: 5px;
548 padding: 5px;
549 position: fixed;
549 position: fixed;
550 }
550 }
551
551
552 div.followlines-cancel {
552 div.followlines-cancel {
553 text-align: right;
553 text-align: right;
554 }
554 }
555
555
556 div.followlines-cancel > button {
556 div.followlines-cancel > button {
557 line-height: 80%;
557 line-height: 80%;
558 padding: 0;
558 padding: 0;
559 border: 0;
559 border: 0;
560 border-radius: 2px;
560 border-radius: 2px;
561 background-color: inherit;
561 background-color: inherit;
562 font-weight: bold;
562 font-weight: bold;
563 }
563 }
564
564
565 div.followlines-cancel > button:hover {
565 div.followlines-cancel > button:hover {
566 color: #FFFFFF;
566 color: #FFFFFF;
567 background-color: #CF1F1F;
567 background-color: #CF1F1F;
568 }
568 }
569
569
570 div.followlines-link {
570 div.followlines-link {
571 margin: 2px;
571 margin: 2px;
572 margin-top: 4px;
572 margin-top: 4px;
573 font-family: sans-serif;
573 font-family: sans-serif;
574 }
574 }
575
575
576 .btn-followlines {
576 .btn-followlines {
577 position: absolute;
577 position: absolute;
578 display: none;
578 display: none;
579 cursor: pointer;
579 cursor: pointer;
580 box-sizing: content-box;
580 box-sizing: content-box;
581 font-size: 11px;
581 font-size: 11px;
582 width: 13px;
582 width: 13px;
583 height: 13px;
583 height: 13px;
584 border-radius: 3px;
584 border-radius: 3px;
585 margin: 0px;
585 margin: 0px;
586 margin-top: -2px;
586 margin-top: -2px;
587 padding: 0px;
587 padding: 0px;
588 background-color: #E5FDE5;
588 background-color: #E5FDE5;
589 border: 1px solid #9BC19B;
589 border: 1px solid #9BC19B;
590 font-family: monospace;
590 font-family: monospace;
591 text-align: center;
591 text-align: center;
592 line-height: 5px;
592 line-height: 5px;
593 }
593 }
594
594
595 span.followlines-select .btn-followlines {
595 span.followlines-select .btn-followlines {
596 margin-left: -1.6em;
596 margin-left: -1.6em;
597 }
597 }
598
598
599 .btn-followlines:hover {
599 .btn-followlines:hover {
600 transform: scale(1.1, 1.1);
600 transform: scale(1.1, 1.1);
601 }
601 }
602
602
603 .btn-followlines .followlines-plus {
603 .btn-followlines .followlines-plus {
604 color: green;
604 color: green;
605 }
605 }
606
606
607 .btn-followlines .followlines-minus {
607 .btn-followlines .followlines-minus {
608 color: red;
608 color: red;
609 }
609 }
610
610
611 .btn-followlines-end {
611 .btn-followlines-end {
612 background-color: #ffdcdc;
612 background-color: #ffdcdc;
613 }
613 }
614
614
615 .sourcelines tr:hover .btn-followlines,
615 .sourcelines tr:hover .btn-followlines,
616 .sourcelines span.followlines-select:hover > .btn-followlines {
616 .sourcelines span.followlines-select:hover > .btn-followlines {
617 display: inline;
617 display: inline;
618 }
618 }
619
619
620 .btn-followlines-hidden,
620 .btn-followlines-hidden,
621 .sourcelines tr:hover .btn-followlines-hidden {
621 .sourcelines tr:hover .btn-followlines-hidden {
622 display: none;
622 display: none;
623 }
623 }
624
624
625 /* Graph */
625 /* Graph */
626 div#wrapper {
626 div#wrapper {
627 position: relative;
627 position: relative;
628 margin: 0;
628 margin: 0;
629 padding: 0;
629 padding: 0;
630 margin-top: 3px;
630 margin-top: 3px;
631 }
631 }
632
632
633 canvas {
633 canvas {
634 position: absolute;
634 position: absolute;
635 z-index: 5;
635 z-index: 5;
636 top: -0.9em;
636 top: -0.9em;
637 margin: 0;
637 margin: 0;
638 }
638 }
639
639
640 ul#graphnodes {
640 ul#graphnodes {
641 list-style: none inside none;
641 list-style: none inside none;
642 padding: 0;
642 padding: 0;
643 margin: 0;
643 margin: 0;
644 }
644 }
645
645
646 ul#graphnodes li {
646 ul#graphnodes li {
647 position: relative;
647 position: relative;
648 height: 37px;
648 height: 37px;
649 overflow: visible;
649 overflow: visible;
650 padding-top: 2px;
650 padding-top: 2px;
651 }
651 }
652
652
653 ul#graphnodes li .fg {
653 ul#graphnodes li .fg {
654 position: absolute;
654 position: absolute;
655 z-index: 10;
655 z-index: 10;
656 }
656 }
657
657
658 ul#graphnodes li .info {
658 ul#graphnodes li .info {
659 font-size: 100%;
659 font-size: 100%;
660 font-style: italic;
660 font-style: italic;
661 }
661 }
662
662
663 /* Comparison */
663 /* Comparison */
664 .legend {
664 .legend {
665 padding: 1.5% 0 1.5% 0;
665 padding: 1.5% 0 1.5% 0;
666 }
666 }
667
667
668 .legendinfo {
668 .legendinfo {
669 border: 1px solid #d9d8d1;
669 border: 1px solid #d9d8d1;
670 font-size: 80%;
670 font-size: 80%;
671 text-align: center;
671 text-align: center;
672 padding: 0.5%;
672 padding: 0.5%;
673 }
673 }
674
674
675 .equal {
675 .equal {
676 background-color: #ffffff;
676 background-color: #ffffff;
677 }
677 }
678
678
679 .delete {
679 .delete {
680 background-color: #faa;
680 background-color: #faa;
681 color: #333;
681 color: #333;
682 }
682 }
683
683
684 .insert {
684 .insert {
685 background-color: #ffa;
685 background-color: #ffa;
686 }
686 }
687
687
688 .replace {
688 .replace {
689 background-color: #e8e8e8;
689 background-color: #e8e8e8;
690 }
690 }
691
691
692 .comparison {
692 .comparison {
693 overflow-x: auto;
693 overflow-x: auto;
694 }
694 }
695
695
696 .header th {
696 .header th {
697 text-align: center;
697 text-align: center;
698 }
698 }
699
699
700 .block {
700 .block {
701 border-top: 1px solid #d9d8d1;
701 border-top: 1px solid #d9d8d1;
702 }
702 }
703
703
704 .scroll-loading {
704 .scroll-loading {
705 -webkit-animation: change_color 1s linear 0s infinite alternate;
705 -webkit-animation: change_color 1s linear 0s infinite alternate;
706 -moz-animation: change_color 1s linear 0s infinite alternate;
706 -moz-animation: change_color 1s linear 0s infinite alternate;
707 -o-animation: change_color 1s linear 0s infinite alternate;
707 -o-animation: change_color 1s linear 0s infinite alternate;
708 animation: change_color 1s linear 0s infinite alternate;
708 animation: change_color 1s linear 0s infinite alternate;
709 }
709 }
710
710
711 @-webkit-keyframes change_color {
711 @-webkit-keyframes change_color {
712 from { background-color: #A0CEFF; } to { }
712 from { background-color: #A0CEFF; } to { }
713 }
713 }
714 @-moz-keyframes change_color {
714 @-moz-keyframes change_color {
715 from { background-color: #A0CEFF; } to { }
715 from { background-color: #A0CEFF; } to { }
716 }
716 }
717 @-o-keyframes change_color {
717 @-o-keyframes change_color {
718 from { background-color: #A0CEFF; } to { }
718 from { background-color: #A0CEFF; } to { }
719 }
719 }
720 @keyframes change_color {
720 @keyframes change_color {
721 from { background-color: #A0CEFF; } to { }
721 from { background-color: #A0CEFF; } to { }
722 }
722 }
723
723
724 .scroll-loading-error {
724 .scroll-loading-error {
725 background-color: #FFCCCC !important;
725 background-color: #FFCCCC !important;
726 }
726 }
727
727
728 #doc {
728 #doc {
729 margin: 0 8px;
729 margin: 0 8px;
730 }
730 }
731 304 Not Modified
731 304 Not Modified
732
732
733
733
734 phase changes are refreshed (issue4061)
734 phase changes are refreshed (issue4061)
735
735
736 $ echo bar >> foo
736 $ echo bar >> foo
737 $ hg ci -msecret --secret
737 $ hg ci -msecret --secret
738 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
738 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
739 200 Script output follows
739 200 Script output follows
740
740
741
741
742 # HG changelog
742 # HG changelog
743 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
743 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
744
744
745 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
745 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
746 revision: 0
746 revision: 0
747 user: test
747 user: test
748 date: Thu, 01 Jan 1970 00:00:00 +0000
748 date: Thu, 01 Jan 1970 00:00:00 +0000
749 summary: base
749 summary: base
750 branch: default
750 branch: default
751 tag: tip
751 tag: tip
752 bookmark: @
752 bookmark: @
753 bookmark: a b c
753 bookmark: a b c
754 bookmark: d/e/f
754 bookmark: d/e/f
755
755
756
756
757 $ hg phase --draft tip
757 $ hg phase --draft tip
758 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
758 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
759 200 Script output follows
759 200 Script output follows
760
760
761
761
762 # HG changelog
762 # HG changelog
763 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
763 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
764
764
765 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
765 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
766 revision: 1
766 revision: 1
767 user: test
767 user: test
768 date: Thu, 01 Jan 1970 00:00:00 +0000
768 date: Thu, 01 Jan 1970 00:00:00 +0000
769 summary: secret
769 summary: secret
770 branch: default
770 branch: default
771 tag: tip
771 tag: tip
772
772
773 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
773 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
774 revision: 0
774 revision: 0
775 user: test
775 user: test
776 date: Thu, 01 Jan 1970 00:00:00 +0000
776 date: Thu, 01 Jan 1970 00:00:00 +0000
777 summary: base
777 summary: base
778 bookmark: @
778 bookmark: @
779 bookmark: a b c
779 bookmark: a b c
780 bookmark: d/e/f
780 bookmark: d/e/f
781
781
782
782
783
783
784 access bookmarks
784 access bookmarks
785
785
786 $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:'
786 $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:'
787 200 Script output follows
787 200 Script output follows
788 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
788 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
789
789
790 $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:'
790 $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:'
791 200 Script output follows
791 200 Script output follows
792 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
792 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
793
793
794 $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:'
794 $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:'
795 200 Script output follows
795 200 Script output follows
796 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
796 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
797
797
798 $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:'
798 $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:'
799 200 Script output follows
799 200 Script output follows
800 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
800 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
801
801
802 no '[up]' entry in file view when in root directory
802 no '[up]' entry in file view when in root directory
803
803
804 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=paper' | grep -F '[up]'
804 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=paper' | grep -F '[up]'
805 [1]
805 [1]
806 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=paper' | grep -F '[up]'
806 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=paper' | grep -F '[up]'
807 <a href="/file/tip/?style=paper">[up]</a>
807 <a href="/file/tip/?style=paper">[up]</a>
808 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=coal' | grep -F '[up]'
808 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=coal' | grep -F '[up]'
809 [1]
809 [1]
810 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=coal' | grep -F '[up]'
810 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=coal' | grep -F '[up]'
811 <a href="/file/tip/?style=coal">[up]</a>
811 <a href="/file/tip/?style=coal">[up]</a>
812 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=gitweb' | grep -F '[up]'
812 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=gitweb' | grep -F '[up]'
813 [1]
813 [1]
814 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=gitweb' | grep -F '[up]'
814 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=gitweb' | grep -F '[up]'
815 <a href="/file/tip/?style=gitweb">[up]</a>
815 <a href="/file/tip/?style=gitweb">[up]</a>
816 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=monoblue' | grep -F '[up]'
816 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=monoblue' | grep -F '[up]'
817 [1]
817 [1]
818 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=monoblue' | grep -F '[up]'
818 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=monoblue' | grep -F '[up]'
819 <a href="/file/tip/?style=monoblue">[up]</a>
819 <a href="/file/tip/?style=monoblue">[up]</a>
820 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=spartan' | grep -F '[up]'
820 $ get-with-headers.py localhost:$HGPORT 'file/tip?style=spartan' | grep -F '[up]'
821 [1]
821 [1]
822 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=spartan' | grep -F '[up]'
822 $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=spartan' | grep -F '[up]'
823 <a href="/file/tip/?style=spartan">[up]</a>
823 <a href="/file/tip/?style=spartan">[up]</a>
824
824
825 no style can be loaded from directories other than the specified paths
825 no style can be loaded from directories other than the specified paths
826
826
827 $ mkdir -p x/templates/fallback
827 $ mkdir -p x/templates/fallback
828 $ cat <<EOF > x/templates/fallback/map
828 $ cat <<EOF > x/templates/fallback/map
829 > default = 'shortlog'
829 > default = 'shortlog'
830 > shortlog = 'fall back to default\n'
830 > shortlog = 'fall back to default\n'
831 > mimetype = 'text/plain'
831 > mimetype = 'text/plain'
832 > EOF
832 > EOF
833 $ cat <<EOF > x/map
833 $ cat <<EOF > x/map
834 > default = 'shortlog'
834 > default = 'shortlog'
835 > shortlog = 'access to outside of templates directory\n'
835 > shortlog = 'access to outside of templates directory\n'
836 > mimetype = 'text/plain'
836 > mimetype = 'text/plain'
837 > EOF
837 > EOF
838
838
839 $ killdaemons.py
839 $ killdaemons.py
840 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \
840 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \
841 > --config web.style=fallback --config web.templates=x/templates
841 > --config web.style=fallback --config web.templates=x/templates
842 $ cat hg.pid >> $DAEMON_PIDS
842 $ cat hg.pid >> $DAEMON_PIDS
843
843
844 $ get-with-headers.py localhost:$HGPORT "?style=`pwd`/x"
844 $ get-with-headers.py localhost:$HGPORT "?style=`pwd`/x"
845 200 Script output follows
845 200 Script output follows
846
846
847 fall back to default
847 fall back to default
848
848
849 $ get-with-headers.py localhost:$HGPORT '?style=..'
849 $ get-with-headers.py localhost:$HGPORT '?style=..'
850 200 Script output follows
850 200 Script output follows
851
851
852 fall back to default
852 fall back to default
853
853
854 $ get-with-headers.py localhost:$HGPORT '?style=./..'
854 $ get-with-headers.py localhost:$HGPORT '?style=./..'
855 200 Script output follows
855 200 Script output follows
856
856
857 fall back to default
857 fall back to default
858
858
859 $ get-with-headers.py localhost:$HGPORT '?style=.../.../'
859 $ get-with-headers.py localhost:$HGPORT '?style=.../.../'
860 200 Script output follows
860 200 Script output follows
861
861
862 fall back to default
862 fall back to default
863
863
864 $ killdaemons.py
864 $ killdaemons.py
865
865
866 Test signal-safe-lock in web and non-web processes
866 Test signal-safe-lock in web and non-web processes
867
867
868 $ cat <<'EOF' > disablesig.py
868 $ cat <<'EOF' > disablesig.py
869 > import signal
869 > import signal
870 > from mercurial import error, extensions
870 > from mercurial import error, extensions
871 > def disabledsig(orig, signalnum, handler):
871 > def disabledsig(orig, signalnum, handler):
872 > if signalnum == signal.SIGTERM:
872 > if signalnum == signal.SIGTERM:
873 > raise error.Abort(b'SIGTERM cannot be replaced')
873 > raise error.Abort(b'SIGTERM cannot be replaced')
874 > try:
874 > try:
875 > return orig(signalnum, handler)
875 > return orig(signalnum, handler)
876 > except ValueError:
876 > except ValueError:
877 > raise error.Abort(b'signal.signal() called in thread?')
877 > raise error.Abort(b'signal.signal() called in thread?')
878 > def uisetup(ui):
878 > def uisetup(ui):
879 > extensions.wrapfunction(signal, b'signal', disabledsig)
879 > extensions.wrapfunction(signal, b'signal', disabledsig)
880 > EOF
880 > EOF
881
881
882 by default, signal interrupt should be disabled while making a lock file
882 by default, signal interrupt should be disabled while making a lock file
883
883
884 $ hg debuglock -s --config extensions.disablesig=disablesig.py
884 $ hg debuglock -s --config extensions.disablesig=disablesig.py
885 abort: SIGTERM cannot be replaced
885 abort: SIGTERM cannot be replaced
886 [255]
886 [255]
887
887
888 but in hgweb, it isn't disabled since some WSGI servers complains about
888 but in hgweb, it isn't disabled since some WSGI servers complains about
889 unsupported signal.signal() calls (see issue5889)
889 unsupported signal.signal() calls (see issue5889)
890
890
891 $ hg serve --config extensions.disablesig=disablesig.py \
891 $ hg serve --config extensions.disablesig=disablesig.py \
892 > --config web.allow-push='*' --config web.push_ssl=False \
892 > --config web.allow-push='*' --config web.push_ssl=False \
893 > -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
893 > -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
894 $ cat hg.pid >> $DAEMON_PIDS
894 $ cat hg.pid >> $DAEMON_PIDS
895
895
896 $ hg clone -q http://localhost:$HGPORT/ repo
896 $ hg clone -q http://localhost:$HGPORT/ repo
897 $ hg bookmark -R repo foo
897 $ hg bookmark -R repo foo
898
898
899 push would fail if signal.signal() were called
899 push would fail if signal.signal() were called
900
900
901 $ hg push -R repo -B foo
901 $ hg push -R repo -B foo
902 pushing to http://localhost:$HGPORT/
902 pushing to http://localhost:$HGPORT/
903 searching for changes
903 searching for changes
904 no changes found
904 no changes found
905 exporting bookmark foo
905 exporting bookmark foo
906 [1]
906 [1]
907
907
908 $ rm -R repo
908 $ rm -R repo
909 $ killdaemons.py
909 $ killdaemons.py
910
910
911 errors
911 errors
912
912
913 $ cat errors.log | "$PYTHON" $TESTDIR/filtertraceback.py
913 $ cat errors.log | "$PYTHON" $TESTDIR/filtertraceback.py
914 $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=spam': (glob)
915 Traceback (most recent call last):
916 error: [Errno 104] $ECONNRESET$
917
918 $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/spam': (glob)
919 Traceback (most recent call last):
920 error: [Errno 104] $ECONNRESET$
921
922 $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/spam/tip/foo': (glob)
923 Traceback (most recent call last):
924 error: [Errno 104] $ECONNRESET$
925
926 $ rm -f errors.log
914 $ rm -f errors.log
927
915
928 Uncaught exceptions result in a logged error and canned HTTP response
916 Uncaught exceptions result in a logged error and canned HTTP response
929
917
930 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
918 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
931 $ cat hg.pid >> $DAEMON_PIDS
919 $ cat hg.pid >> $DAEMON_PIDS
932
920
933 $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
921 $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
934 500 Internal Server Error
922 500 Internal Server Error
935 transfer-encoding: chunked
923 transfer-encoding: chunked
936
924
937 Internal Server Error (no-eol)
925 Internal Server Error (no-eol)
938 [1]
926 [1]
939
927
940 $ killdaemons.py
928 $ killdaemons.py
941 $ cat errors.log | "$PYTHON" $TESTDIR/filtertraceback.py
929 $ cat errors.log | "$PYTHON" $TESTDIR/filtertraceback.py
942 .* Exception happened during processing request '/raiseerror': (re)
930 .* Exception happened during processing request '/raiseerror': (re)
943 Traceback (most recent call last):
931 Traceback (most recent call last):
944 AttributeError: I am an uncaught error!
932 AttributeError: I am an uncaught error!
945
933
946
934
947 Uncaught exception after partial content sent
935 Uncaught exception after partial content sent
948
936
949 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
937 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
950 $ cat hg.pid >> $DAEMON_PIDS
938 $ cat hg.pid >> $DAEMON_PIDS
951 $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
939 $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
952 200 Script output follows
940 200 Script output follows
953 transfer-encoding: chunked
941 transfer-encoding: chunked
954 content-type: text/plain
942 content-type: text/plain
955
943
956 partial content
944 partial content
957 Internal Server Error (no-eol)
945 Internal Server Error (no-eol)
958
946
959 $ killdaemons.py
947 $ killdaemons.py
960
948
961 HTTP 304 works with hgwebdir (issue5844)
949 HTTP 304 works with hgwebdir (issue5844)
962
950
963 $ cat > hgweb.conf << EOF
951 $ cat > hgweb.conf << EOF
964 > [paths]
952 > [paths]
965 > /repo = $TESTTMP/test
953 > /repo = $TESTTMP/test
966 > EOF
954 > EOF
967
955
968 $ hg serve --web-conf hgweb.conf -p $HGPORT -d --pid-file hg.pid -E error.log
956 $ hg serve --web-conf hgweb.conf -p $HGPORT -d --pid-file hg.pid -E error.log
969 $ cat hg.pid >> $DAEMON_PIDS
957 $ cat hg.pid >> $DAEMON_PIDS
970
958
971 $ get-with-headers.py --twice --headeronly localhost:$HGPORT 'repo/static/style.css' - date etag server
959 $ get-with-headers.py --twice --headeronly localhost:$HGPORT 'repo/static/style.css' - date etag server
972 200 Script output follows
960 200 Script output follows
973 content-length: 2677
961 content-length: 2677
974 content-type: text/css
962 content-type: text/css
975 304 Not Modified
963 304 Not Modified
976
964
977 $ killdaemons.py
965 $ killdaemons.py
978
966
979 $ cd ..
967 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now