##// END OF EJS Templates
serve: don't send any content headers with 304 responses...
Mads Kiilerich -
r18380:a4d7fd7a default
parent child Browse files
Show More
@@ -1,329 +1,330
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 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 from mercurial import util, error
10 from mercurial import util, error
11 from mercurial.hgweb import common
11 from mercurial.hgweb import common
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 def _splitURI(uri):
14 def _splitURI(uri):
15 """Return path and query that has been split from uri
15 """Return path and query that has been split from uri
16
16
17 Just like CGI environment, the path is unquoted, the query is
17 Just like CGI environment, the path is unquoted, the query is
18 not.
18 not.
19 """
19 """
20 if '?' in uri:
20 if '?' in uri:
21 path, query = uri.split('?', 1)
21 path, query = uri.split('?', 1)
22 else:
22 else:
23 path, query = uri, ''
23 path, query = uri, ''
24 return urllib.unquote(path), query
24 return urllib.unquote(path), query
25
25
26 class _error_logger(object):
26 class _error_logger(object):
27 def __init__(self, handler):
27 def __init__(self, handler):
28 self.handler = handler
28 self.handler = handler
29 def flush(self):
29 def flush(self):
30 pass
30 pass
31 def write(self, str):
31 def write(self, str):
32 self.writelines(str.split('\n'))
32 self.writelines(str.split('\n'))
33 def writelines(self, seq):
33 def writelines(self, seq):
34 for msg in seq:
34 for msg in seq:
35 self.handler.log_error("HG error: %s", msg)
35 self.handler.log_error("HG error: %s", msg)
36
36
37 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
37 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
38
38
39 url_scheme = 'http'
39 url_scheme = 'http'
40
40
41 @staticmethod
41 @staticmethod
42 def preparehttpserver(httpserver, ssl_cert):
42 def preparehttpserver(httpserver, ssl_cert):
43 """Prepare .socket of new HTTPServer instance"""
43 """Prepare .socket of new HTTPServer instance"""
44 pass
44 pass
45
45
46 def __init__(self, *args, **kargs):
46 def __init__(self, *args, **kargs):
47 self.protocol_version = 'HTTP/1.1'
47 self.protocol_version = 'HTTP/1.1'
48 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
48 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
49
49
50 def _log_any(self, fp, format, *args):
50 def _log_any(self, fp, format, *args):
51 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
51 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
52 self.log_date_time_string(),
52 self.log_date_time_string(),
53 format % args))
53 format % args))
54 fp.flush()
54 fp.flush()
55
55
56 def log_error(self, format, *args):
56 def log_error(self, format, *args):
57 self._log_any(self.server.errorlog, format, *args)
57 self._log_any(self.server.errorlog, format, *args)
58
58
59 def log_message(self, format, *args):
59 def log_message(self, format, *args):
60 self._log_any(self.server.accesslog, format, *args)
60 self._log_any(self.server.accesslog, format, *args)
61
61
62 def log_request(self, code='-', size='-'):
62 def log_request(self, code='-', size='-'):
63 xheaders = [h for h in self.headers.items() if h[0].startswith('x-')]
63 xheaders = [h for h in self.headers.items() if h[0].startswith('x-')]
64 self.log_message('"%s" %s %s%s',
64 self.log_message('"%s" %s %s%s',
65 self.requestline, str(code), str(size),
65 self.requestline, str(code), str(size),
66 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
66 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
67
67
68 def do_write(self):
68 def do_write(self):
69 try:
69 try:
70 self.do_hgweb()
70 self.do_hgweb()
71 except socket.error, inst:
71 except socket.error, inst:
72 if inst[0] != errno.EPIPE:
72 if inst[0] != errno.EPIPE:
73 raise
73 raise
74
74
75 def do_POST(self):
75 def do_POST(self):
76 try:
76 try:
77 self.do_write()
77 self.do_write()
78 except Exception:
78 except Exception:
79 self._start_response("500 Internal Server Error", [])
79 self._start_response("500 Internal Server Error", [])
80 self._write("Internal Server Error")
80 self._write("Internal Server Error")
81 tb = "".join(traceback.format_exception(*sys.exc_info()))
81 tb = "".join(traceback.format_exception(*sys.exc_info()))
82 self.log_error("Exception happened during processing "
82 self.log_error("Exception happened during processing "
83 "request '%s':\n%s", self.path, tb)
83 "request '%s':\n%s", self.path, tb)
84
84
85 def do_GET(self):
85 def do_GET(self):
86 self.do_POST()
86 self.do_POST()
87
87
88 def do_hgweb(self):
88 def do_hgweb(self):
89 path, query = _splitURI(self.path)
89 path, query = _splitURI(self.path)
90
90
91 env = {}
91 env = {}
92 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
92 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
93 env['REQUEST_METHOD'] = self.command
93 env['REQUEST_METHOD'] = self.command
94 env['SERVER_NAME'] = self.server.server_name
94 env['SERVER_NAME'] = self.server.server_name
95 env['SERVER_PORT'] = str(self.server.server_port)
95 env['SERVER_PORT'] = str(self.server.server_port)
96 env['REQUEST_URI'] = self.path
96 env['REQUEST_URI'] = self.path
97 env['SCRIPT_NAME'] = self.server.prefix
97 env['SCRIPT_NAME'] = self.server.prefix
98 env['PATH_INFO'] = path[len(self.server.prefix):]
98 env['PATH_INFO'] = path[len(self.server.prefix):]
99 env['REMOTE_HOST'] = self.client_address[0]
99 env['REMOTE_HOST'] = self.client_address[0]
100 env['REMOTE_ADDR'] = self.client_address[0]
100 env['REMOTE_ADDR'] = self.client_address[0]
101 if query:
101 if query:
102 env['QUERY_STRING'] = query
102 env['QUERY_STRING'] = query
103
103
104 if self.headers.typeheader is None:
104 if self.headers.typeheader is None:
105 env['CONTENT_TYPE'] = self.headers.type
105 env['CONTENT_TYPE'] = self.headers.type
106 else:
106 else:
107 env['CONTENT_TYPE'] = self.headers.typeheader
107 env['CONTENT_TYPE'] = self.headers.typeheader
108 length = self.headers.getheader('content-length')
108 length = self.headers.getheader('content-length')
109 if length:
109 if length:
110 env['CONTENT_LENGTH'] = length
110 env['CONTENT_LENGTH'] = length
111 for header in [h for h in self.headers.keys()
111 for header in [h for h in self.headers.keys()
112 if h not in ('content-type', 'content-length')]:
112 if h not in ('content-type', 'content-length')]:
113 hkey = 'HTTP_' + header.replace('-', '_').upper()
113 hkey = 'HTTP_' + header.replace('-', '_').upper()
114 hval = self.headers.getheader(header)
114 hval = self.headers.getheader(header)
115 hval = hval.replace('\n', '').strip()
115 hval = hval.replace('\n', '').strip()
116 if hval:
116 if hval:
117 env[hkey] = hval
117 env[hkey] = hval
118 env['SERVER_PROTOCOL'] = self.request_version
118 env['SERVER_PROTOCOL'] = self.request_version
119 env['wsgi.version'] = (1, 0)
119 env['wsgi.version'] = (1, 0)
120 env['wsgi.url_scheme'] = self.url_scheme
120 env['wsgi.url_scheme'] = self.url_scheme
121 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
121 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
122 self.rfile = common.continuereader(self.rfile, self.wfile.write)
122 self.rfile = common.continuereader(self.rfile, self.wfile.write)
123
123
124 env['wsgi.input'] = self.rfile
124 env['wsgi.input'] = self.rfile
125 env['wsgi.errors'] = _error_logger(self)
125 env['wsgi.errors'] = _error_logger(self)
126 env['wsgi.multithread'] = isinstance(self.server,
126 env['wsgi.multithread'] = isinstance(self.server,
127 SocketServer.ThreadingMixIn)
127 SocketServer.ThreadingMixIn)
128 env['wsgi.multiprocess'] = isinstance(self.server,
128 env['wsgi.multiprocess'] = isinstance(self.server,
129 SocketServer.ForkingMixIn)
129 SocketServer.ForkingMixIn)
130 env['wsgi.run_once'] = 0
130 env['wsgi.run_once'] = 0
131
131
132 self.saved_status = None
132 self.saved_status = None
133 self.saved_headers = []
133 self.saved_headers = []
134 self.sent_headers = False
134 self.sent_headers = False
135 self.length = None
135 self.length = None
136 self._chunked = None
136 self._chunked = None
137 for chunk in self.server.application(env, self._start_response):
137 for chunk in self.server.application(env, self._start_response):
138 self._write(chunk)
138 self._write(chunk)
139 if not self.sent_headers:
139 if not self.sent_headers:
140 self.send_headers()
140 self.send_headers()
141 self._done()
141 self._done()
142
142
143 def send_headers(self):
143 def send_headers(self):
144 if not self.saved_status:
144 if not self.saved_status:
145 raise AssertionError("Sending headers before "
145 raise AssertionError("Sending headers before "
146 "start_response() called")
146 "start_response() called")
147 saved_status = self.saved_status.split(None, 1)
147 saved_status = self.saved_status.split(None, 1)
148 saved_status[0] = int(saved_status[0])
148 saved_status[0] = int(saved_status[0])
149 self.send_response(*saved_status)
149 self.send_response(*saved_status)
150 self.length = None
150 self.length = None
151 self._chunked = False
151 self._chunked = False
152 for h in self.saved_headers:
152 for h in self.saved_headers:
153 self.send_header(*h)
153 self.send_header(*h)
154 if h[0].lower() == 'content-length':
154 if h[0].lower() == 'content-length':
155 self.length = int(h[1])
155 self.length = int(h[1])
156 if self.length is None:
156 if (self.length is None and
157 saved_status[0] != common.HTTP_NOT_MODIFIED):
157 self._chunked = (not self.close_connection and
158 self._chunked = (not self.close_connection and
158 self.request_version == "HTTP/1.1")
159 self.request_version == "HTTP/1.1")
159 if self._chunked:
160 if self._chunked:
160 self.send_header('Transfer-Encoding', 'chunked')
161 self.send_header('Transfer-Encoding', 'chunked')
161 else:
162 else:
162 self.send_header('Connection', 'close')
163 self.send_header('Connection', 'close')
163 self.end_headers()
164 self.end_headers()
164 self.sent_headers = True
165 self.sent_headers = True
165
166
166 def _start_response(self, http_status, headers, exc_info=None):
167 def _start_response(self, http_status, headers, exc_info=None):
167 code, msg = http_status.split(None, 1)
168 code, msg = http_status.split(None, 1)
168 code = int(code)
169 code = int(code)
169 self.saved_status = http_status
170 self.saved_status = http_status
170 bad_headers = ('connection', 'transfer-encoding')
171 bad_headers = ('connection', 'transfer-encoding')
171 self.saved_headers = [h for h in headers
172 self.saved_headers = [h for h in headers
172 if h[0].lower() not in bad_headers]
173 if h[0].lower() not in bad_headers]
173 return self._write
174 return self._write
174
175
175 def _write(self, data):
176 def _write(self, data):
176 if not self.saved_status:
177 if not self.saved_status:
177 raise AssertionError("data written before start_response() called")
178 raise AssertionError("data written before start_response() called")
178 elif not self.sent_headers:
179 elif not self.sent_headers:
179 self.send_headers()
180 self.send_headers()
180 if self.length is not None:
181 if self.length is not None:
181 if len(data) > self.length:
182 if len(data) > self.length:
182 raise AssertionError("Content-length header sent, but more "
183 raise AssertionError("Content-length header sent, but more "
183 "bytes than specified are being written.")
184 "bytes than specified are being written.")
184 self.length = self.length - len(data)
185 self.length = self.length - len(data)
185 elif self._chunked and data:
186 elif self._chunked and data:
186 data = '%x\r\n%s\r\n' % (len(data), data)
187 data = '%x\r\n%s\r\n' % (len(data), data)
187 self.wfile.write(data)
188 self.wfile.write(data)
188 self.wfile.flush()
189 self.wfile.flush()
189
190
190 def _done(self):
191 def _done(self):
191 if self._chunked:
192 if self._chunked:
192 self.wfile.write('0\r\n\r\n')
193 self.wfile.write('0\r\n\r\n')
193 self.wfile.flush()
194 self.wfile.flush()
194
195
195 class _httprequesthandleropenssl(_httprequesthandler):
196 class _httprequesthandleropenssl(_httprequesthandler):
196 """HTTPS handler based on pyOpenSSL"""
197 """HTTPS handler based on pyOpenSSL"""
197
198
198 url_scheme = 'https'
199 url_scheme = 'https'
199
200
200 @staticmethod
201 @staticmethod
201 def preparehttpserver(httpserver, ssl_cert):
202 def preparehttpserver(httpserver, ssl_cert):
202 try:
203 try:
203 import OpenSSL
204 import OpenSSL
204 OpenSSL.SSL.Context
205 OpenSSL.SSL.Context
205 except ImportError:
206 except ImportError:
206 raise util.Abort(_("SSL support is unavailable"))
207 raise util.Abort(_("SSL support is unavailable"))
207 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
208 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
208 ctx.use_privatekey_file(ssl_cert)
209 ctx.use_privatekey_file(ssl_cert)
209 ctx.use_certificate_file(ssl_cert)
210 ctx.use_certificate_file(ssl_cert)
210 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
211 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
211 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
212 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
212 httpserver.server_bind()
213 httpserver.server_bind()
213 httpserver.server_activate()
214 httpserver.server_activate()
214
215
215 def setup(self):
216 def setup(self):
216 self.connection = self.request
217 self.connection = self.request
217 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
218 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
218 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
219 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
219
220
220 def do_write(self):
221 def do_write(self):
221 import OpenSSL
222 import OpenSSL
222 try:
223 try:
223 _httprequesthandler.do_write(self)
224 _httprequesthandler.do_write(self)
224 except OpenSSL.SSL.SysCallError, inst:
225 except OpenSSL.SSL.SysCallError, inst:
225 if inst.args[0] != errno.EPIPE:
226 if inst.args[0] != errno.EPIPE:
226 raise
227 raise
227
228
228 def handle_one_request(self):
229 def handle_one_request(self):
229 import OpenSSL
230 import OpenSSL
230 try:
231 try:
231 _httprequesthandler.handle_one_request(self)
232 _httprequesthandler.handle_one_request(self)
232 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
233 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
233 self.close_connection = True
234 self.close_connection = True
234 pass
235 pass
235
236
236 class _httprequesthandlerssl(_httprequesthandler):
237 class _httprequesthandlerssl(_httprequesthandler):
237 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
238 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
238
239
239 url_scheme = 'https'
240 url_scheme = 'https'
240
241
241 @staticmethod
242 @staticmethod
242 def preparehttpserver(httpserver, ssl_cert):
243 def preparehttpserver(httpserver, ssl_cert):
243 try:
244 try:
244 import ssl
245 import ssl
245 ssl.wrap_socket
246 ssl.wrap_socket
246 except ImportError:
247 except ImportError:
247 raise util.Abort(_("SSL support is unavailable"))
248 raise util.Abort(_("SSL support is unavailable"))
248 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
249 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
249 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
250 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
250
251
251 def setup(self):
252 def setup(self):
252 self.connection = self.request
253 self.connection = self.request
253 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
254 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
254 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
255 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
255
256
256 try:
257 try:
257 from threading import activeCount
258 from threading import activeCount
258 activeCount() # silence pyflakes
259 activeCount() # silence pyflakes
259 _mixin = SocketServer.ThreadingMixIn
260 _mixin = SocketServer.ThreadingMixIn
260 except ImportError:
261 except ImportError:
261 if util.safehasattr(os, "fork"):
262 if util.safehasattr(os, "fork"):
262 _mixin = SocketServer.ForkingMixIn
263 _mixin = SocketServer.ForkingMixIn
263 else:
264 else:
264 class _mixin(object):
265 class _mixin(object):
265 pass
266 pass
266
267
267 def openlog(opt, default):
268 def openlog(opt, default):
268 if opt and opt != '-':
269 if opt and opt != '-':
269 return open(opt, 'a')
270 return open(opt, 'a')
270 return default
271 return default
271
272
272 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
273 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
273
274
274 # SO_REUSEADDR has broken semantics on windows
275 # SO_REUSEADDR has broken semantics on windows
275 if os.name == 'nt':
276 if os.name == 'nt':
276 allow_reuse_address = 0
277 allow_reuse_address = 0
277
278
278 def __init__(self, ui, app, addr, handler, **kwargs):
279 def __init__(self, ui, app, addr, handler, **kwargs):
279 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
280 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
280 self.daemon_threads = True
281 self.daemon_threads = True
281 self.application = app
282 self.application = app
282
283
283 handler.preparehttpserver(self, ui.config('web', 'certificate'))
284 handler.preparehttpserver(self, ui.config('web', 'certificate'))
284
285
285 prefix = ui.config('web', 'prefix', '')
286 prefix = ui.config('web', 'prefix', '')
286 if prefix:
287 if prefix:
287 prefix = '/' + prefix.strip('/')
288 prefix = '/' + prefix.strip('/')
288 self.prefix = prefix
289 self.prefix = prefix
289
290
290 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
291 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
291 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
292 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
292 self.accesslog = alog
293 self.accesslog = alog
293 self.errorlog = elog
294 self.errorlog = elog
294
295
295 self.addr, self.port = self.socket.getsockname()[0:2]
296 self.addr, self.port = self.socket.getsockname()[0:2]
296 self.fqaddr = socket.getfqdn(addr[0])
297 self.fqaddr = socket.getfqdn(addr[0])
297
298
298 class IPv6HTTPServer(MercurialHTTPServer):
299 class IPv6HTTPServer(MercurialHTTPServer):
299 address_family = getattr(socket, 'AF_INET6', None)
300 address_family = getattr(socket, 'AF_INET6', None)
300 def __init__(self, *args, **kwargs):
301 def __init__(self, *args, **kwargs):
301 if self.address_family is None:
302 if self.address_family is None:
302 raise error.RepoError(_('IPv6 is not available on this system'))
303 raise error.RepoError(_('IPv6 is not available on this system'))
303 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
304 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
304
305
305 def create_server(ui, app):
306 def create_server(ui, app):
306
307
307 if ui.config('web', 'certificate'):
308 if ui.config('web', 'certificate'):
308 if sys.version_info >= (2, 6):
309 if sys.version_info >= (2, 6):
309 handler = _httprequesthandlerssl
310 handler = _httprequesthandlerssl
310 else:
311 else:
311 handler = _httprequesthandleropenssl
312 handler = _httprequesthandleropenssl
312 else:
313 else:
313 handler = _httprequesthandler
314 handler = _httprequesthandler
314
315
315 if ui.configbool('web', 'ipv6'):
316 if ui.configbool('web', 'ipv6'):
316 cls = IPv6HTTPServer
317 cls = IPv6HTTPServer
317 else:
318 else:
318 cls = MercurialHTTPServer
319 cls = MercurialHTTPServer
319
320
320 # ugly hack due to python issue5853 (for threaded use)
321 # ugly hack due to python issue5853 (for threaded use)
321 import mimetypes; mimetypes.init()
322 import mimetypes; mimetypes.init()
322
323
323 address = ui.config('web', 'address', '')
324 address = ui.config('web', 'address', '')
324 port = util.getport(ui.config('web', 'port', 8000))
325 port = util.getport(ui.config('web', 'port', 8000))
325 try:
326 try:
326 return cls(ui, app, (address, port), handler)
327 return cls(ui, app, (address, port), handler)
327 except socket.error, inst:
328 except socket.error, inst:
328 raise util.Abort(_("cannot start server at '%s:%d': %s")
329 raise util.Abort(_("cannot start server at '%s:%d': %s")
329 % (address, port, inst.args[1]))
330 % (address, port, inst.args[1]))
@@ -1,53 +1,55
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 """This does HTTP GET requests given a host:port and path and returns
3 """This does HTTP GET requests given a host:port and path and returns
4 a subset of the headers plus the body of the result."""
4 a subset of the headers plus the body of the result."""
5
5
6 import httplib, sys
6 import httplib, sys
7
7
8 try:
8 try:
9 import msvcrt, os
9 import msvcrt, os
10 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
10 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
11 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
11 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
12 except ImportError:
12 except ImportError:
13 pass
13 pass
14
14
15 twice = False
15 twice = False
16 if '--twice' in sys.argv:
16 if '--twice' in sys.argv:
17 sys.argv.remove('--twice')
17 sys.argv.remove('--twice')
18 twice = True
18 twice = True
19
19
20 reasons = {'Not modified': 'Not Modified'} # python 2.4
20 reasons = {'Not modified': 'Not Modified'} # python 2.4
21
21
22 tag = None
22 tag = None
23 def request(host, path, show):
23 def request(host, path, show):
24 assert not path.startswith('/'), path
24 assert not path.startswith('/'), path
25 global tag
25 global tag
26 headers = {}
26 headers = {}
27 if tag:
27 if tag:
28 headers['If-None-Match'] = tag
28 headers['If-None-Match'] = tag
29
29
30 conn = httplib.HTTPConnection(host)
30 conn = httplib.HTTPConnection(host)
31 conn.request("GET", '/' + path, None, headers)
31 conn.request("GET", '/' + path, None, headers)
32 response = conn.getresponse()
32 response = conn.getresponse()
33 print response.status, reasons.get(response.reason, response.reason)
33 print response.status, reasons.get(response.reason, response.reason)
34 if show[:1] == ['-']:
35 show = [h for h, v in response.getheaders() if h.lower() not in show]
34 for h in [h.lower() for h in show]:
36 for h in [h.lower() for h in show]:
35 if response.getheader(h, None) is not None:
37 if response.getheader(h, None) is not None:
36 print "%s: %s" % (h, response.getheader(h))
38 print "%s: %s" % (h, response.getheader(h))
37
39
38 print
40 print
39 data = response.read()
41 data = response.read()
40 sys.stdout.write(data)
42 sys.stdout.write(data)
41
43
42 if twice and response.getheader('ETag', None):
44 if twice and response.getheader('ETag', None):
43 tag = response.getheader('ETag')
45 tag = response.getheader('ETag')
44
46
45 return response.status
47 return response.status
46
48
47 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
49 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
48 if twice:
50 if twice:
49 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
51 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
50
52
51 if 200 <= status <= 305:
53 if 200 <= status <= 305:
52 sys.exit(0)
54 sys.exit(0)
53 sys.exit(1)
55 sys.exit(1)
@@ -1,492 +1,494
1 $ "$TESTDIR/hghave" serve || exit 80
1 $ "$TESTDIR/hghave" serve || exit 80
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 serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
13 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
14 $ cat hg.pid >> $DAEMON_PIDS
14 $ cat hg.pid >> $DAEMON_PIDS
15
15
16 manifest
16 manifest
17
17
18 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
18 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
19 200 Script output follows
19 200 Script output follows
20
20
21
21
22 drwxr-xr-x da
22 drwxr-xr-x da
23 -rw-r--r-- 4 foo
23 -rw-r--r-- 4 foo
24
24
25
25
26 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
26 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
27 200 Script output follows
27 200 Script output follows
28
28
29
29
30 -rw-r--r-- 4 foo
30 -rw-r--r-- 4 foo
31
31
32
32
33
33
34 plain file
34 plain file
35
35
36 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
36 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
37 200 Script output follows
37 200 Script output follows
38
38
39 foo
39 foo
40
40
41 should give a 404 - static file that does not exist
41 should give a 404 - static file that does not exist
42
42
43 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
43 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
44 404 Not Found
44 404 Not Found
45
45
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
48 <head>
48 <head>
49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
50 <meta name="robots" content="index, nofollow" />
50 <meta name="robots" content="index, nofollow" />
51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
52 <script type="text/javascript" src="/static/mercurial.js"></script>
52 <script type="text/javascript" src="/static/mercurial.js"></script>
53
53
54 <title>test: error</title>
54 <title>test: error</title>
55 </head>
55 </head>
56 <body>
56 <body>
57
57
58 <div class="container">
58 <div class="container">
59 <div class="menu">
59 <div class="menu">
60 <div class="logo">
60 <div class="logo">
61 <a href="http://mercurial.selenic.com/">
61 <a href="http://mercurial.selenic.com/">
62 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
62 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
63 </div>
63 </div>
64 <ul>
64 <ul>
65 <li><a href="/shortlog">log</a></li>
65 <li><a href="/shortlog">log</a></li>
66 <li><a href="/graph">graph</a></li>
66 <li><a href="/graph">graph</a></li>
67 <li><a href="/tags">tags</a></li>
67 <li><a href="/tags">tags</a></li>
68 <li><a href="/bookmarks">bookmarks</a></li>
68 <li><a href="/bookmarks">bookmarks</a></li>
69 <li><a href="/branches">branches</a></li>
69 <li><a href="/branches">branches</a></li>
70 </ul>
70 </ul>
71 <ul>
71 <ul>
72 <li><a href="/help">help</a></li>
72 <li><a href="/help">help</a></li>
73 </ul>
73 </ul>
74 </div>
74 </div>
75
75
76 <div class="main">
76 <div class="main">
77
77
78 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
78 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
79 <h3>error</h3>
79 <h3>error</h3>
80
80
81 <form class="search" action="/log">
81 <form class="search" action="/log">
82
82
83 <p><input name="rev" id="search1" type="text" size="30"></p>
83 <p><input name="rev" id="search1" type="text" size="30"></p>
84 <div id="hint">find changesets by author, revision,
84 <div id="hint">find changesets by author, revision,
85 files, or words in the commit message</div>
85 files, or words in the commit message</div>
86 </form>
86 </form>
87
87
88 <div class="description">
88 <div class="description">
89 <p>
89 <p>
90 An error occurred while processing your request:
90 An error occurred while processing your request:
91 </p>
91 </p>
92 <p>
92 <p>
93 Not Found
93 Not Found
94 </p>
94 </p>
95 </div>
95 </div>
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 <script type="text/javascript">process_dates()</script>
99 <script type="text/javascript">process_dates()</script>
100
100
101
101
102 </body>
102 </body>
103 </html>
103 </html>
104
104
105 [1]
105 [1]
106
106
107 should give a 404 - bad revision
107 should give a 404 - bad revision
108
108
109 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
109 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
110 404 Not Found
110 404 Not Found
111
111
112
112
113 error: revision not found: spam
113 error: revision not found: spam
114 [1]
114 [1]
115
115
116 should give a 400 - bad command
116 should give a 400 - bad command
117
117
118 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
118 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
119 400* (glob)
119 400* (glob)
120
120
121
121
122 error: no such method: spam
122 error: no such method: spam
123 [1]
123 [1]
124
124
125 should give a 404 - file does not exist
125 should give a 404 - file does not exist
126
126
127 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
127 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
128 404 Not Found
128 404 Not Found
129
129
130
130
131 error: bork@2ef0ac749a14: not found in manifest
131 error: bork@2ef0ac749a14: not found in manifest
132 [1]
132 [1]
133 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
133 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
134 404 Not Found
134 404 Not Found
135
135
136 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
136 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
137 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
137 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
138 <head>
138 <head>
139 <link rel="icon" href="/static/hgicon.png" type="image/png" />
139 <link rel="icon" href="/static/hgicon.png" type="image/png" />
140 <meta name="robots" content="index, nofollow" />
140 <meta name="robots" content="index, nofollow" />
141 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
141 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
142 <script type="text/javascript" src="/static/mercurial.js"></script>
142 <script type="text/javascript" src="/static/mercurial.js"></script>
143
143
144 <title>test: error</title>
144 <title>test: error</title>
145 </head>
145 </head>
146 <body>
146 <body>
147
147
148 <div class="container">
148 <div class="container">
149 <div class="menu">
149 <div class="menu">
150 <div class="logo">
150 <div class="logo">
151 <a href="http://mercurial.selenic.com/">
151 <a href="http://mercurial.selenic.com/">
152 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
152 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
153 </div>
153 </div>
154 <ul>
154 <ul>
155 <li><a href="/shortlog">log</a></li>
155 <li><a href="/shortlog">log</a></li>
156 <li><a href="/graph">graph</a></li>
156 <li><a href="/graph">graph</a></li>
157 <li><a href="/tags">tags</a></li>
157 <li><a href="/tags">tags</a></li>
158 <li><a href="/bookmarks">bookmarks</a></li>
158 <li><a href="/bookmarks">bookmarks</a></li>
159 <li><a href="/branches">branches</a></li>
159 <li><a href="/branches">branches</a></li>
160 </ul>
160 </ul>
161 <ul>
161 <ul>
162 <li><a href="/help">help</a></li>
162 <li><a href="/help">help</a></li>
163 </ul>
163 </ul>
164 </div>
164 </div>
165
165
166 <div class="main">
166 <div class="main">
167
167
168 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
168 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
169 <h3>error</h3>
169 <h3>error</h3>
170
170
171 <form class="search" action="/log">
171 <form class="search" action="/log">
172
172
173 <p><input name="rev" id="search1" type="text" size="30"></p>
173 <p><input name="rev" id="search1" type="text" size="30"></p>
174 <div id="hint">find changesets by author, revision,
174 <div id="hint">find changesets by author, revision,
175 files, or words in the commit message</div>
175 files, or words in the commit message</div>
176 </form>
176 </form>
177
177
178 <div class="description">
178 <div class="description">
179 <p>
179 <p>
180 An error occurred while processing your request:
180 An error occurred while processing your request:
181 </p>
181 </p>
182 <p>
182 <p>
183 bork@2ef0ac749a14: not found in manifest
183 bork@2ef0ac749a14: not found in manifest
184 </p>
184 </p>
185 </div>
185 </div>
186 </div>
186 </div>
187 </div>
187 </div>
188
188
189 <script type="text/javascript">process_dates()</script>
189 <script type="text/javascript">process_dates()</script>
190
190
191
191
192 </body>
192 </body>
193 </html>
193 </html>
194
194
195 [1]
195 [1]
196 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
196 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
197 404 Not Found
197 404 Not Found
198
198
199
199
200 error: bork@2ef0ac749a14: not found in manifest
200 error: bork@2ef0ac749a14: not found in manifest
201 [1]
201 [1]
202
202
203 try bad style
203 try bad style
204
204
205 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
205 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
206 200 Script output follows
206 200 Script output follows
207
207
208 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
208 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
209 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
209 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
210 <head>
210 <head>
211 <link rel="icon" href="/static/hgicon.png" type="image/png" />
211 <link rel="icon" href="/static/hgicon.png" type="image/png" />
212 <meta name="robots" content="index, nofollow" />
212 <meta name="robots" content="index, nofollow" />
213 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
213 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
214 <script type="text/javascript" src="/static/mercurial.js"></script>
214 <script type="text/javascript" src="/static/mercurial.js"></script>
215
215
216 <title>test: 2ef0ac749a14 /</title>
216 <title>test: 2ef0ac749a14 /</title>
217 </head>
217 </head>
218 <body>
218 <body>
219
219
220 <div class="container">
220 <div class="container">
221 <div class="menu">
221 <div class="menu">
222 <div class="logo">
222 <div class="logo">
223 <a href="http://mercurial.selenic.com/">
223 <a href="http://mercurial.selenic.com/">
224 <img src="/static/hglogo.png" alt="mercurial" /></a>
224 <img src="/static/hglogo.png" alt="mercurial" /></a>
225 </div>
225 </div>
226 <ul>
226 <ul>
227 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
227 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
228 <li><a href="/graph/2ef0ac749a14">graph</a></li>
228 <li><a href="/graph/2ef0ac749a14">graph</a></li>
229 <li><a href="/tags">tags</a></li>
229 <li><a href="/tags">tags</a></li>
230 <li><a href="/bookmarks">bookmarks</a></li>
230 <li><a href="/bookmarks">bookmarks</a></li>
231 <li><a href="/branches">branches</a></li>
231 <li><a href="/branches">branches</a></li>
232 </ul>
232 </ul>
233 <ul>
233 <ul>
234 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
234 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
235 <li class="active">browse</li>
235 <li class="active">browse</li>
236 </ul>
236 </ul>
237 <ul>
237 <ul>
238
238
239 </ul>
239 </ul>
240 <ul>
240 <ul>
241 <li><a href="/help">help</a></li>
241 <li><a href="/help">help</a></li>
242 </ul>
242 </ul>
243 </div>
243 </div>
244
244
245 <div class="main">
245 <div class="main">
246 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
246 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
247 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
247 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
248
248
249 <form class="search" action="/log">
249 <form class="search" action="/log">
250
250
251 <p><input name="rev" id="search1" type="text" size="30" /></p>
251 <p><input name="rev" id="search1" type="text" size="30" /></p>
252 <div id="hint">find changesets by author, revision,
252 <div id="hint">find changesets by author, revision,
253 files, or words in the commit message</div>
253 files, or words in the commit message</div>
254 </form>
254 </form>
255
255
256 <table class="bigtable">
256 <table class="bigtable">
257 <tr>
257 <tr>
258 <th class="name">name</th>
258 <th class="name">name</th>
259 <th class="size">size</th>
259 <th class="size">size</th>
260 <th class="permissions">permissions</th>
260 <th class="permissions">permissions</th>
261 </tr>
261 </tr>
262 <tr class="fileline parity0">
262 <tr class="fileline parity0">
263 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
263 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
264 <td class="size"></td>
264 <td class="size"></td>
265 <td class="permissions">drwxr-xr-x</td>
265 <td class="permissions">drwxr-xr-x</td>
266 </tr>
266 </tr>
267
267
268 <tr class="fileline parity1">
268 <tr class="fileline parity1">
269 <td class="name">
269 <td class="name">
270 <a href="/file/2ef0ac749a14/da">
270 <a href="/file/2ef0ac749a14/da">
271 <img src="/static/coal-folder.png" alt="dir."/> da/
271 <img src="/static/coal-folder.png" alt="dir."/> da/
272 </a>
272 </a>
273 <a href="/file/2ef0ac749a14/da/">
273 <a href="/file/2ef0ac749a14/da/">
274
274
275 </a>
275 </a>
276 </td>
276 </td>
277 <td class="size"></td>
277 <td class="size"></td>
278 <td class="permissions">drwxr-xr-x</td>
278 <td class="permissions">drwxr-xr-x</td>
279 </tr>
279 </tr>
280
280
281 <tr class="fileline parity0">
281 <tr class="fileline parity0">
282 <td class="filename">
282 <td class="filename">
283 <a href="/file/2ef0ac749a14/foo">
283 <a href="/file/2ef0ac749a14/foo">
284 <img src="/static/coal-file.png" alt="file"/> foo
284 <img src="/static/coal-file.png" alt="file"/> foo
285 </a>
285 </a>
286 </td>
286 </td>
287 <td class="size">4</td>
287 <td class="size">4</td>
288 <td class="permissions">-rw-r--r--</td>
288 <td class="permissions">-rw-r--r--</td>
289 </tr>
289 </tr>
290 </table>
290 </table>
291 </div>
291 </div>
292 </div>
292 </div>
293 <script type="text/javascript">process_dates()</script>
293 <script type="text/javascript">process_dates()</script>
294
294
295
295
296 </body>
296 </body>
297 </html>
297 </html>
298
298
299
299
300 stop and restart
300 stop and restart
301
301
302 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
302 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
303 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
303 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
304 $ cat hg.pid >> $DAEMON_PIDS
304 $ cat hg.pid >> $DAEMON_PIDS
305
305
306 Test the access/error files are opened in append mode
306 Test the access/error files are opened in append mode
307
307
308 $ python -c "print len(file('access.log').readlines()), 'log lines written'"
308 $ python -c "print len(file('access.log').readlines()), 'log lines written'"
309 10 log lines written
309 10 log lines written
310
310
311 static file
311 static file
312
312
313 $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css'
313 $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
314 200 Script output follows
314 200 Script output follows
315 content-length: 4619
316 content-type: text/css
315
317
316 body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
318 body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
317 a { color:#0000cc; }
319 a { color:#0000cc; }
318 a:hover, a:visited, a:active { color:#880000; }
320 a:hover, a:visited, a:active { color:#880000; }
319 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
321 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
320 div.page_header a:visited { color:#0000cc; }
322 div.page_header a:visited { color:#0000cc; }
321 div.page_header a:hover { color:#880000; }
323 div.page_header a:hover { color:#880000; }
322 div.page_nav { padding:8px; }
324 div.page_nav { padding:8px; }
323 div.page_nav a:visited { color:#0000cc; }
325 div.page_nav a:visited { color:#0000cc; }
324 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
326 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
325 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
327 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
326 div.page_footer_text { float:left; color:#555555; font-style:italic; }
328 div.page_footer_text { float:left; color:#555555; font-style:italic; }
327 div.page_body { padding:8px; }
329 div.page_body { padding:8px; }
328 div.title, a.title {
330 div.title, a.title {
329 display:block; padding:6px 8px;
331 display:block; padding:6px 8px;
330 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
332 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
331 }
333 }
332 a.title:hover { background-color: #d9d8d1; }
334 a.title:hover { background-color: #d9d8d1; }
333 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
335 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
334 div.log_body { padding:8px 8px 8px 150px; }
336 div.log_body { padding:8px 8px 8px 150px; }
335 .age { white-space:nowrap; }
337 .age { white-space:nowrap; }
336 span.age { position:relative; float:left; width:142px; font-style:italic; }
338 span.age { position:relative; float:left; width:142px; font-style:italic; }
337 div.log_link {
339 div.log_link {
338 padding:0px 8px;
340 padding:0px 8px;
339 font-size:10px; font-family:sans-serif; font-style:normal;
341 font-size:10px; font-family:sans-serif; font-style:normal;
340 position:relative; float:left; width:136px;
342 position:relative; float:left; width:136px;
341 }
343 }
342 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
344 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
343 a.list { text-decoration:none; color:#000000; }
345 a.list { text-decoration:none; color:#000000; }
344 a.list:hover { text-decoration:underline; color:#880000; }
346 a.list:hover { text-decoration:underline; color:#880000; }
345 table { padding:8px 4px; }
347 table { padding:8px 4px; }
346 th { padding:2px 5px; font-size:12px; text-align:left; }
348 th { padding:2px 5px; font-size:12px; text-align:left; }
347 tr.light:hover, .parity0:hover { background-color:#edece6; }
349 tr.light:hover, .parity0:hover { background-color:#edece6; }
348 tr.dark, .parity1 { background-color:#f6f6f0; }
350 tr.dark, .parity1 { background-color:#f6f6f0; }
349 tr.dark:hover, .parity1:hover { background-color:#edece6; }
351 tr.dark:hover, .parity1:hover { background-color:#edece6; }
350 td { padding:2px 5px; font-size:12px; vertical-align:top; }
352 td { padding:2px 5px; font-size:12px; vertical-align:top; }
351 td.closed { background-color: #99f; }
353 td.closed { background-color: #99f; }
352 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
354 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
353 td.indexlinks { white-space: nowrap; }
355 td.indexlinks { white-space: nowrap; }
354 td.indexlinks a {
356 td.indexlinks a {
355 padding: 2px 5px; line-height: 10px;
357 padding: 2px 5px; line-height: 10px;
356 border: 1px solid;
358 border: 1px solid;
357 color: #ffffff; background-color: #7777bb;
359 color: #ffffff; background-color: #7777bb;
358 border-color: #aaaadd #333366 #333366 #aaaadd;
360 border-color: #aaaadd #333366 #333366 #aaaadd;
359 font-weight: bold; text-align: center; text-decoration: none;
361 font-weight: bold; text-align: center; text-decoration: none;
360 font-size: 10px;
362 font-size: 10px;
361 }
363 }
362 td.indexlinks a:hover { background-color: #6666aa; }
364 td.indexlinks a:hover { background-color: #6666aa; }
363 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
365 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
364 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
366 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
365 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
367 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
366 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
368 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
367 .linenr { color:#999999; text-decoration:none }
369 .linenr { color:#999999; text-decoration:none }
368 div.rss_logo { float: right; white-space: nowrap; }
370 div.rss_logo { float: right; white-space: nowrap; }
369 div.rss_logo a {
371 div.rss_logo a {
370 padding:3px 6px; line-height:10px;
372 padding:3px 6px; line-height:10px;
371 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
373 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
372 color:#ffffff; background-color:#ff6600;
374 color:#ffffff; background-color:#ff6600;
373 font-weight:bold; font-family:sans-serif; font-size:10px;
375 font-weight:bold; font-family:sans-serif; font-size:10px;
374 text-align:center; text-decoration:none;
376 text-align:center; text-decoration:none;
375 }
377 }
376 div.rss_logo a:hover { background-color:#ee5500; }
378 div.rss_logo a:hover { background-color:#ee5500; }
377 pre { margin: 0; }
379 pre { margin: 0; }
378 span.logtags span {
380 span.logtags span {
379 padding: 0px 4px;
381 padding: 0px 4px;
380 font-size: 10px;
382 font-size: 10px;
381 font-weight: normal;
383 font-weight: normal;
382 border: 1px solid;
384 border: 1px solid;
383 background-color: #ffaaff;
385 background-color: #ffaaff;
384 border-color: #ffccff #ff00ee #ff00ee #ffccff;
386 border-color: #ffccff #ff00ee #ff00ee #ffccff;
385 }
387 }
386 span.logtags span.tagtag {
388 span.logtags span.tagtag {
387 background-color: #ffffaa;
389 background-color: #ffffaa;
388 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
390 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
389 }
391 }
390 span.logtags span.branchtag {
392 span.logtags span.branchtag {
391 background-color: #aaffaa;
393 background-color: #aaffaa;
392 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
394 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
393 }
395 }
394 span.logtags span.inbranchtag {
396 span.logtags span.inbranchtag {
395 background-color: #d5dde6;
397 background-color: #d5dde6;
396 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
398 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
397 }
399 }
398 span.logtags span.bookmarktag {
400 span.logtags span.bookmarktag {
399 background-color: #afdffa;
401 background-color: #afdffa;
400 border-color: #ccecff #46ace6 #46ace6 #ccecff;
402 border-color: #ccecff #46ace6 #46ace6 #ccecff;
401 }
403 }
402
404
403 /* Graph */
405 /* Graph */
404 div#wrapper {
406 div#wrapper {
405 position: relative;
407 position: relative;
406 margin: 0;
408 margin: 0;
407 padding: 0;
409 padding: 0;
408 margin-top: 3px;
410 margin-top: 3px;
409 }
411 }
410
412
411 canvas {
413 canvas {
412 position: absolute;
414 position: absolute;
413 z-index: 5;
415 z-index: 5;
414 top: -0.9em;
416 top: -0.9em;
415 margin: 0;
417 margin: 0;
416 }
418 }
417
419
418 ul#nodebgs {
420 ul#nodebgs {
419 list-style: none inside none;
421 list-style: none inside none;
420 padding: 0;
422 padding: 0;
421 margin: 0;
423 margin: 0;
422 top: -0.7em;
424 top: -0.7em;
423 }
425 }
424
426
425 ul#graphnodes li, ul#nodebgs li {
427 ul#graphnodes li, ul#nodebgs li {
426 height: 39px;
428 height: 39px;
427 }
429 }
428
430
429 ul#graphnodes {
431 ul#graphnodes {
430 position: absolute;
432 position: absolute;
431 z-index: 10;
433 z-index: 10;
432 top: -0.8em;
434 top: -0.8em;
433 list-style: none inside none;
435 list-style: none inside none;
434 padding: 0;
436 padding: 0;
435 }
437 }
436
438
437 ul#graphnodes li .info {
439 ul#graphnodes li .info {
438 display: block;
440 display: block;
439 font-size: 100%;
441 font-size: 100%;
440 position: relative;
442 position: relative;
441 top: -3px;
443 top: -3px;
442 font-style: italic;
444 font-style: italic;
443 }
445 }
444
446
445 /* Comparison */
447 /* Comparison */
446 .legend {
448 .legend {
447 padding: 1.5% 0 1.5% 0;
449 padding: 1.5% 0 1.5% 0;
448 }
450 }
449
451
450 .legendinfo {
452 .legendinfo {
451 border: 1px solid #d9d8d1;
453 border: 1px solid #d9d8d1;
452 font-size: 80%;
454 font-size: 80%;
453 text-align: center;
455 text-align: center;
454 padding: 0.5%;
456 padding: 0.5%;
455 }
457 }
456
458
457 .equal {
459 .equal {
458 background-color: #ffffff;
460 background-color: #ffffff;
459 }
461 }
460
462
461 .delete {
463 .delete {
462 background-color: #faa;
464 background-color: #faa;
463 color: #333;
465 color: #333;
464 }
466 }
465
467
466 .insert {
468 .insert {
467 background-color: #ffa;
469 background-color: #ffa;
468 }
470 }
469
471
470 .replace {
472 .replace {
471 background-color: #e8e8e8;
473 background-color: #e8e8e8;
472 }
474 }
473
475
474 .comparison {
476 .comparison {
475 overflow-x: auto;
477 overflow-x: auto;
476 }
478 }
477
479
478 .header th {
480 .header th {
479 text-align: center;
481 text-align: center;
480 }
482 }
481
483
482 .block {
484 .block {
483 border-top: 1px solid #d9d8d1;
485 border-top: 1px solid #d9d8d1;
484 }
486 }
485 304 Not Modified
487 304 Not Modified
486
488
487
489
488 errors
490 errors
489
491
490 $ cat errors.log
492 $ cat errors.log
491
493
492 $ cd ..
494 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now