##// END OF EJS Templates
hgweb: move remaining hasattr calls to safehasattr
Augie Fackler -
r14957:16e5271b default
parent child Browse files
Show More
@@ -1,319 +1,319 b''
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 splited from uri
15 """ Return path and query splited 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.close_connection = True
132 self.close_connection = True
133 self.saved_status = None
133 self.saved_status = None
134 self.saved_headers = []
134 self.saved_headers = []
135 self.sent_headers = False
135 self.sent_headers = False
136 self.length = None
136 self.length = 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
139
140 def send_headers(self):
140 def send_headers(self):
141 if not self.saved_status:
141 if not self.saved_status:
142 raise AssertionError("Sending headers before "
142 raise AssertionError("Sending headers before "
143 "start_response() called")
143 "start_response() called")
144 saved_status = self.saved_status.split(None, 1)
144 saved_status = self.saved_status.split(None, 1)
145 saved_status[0] = int(saved_status[0])
145 saved_status[0] = int(saved_status[0])
146 self.send_response(*saved_status)
146 self.send_response(*saved_status)
147 should_close = True
147 should_close = True
148 for h in self.saved_headers:
148 for h in self.saved_headers:
149 self.send_header(*h)
149 self.send_header(*h)
150 if h[0].lower() == 'content-length':
150 if h[0].lower() == 'content-length':
151 should_close = False
151 should_close = False
152 self.length = int(h[1])
152 self.length = int(h[1])
153 # The value of the Connection header is a list of case-insensitive
153 # The value of the Connection header is a list of case-insensitive
154 # tokens separated by commas and optional whitespace.
154 # tokens separated by commas and optional whitespace.
155 if 'close' in [token.strip().lower() for token in
155 if 'close' in [token.strip().lower() for token in
156 self.headers.get('connection', '').split(',')]:
156 self.headers.get('connection', '').split(',')]:
157 should_close = True
157 should_close = True
158 if should_close:
158 if should_close:
159 self.send_header('Connection', 'close')
159 self.send_header('Connection', 'close')
160 self.close_connection = should_close
160 self.close_connection = should_close
161 self.end_headers()
161 self.end_headers()
162 self.sent_headers = True
162 self.sent_headers = True
163
163
164 def _start_response(self, http_status, headers, exc_info=None):
164 def _start_response(self, http_status, headers, exc_info=None):
165 code, msg = http_status.split(None, 1)
165 code, msg = http_status.split(None, 1)
166 code = int(code)
166 code = int(code)
167 self.saved_status = http_status
167 self.saved_status = http_status
168 bad_headers = ('connection', 'transfer-encoding')
168 bad_headers = ('connection', 'transfer-encoding')
169 self.saved_headers = [h for h in headers
169 self.saved_headers = [h for h in headers
170 if h[0].lower() not in bad_headers]
170 if h[0].lower() not in bad_headers]
171 return self._write
171 return self._write
172
172
173 def _write(self, data):
173 def _write(self, data):
174 if not self.saved_status:
174 if not self.saved_status:
175 raise AssertionError("data written before start_response() called")
175 raise AssertionError("data written before start_response() called")
176 elif not self.sent_headers:
176 elif not self.sent_headers:
177 self.send_headers()
177 self.send_headers()
178 if self.length is not None:
178 if self.length is not None:
179 if len(data) > self.length:
179 if len(data) > self.length:
180 raise AssertionError("Content-length header sent, but more "
180 raise AssertionError("Content-length header sent, but more "
181 "bytes than specified are being written.")
181 "bytes than specified are being written.")
182 self.length = self.length - len(data)
182 self.length = self.length - len(data)
183 self.wfile.write(data)
183 self.wfile.write(data)
184 self.wfile.flush()
184 self.wfile.flush()
185
185
186 class _httprequesthandleropenssl(_httprequesthandler):
186 class _httprequesthandleropenssl(_httprequesthandler):
187 """HTTPS handler based on pyOpenSSL"""
187 """HTTPS handler based on pyOpenSSL"""
188
188
189 url_scheme = 'https'
189 url_scheme = 'https'
190
190
191 @staticmethod
191 @staticmethod
192 def preparehttpserver(httpserver, ssl_cert):
192 def preparehttpserver(httpserver, ssl_cert):
193 try:
193 try:
194 import OpenSSL
194 import OpenSSL
195 OpenSSL.SSL.Context
195 OpenSSL.SSL.Context
196 except ImportError:
196 except ImportError:
197 raise util.Abort(_("SSL support is unavailable"))
197 raise util.Abort(_("SSL support is unavailable"))
198 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
198 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
199 ctx.use_privatekey_file(ssl_cert)
199 ctx.use_privatekey_file(ssl_cert)
200 ctx.use_certificate_file(ssl_cert)
200 ctx.use_certificate_file(ssl_cert)
201 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
201 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
202 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
202 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
203 httpserver.server_bind()
203 httpserver.server_bind()
204 httpserver.server_activate()
204 httpserver.server_activate()
205
205
206 def setup(self):
206 def setup(self):
207 self.connection = self.request
207 self.connection = self.request
208 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
208 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
209 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
209 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
210
210
211 def do_write(self):
211 def do_write(self):
212 import OpenSSL
212 import OpenSSL
213 try:
213 try:
214 _httprequesthandler.do_write(self)
214 _httprequesthandler.do_write(self)
215 except OpenSSL.SSL.SysCallError, inst:
215 except OpenSSL.SSL.SysCallError, inst:
216 if inst.args[0] != errno.EPIPE:
216 if inst.args[0] != errno.EPIPE:
217 raise
217 raise
218
218
219 def handle_one_request(self):
219 def handle_one_request(self):
220 import OpenSSL
220 import OpenSSL
221 try:
221 try:
222 _httprequesthandler.handle_one_request(self)
222 _httprequesthandler.handle_one_request(self)
223 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
223 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
224 self.close_connection = True
224 self.close_connection = True
225 pass
225 pass
226
226
227 class _httprequesthandlerssl(_httprequesthandler):
227 class _httprequesthandlerssl(_httprequesthandler):
228 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
228 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
229
229
230 url_scheme = 'https'
230 url_scheme = 'https'
231
231
232 @staticmethod
232 @staticmethod
233 def preparehttpserver(httpserver, ssl_cert):
233 def preparehttpserver(httpserver, ssl_cert):
234 try:
234 try:
235 import ssl
235 import ssl
236 ssl.wrap_socket
236 ssl.wrap_socket
237 except ImportError:
237 except ImportError:
238 raise util.Abort(_("SSL support is unavailable"))
238 raise util.Abort(_("SSL support is unavailable"))
239 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
239 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
240 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
240 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
241
241
242 def setup(self):
242 def setup(self):
243 self.connection = self.request
243 self.connection = self.request
244 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
244 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
245 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
245 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
246
246
247 try:
247 try:
248 from threading import activeCount
248 from threading import activeCount
249 _mixin = SocketServer.ThreadingMixIn
249 _mixin = SocketServer.ThreadingMixIn
250 except ImportError:
250 except ImportError:
251 if hasattr(os, "fork"):
251 if util.safehasattr(os, "fork"):
252 _mixin = SocketServer.ForkingMixIn
252 _mixin = SocketServer.ForkingMixIn
253 else:
253 else:
254 class _mixin(object):
254 class _mixin(object):
255 pass
255 pass
256
256
257 def openlog(opt, default):
257 def openlog(opt, default):
258 if opt and opt != '-':
258 if opt and opt != '-':
259 return open(opt, 'a')
259 return open(opt, 'a')
260 return default
260 return default
261
261
262 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
262 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
263
263
264 # SO_REUSEADDR has broken semantics on windows
264 # SO_REUSEADDR has broken semantics on windows
265 if os.name == 'nt':
265 if os.name == 'nt':
266 allow_reuse_address = 0
266 allow_reuse_address = 0
267
267
268 def __init__(self, ui, app, addr, handler, **kwargs):
268 def __init__(self, ui, app, addr, handler, **kwargs):
269 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
269 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
270 self.daemon_threads = True
270 self.daemon_threads = True
271 self.application = app
271 self.application = app
272
272
273 handler.preparehttpserver(self, ui.config('web', 'certificate'))
273 handler.preparehttpserver(self, ui.config('web', 'certificate'))
274
274
275 prefix = ui.config('web', 'prefix', '')
275 prefix = ui.config('web', 'prefix', '')
276 if prefix:
276 if prefix:
277 prefix = '/' + prefix.strip('/')
277 prefix = '/' + prefix.strip('/')
278 self.prefix = prefix
278 self.prefix = prefix
279
279
280 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
280 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
281 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
281 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
282 self.accesslog = alog
282 self.accesslog = alog
283 self.errorlog = elog
283 self.errorlog = elog
284
284
285 self.addr, self.port = self.socket.getsockname()[0:2]
285 self.addr, self.port = self.socket.getsockname()[0:2]
286 self.fqaddr = socket.getfqdn(addr[0])
286 self.fqaddr = socket.getfqdn(addr[0])
287
287
288 class IPv6HTTPServer(MercurialHTTPServer):
288 class IPv6HTTPServer(MercurialHTTPServer):
289 address_family = getattr(socket, 'AF_INET6', None)
289 address_family = getattr(socket, 'AF_INET6', None)
290 def __init__(self, *args, **kwargs):
290 def __init__(self, *args, **kwargs):
291 if self.address_family is None:
291 if self.address_family is None:
292 raise error.RepoError(_('IPv6 is not available on this system'))
292 raise error.RepoError(_('IPv6 is not available on this system'))
293 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
293 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
294
294
295 def create_server(ui, app):
295 def create_server(ui, app):
296
296
297 if ui.config('web', 'certificate'):
297 if ui.config('web', 'certificate'):
298 if sys.version_info >= (2, 6):
298 if sys.version_info >= (2, 6):
299 handler = _httprequesthandlerssl
299 handler = _httprequesthandlerssl
300 else:
300 else:
301 handler = _httprequesthandleropenssl
301 handler = _httprequesthandleropenssl
302 else:
302 else:
303 handler = _httprequesthandler
303 handler = _httprequesthandler
304
304
305 if ui.configbool('web', 'ipv6'):
305 if ui.configbool('web', 'ipv6'):
306 cls = IPv6HTTPServer
306 cls = IPv6HTTPServer
307 else:
307 else:
308 cls = MercurialHTTPServer
308 cls = MercurialHTTPServer
309
309
310 # ugly hack due to python issue5853 (for threaded use)
310 # ugly hack due to python issue5853 (for threaded use)
311 import mimetypes; mimetypes.init()
311 import mimetypes; mimetypes.init()
312
312
313 address = ui.config('web', 'address', '')
313 address = ui.config('web', 'address', '')
314 port = util.getport(ui.config('web', 'port', 8000))
314 port = util.getport(ui.config('web', 'port', 8000))
315 try:
315 try:
316 return cls(ui, app, (address, port), handler)
316 return cls(ui, app, (address, port), handler)
317 except socket.error, inst:
317 except socket.error, inst:
318 raise util.Abort(_("cannot start server at '%s:%d': %s")
318 raise util.Abort(_("cannot start server at '%s:%d': %s")
319 % (address, port, inst.args[1]))
319 % (address, port, inst.args[1]))
@@ -1,269 +1,269 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
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, copy
9 import os, copy
10 from mercurial import match, patch, scmutil, error, ui, util
10 from mercurial import match, patch, scmutil, error, ui, util
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import hex, nullid
12 from mercurial.node import hex, nullid
13
13
14 def up(p):
14 def up(p):
15 if p[0] != "/":
15 if p[0] != "/":
16 p = "/" + p
16 p = "/" + p
17 if p[-1] == "/":
17 if p[-1] == "/":
18 p = p[:-1]
18 p = p[:-1]
19 up = os.path.dirname(p)
19 up = os.path.dirname(p)
20 if up == "/":
20 if up == "/":
21 return "/"
21 return "/"
22 return up + "/"
22 return up + "/"
23
23
24 def revnavgen(pos, pagelen, limit, nodefunc):
24 def revnavgen(pos, pagelen, limit, nodefunc):
25 def seq(factor, limit=None):
25 def seq(factor, limit=None):
26 if limit:
26 if limit:
27 yield limit
27 yield limit
28 if limit >= 20 and limit <= 40:
28 if limit >= 20 and limit <= 40:
29 yield 50
29 yield 50
30 else:
30 else:
31 yield 1 * factor
31 yield 1 * factor
32 yield 3 * factor
32 yield 3 * factor
33 for f in seq(factor * 10):
33 for f in seq(factor * 10):
34 yield f
34 yield f
35
35
36 navbefore = []
36 navbefore = []
37 navafter = []
37 navafter = []
38
38
39 last = 0
39 last = 0
40 for f in seq(1, pagelen):
40 for f in seq(1, pagelen):
41 if f < pagelen or f <= last:
41 if f < pagelen or f <= last:
42 continue
42 continue
43 if f > limit:
43 if f > limit:
44 break
44 break
45 last = f
45 last = f
46 if pos + f < limit:
46 if pos + f < limit:
47 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
47 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
48 if pos - f >= 0:
48 if pos - f >= 0:
49 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
49 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
50
50
51 navafter.append(("tip", "tip"))
51 navafter.append(("tip", "tip"))
52 try:
52 try:
53 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
53 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
54 except error.RepoError:
54 except error.RepoError:
55 pass
55 pass
56
56
57 def gen(l):
57 def gen(l):
58 def f(**map):
58 def f(**map):
59 for label, node in l:
59 for label, node in l:
60 yield {"label": label, "node": node}
60 yield {"label": label, "node": node}
61 return f
61 return f
62
62
63 return (dict(before=gen(navbefore), after=gen(navafter)),)
63 return (dict(before=gen(navbefore), after=gen(navafter)),)
64
64
65 def _siblings(siblings=[], hiderev=None):
65 def _siblings(siblings=[], hiderev=None):
66 siblings = [s for s in siblings if s.node() != nullid]
66 siblings = [s for s in siblings if s.node() != nullid]
67 if len(siblings) == 1 and siblings[0].rev() == hiderev:
67 if len(siblings) == 1 and siblings[0].rev() == hiderev:
68 return
68 return
69 for s in siblings:
69 for s in siblings:
70 d = {'node': s.hex(), 'rev': s.rev()}
70 d = {'node': s.hex(), 'rev': s.rev()}
71 d['user'] = s.user()
71 d['user'] = s.user()
72 d['date'] = s.date()
72 d['date'] = s.date()
73 d['description'] = s.description()
73 d['description'] = s.description()
74 d['branch'] = s.branch()
74 d['branch'] = s.branch()
75 if hasattr(s, 'path'):
75 if util.safehasattr(s, 'path'):
76 d['file'] = s.path()
76 d['file'] = s.path()
77 yield d
77 yield d
78
78
79 def parents(ctx, hide=None):
79 def parents(ctx, hide=None):
80 return _siblings(ctx.parents(), hide)
80 return _siblings(ctx.parents(), hide)
81
81
82 def children(ctx, hide=None):
82 def children(ctx, hide=None):
83 return _siblings(ctx.children(), hide)
83 return _siblings(ctx.children(), hide)
84
84
85 def renamelink(fctx):
85 def renamelink(fctx):
86 r = fctx.renamed()
86 r = fctx.renamed()
87 if r:
87 if r:
88 return [dict(file=r[0], node=hex(r[1]))]
88 return [dict(file=r[0], node=hex(r[1]))]
89 return []
89 return []
90
90
91 def nodetagsdict(repo, node):
91 def nodetagsdict(repo, node):
92 return [{"name": i} for i in repo.nodetags(node)]
92 return [{"name": i} for i in repo.nodetags(node)]
93
93
94 def nodebookmarksdict(repo, node):
94 def nodebookmarksdict(repo, node):
95 return [{"name": i} for i in repo.nodebookmarks(node)]
95 return [{"name": i} for i in repo.nodebookmarks(node)]
96
96
97 def nodebranchdict(repo, ctx):
97 def nodebranchdict(repo, ctx):
98 branches = []
98 branches = []
99 branch = ctx.branch()
99 branch = ctx.branch()
100 # If this is an empty repo, ctx.node() == nullid,
100 # If this is an empty repo, ctx.node() == nullid,
101 # ctx.branch() == 'default', but branchtags() is
101 # ctx.branch() == 'default', but branchtags() is
102 # an empty dict. Using dict.get avoids a traceback.
102 # an empty dict. Using dict.get avoids a traceback.
103 if repo.branchtags().get(branch) == ctx.node():
103 if repo.branchtags().get(branch) == ctx.node():
104 branches.append({"name": branch})
104 branches.append({"name": branch})
105 return branches
105 return branches
106
106
107 def nodeinbranch(repo, ctx):
107 def nodeinbranch(repo, ctx):
108 branches = []
108 branches = []
109 branch = ctx.branch()
109 branch = ctx.branch()
110 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
110 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
111 branches.append({"name": branch})
111 branches.append({"name": branch})
112 return branches
112 return branches
113
113
114 def nodebranchnodefault(ctx):
114 def nodebranchnodefault(ctx):
115 branches = []
115 branches = []
116 branch = ctx.branch()
116 branch = ctx.branch()
117 if branch != 'default':
117 if branch != 'default':
118 branches.append({"name": branch})
118 branches.append({"name": branch})
119 return branches
119 return branches
120
120
121 def showtag(repo, tmpl, t1, node=nullid, **args):
121 def showtag(repo, tmpl, t1, node=nullid, **args):
122 for t in repo.nodetags(node):
122 for t in repo.nodetags(node):
123 yield tmpl(t1, tag=t, **args)
123 yield tmpl(t1, tag=t, **args)
124
124
125 def showbookmark(repo, tmpl, t1, node=nullid, **args):
125 def showbookmark(repo, tmpl, t1, node=nullid, **args):
126 for t in repo.nodebookmarks(node):
126 for t in repo.nodebookmarks(node):
127 yield tmpl(t1, bookmark=t, **args)
127 yield tmpl(t1, bookmark=t, **args)
128
128
129 def cleanpath(repo, path):
129 def cleanpath(repo, path):
130 path = path.lstrip('/')
130 path = path.lstrip('/')
131 return scmutil.canonpath(repo.root, '', path)
131 return scmutil.canonpath(repo.root, '', path)
132
132
133 def changectx(repo, req):
133 def changectx(repo, req):
134 changeid = "tip"
134 changeid = "tip"
135 if 'node' in req.form:
135 if 'node' in req.form:
136 changeid = req.form['node'][0]
136 changeid = req.form['node'][0]
137 elif 'manifest' in req.form:
137 elif 'manifest' in req.form:
138 changeid = req.form['manifest'][0]
138 changeid = req.form['manifest'][0]
139
139
140 try:
140 try:
141 ctx = repo[changeid]
141 ctx = repo[changeid]
142 except error.RepoError:
142 except error.RepoError:
143 man = repo.manifest
143 man = repo.manifest
144 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
144 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
145
145
146 return ctx
146 return ctx
147
147
148 def filectx(repo, req):
148 def filectx(repo, req):
149 path = cleanpath(repo, req.form['file'][0])
149 path = cleanpath(repo, req.form['file'][0])
150 if 'node' in req.form:
150 if 'node' in req.form:
151 changeid = req.form['node'][0]
151 changeid = req.form['node'][0]
152 else:
152 else:
153 changeid = req.form['filenode'][0]
153 changeid = req.form['filenode'][0]
154 try:
154 try:
155 fctx = repo[changeid][path]
155 fctx = repo[changeid][path]
156 except error.RepoError:
156 except error.RepoError:
157 fctx = repo.filectx(path, fileid=changeid)
157 fctx = repo.filectx(path, fileid=changeid)
158
158
159 return fctx
159 return fctx
160
160
161 def listfilediffs(tmpl, files, node, max):
161 def listfilediffs(tmpl, files, node, max):
162 for f in files[:max]:
162 for f in files[:max]:
163 yield tmpl('filedifflink', node=hex(node), file=f)
163 yield tmpl('filedifflink', node=hex(node), file=f)
164 if len(files) > max:
164 if len(files) > max:
165 yield tmpl('fileellipses')
165 yield tmpl('fileellipses')
166
166
167 def diffs(repo, tmpl, ctx, files, parity, style):
167 def diffs(repo, tmpl, ctx, files, parity, style):
168
168
169 def countgen():
169 def countgen():
170 start = 1
170 start = 1
171 while True:
171 while True:
172 yield start
172 yield start
173 start += 1
173 start += 1
174
174
175 blockcount = countgen()
175 blockcount = countgen()
176 def prettyprintlines(diff):
176 def prettyprintlines(diff):
177 blockno = blockcount.next()
177 blockno = blockcount.next()
178 for lineno, l in enumerate(diff.splitlines(True)):
178 for lineno, l in enumerate(diff.splitlines(True)):
179 lineno = "%d.%d" % (blockno, lineno + 1)
179 lineno = "%d.%d" % (blockno, lineno + 1)
180 if l.startswith('+'):
180 if l.startswith('+'):
181 ltype = "difflineplus"
181 ltype = "difflineplus"
182 elif l.startswith('-'):
182 elif l.startswith('-'):
183 ltype = "difflineminus"
183 ltype = "difflineminus"
184 elif l.startswith('@'):
184 elif l.startswith('@'):
185 ltype = "difflineat"
185 ltype = "difflineat"
186 else:
186 else:
187 ltype = "diffline"
187 ltype = "diffline"
188 yield tmpl(ltype,
188 yield tmpl(ltype,
189 line=l,
189 line=l,
190 lineid="l%s" % lineno,
190 lineid="l%s" % lineno,
191 linenumber="% 8s" % lineno)
191 linenumber="% 8s" % lineno)
192
192
193 if files:
193 if files:
194 m = match.exact(repo.root, repo.getcwd(), files)
194 m = match.exact(repo.root, repo.getcwd(), files)
195 else:
195 else:
196 m = match.always(repo.root, repo.getcwd())
196 m = match.always(repo.root, repo.getcwd())
197
197
198 diffopts = patch.diffopts(repo.ui, untrusted=True)
198 diffopts = patch.diffopts(repo.ui, untrusted=True)
199 parents = ctx.parents()
199 parents = ctx.parents()
200 node1 = parents and parents[0].node() or nullid
200 node1 = parents and parents[0].node() or nullid
201 node2 = ctx.node()
201 node2 = ctx.node()
202
202
203 block = []
203 block = []
204 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
204 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
205 if chunk.startswith('diff') and block:
205 if chunk.startswith('diff') and block:
206 yield tmpl('diffblock', parity=parity.next(),
206 yield tmpl('diffblock', parity=parity.next(),
207 lines=prettyprintlines(''.join(block)))
207 lines=prettyprintlines(''.join(block)))
208 block = []
208 block = []
209 if chunk.startswith('diff') and style != 'raw':
209 if chunk.startswith('diff') and style != 'raw':
210 chunk = ''.join(chunk.splitlines(True)[1:])
210 chunk = ''.join(chunk.splitlines(True)[1:])
211 block.append(chunk)
211 block.append(chunk)
212 yield tmpl('diffblock', parity=parity.next(),
212 yield tmpl('diffblock', parity=parity.next(),
213 lines=prettyprintlines(''.join(block)))
213 lines=prettyprintlines(''.join(block)))
214
214
215 def diffstatgen(ctx):
215 def diffstatgen(ctx):
216 '''Generator function that provides the diffstat data.'''
216 '''Generator function that provides the diffstat data.'''
217
217
218 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
218 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
219 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
219 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
220 while True:
220 while True:
221 yield stats, maxname, maxtotal, addtotal, removetotal, binary
221 yield stats, maxname, maxtotal, addtotal, removetotal, binary
222
222
223 def diffsummary(statgen):
223 def diffsummary(statgen):
224 '''Return a short summary of the diff.'''
224 '''Return a short summary of the diff.'''
225
225
226 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
226 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
227 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
227 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
228 len(stats), addtotal, removetotal)
228 len(stats), addtotal, removetotal)
229
229
230 def diffstat(tmpl, ctx, statgen, parity):
230 def diffstat(tmpl, ctx, statgen, parity):
231 '''Return a diffstat template for each file in the diff.'''
231 '''Return a diffstat template for each file in the diff.'''
232
232
233 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
233 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
234 files = ctx.files()
234 files = ctx.files()
235
235
236 def pct(i):
236 def pct(i):
237 if maxtotal == 0:
237 if maxtotal == 0:
238 return 0
238 return 0
239 return (float(i) / maxtotal) * 100
239 return (float(i) / maxtotal) * 100
240
240
241 fileno = 0
241 fileno = 0
242 for filename, adds, removes, isbinary in stats:
242 for filename, adds, removes, isbinary in stats:
243 template = filename in files and 'diffstatlink' or 'diffstatnolink'
243 template = filename in files and 'diffstatlink' or 'diffstatnolink'
244 total = adds + removes
244 total = adds + removes
245 fileno += 1
245 fileno += 1
246 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
246 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
247 total=total, addpct=pct(adds), removepct=pct(removes),
247 total=total, addpct=pct(adds), removepct=pct(removes),
248 parity=parity.next())
248 parity=parity.next())
249
249
250 class sessionvars(object):
250 class sessionvars(object):
251 def __init__(self, vars, start='?'):
251 def __init__(self, vars, start='?'):
252 self.start = start
252 self.start = start
253 self.vars = vars
253 self.vars = vars
254 def __getitem__(self, key):
254 def __getitem__(self, key):
255 return self.vars[key]
255 return self.vars[key]
256 def __setitem__(self, key, value):
256 def __setitem__(self, key, value):
257 self.vars[key] = value
257 self.vars[key] = value
258 def __copy__(self):
258 def __copy__(self):
259 return sessionvars(copy.copy(self.vars), self.start)
259 return sessionvars(copy.copy(self.vars), self.start)
260 def __iter__(self):
260 def __iter__(self):
261 separator = self.start
261 separator = self.start
262 for key, value in self.vars.iteritems():
262 for key, value in self.vars.iteritems():
263 yield {'name': key, 'value': str(value), 'separator': separator}
263 yield {'name': key, 'value': str(value), 'separator': separator}
264 separator = '&'
264 separator = '&'
265
265
266 class wsgiui(ui.ui):
266 class wsgiui(ui.ui):
267 # default termwidth breaks under mod_wsgi
267 # default termwidth breaks under mod_wsgi
268 def termwidth(self):
268 def termwidth(self):
269 return 80
269 return 80
General Comments 0
You need to be logged in to leave comments. Login now