##// END OF EJS Templates
util: remove md5...
Martin Geisler -
r8296:908c5906 default
parent child Browse files
Show More
@@ -1,661 +1,671 b''
1 # This library is free software; you can redistribute it and/or
1 # This library is free software; you can redistribute it and/or
2 # modify it under the terms of the GNU Lesser General Public
2 # modify it under the terms of the GNU Lesser General Public
3 # License as published by the Free Software Foundation; either
3 # License as published by the Free Software Foundation; either
4 # version 2.1 of the License, or (at your option) any later version.
4 # version 2.1 of the License, or (at your option) any later version.
5 #
5 #
6 # This library is distributed in the hope that it will be useful,
6 # This library is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 # Lesser General Public License for more details.
9 # Lesser General Public License for more details.
10 #
10 #
11 # You should have received a copy of the GNU Lesser General Public
11 # You should have received a copy of the GNU Lesser General Public
12 # License along with this library; if not, write to the
12 # License along with this library; if not, write to the
13 # Free Software Foundation, Inc.,
13 # Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330,
14 # 59 Temple Place, Suite 330,
15 # Boston, MA 02111-1307 USA
15 # Boston, MA 02111-1307 USA
16
16
17 # This file is part of urlgrabber, a high-level cross-protocol url-grabber
17 # This file is part of urlgrabber, a high-level cross-protocol url-grabber
18 # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
18 # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
19
19
20 # Modified by Benoit Boissinot:
20 # Modified by Benoit Boissinot:
21 # - fix for digest auth (inspired from urllib2.py @ Python v2.4)
21 # - fix for digest auth (inspired from urllib2.py @ Python v2.4)
22 # Modified by Dirkjan Ochtman:
22 # Modified by Dirkjan Ochtman:
23 # - import md5 function from a local util module
23 # - import md5 function from a local util module
24 # Modified by Martin Geisler:
25 # - moved md5 function from local util module to this module
24
26
25 """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
27 """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
26
28
27 >>> import urllib2
29 >>> import urllib2
28 >>> from keepalive import HTTPHandler
30 >>> from keepalive import HTTPHandler
29 >>> keepalive_handler = HTTPHandler()
31 >>> keepalive_handler = HTTPHandler()
30 >>> opener = urllib2.build_opener(keepalive_handler)
32 >>> opener = urllib2.build_opener(keepalive_handler)
31 >>> urllib2.install_opener(opener)
33 >>> urllib2.install_opener(opener)
32 >>>
34 >>>
33 >>> fo = urllib2.urlopen('http://www.python.org')
35 >>> fo = urllib2.urlopen('http://www.python.org')
34
36
35 If a connection to a given host is requested, and all of the existing
37 If a connection to a given host is requested, and all of the existing
36 connections are still in use, another connection will be opened. If
38 connections are still in use, another connection will be opened. If
37 the handler tries to use an existing connection but it fails in some
39 the handler tries to use an existing connection but it fails in some
38 way, it will be closed and removed from the pool.
40 way, it will be closed and removed from the pool.
39
41
40 To remove the handler, simply re-run build_opener with no arguments, and
42 To remove the handler, simply re-run build_opener with no arguments, and
41 install that opener.
43 install that opener.
42
44
43 You can explicitly close connections by using the close_connection()
45 You can explicitly close connections by using the close_connection()
44 method of the returned file-like object (described below) or you can
46 method of the returned file-like object (described below) or you can
45 use the handler methods:
47 use the handler methods:
46
48
47 close_connection(host)
49 close_connection(host)
48 close_all()
50 close_all()
49 open_connections()
51 open_connections()
50
52
51 NOTE: using the close_connection and close_all methods of the handler
53 NOTE: using the close_connection and close_all methods of the handler
52 should be done with care when using multiple threads.
54 should be done with care when using multiple threads.
53 * there is nothing that prevents another thread from creating new
55 * there is nothing that prevents another thread from creating new
54 connections immediately after connections are closed
56 connections immediately after connections are closed
55 * no checks are done to prevent in-use connections from being closed
57 * no checks are done to prevent in-use connections from being closed
56
58
57 >>> keepalive_handler.close_all()
59 >>> keepalive_handler.close_all()
58
60
59 EXTRA ATTRIBUTES AND METHODS
61 EXTRA ATTRIBUTES AND METHODS
60
62
61 Upon a status of 200, the object returned has a few additional
63 Upon a status of 200, the object returned has a few additional
62 attributes and methods, which should not be used if you want to
64 attributes and methods, which should not be used if you want to
63 remain consistent with the normal urllib2-returned objects:
65 remain consistent with the normal urllib2-returned objects:
64
66
65 close_connection() - close the connection to the host
67 close_connection() - close the connection to the host
66 readlines() - you know, readlines()
68 readlines() - you know, readlines()
67 status - the return status (ie 404)
69 status - the return status (ie 404)
68 reason - english translation of status (ie 'File not found')
70 reason - english translation of status (ie 'File not found')
69
71
70 If you want the best of both worlds, use this inside an
72 If you want the best of both worlds, use this inside an
71 AttributeError-catching try:
73 AttributeError-catching try:
72
74
73 >>> try: status = fo.status
75 >>> try: status = fo.status
74 >>> except AttributeError: status = None
76 >>> except AttributeError: status = None
75
77
76 Unfortunately, these are ONLY there if status == 200, so it's not
78 Unfortunately, these are ONLY there if status == 200, so it's not
77 easy to distinguish between non-200 responses. The reason is that
79 easy to distinguish between non-200 responses. The reason is that
78 urllib2 tries to do clever things with error codes 301, 302, 401,
80 urllib2 tries to do clever things with error codes 301, 302, 401,
79 and 407, and it wraps the object upon return.
81 and 407, and it wraps the object upon return.
80
82
81 For python versions earlier than 2.4, you can avoid this fancy error
83 For python versions earlier than 2.4, you can avoid this fancy error
82 handling by setting the module-level global HANDLE_ERRORS to zero.
84 handling by setting the module-level global HANDLE_ERRORS to zero.
83 You see, prior to 2.4, it's the HTTP Handler's job to determine what
85 You see, prior to 2.4, it's the HTTP Handler's job to determine what
84 to handle specially, and what to just pass up. HANDLE_ERRORS == 0
86 to handle specially, and what to just pass up. HANDLE_ERRORS == 0
85 means "pass everything up". In python 2.4, however, this job no
87 means "pass everything up". In python 2.4, however, this job no
86 longer belongs to the HTTP Handler and is now done by a NEW handler,
88 longer belongs to the HTTP Handler and is now done by a NEW handler,
87 HTTPErrorProcessor. Here's the bottom line:
89 HTTPErrorProcessor. Here's the bottom line:
88
90
89 python version < 2.4
91 python version < 2.4
90 HANDLE_ERRORS == 1 (default) pass up 200, treat the rest as
92 HANDLE_ERRORS == 1 (default) pass up 200, treat the rest as
91 errors
93 errors
92 HANDLE_ERRORS == 0 pass everything up, error processing is
94 HANDLE_ERRORS == 0 pass everything up, error processing is
93 left to the calling code
95 left to the calling code
94 python version >= 2.4
96 python version >= 2.4
95 HANDLE_ERRORS == 1 pass up 200, treat the rest as errors
97 HANDLE_ERRORS == 1 pass up 200, treat the rest as errors
96 HANDLE_ERRORS == 0 (default) pass everything up, let the
98 HANDLE_ERRORS == 0 (default) pass everything up, let the
97 other handlers (specifically,
99 other handlers (specifically,
98 HTTPErrorProcessor) decide what to do
100 HTTPErrorProcessor) decide what to do
99
101
100 In practice, setting the variable either way makes little difference
102 In practice, setting the variable either way makes little difference
101 in python 2.4, so for the most consistent behavior across versions,
103 in python 2.4, so for the most consistent behavior across versions,
102 you probably just want to use the defaults, which will give you
104 you probably just want to use the defaults, which will give you
103 exceptions on errors.
105 exceptions on errors.
104
106
105 """
107 """
106
108
107 # $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $
109 # $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $
108
110
109 import urllib2
111 import urllib2
110 import httplib
112 import httplib
111 import socket
113 import socket
112 import thread
114 import thread
113
115
114 DEBUG = None
116 DEBUG = None
115
117
116 import sys
118 import sys
117 if sys.version_info < (2, 4): HANDLE_ERRORS = 1
119 if sys.version_info < (2, 4): HANDLE_ERRORS = 1
118 else: HANDLE_ERRORS = 0
120 else: HANDLE_ERRORS = 0
119
121
120 class ConnectionManager:
122 class ConnectionManager:
121 """
123 """
122 The connection manager must be able to:
124 The connection manager must be able to:
123 * keep track of all existing
125 * keep track of all existing
124 """
126 """
125 def __init__(self):
127 def __init__(self):
126 self._lock = thread.allocate_lock()
128 self._lock = thread.allocate_lock()
127 self._hostmap = {} # map hosts to a list of connections
129 self._hostmap = {} # map hosts to a list of connections
128 self._connmap = {} # map connections to host
130 self._connmap = {} # map connections to host
129 self._readymap = {} # map connection to ready state
131 self._readymap = {} # map connection to ready state
130
132
131 def add(self, host, connection, ready):
133 def add(self, host, connection, ready):
132 self._lock.acquire()
134 self._lock.acquire()
133 try:
135 try:
134 if not host in self._hostmap: self._hostmap[host] = []
136 if not host in self._hostmap: self._hostmap[host] = []
135 self._hostmap[host].append(connection)
137 self._hostmap[host].append(connection)
136 self._connmap[connection] = host
138 self._connmap[connection] = host
137 self._readymap[connection] = ready
139 self._readymap[connection] = ready
138 finally:
140 finally:
139 self._lock.release()
141 self._lock.release()
140
142
141 def remove(self, connection):
143 def remove(self, connection):
142 self._lock.acquire()
144 self._lock.acquire()
143 try:
145 try:
144 try:
146 try:
145 host = self._connmap[connection]
147 host = self._connmap[connection]
146 except KeyError:
148 except KeyError:
147 pass
149 pass
148 else:
150 else:
149 del self._connmap[connection]
151 del self._connmap[connection]
150 del self._readymap[connection]
152 del self._readymap[connection]
151 self._hostmap[host].remove(connection)
153 self._hostmap[host].remove(connection)
152 if not self._hostmap[host]: del self._hostmap[host]
154 if not self._hostmap[host]: del self._hostmap[host]
153 finally:
155 finally:
154 self._lock.release()
156 self._lock.release()
155
157
156 def set_ready(self, connection, ready):
158 def set_ready(self, connection, ready):
157 try: self._readymap[connection] = ready
159 try: self._readymap[connection] = ready
158 except KeyError: pass
160 except KeyError: pass
159
161
160 def get_ready_conn(self, host):
162 def get_ready_conn(self, host):
161 conn = None
163 conn = None
162 self._lock.acquire()
164 self._lock.acquire()
163 try:
165 try:
164 if host in self._hostmap:
166 if host in self._hostmap:
165 for c in self._hostmap[host]:
167 for c in self._hostmap[host]:
166 if self._readymap[c]:
168 if self._readymap[c]:
167 self._readymap[c] = 0
169 self._readymap[c] = 0
168 conn = c
170 conn = c
169 break
171 break
170 finally:
172 finally:
171 self._lock.release()
173 self._lock.release()
172 return conn
174 return conn
173
175
174 def get_all(self, host=None):
176 def get_all(self, host=None):
175 if host:
177 if host:
176 return list(self._hostmap.get(host, []))
178 return list(self._hostmap.get(host, []))
177 else:
179 else:
178 return dict(self._hostmap)
180 return dict(self._hostmap)
179
181
180 class KeepAliveHandler:
182 class KeepAliveHandler:
181 def __init__(self):
183 def __init__(self):
182 self._cm = ConnectionManager()
184 self._cm = ConnectionManager()
183
185
184 #### Connection Management
186 #### Connection Management
185 def open_connections(self):
187 def open_connections(self):
186 """return a list of connected hosts and the number of connections
188 """return a list of connected hosts and the number of connections
187 to each. [('foo.com:80', 2), ('bar.org', 1)]"""
189 to each. [('foo.com:80', 2), ('bar.org', 1)]"""
188 return [(host, len(li)) for (host, li) in self._cm.get_all().items()]
190 return [(host, len(li)) for (host, li) in self._cm.get_all().items()]
189
191
190 def close_connection(self, host):
192 def close_connection(self, host):
191 """close connection(s) to <host>
193 """close connection(s) to <host>
192 host is the host:port spec, as in 'www.cnn.com:8080' as passed in.
194 host is the host:port spec, as in 'www.cnn.com:8080' as passed in.
193 no error occurs if there is no connection to that host."""
195 no error occurs if there is no connection to that host."""
194 for h in self._cm.get_all(host):
196 for h in self._cm.get_all(host):
195 self._cm.remove(h)
197 self._cm.remove(h)
196 h.close()
198 h.close()
197
199
198 def close_all(self):
200 def close_all(self):
199 """close all open connections"""
201 """close all open connections"""
200 for host, conns in self._cm.get_all().iteritems():
202 for host, conns in self._cm.get_all().iteritems():
201 for h in conns:
203 for h in conns:
202 self._cm.remove(h)
204 self._cm.remove(h)
203 h.close()
205 h.close()
204
206
205 def _request_closed(self, request, host, connection):
207 def _request_closed(self, request, host, connection):
206 """tells us that this request is now closed and the the
208 """tells us that this request is now closed and the the
207 connection is ready for another request"""
209 connection is ready for another request"""
208 self._cm.set_ready(connection, 1)
210 self._cm.set_ready(connection, 1)
209
211
210 def _remove_connection(self, host, connection, close=0):
212 def _remove_connection(self, host, connection, close=0):
211 if close: connection.close()
213 if close: connection.close()
212 self._cm.remove(connection)
214 self._cm.remove(connection)
213
215
214 #### Transaction Execution
216 #### Transaction Execution
215 def http_open(self, req):
217 def http_open(self, req):
216 return self.do_open(HTTPConnection, req)
218 return self.do_open(HTTPConnection, req)
217
219
218 def do_open(self, http_class, req):
220 def do_open(self, http_class, req):
219 host = req.get_host()
221 host = req.get_host()
220 if not host:
222 if not host:
221 raise urllib2.URLError('no host given')
223 raise urllib2.URLError('no host given')
222
224
223 try:
225 try:
224 h = self._cm.get_ready_conn(host)
226 h = self._cm.get_ready_conn(host)
225 while h:
227 while h:
226 r = self._reuse_connection(h, req, host)
228 r = self._reuse_connection(h, req, host)
227
229
228 # if this response is non-None, then it worked and we're
230 # if this response is non-None, then it worked and we're
229 # done. Break out, skipping the else block.
231 # done. Break out, skipping the else block.
230 if r: break
232 if r: break
231
233
232 # connection is bad - possibly closed by server
234 # connection is bad - possibly closed by server
233 # discard it and ask for the next free connection
235 # discard it and ask for the next free connection
234 h.close()
236 h.close()
235 self._cm.remove(h)
237 self._cm.remove(h)
236 h = self._cm.get_ready_conn(host)
238 h = self._cm.get_ready_conn(host)
237 else:
239 else:
238 # no (working) free connections were found. Create a new one.
240 # no (working) free connections were found. Create a new one.
239 h = http_class(host)
241 h = http_class(host)
240 if DEBUG: DEBUG.info("creating new connection to %s (%d)",
242 if DEBUG: DEBUG.info("creating new connection to %s (%d)",
241 host, id(h))
243 host, id(h))
242 self._cm.add(host, h, 0)
244 self._cm.add(host, h, 0)
243 self._start_transaction(h, req)
245 self._start_transaction(h, req)
244 r = h.getresponse()
246 r = h.getresponse()
245 except (socket.error, httplib.HTTPException), err:
247 except (socket.error, httplib.HTTPException), err:
246 raise urllib2.URLError(err)
248 raise urllib2.URLError(err)
247
249
248 # if not a persistent connection, don't try to reuse it
250 # if not a persistent connection, don't try to reuse it
249 if r.will_close: self._cm.remove(h)
251 if r.will_close: self._cm.remove(h)
250
252
251 if DEBUG: DEBUG.info("STATUS: %s, %s", r.status, r.reason)
253 if DEBUG: DEBUG.info("STATUS: %s, %s", r.status, r.reason)
252 r._handler = self
254 r._handler = self
253 r._host = host
255 r._host = host
254 r._url = req.get_full_url()
256 r._url = req.get_full_url()
255 r._connection = h
257 r._connection = h
256 r.code = r.status
258 r.code = r.status
257 r.headers = r.msg
259 r.headers = r.msg
258 r.msg = r.reason
260 r.msg = r.reason
259
261
260 if r.status == 200 or not HANDLE_ERRORS:
262 if r.status == 200 or not HANDLE_ERRORS:
261 return r
263 return r
262 else:
264 else:
263 return self.parent.error('http', req, r,
265 return self.parent.error('http', req, r,
264 r.status, r.msg, r.headers)
266 r.status, r.msg, r.headers)
265
267
266 def _reuse_connection(self, h, req, host):
268 def _reuse_connection(self, h, req, host):
267 """start the transaction with a re-used connection
269 """start the transaction with a re-used connection
268 return a response object (r) upon success or None on failure.
270 return a response object (r) upon success or None on failure.
269 This DOES not close or remove bad connections in cases where
271 This DOES not close or remove bad connections in cases where
270 it returns. However, if an unexpected exception occurs, it
272 it returns. However, if an unexpected exception occurs, it
271 will close and remove the connection before re-raising.
273 will close and remove the connection before re-raising.
272 """
274 """
273 try:
275 try:
274 self._start_transaction(h, req)
276 self._start_transaction(h, req)
275 r = h.getresponse()
277 r = h.getresponse()
276 # note: just because we got something back doesn't mean it
278 # note: just because we got something back doesn't mean it
277 # worked. We'll check the version below, too.
279 # worked. We'll check the version below, too.
278 except (socket.error, httplib.HTTPException):
280 except (socket.error, httplib.HTTPException):
279 r = None
281 r = None
280 except:
282 except:
281 # adding this block just in case we've missed
283 # adding this block just in case we've missed
282 # something we will still raise the exception, but
284 # something we will still raise the exception, but
283 # lets try and close the connection and remove it
285 # lets try and close the connection and remove it
284 # first. We previously got into a nasty loop
286 # first. We previously got into a nasty loop
285 # where an exception was uncaught, and so the
287 # where an exception was uncaught, and so the
286 # connection stayed open. On the next try, the
288 # connection stayed open. On the next try, the
287 # same exception was raised, etc. The tradeoff is
289 # same exception was raised, etc. The tradeoff is
288 # that it's now possible this call will raise
290 # that it's now possible this call will raise
289 # a DIFFERENT exception
291 # a DIFFERENT exception
290 if DEBUG: DEBUG.error("unexpected exception - closing " + \
292 if DEBUG: DEBUG.error("unexpected exception - closing " + \
291 "connection to %s (%d)", host, id(h))
293 "connection to %s (%d)", host, id(h))
292 self._cm.remove(h)
294 self._cm.remove(h)
293 h.close()
295 h.close()
294 raise
296 raise
295
297
296 if r is None or r.version == 9:
298 if r is None or r.version == 9:
297 # httplib falls back to assuming HTTP 0.9 if it gets a
299 # httplib falls back to assuming HTTP 0.9 if it gets a
298 # bad header back. This is most likely to happen if
300 # bad header back. This is most likely to happen if
299 # the socket has been closed by the server since we
301 # the socket has been closed by the server since we
300 # last used the connection.
302 # last used the connection.
301 if DEBUG: DEBUG.info("failed to re-use connection to %s (%d)",
303 if DEBUG: DEBUG.info("failed to re-use connection to %s (%d)",
302 host, id(h))
304 host, id(h))
303 r = None
305 r = None
304 else:
306 else:
305 if DEBUG: DEBUG.info("re-using connection to %s (%d)", host, id(h))
307 if DEBUG: DEBUG.info("re-using connection to %s (%d)", host, id(h))
306
308
307 return r
309 return r
308
310
309 def _start_transaction(self, h, req):
311 def _start_transaction(self, h, req):
310 # What follows mostly reimplements HTTPConnection.request()
312 # What follows mostly reimplements HTTPConnection.request()
311 # except it adds self.parent.addheaders in the mix.
313 # except it adds self.parent.addheaders in the mix.
312 headers = req.headers.copy()
314 headers = req.headers.copy()
313 if sys.version_info >= (2, 4):
315 if sys.version_info >= (2, 4):
314 headers.update(req.unredirected_hdrs)
316 headers.update(req.unredirected_hdrs)
315 headers.update(self.parent.addheaders)
317 headers.update(self.parent.addheaders)
316 headers = dict((n.lower(), v) for n,v in headers.items())
318 headers = dict((n.lower(), v) for n,v in headers.items())
317 skipheaders = {}
319 skipheaders = {}
318 for n in ('host', 'accept-encoding'):
320 for n in ('host', 'accept-encoding'):
319 if n in headers:
321 if n in headers:
320 skipheaders['skip_' + n.replace('-', '_')] = 1
322 skipheaders['skip_' + n.replace('-', '_')] = 1
321 try:
323 try:
322 if req.has_data():
324 if req.has_data():
323 data = req.get_data()
325 data = req.get_data()
324 h.putrequest('POST', req.get_selector(), **skipheaders)
326 h.putrequest('POST', req.get_selector(), **skipheaders)
325 if 'content-type' not in headers:
327 if 'content-type' not in headers:
326 h.putheader('Content-type',
328 h.putheader('Content-type',
327 'application/x-www-form-urlencoded')
329 'application/x-www-form-urlencoded')
328 if 'content-length' not in headers:
330 if 'content-length' not in headers:
329 h.putheader('Content-length', '%d' % len(data))
331 h.putheader('Content-length', '%d' % len(data))
330 else:
332 else:
331 h.putrequest('GET', req.get_selector(), **skipheaders)
333 h.putrequest('GET', req.get_selector(), **skipheaders)
332 except (socket.error), err:
334 except (socket.error), err:
333 raise urllib2.URLError(err)
335 raise urllib2.URLError(err)
334 for k, v in headers.items():
336 for k, v in headers.items():
335 h.putheader(k, v)
337 h.putheader(k, v)
336 h.endheaders()
338 h.endheaders()
337 if req.has_data():
339 if req.has_data():
338 h.send(data)
340 h.send(data)
339
341
340 class HTTPHandler(KeepAliveHandler, urllib2.HTTPHandler):
342 class HTTPHandler(KeepAliveHandler, urllib2.HTTPHandler):
341 pass
343 pass
342
344
343 class HTTPResponse(httplib.HTTPResponse):
345 class HTTPResponse(httplib.HTTPResponse):
344 # we need to subclass HTTPResponse in order to
346 # we need to subclass HTTPResponse in order to
345 # 1) add readline() and readlines() methods
347 # 1) add readline() and readlines() methods
346 # 2) add close_connection() methods
348 # 2) add close_connection() methods
347 # 3) add info() and geturl() methods
349 # 3) add info() and geturl() methods
348
350
349 # in order to add readline(), read must be modified to deal with a
351 # in order to add readline(), read must be modified to deal with a
350 # buffer. example: readline must read a buffer and then spit back
352 # buffer. example: readline must read a buffer and then spit back
351 # one line at a time. The only real alternative is to read one
353 # one line at a time. The only real alternative is to read one
352 # BYTE at a time (ick). Once something has been read, it can't be
354 # BYTE at a time (ick). Once something has been read, it can't be
353 # put back (ok, maybe it can, but that's even uglier than this),
355 # put back (ok, maybe it can, but that's even uglier than this),
354 # so if you THEN do a normal read, you must first take stuff from
356 # so if you THEN do a normal read, you must first take stuff from
355 # the buffer.
357 # the buffer.
356
358
357 # the read method wraps the original to accomodate buffering,
359 # the read method wraps the original to accomodate buffering,
358 # although read() never adds to the buffer.
360 # although read() never adds to the buffer.
359 # Both readline and readlines have been stolen with almost no
361 # Both readline and readlines have been stolen with almost no
360 # modification from socket.py
362 # modification from socket.py
361
363
362
364
363 def __init__(self, sock, debuglevel=0, strict=0, method=None):
365 def __init__(self, sock, debuglevel=0, strict=0, method=None):
364 if method: # the httplib in python 2.3 uses the method arg
366 if method: # the httplib in python 2.3 uses the method arg
365 httplib.HTTPResponse.__init__(self, sock, debuglevel, method)
367 httplib.HTTPResponse.__init__(self, sock, debuglevel, method)
366 else: # 2.2 doesn't
368 else: # 2.2 doesn't
367 httplib.HTTPResponse.__init__(self, sock, debuglevel)
369 httplib.HTTPResponse.__init__(self, sock, debuglevel)
368 self.fileno = sock.fileno
370 self.fileno = sock.fileno
369 self.code = None
371 self.code = None
370 self._rbuf = ''
372 self._rbuf = ''
371 self._rbufsize = 8096
373 self._rbufsize = 8096
372 self._handler = None # inserted by the handler later
374 self._handler = None # inserted by the handler later
373 self._host = None # (same)
375 self._host = None # (same)
374 self._url = None # (same)
376 self._url = None # (same)
375 self._connection = None # (same)
377 self._connection = None # (same)
376
378
377 _raw_read = httplib.HTTPResponse.read
379 _raw_read = httplib.HTTPResponse.read
378
380
379 def close(self):
381 def close(self):
380 if self.fp:
382 if self.fp:
381 self.fp.close()
383 self.fp.close()
382 self.fp = None
384 self.fp = None
383 if self._handler:
385 if self._handler:
384 self._handler._request_closed(self, self._host,
386 self._handler._request_closed(self, self._host,
385 self._connection)
387 self._connection)
386
388
387 def close_connection(self):
389 def close_connection(self):
388 self._handler._remove_connection(self._host, self._connection, close=1)
390 self._handler._remove_connection(self._host, self._connection, close=1)
389 self.close()
391 self.close()
390
392
391 def info(self):
393 def info(self):
392 return self.headers
394 return self.headers
393
395
394 def geturl(self):
396 def geturl(self):
395 return self._url
397 return self._url
396
398
397 def read(self, amt=None):
399 def read(self, amt=None):
398 # the _rbuf test is only in this first if for speed. It's not
400 # the _rbuf test is only in this first if for speed. It's not
399 # logically necessary
401 # logically necessary
400 if self._rbuf and not amt is None:
402 if self._rbuf and not amt is None:
401 L = len(self._rbuf)
403 L = len(self._rbuf)
402 if amt > L:
404 if amt > L:
403 amt -= L
405 amt -= L
404 else:
406 else:
405 s = self._rbuf[:amt]
407 s = self._rbuf[:amt]
406 self._rbuf = self._rbuf[amt:]
408 self._rbuf = self._rbuf[amt:]
407 return s
409 return s
408
410
409 s = self._rbuf + self._raw_read(amt)
411 s = self._rbuf + self._raw_read(amt)
410 self._rbuf = ''
412 self._rbuf = ''
411 return s
413 return s
412
414
413 # stolen from Python SVN #68532 to fix issue1088
415 # stolen from Python SVN #68532 to fix issue1088
414 def _read_chunked(self, amt):
416 def _read_chunked(self, amt):
415 chunk_left = self.chunk_left
417 chunk_left = self.chunk_left
416 value = ''
418 value = ''
417
419
418 # XXX This accumulates chunks by repeated string concatenation,
420 # XXX This accumulates chunks by repeated string concatenation,
419 # which is not efficient as the number or size of chunks gets big.
421 # which is not efficient as the number or size of chunks gets big.
420 while True:
422 while True:
421 if chunk_left is None:
423 if chunk_left is None:
422 line = self.fp.readline()
424 line = self.fp.readline()
423 i = line.find(';')
425 i = line.find(';')
424 if i >= 0:
426 if i >= 0:
425 line = line[:i] # strip chunk-extensions
427 line = line[:i] # strip chunk-extensions
426 try:
428 try:
427 chunk_left = int(line, 16)
429 chunk_left = int(line, 16)
428 except ValueError:
430 except ValueError:
429 # close the connection as protocol synchronisation is
431 # close the connection as protocol synchronisation is
430 # probably lost
432 # probably lost
431 self.close()
433 self.close()
432 raise httplib.IncompleteRead(value)
434 raise httplib.IncompleteRead(value)
433 if chunk_left == 0:
435 if chunk_left == 0:
434 break
436 break
435 if amt is None:
437 if amt is None:
436 value += self._safe_read(chunk_left)
438 value += self._safe_read(chunk_left)
437 elif amt < chunk_left:
439 elif amt < chunk_left:
438 value += self._safe_read(amt)
440 value += self._safe_read(amt)
439 self.chunk_left = chunk_left - amt
441 self.chunk_left = chunk_left - amt
440 return value
442 return value
441 elif amt == chunk_left:
443 elif amt == chunk_left:
442 value += self._safe_read(amt)
444 value += self._safe_read(amt)
443 self._safe_read(2) # toss the CRLF at the end of the chunk
445 self._safe_read(2) # toss the CRLF at the end of the chunk
444 self.chunk_left = None
446 self.chunk_left = None
445 return value
447 return value
446 else:
448 else:
447 value += self._safe_read(chunk_left)
449 value += self._safe_read(chunk_left)
448 amt -= chunk_left
450 amt -= chunk_left
449
451
450 # we read the whole chunk, get another
452 # we read the whole chunk, get another
451 self._safe_read(2) # toss the CRLF at the end of the chunk
453 self._safe_read(2) # toss the CRLF at the end of the chunk
452 chunk_left = None
454 chunk_left = None
453
455
454 # read and discard trailer up to the CRLF terminator
456 # read and discard trailer up to the CRLF terminator
455 ### note: we shouldn't have any trailers!
457 ### note: we shouldn't have any trailers!
456 while True:
458 while True:
457 line = self.fp.readline()
459 line = self.fp.readline()
458 if not line:
460 if not line:
459 # a vanishingly small number of sites EOF without
461 # a vanishingly small number of sites EOF without
460 # sending the trailer
462 # sending the trailer
461 break
463 break
462 if line == '\r\n':
464 if line == '\r\n':
463 break
465 break
464
466
465 # we read everything; close the "file"
467 # we read everything; close the "file"
466 self.close()
468 self.close()
467
469
468 return value
470 return value
469
471
470 def readline(self, limit=-1):
472 def readline(self, limit=-1):
471 i = self._rbuf.find('\n')
473 i = self._rbuf.find('\n')
472 while i < 0 and not (0 < limit <= len(self._rbuf)):
474 while i < 0 and not (0 < limit <= len(self._rbuf)):
473 new = self._raw_read(self._rbufsize)
475 new = self._raw_read(self._rbufsize)
474 if not new: break
476 if not new: break
475 i = new.find('\n')
477 i = new.find('\n')
476 if i >= 0: i = i + len(self._rbuf)
478 if i >= 0: i = i + len(self._rbuf)
477 self._rbuf = self._rbuf + new
479 self._rbuf = self._rbuf + new
478 if i < 0: i = len(self._rbuf)
480 if i < 0: i = len(self._rbuf)
479 else: i = i+1
481 else: i = i+1
480 if 0 <= limit < len(self._rbuf): i = limit
482 if 0 <= limit < len(self._rbuf): i = limit
481 data, self._rbuf = self._rbuf[:i], self._rbuf[i:]
483 data, self._rbuf = self._rbuf[:i], self._rbuf[i:]
482 return data
484 return data
483
485
484 def readlines(self, sizehint = 0):
486 def readlines(self, sizehint = 0):
485 total = 0
487 total = 0
486 list = []
488 list = []
487 while 1:
489 while 1:
488 line = self.readline()
490 line = self.readline()
489 if not line: break
491 if not line: break
490 list.append(line)
492 list.append(line)
491 total += len(line)
493 total += len(line)
492 if sizehint and total >= sizehint:
494 if sizehint and total >= sizehint:
493 break
495 break
494 return list
496 return list
495
497
496
498
497 class HTTPConnection(httplib.HTTPConnection):
499 class HTTPConnection(httplib.HTTPConnection):
498 # use the modified response class
500 # use the modified response class
499 response_class = HTTPResponse
501 response_class = HTTPResponse
500
502
501 #########################################################################
503 #########################################################################
502 ##### TEST FUNCTIONS
504 ##### TEST FUNCTIONS
503 #########################################################################
505 #########################################################################
504
506
505 def error_handler(url):
507 def error_handler(url):
506 global HANDLE_ERRORS
508 global HANDLE_ERRORS
507 orig = HANDLE_ERRORS
509 orig = HANDLE_ERRORS
508 keepalive_handler = HTTPHandler()
510 keepalive_handler = HTTPHandler()
509 opener = urllib2.build_opener(keepalive_handler)
511 opener = urllib2.build_opener(keepalive_handler)
510 urllib2.install_opener(opener)
512 urllib2.install_opener(opener)
511 pos = {0: 'off', 1: 'on'}
513 pos = {0: 'off', 1: 'on'}
512 for i in (0, 1):
514 for i in (0, 1):
513 print " fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i)
515 print " fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i)
514 HANDLE_ERRORS = i
516 HANDLE_ERRORS = i
515 try:
517 try:
516 fo = urllib2.urlopen(url)
518 fo = urllib2.urlopen(url)
517 fo.read()
519 fo.read()
518 fo.close()
520 fo.close()
519 try: status, reason = fo.status, fo.reason
521 try: status, reason = fo.status, fo.reason
520 except AttributeError: status, reason = None, None
522 except AttributeError: status, reason = None, None
521 except IOError, e:
523 except IOError, e:
522 print " EXCEPTION: %s" % e
524 print " EXCEPTION: %s" % e
523 raise
525 raise
524 else:
526 else:
525 print " status = %s, reason = %s" % (status, reason)
527 print " status = %s, reason = %s" % (status, reason)
526 HANDLE_ERRORS = orig
528 HANDLE_ERRORS = orig
527 hosts = keepalive_handler.open_connections()
529 hosts = keepalive_handler.open_connections()
528 print "open connections:", hosts
530 print "open connections:", hosts
529 keepalive_handler.close_all()
531 keepalive_handler.close_all()
530
532
533 def md5(s):
534 try:
535 from hashlib import md5 as _md5
536 except ImportError:
537 from md5 import md5 as _md5
538 global md5
539 md5 = _md5
540 return _md5(s)
541
531 def continuity(url):
542 def continuity(url):
532 from util import md5
533 format = '%25s: %s'
543 format = '%25s: %s'
534
544
535 # first fetch the file with the normal http handler
545 # first fetch the file with the normal http handler
536 opener = urllib2.build_opener()
546 opener = urllib2.build_opener()
537 urllib2.install_opener(opener)
547 urllib2.install_opener(opener)
538 fo = urllib2.urlopen(url)
548 fo = urllib2.urlopen(url)
539 foo = fo.read()
549 foo = fo.read()
540 fo.close()
550 fo.close()
541 m = md5.new(foo)
551 m = md5.new(foo)
542 print format % ('normal urllib', m.hexdigest())
552 print format % ('normal urllib', m.hexdigest())
543
553
544 # now install the keepalive handler and try again
554 # now install the keepalive handler and try again
545 opener = urllib2.build_opener(HTTPHandler())
555 opener = urllib2.build_opener(HTTPHandler())
546 urllib2.install_opener(opener)
556 urllib2.install_opener(opener)
547
557
548 fo = urllib2.urlopen(url)
558 fo = urllib2.urlopen(url)
549 foo = fo.read()
559 foo = fo.read()
550 fo.close()
560 fo.close()
551 m = md5.new(foo)
561 m = md5.new(foo)
552 print format % ('keepalive read', m.hexdigest())
562 print format % ('keepalive read', m.hexdigest())
553
563
554 fo = urllib2.urlopen(url)
564 fo = urllib2.urlopen(url)
555 foo = ''
565 foo = ''
556 while 1:
566 while 1:
557 f = fo.readline()
567 f = fo.readline()
558 if f: foo = foo + f
568 if f: foo = foo + f
559 else: break
569 else: break
560 fo.close()
570 fo.close()
561 m = md5.new(foo)
571 m = md5.new(foo)
562 print format % ('keepalive readline', m.hexdigest())
572 print format % ('keepalive readline', m.hexdigest())
563
573
564 def comp(N, url):
574 def comp(N, url):
565 print ' making %i connections to:\n %s' % (N, url)
575 print ' making %i connections to:\n %s' % (N, url)
566
576
567 sys.stdout.write(' first using the normal urllib handlers')
577 sys.stdout.write(' first using the normal urllib handlers')
568 # first use normal opener
578 # first use normal opener
569 opener = urllib2.build_opener()
579 opener = urllib2.build_opener()
570 urllib2.install_opener(opener)
580 urllib2.install_opener(opener)
571 t1 = fetch(N, url)
581 t1 = fetch(N, url)
572 print ' TIME: %.3f s' % t1
582 print ' TIME: %.3f s' % t1
573
583
574 sys.stdout.write(' now using the keepalive handler ')
584 sys.stdout.write(' now using the keepalive handler ')
575 # now install the keepalive handler and try again
585 # now install the keepalive handler and try again
576 opener = urllib2.build_opener(HTTPHandler())
586 opener = urllib2.build_opener(HTTPHandler())
577 urllib2.install_opener(opener)
587 urllib2.install_opener(opener)
578 t2 = fetch(N, url)
588 t2 = fetch(N, url)
579 print ' TIME: %.3f s' % t2
589 print ' TIME: %.3f s' % t2
580 print ' improvement factor: %.2f' % (t1/t2, )
590 print ' improvement factor: %.2f' % (t1/t2, )
581
591
582 def fetch(N, url, delay=0):
592 def fetch(N, url, delay=0):
583 import time
593 import time
584 lens = []
594 lens = []
585 starttime = time.time()
595 starttime = time.time()
586 for i in range(N):
596 for i in range(N):
587 if delay and i > 0: time.sleep(delay)
597 if delay and i > 0: time.sleep(delay)
588 fo = urllib2.urlopen(url)
598 fo = urllib2.urlopen(url)
589 foo = fo.read()
599 foo = fo.read()
590 fo.close()
600 fo.close()
591 lens.append(len(foo))
601 lens.append(len(foo))
592 diff = time.time() - starttime
602 diff = time.time() - starttime
593
603
594 j = 0
604 j = 0
595 for i in lens[1:]:
605 for i in lens[1:]:
596 j = j + 1
606 j = j + 1
597 if not i == lens[0]:
607 if not i == lens[0]:
598 print "WARNING: inconsistent length on read %i: %i" % (j, i)
608 print "WARNING: inconsistent length on read %i: %i" % (j, i)
599
609
600 return diff
610 return diff
601
611
602 def test_timeout(url):
612 def test_timeout(url):
603 global DEBUG
613 global DEBUG
604 dbbackup = DEBUG
614 dbbackup = DEBUG
605 class FakeLogger:
615 class FakeLogger:
606 def debug(self, msg, *args): print msg % args
616 def debug(self, msg, *args): print msg % args
607 info = warning = error = debug
617 info = warning = error = debug
608 DEBUG = FakeLogger()
618 DEBUG = FakeLogger()
609 print " fetching the file to establish a connection"
619 print " fetching the file to establish a connection"
610 fo = urllib2.urlopen(url)
620 fo = urllib2.urlopen(url)
611 data1 = fo.read()
621 data1 = fo.read()
612 fo.close()
622 fo.close()
613
623
614 i = 20
624 i = 20
615 print " waiting %i seconds for the server to close the connection" % i
625 print " waiting %i seconds for the server to close the connection" % i
616 while i > 0:
626 while i > 0:
617 sys.stdout.write('\r %2i' % i)
627 sys.stdout.write('\r %2i' % i)
618 sys.stdout.flush()
628 sys.stdout.flush()
619 time.sleep(1)
629 time.sleep(1)
620 i -= 1
630 i -= 1
621 sys.stderr.write('\r')
631 sys.stderr.write('\r')
622
632
623 print " fetching the file a second time"
633 print " fetching the file a second time"
624 fo = urllib2.urlopen(url)
634 fo = urllib2.urlopen(url)
625 data2 = fo.read()
635 data2 = fo.read()
626 fo.close()
636 fo.close()
627
637
628 if data1 == data2:
638 if data1 == data2:
629 print ' data are identical'
639 print ' data are identical'
630 else:
640 else:
631 print ' ERROR: DATA DIFFER'
641 print ' ERROR: DATA DIFFER'
632
642
633 DEBUG = dbbackup
643 DEBUG = dbbackup
634
644
635
645
636 def test(url, N=10):
646 def test(url, N=10):
637 print "checking error hander (do this on a non-200)"
647 print "checking error hander (do this on a non-200)"
638 try: error_handler(url)
648 try: error_handler(url)
639 except IOError:
649 except IOError:
640 print "exiting - exception will prevent further tests"
650 print "exiting - exception will prevent further tests"
641 sys.exit()
651 sys.exit()
642 print
652 print
643 print "performing continuity test (making sure stuff isn't corrupted)"
653 print "performing continuity test (making sure stuff isn't corrupted)"
644 continuity(url)
654 continuity(url)
645 print
655 print
646 print "performing speed comparison"
656 print "performing speed comparison"
647 comp(N, url)
657 comp(N, url)
648 print
658 print
649 print "performing dropped-connection check"
659 print "performing dropped-connection check"
650 test_timeout(url)
660 test_timeout(url)
651
661
652 if __name__ == '__main__':
662 if __name__ == '__main__':
653 import time
663 import time
654 import sys
664 import sys
655 try:
665 try:
656 N = int(sys.argv[1])
666 N = int(sys.argv[1])
657 url = sys.argv[2]
667 url = sys.argv[2]
658 except:
668 except:
659 print "%s <integer> <url>" % sys.argv[0]
669 print "%s <integer> <url>" % sys.argv[0]
660 else:
670 else:
661 test(url, N)
671 test(url, N)
@@ -1,1481 +1,1471 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error
17 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error
18 import os, stat, threading, time, calendar, glob, osutil, random
18 import os, stat, threading, time, calendar, glob, osutil, random
19 import imp
19 import imp
20
20
21 # Python compatibility
21 # Python compatibility
22
22
23 def md5(s):
24 try:
25 import hashlib
26 _md5 = hashlib.md5
27 except ImportError:
28 from md5 import md5 as _md5
29 global md5
30 md5 = _md5
31 return _md5(s)
32
33 def sha1(s):
23 def sha1(s):
34 try:
24 try:
35 import hashlib
25 import hashlib
36 _sha1 = hashlib.sha1
26 _sha1 = hashlib.sha1
37 except ImportError:
27 except ImportError:
38 from sha import sha as _sha1
28 from sha import sha as _sha1
39 global sha1
29 global sha1
40 sha1 = _sha1
30 sha1 = _sha1
41 return _sha1(s)
31 return _sha1(s)
42
32
43 import subprocess
33 import subprocess
44 closefds = os.name == 'posix'
34 closefds = os.name == 'posix'
45 def popen2(cmd, mode='t', bufsize=-1):
35 def popen2(cmd, mode='t', bufsize=-1):
46 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
36 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
47 close_fds=closefds,
37 close_fds=closefds,
48 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
38 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
49 return p.stdin, p.stdout
39 return p.stdin, p.stdout
50 def popen3(cmd, mode='t', bufsize=-1):
40 def popen3(cmd, mode='t', bufsize=-1):
51 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
41 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
52 close_fds=closefds,
42 close_fds=closefds,
53 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
43 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
54 stderr=subprocess.PIPE)
44 stderr=subprocess.PIPE)
55 return p.stdin, p.stdout, p.stderr
45 return p.stdin, p.stdout, p.stderr
56 def Popen3(cmd, capturestderr=False, bufsize=-1):
46 def Popen3(cmd, capturestderr=False, bufsize=-1):
57 stderr = capturestderr and subprocess.PIPE or None
47 stderr = capturestderr and subprocess.PIPE or None
58 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
48 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
59 close_fds=closefds,
49 close_fds=closefds,
60 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
50 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stderr=stderr)
51 stderr=stderr)
62 p.fromchild = p.stdout
52 p.fromchild = p.stdout
63 p.tochild = p.stdin
53 p.tochild = p.stdin
64 p.childerr = p.stderr
54 p.childerr = p.stderr
65 return p
55 return p
66
56
67 def version():
57 def version():
68 """Return version information if available."""
58 """Return version information if available."""
69 try:
59 try:
70 import __version__
60 import __version__
71 return __version__.version
61 return __version__.version
72 except ImportError:
62 except ImportError:
73 return 'unknown'
63 return 'unknown'
74
64
75 # used by parsedate
65 # used by parsedate
76 defaultdateformats = (
66 defaultdateformats = (
77 '%Y-%m-%d %H:%M:%S',
67 '%Y-%m-%d %H:%M:%S',
78 '%Y-%m-%d %I:%M:%S%p',
68 '%Y-%m-%d %I:%M:%S%p',
79 '%Y-%m-%d %H:%M',
69 '%Y-%m-%d %H:%M',
80 '%Y-%m-%d %I:%M%p',
70 '%Y-%m-%d %I:%M%p',
81 '%Y-%m-%d',
71 '%Y-%m-%d',
82 '%m-%d',
72 '%m-%d',
83 '%m/%d',
73 '%m/%d',
84 '%m/%d/%y',
74 '%m/%d/%y',
85 '%m/%d/%Y',
75 '%m/%d/%Y',
86 '%a %b %d %H:%M:%S %Y',
76 '%a %b %d %H:%M:%S %Y',
87 '%a %b %d %I:%M:%S%p %Y',
77 '%a %b %d %I:%M:%S%p %Y',
88 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
78 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
89 '%b %d %H:%M:%S %Y',
79 '%b %d %H:%M:%S %Y',
90 '%b %d %I:%M:%S%p %Y',
80 '%b %d %I:%M:%S%p %Y',
91 '%b %d %H:%M:%S',
81 '%b %d %H:%M:%S',
92 '%b %d %I:%M:%S%p',
82 '%b %d %I:%M:%S%p',
93 '%b %d %H:%M',
83 '%b %d %H:%M',
94 '%b %d %I:%M%p',
84 '%b %d %I:%M%p',
95 '%b %d %Y',
85 '%b %d %Y',
96 '%b %d',
86 '%b %d',
97 '%H:%M:%S',
87 '%H:%M:%S',
98 '%I:%M:%SP',
88 '%I:%M:%SP',
99 '%H:%M',
89 '%H:%M',
100 '%I:%M%p',
90 '%I:%M%p',
101 )
91 )
102
92
103 extendeddateformats = defaultdateformats + (
93 extendeddateformats = defaultdateformats + (
104 "%Y",
94 "%Y",
105 "%Y-%m",
95 "%Y-%m",
106 "%b",
96 "%b",
107 "%b %Y",
97 "%b %Y",
108 )
98 )
109
99
110 def cachefunc(func):
100 def cachefunc(func):
111 '''cache the result of function calls'''
101 '''cache the result of function calls'''
112 # XXX doesn't handle keywords args
102 # XXX doesn't handle keywords args
113 cache = {}
103 cache = {}
114 if func.func_code.co_argcount == 1:
104 if func.func_code.co_argcount == 1:
115 # we gain a small amount of time because
105 # we gain a small amount of time because
116 # we don't need to pack/unpack the list
106 # we don't need to pack/unpack the list
117 def f(arg):
107 def f(arg):
118 if arg not in cache:
108 if arg not in cache:
119 cache[arg] = func(arg)
109 cache[arg] = func(arg)
120 return cache[arg]
110 return cache[arg]
121 else:
111 else:
122 def f(*args):
112 def f(*args):
123 if args not in cache:
113 if args not in cache:
124 cache[args] = func(*args)
114 cache[args] = func(*args)
125 return cache[args]
115 return cache[args]
126
116
127 return f
117 return f
128
118
129 class propertycache(object):
119 class propertycache(object):
130 def __init__(self, func):
120 def __init__(self, func):
131 self.func = func
121 self.func = func
132 self.name = func.__name__
122 self.name = func.__name__
133 def __get__(self, obj, type=None):
123 def __get__(self, obj, type=None):
134 result = self.func(obj)
124 result = self.func(obj)
135 setattr(obj, self.name, result)
125 setattr(obj, self.name, result)
136 return result
126 return result
137
127
138 def pipefilter(s, cmd):
128 def pipefilter(s, cmd):
139 '''filter string S through command CMD, returning its output'''
129 '''filter string S through command CMD, returning its output'''
140 (pin, pout) = popen2(cmd, 'b')
130 (pin, pout) = popen2(cmd, 'b')
141 def writer():
131 def writer():
142 try:
132 try:
143 pin.write(s)
133 pin.write(s)
144 pin.close()
134 pin.close()
145 except IOError, inst:
135 except IOError, inst:
146 if inst.errno != errno.EPIPE:
136 if inst.errno != errno.EPIPE:
147 raise
137 raise
148
138
149 # we should use select instead on UNIX, but this will work on most
139 # we should use select instead on UNIX, but this will work on most
150 # systems, including Windows
140 # systems, including Windows
151 w = threading.Thread(target=writer)
141 w = threading.Thread(target=writer)
152 w.start()
142 w.start()
153 f = pout.read()
143 f = pout.read()
154 pout.close()
144 pout.close()
155 w.join()
145 w.join()
156 return f
146 return f
157
147
158 def tempfilter(s, cmd):
148 def tempfilter(s, cmd):
159 '''filter string S through a pair of temporary files with CMD.
149 '''filter string S through a pair of temporary files with CMD.
160 CMD is used as a template to create the real command to be run,
150 CMD is used as a template to create the real command to be run,
161 with the strings INFILE and OUTFILE replaced by the real names of
151 with the strings INFILE and OUTFILE replaced by the real names of
162 the temporary files generated.'''
152 the temporary files generated.'''
163 inname, outname = None, None
153 inname, outname = None, None
164 try:
154 try:
165 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
155 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
166 fp = os.fdopen(infd, 'wb')
156 fp = os.fdopen(infd, 'wb')
167 fp.write(s)
157 fp.write(s)
168 fp.close()
158 fp.close()
169 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
159 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
170 os.close(outfd)
160 os.close(outfd)
171 cmd = cmd.replace('INFILE', inname)
161 cmd = cmd.replace('INFILE', inname)
172 cmd = cmd.replace('OUTFILE', outname)
162 cmd = cmd.replace('OUTFILE', outname)
173 code = os.system(cmd)
163 code = os.system(cmd)
174 if sys.platform == 'OpenVMS' and code & 1:
164 if sys.platform == 'OpenVMS' and code & 1:
175 code = 0
165 code = 0
176 if code: raise Abort(_("command '%s' failed: %s") %
166 if code: raise Abort(_("command '%s' failed: %s") %
177 (cmd, explain_exit(code)))
167 (cmd, explain_exit(code)))
178 return open(outname, 'rb').read()
168 return open(outname, 'rb').read()
179 finally:
169 finally:
180 try:
170 try:
181 if inname: os.unlink(inname)
171 if inname: os.unlink(inname)
182 except: pass
172 except: pass
183 try:
173 try:
184 if outname: os.unlink(outname)
174 if outname: os.unlink(outname)
185 except: pass
175 except: pass
186
176
187 filtertable = {
177 filtertable = {
188 'tempfile:': tempfilter,
178 'tempfile:': tempfilter,
189 'pipe:': pipefilter,
179 'pipe:': pipefilter,
190 }
180 }
191
181
192 def filter(s, cmd):
182 def filter(s, cmd):
193 "filter a string through a command that transforms its input to its output"
183 "filter a string through a command that transforms its input to its output"
194 for name, fn in filtertable.iteritems():
184 for name, fn in filtertable.iteritems():
195 if cmd.startswith(name):
185 if cmd.startswith(name):
196 return fn(s, cmd[len(name):].lstrip())
186 return fn(s, cmd[len(name):].lstrip())
197 return pipefilter(s, cmd)
187 return pipefilter(s, cmd)
198
188
199 def binary(s):
189 def binary(s):
200 """return true if a string is binary data"""
190 """return true if a string is binary data"""
201 return bool(s and '\0' in s)
191 return bool(s and '\0' in s)
202
192
203 def increasingchunks(source, min=1024, max=65536):
193 def increasingchunks(source, min=1024, max=65536):
204 '''return no less than min bytes per chunk while data remains,
194 '''return no less than min bytes per chunk while data remains,
205 doubling min after each chunk until it reaches max'''
195 doubling min after each chunk until it reaches max'''
206 def log2(x):
196 def log2(x):
207 if not x:
197 if not x:
208 return 0
198 return 0
209 i = 0
199 i = 0
210 while x:
200 while x:
211 x >>= 1
201 x >>= 1
212 i += 1
202 i += 1
213 return i - 1
203 return i - 1
214
204
215 buf = []
205 buf = []
216 blen = 0
206 blen = 0
217 for chunk in source:
207 for chunk in source:
218 buf.append(chunk)
208 buf.append(chunk)
219 blen += len(chunk)
209 blen += len(chunk)
220 if blen >= min:
210 if blen >= min:
221 if min < max:
211 if min < max:
222 min = min << 1
212 min = min << 1
223 nmin = 1 << log2(blen)
213 nmin = 1 << log2(blen)
224 if nmin > min:
214 if nmin > min:
225 min = nmin
215 min = nmin
226 if min > max:
216 if min > max:
227 min = max
217 min = max
228 yield ''.join(buf)
218 yield ''.join(buf)
229 blen = 0
219 blen = 0
230 buf = []
220 buf = []
231 if buf:
221 if buf:
232 yield ''.join(buf)
222 yield ''.join(buf)
233
223
234 Abort = error.Abort
224 Abort = error.Abort
235
225
236 def always(fn): return True
226 def always(fn): return True
237 def never(fn): return False
227 def never(fn): return False
238
228
239 def patkind(name, default):
229 def patkind(name, default):
240 """Split a string into an optional pattern kind prefix and the
230 """Split a string into an optional pattern kind prefix and the
241 actual pattern."""
231 actual pattern."""
242 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
232 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
243 if name.startswith(prefix + ':'): return name.split(':', 1)
233 if name.startswith(prefix + ':'): return name.split(':', 1)
244 return default, name
234 return default, name
245
235
246 def globre(pat, head='^', tail='$'):
236 def globre(pat, head='^', tail='$'):
247 "convert a glob pattern into a regexp"
237 "convert a glob pattern into a regexp"
248 i, n = 0, len(pat)
238 i, n = 0, len(pat)
249 res = ''
239 res = ''
250 group = 0
240 group = 0
251 def peek(): return i < n and pat[i]
241 def peek(): return i < n and pat[i]
252 while i < n:
242 while i < n:
253 c = pat[i]
243 c = pat[i]
254 i = i+1
244 i = i+1
255 if c == '*':
245 if c == '*':
256 if peek() == '*':
246 if peek() == '*':
257 i += 1
247 i += 1
258 res += '.*'
248 res += '.*'
259 else:
249 else:
260 res += '[^/]*'
250 res += '[^/]*'
261 elif c == '?':
251 elif c == '?':
262 res += '.'
252 res += '.'
263 elif c == '[':
253 elif c == '[':
264 j = i
254 j = i
265 if j < n and pat[j] in '!]':
255 if j < n and pat[j] in '!]':
266 j += 1
256 j += 1
267 while j < n and pat[j] != ']':
257 while j < n and pat[j] != ']':
268 j += 1
258 j += 1
269 if j >= n:
259 if j >= n:
270 res += '\\['
260 res += '\\['
271 else:
261 else:
272 stuff = pat[i:j].replace('\\','\\\\')
262 stuff = pat[i:j].replace('\\','\\\\')
273 i = j + 1
263 i = j + 1
274 if stuff[0] == '!':
264 if stuff[0] == '!':
275 stuff = '^' + stuff[1:]
265 stuff = '^' + stuff[1:]
276 elif stuff[0] == '^':
266 elif stuff[0] == '^':
277 stuff = '\\' + stuff
267 stuff = '\\' + stuff
278 res = '%s[%s]' % (res, stuff)
268 res = '%s[%s]' % (res, stuff)
279 elif c == '{':
269 elif c == '{':
280 group += 1
270 group += 1
281 res += '(?:'
271 res += '(?:'
282 elif c == '}' and group:
272 elif c == '}' and group:
283 res += ')'
273 res += ')'
284 group -= 1
274 group -= 1
285 elif c == ',' and group:
275 elif c == ',' and group:
286 res += '|'
276 res += '|'
287 elif c == '\\':
277 elif c == '\\':
288 p = peek()
278 p = peek()
289 if p:
279 if p:
290 i += 1
280 i += 1
291 res += re.escape(p)
281 res += re.escape(p)
292 else:
282 else:
293 res += re.escape(c)
283 res += re.escape(c)
294 else:
284 else:
295 res += re.escape(c)
285 res += re.escape(c)
296 return head + res + tail
286 return head + res + tail
297
287
298 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
288 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
299
289
300 def pathto(root, n1, n2):
290 def pathto(root, n1, n2):
301 '''return the relative path from one place to another.
291 '''return the relative path from one place to another.
302 root should use os.sep to separate directories
292 root should use os.sep to separate directories
303 n1 should use os.sep to separate directories
293 n1 should use os.sep to separate directories
304 n2 should use "/" to separate directories
294 n2 should use "/" to separate directories
305 returns an os.sep-separated path.
295 returns an os.sep-separated path.
306
296
307 If n1 is a relative path, it's assumed it's
297 If n1 is a relative path, it's assumed it's
308 relative to root.
298 relative to root.
309 n2 should always be relative to root.
299 n2 should always be relative to root.
310 '''
300 '''
311 if not n1: return localpath(n2)
301 if not n1: return localpath(n2)
312 if os.path.isabs(n1):
302 if os.path.isabs(n1):
313 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
303 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
314 return os.path.join(root, localpath(n2))
304 return os.path.join(root, localpath(n2))
315 n2 = '/'.join((pconvert(root), n2))
305 n2 = '/'.join((pconvert(root), n2))
316 a, b = splitpath(n1), n2.split('/')
306 a, b = splitpath(n1), n2.split('/')
317 a.reverse()
307 a.reverse()
318 b.reverse()
308 b.reverse()
319 while a and b and a[-1] == b[-1]:
309 while a and b and a[-1] == b[-1]:
320 a.pop()
310 a.pop()
321 b.pop()
311 b.pop()
322 b.reverse()
312 b.reverse()
323 return os.sep.join((['..'] * len(a)) + b) or '.'
313 return os.sep.join((['..'] * len(a)) + b) or '.'
324
314
325 def canonpath(root, cwd, myname):
315 def canonpath(root, cwd, myname):
326 """return the canonical path of myname, given cwd and root"""
316 """return the canonical path of myname, given cwd and root"""
327 if root == os.sep:
317 if root == os.sep:
328 rootsep = os.sep
318 rootsep = os.sep
329 elif endswithsep(root):
319 elif endswithsep(root):
330 rootsep = root
320 rootsep = root
331 else:
321 else:
332 rootsep = root + os.sep
322 rootsep = root + os.sep
333 name = myname
323 name = myname
334 if not os.path.isabs(name):
324 if not os.path.isabs(name):
335 name = os.path.join(root, cwd, name)
325 name = os.path.join(root, cwd, name)
336 name = os.path.normpath(name)
326 name = os.path.normpath(name)
337 audit_path = path_auditor(root)
327 audit_path = path_auditor(root)
338 if name != rootsep and name.startswith(rootsep):
328 if name != rootsep and name.startswith(rootsep):
339 name = name[len(rootsep):]
329 name = name[len(rootsep):]
340 audit_path(name)
330 audit_path(name)
341 return pconvert(name)
331 return pconvert(name)
342 elif name == root:
332 elif name == root:
343 return ''
333 return ''
344 else:
334 else:
345 # Determine whether `name' is in the hierarchy at or beneath `root',
335 # Determine whether `name' is in the hierarchy at or beneath `root',
346 # by iterating name=dirname(name) until that causes no change (can't
336 # by iterating name=dirname(name) until that causes no change (can't
347 # check name == '/', because that doesn't work on windows). For each
337 # check name == '/', because that doesn't work on windows). For each
348 # `name', compare dev/inode numbers. If they match, the list `rel'
338 # `name', compare dev/inode numbers. If they match, the list `rel'
349 # holds the reversed list of components making up the relative file
339 # holds the reversed list of components making up the relative file
350 # name we want.
340 # name we want.
351 root_st = os.stat(root)
341 root_st = os.stat(root)
352 rel = []
342 rel = []
353 while True:
343 while True:
354 try:
344 try:
355 name_st = os.stat(name)
345 name_st = os.stat(name)
356 except OSError:
346 except OSError:
357 break
347 break
358 if samestat(name_st, root_st):
348 if samestat(name_st, root_st):
359 if not rel:
349 if not rel:
360 # name was actually the same as root (maybe a symlink)
350 # name was actually the same as root (maybe a symlink)
361 return ''
351 return ''
362 rel.reverse()
352 rel.reverse()
363 name = os.path.join(*rel)
353 name = os.path.join(*rel)
364 audit_path(name)
354 audit_path(name)
365 return pconvert(name)
355 return pconvert(name)
366 dirname, basename = os.path.split(name)
356 dirname, basename = os.path.split(name)
367 rel.append(basename)
357 rel.append(basename)
368 if dirname == name:
358 if dirname == name:
369 break
359 break
370 name = dirname
360 name = dirname
371
361
372 raise Abort('%s not under root' % myname)
362 raise Abort('%s not under root' % myname)
373
363
374 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
364 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
375 """build a function to match a set of file patterns
365 """build a function to match a set of file patterns
376
366
377 arguments:
367 arguments:
378 canonroot - the canonical root of the tree you're matching against
368 canonroot - the canonical root of the tree you're matching against
379 cwd - the current working directory, if relevant
369 cwd - the current working directory, if relevant
380 names - patterns to find
370 names - patterns to find
381 inc - patterns to include
371 inc - patterns to include
382 exc - patterns to exclude
372 exc - patterns to exclude
383 dflt_pat - if a pattern in names has no explicit type, assume this one
373 dflt_pat - if a pattern in names has no explicit type, assume this one
384 src - where these patterns came from (e.g. .hgignore)
374 src - where these patterns came from (e.g. .hgignore)
385
375
386 a pattern is one of:
376 a pattern is one of:
387 'glob:<glob>' - a glob relative to cwd
377 'glob:<glob>' - a glob relative to cwd
388 're:<regexp>' - a regular expression
378 're:<regexp>' - a regular expression
389 'path:<path>' - a path relative to canonroot
379 'path:<path>' - a path relative to canonroot
390 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
380 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
391 'relpath:<path>' - a path relative to cwd
381 'relpath:<path>' - a path relative to cwd
392 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
382 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
393 '<something>' - one of the cases above, selected by the dflt_pat argument
383 '<something>' - one of the cases above, selected by the dflt_pat argument
394
384
395 returns:
385 returns:
396 a 3-tuple containing
386 a 3-tuple containing
397 - list of roots (places where one should start a recursive walk of the fs);
387 - list of roots (places where one should start a recursive walk of the fs);
398 this often matches the explicit non-pattern names passed in, but also
388 this often matches the explicit non-pattern names passed in, but also
399 includes the initial part of glob: patterns that has no glob characters
389 includes the initial part of glob: patterns that has no glob characters
400 - a bool match(filename) function
390 - a bool match(filename) function
401 - a bool indicating if any patterns were passed in
391 - a bool indicating if any patterns were passed in
402 """
392 """
403
393
404 # a common case: no patterns at all
394 # a common case: no patterns at all
405 if not names and not inc and not exc:
395 if not names and not inc and not exc:
406 return [], always, False
396 return [], always, False
407
397
408 def contains_glob(name):
398 def contains_glob(name):
409 for c in name:
399 for c in name:
410 if c in _globchars: return True
400 if c in _globchars: return True
411 return False
401 return False
412
402
413 def regex(kind, name, tail):
403 def regex(kind, name, tail):
414 '''convert a pattern into a regular expression'''
404 '''convert a pattern into a regular expression'''
415 if not name:
405 if not name:
416 return ''
406 return ''
417 if kind == 're':
407 if kind == 're':
418 return name
408 return name
419 elif kind == 'path':
409 elif kind == 'path':
420 return '^' + re.escape(name) + '(?:/|$)'
410 return '^' + re.escape(name) + '(?:/|$)'
421 elif kind == 'relglob':
411 elif kind == 'relglob':
422 return globre(name, '(?:|.*/)', tail)
412 return globre(name, '(?:|.*/)', tail)
423 elif kind == 'relpath':
413 elif kind == 'relpath':
424 return re.escape(name) + '(?:/|$)'
414 return re.escape(name) + '(?:/|$)'
425 elif kind == 'relre':
415 elif kind == 'relre':
426 if name.startswith('^'):
416 if name.startswith('^'):
427 return name
417 return name
428 return '.*' + name
418 return '.*' + name
429 return globre(name, '', tail)
419 return globre(name, '', tail)
430
420
431 def matchfn(pats, tail):
421 def matchfn(pats, tail):
432 """build a matching function from a set of patterns"""
422 """build a matching function from a set of patterns"""
433 if not pats:
423 if not pats:
434 return
424 return
435 try:
425 try:
436 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
426 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
437 if len(pat) > 20000:
427 if len(pat) > 20000:
438 raise OverflowError()
428 raise OverflowError()
439 return re.compile(pat).match
429 return re.compile(pat).match
440 except OverflowError:
430 except OverflowError:
441 # We're using a Python with a tiny regex engine and we
431 # We're using a Python with a tiny regex engine and we
442 # made it explode, so we'll divide the pattern list in two
432 # made it explode, so we'll divide the pattern list in two
443 # until it works
433 # until it works
444 l = len(pats)
434 l = len(pats)
445 if l < 2:
435 if l < 2:
446 raise
436 raise
447 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
437 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
448 return lambda s: a(s) or b(s)
438 return lambda s: a(s) or b(s)
449 except re.error:
439 except re.error:
450 for k, p in pats:
440 for k, p in pats:
451 try:
441 try:
452 re.compile('(?:%s)' % regex(k, p, tail))
442 re.compile('(?:%s)' % regex(k, p, tail))
453 except re.error:
443 except re.error:
454 if src:
444 if src:
455 raise Abort("%s: invalid pattern (%s): %s" %
445 raise Abort("%s: invalid pattern (%s): %s" %
456 (src, k, p))
446 (src, k, p))
457 else:
447 else:
458 raise Abort("invalid pattern (%s): %s" % (k, p))
448 raise Abort("invalid pattern (%s): %s" % (k, p))
459 raise Abort("invalid pattern")
449 raise Abort("invalid pattern")
460
450
461 def globprefix(pat):
451 def globprefix(pat):
462 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
452 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
463 root = []
453 root = []
464 for p in pat.split('/'):
454 for p in pat.split('/'):
465 if contains_glob(p): break
455 if contains_glob(p): break
466 root.append(p)
456 root.append(p)
467 return '/'.join(root) or '.'
457 return '/'.join(root) or '.'
468
458
469 def normalizepats(names, default):
459 def normalizepats(names, default):
470 pats = []
460 pats = []
471 roots = []
461 roots = []
472 anypats = False
462 anypats = False
473 for kind, name in [patkind(p, default) for p in names]:
463 for kind, name in [patkind(p, default) for p in names]:
474 if kind in ('glob', 'relpath'):
464 if kind in ('glob', 'relpath'):
475 name = canonpath(canonroot, cwd, name)
465 name = canonpath(canonroot, cwd, name)
476 elif kind in ('relglob', 'path'):
466 elif kind in ('relglob', 'path'):
477 name = normpath(name)
467 name = normpath(name)
478
468
479 pats.append((kind, name))
469 pats.append((kind, name))
480
470
481 if kind in ('glob', 're', 'relglob', 'relre'):
471 if kind in ('glob', 're', 'relglob', 'relre'):
482 anypats = True
472 anypats = True
483
473
484 if kind == 'glob':
474 if kind == 'glob':
485 root = globprefix(name)
475 root = globprefix(name)
486 roots.append(root)
476 roots.append(root)
487 elif kind in ('relpath', 'path'):
477 elif kind in ('relpath', 'path'):
488 roots.append(name or '.')
478 roots.append(name or '.')
489 elif kind == 'relglob':
479 elif kind == 'relglob':
490 roots.append('.')
480 roots.append('.')
491 return roots, pats, anypats
481 return roots, pats, anypats
492
482
493 roots, pats, anypats = normalizepats(names, dflt_pat)
483 roots, pats, anypats = normalizepats(names, dflt_pat)
494
484
495 patmatch = matchfn(pats, '$') or always
485 patmatch = matchfn(pats, '$') or always
496 incmatch = always
486 incmatch = always
497 if inc:
487 if inc:
498 dummy, inckinds, dummy = normalizepats(inc, 'glob')
488 dummy, inckinds, dummy = normalizepats(inc, 'glob')
499 incmatch = matchfn(inckinds, '(?:/|$)')
489 incmatch = matchfn(inckinds, '(?:/|$)')
500 excmatch = never
490 excmatch = never
501 if exc:
491 if exc:
502 dummy, exckinds, dummy = normalizepats(exc, 'glob')
492 dummy, exckinds, dummy = normalizepats(exc, 'glob')
503 excmatch = matchfn(exckinds, '(?:/|$)')
493 excmatch = matchfn(exckinds, '(?:/|$)')
504
494
505 if not names and inc and not exc:
495 if not names and inc and not exc:
506 # common case: hgignore patterns
496 # common case: hgignore patterns
507 match = incmatch
497 match = incmatch
508 else:
498 else:
509 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
499 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
510
500
511 return (roots, match, (inc or exc or anypats) and True)
501 return (roots, match, (inc or exc or anypats) and True)
512
502
513 _hgexecutable = None
503 _hgexecutable = None
514
504
515 def main_is_frozen():
505 def main_is_frozen():
516 """return True if we are a frozen executable.
506 """return True if we are a frozen executable.
517
507
518 The code supports py2exe (most common, Windows only) and tools/freeze
508 The code supports py2exe (most common, Windows only) and tools/freeze
519 (portable, not much used).
509 (portable, not much used).
520 """
510 """
521 return (hasattr(sys, "frozen") or # new py2exe
511 return (hasattr(sys, "frozen") or # new py2exe
522 hasattr(sys, "importers") or # old py2exe
512 hasattr(sys, "importers") or # old py2exe
523 imp.is_frozen("__main__")) # tools/freeze
513 imp.is_frozen("__main__")) # tools/freeze
524
514
525 def hgexecutable():
515 def hgexecutable():
526 """return location of the 'hg' executable.
516 """return location of the 'hg' executable.
527
517
528 Defaults to $HG or 'hg' in the search path.
518 Defaults to $HG or 'hg' in the search path.
529 """
519 """
530 if _hgexecutable is None:
520 if _hgexecutable is None:
531 hg = os.environ.get('HG')
521 hg = os.environ.get('HG')
532 if hg:
522 if hg:
533 set_hgexecutable(hg)
523 set_hgexecutable(hg)
534 elif main_is_frozen():
524 elif main_is_frozen():
535 set_hgexecutable(sys.executable)
525 set_hgexecutable(sys.executable)
536 else:
526 else:
537 set_hgexecutable(find_exe('hg') or 'hg')
527 set_hgexecutable(find_exe('hg') or 'hg')
538 return _hgexecutable
528 return _hgexecutable
539
529
540 def set_hgexecutable(path):
530 def set_hgexecutable(path):
541 """set location of the 'hg' executable"""
531 """set location of the 'hg' executable"""
542 global _hgexecutable
532 global _hgexecutable
543 _hgexecutable = path
533 _hgexecutable = path
544
534
545 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
535 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
546 '''enhanced shell command execution.
536 '''enhanced shell command execution.
547 run with environment maybe modified, maybe in different dir.
537 run with environment maybe modified, maybe in different dir.
548
538
549 if command fails and onerr is None, return status. if ui object,
539 if command fails and onerr is None, return status. if ui object,
550 print error message and return status, else raise onerr object as
540 print error message and return status, else raise onerr object as
551 exception.'''
541 exception.'''
552 def py2shell(val):
542 def py2shell(val):
553 'convert python object into string that is useful to shell'
543 'convert python object into string that is useful to shell'
554 if val in (None, False):
544 if val in (None, False):
555 return '0'
545 return '0'
556 if val == True:
546 if val == True:
557 return '1'
547 return '1'
558 return str(val)
548 return str(val)
559 oldenv = {}
549 oldenv = {}
560 for k in environ:
550 for k in environ:
561 oldenv[k] = os.environ.get(k)
551 oldenv[k] = os.environ.get(k)
562 if cwd is not None:
552 if cwd is not None:
563 oldcwd = os.getcwd()
553 oldcwd = os.getcwd()
564 origcmd = cmd
554 origcmd = cmd
565 if os.name == 'nt':
555 if os.name == 'nt':
566 cmd = '"%s"' % cmd
556 cmd = '"%s"' % cmd
567 try:
557 try:
568 for k, v in environ.iteritems():
558 for k, v in environ.iteritems():
569 os.environ[k] = py2shell(v)
559 os.environ[k] = py2shell(v)
570 os.environ['HG'] = hgexecutable()
560 os.environ['HG'] = hgexecutable()
571 if cwd is not None and oldcwd != cwd:
561 if cwd is not None and oldcwd != cwd:
572 os.chdir(cwd)
562 os.chdir(cwd)
573 rc = os.system(cmd)
563 rc = os.system(cmd)
574 if sys.platform == 'OpenVMS' and rc & 1:
564 if sys.platform == 'OpenVMS' and rc & 1:
575 rc = 0
565 rc = 0
576 if rc and onerr:
566 if rc and onerr:
577 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
567 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
578 explain_exit(rc)[0])
568 explain_exit(rc)[0])
579 if errprefix:
569 if errprefix:
580 errmsg = '%s: %s' % (errprefix, errmsg)
570 errmsg = '%s: %s' % (errprefix, errmsg)
581 try:
571 try:
582 onerr.warn(errmsg + '\n')
572 onerr.warn(errmsg + '\n')
583 except AttributeError:
573 except AttributeError:
584 raise onerr(errmsg)
574 raise onerr(errmsg)
585 return rc
575 return rc
586 finally:
576 finally:
587 for k, v in oldenv.iteritems():
577 for k, v in oldenv.iteritems():
588 if v is None:
578 if v is None:
589 del os.environ[k]
579 del os.environ[k]
590 else:
580 else:
591 os.environ[k] = v
581 os.environ[k] = v
592 if cwd is not None and oldcwd != cwd:
582 if cwd is not None and oldcwd != cwd:
593 os.chdir(oldcwd)
583 os.chdir(oldcwd)
594
584
595 def checksignature(func):
585 def checksignature(func):
596 '''wrap a function with code to check for calling errors'''
586 '''wrap a function with code to check for calling errors'''
597 def check(*args, **kwargs):
587 def check(*args, **kwargs):
598 try:
588 try:
599 return func(*args, **kwargs)
589 return func(*args, **kwargs)
600 except TypeError:
590 except TypeError:
601 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
591 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
602 raise error.SignatureError
592 raise error.SignatureError
603 raise
593 raise
604
594
605 return check
595 return check
606
596
607 # os.path.lexists is not available on python2.3
597 # os.path.lexists is not available on python2.3
608 def lexists(filename):
598 def lexists(filename):
609 "test whether a file with this name exists. does not follow symlinks"
599 "test whether a file with this name exists. does not follow symlinks"
610 try:
600 try:
611 os.lstat(filename)
601 os.lstat(filename)
612 except:
602 except:
613 return False
603 return False
614 return True
604 return True
615
605
616 def rename(src, dst):
606 def rename(src, dst):
617 """forcibly rename a file"""
607 """forcibly rename a file"""
618 try:
608 try:
619 os.rename(src, dst)
609 os.rename(src, dst)
620 except OSError, err: # FIXME: check err (EEXIST ?)
610 except OSError, err: # FIXME: check err (EEXIST ?)
621
611
622 # On windows, rename to existing file is not allowed, so we
612 # On windows, rename to existing file is not allowed, so we
623 # must delete destination first. But if a file is open, unlink
613 # must delete destination first. But if a file is open, unlink
624 # schedules it for delete but does not delete it. Rename
614 # schedules it for delete but does not delete it. Rename
625 # happens immediately even for open files, so we rename
615 # happens immediately even for open files, so we rename
626 # destination to a temporary name, then delete that. Then
616 # destination to a temporary name, then delete that. Then
627 # rename is safe to do.
617 # rename is safe to do.
628 # The temporary name is chosen at random to avoid the situation
618 # The temporary name is chosen at random to avoid the situation
629 # where a file is left lying around from a previous aborted run.
619 # where a file is left lying around from a previous aborted run.
630 # The usual race condition this introduces can't be avoided as
620 # The usual race condition this introduces can't be avoided as
631 # we need the name to rename into, and not the file itself. Due
621 # we need the name to rename into, and not the file itself. Due
632 # to the nature of the operation however, any races will at worst
622 # to the nature of the operation however, any races will at worst
633 # lead to the rename failing and the current operation aborting.
623 # lead to the rename failing and the current operation aborting.
634
624
635 def tempname(prefix):
625 def tempname(prefix):
636 for tries in xrange(10):
626 for tries in xrange(10):
637 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
627 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
638 if not os.path.exists(temp):
628 if not os.path.exists(temp):
639 return temp
629 return temp
640 raise IOError, (errno.EEXIST, "No usable temporary filename found")
630 raise IOError, (errno.EEXIST, "No usable temporary filename found")
641
631
642 temp = tempname(dst)
632 temp = tempname(dst)
643 os.rename(dst, temp)
633 os.rename(dst, temp)
644 os.unlink(temp)
634 os.unlink(temp)
645 os.rename(src, dst)
635 os.rename(src, dst)
646
636
647 def unlink(f):
637 def unlink(f):
648 """unlink and remove the directory if it is empty"""
638 """unlink and remove the directory if it is empty"""
649 os.unlink(f)
639 os.unlink(f)
650 # try removing directories that might now be empty
640 # try removing directories that might now be empty
651 try:
641 try:
652 os.removedirs(os.path.dirname(f))
642 os.removedirs(os.path.dirname(f))
653 except OSError:
643 except OSError:
654 pass
644 pass
655
645
656 def copyfile(src, dest):
646 def copyfile(src, dest):
657 "copy a file, preserving mode and atime/mtime"
647 "copy a file, preserving mode and atime/mtime"
658 if os.path.islink(src):
648 if os.path.islink(src):
659 try:
649 try:
660 os.unlink(dest)
650 os.unlink(dest)
661 except:
651 except:
662 pass
652 pass
663 os.symlink(os.readlink(src), dest)
653 os.symlink(os.readlink(src), dest)
664 else:
654 else:
665 try:
655 try:
666 shutil.copyfile(src, dest)
656 shutil.copyfile(src, dest)
667 shutil.copystat(src, dest)
657 shutil.copystat(src, dest)
668 except shutil.Error, inst:
658 except shutil.Error, inst:
669 raise Abort(str(inst))
659 raise Abort(str(inst))
670
660
671 def copyfiles(src, dst, hardlink=None):
661 def copyfiles(src, dst, hardlink=None):
672 """Copy a directory tree using hardlinks if possible"""
662 """Copy a directory tree using hardlinks if possible"""
673
663
674 if hardlink is None:
664 if hardlink is None:
675 hardlink = (os.stat(src).st_dev ==
665 hardlink = (os.stat(src).st_dev ==
676 os.stat(os.path.dirname(dst)).st_dev)
666 os.stat(os.path.dirname(dst)).st_dev)
677
667
678 if os.path.isdir(src):
668 if os.path.isdir(src):
679 os.mkdir(dst)
669 os.mkdir(dst)
680 for name, kind in osutil.listdir(src):
670 for name, kind in osutil.listdir(src):
681 srcname = os.path.join(src, name)
671 srcname = os.path.join(src, name)
682 dstname = os.path.join(dst, name)
672 dstname = os.path.join(dst, name)
683 copyfiles(srcname, dstname, hardlink)
673 copyfiles(srcname, dstname, hardlink)
684 else:
674 else:
685 if hardlink:
675 if hardlink:
686 try:
676 try:
687 os_link(src, dst)
677 os_link(src, dst)
688 except (IOError, OSError):
678 except (IOError, OSError):
689 hardlink = False
679 hardlink = False
690 shutil.copy(src, dst)
680 shutil.copy(src, dst)
691 else:
681 else:
692 shutil.copy(src, dst)
682 shutil.copy(src, dst)
693
683
694 class path_auditor(object):
684 class path_auditor(object):
695 '''ensure that a filesystem path contains no banned components.
685 '''ensure that a filesystem path contains no banned components.
696 the following properties of a path are checked:
686 the following properties of a path are checked:
697
687
698 - under top-level .hg
688 - under top-level .hg
699 - starts at the root of a windows drive
689 - starts at the root of a windows drive
700 - contains ".."
690 - contains ".."
701 - traverses a symlink (e.g. a/symlink_here/b)
691 - traverses a symlink (e.g. a/symlink_here/b)
702 - inside a nested repository'''
692 - inside a nested repository'''
703
693
704 def __init__(self, root):
694 def __init__(self, root):
705 self.audited = set()
695 self.audited = set()
706 self.auditeddir = set()
696 self.auditeddir = set()
707 self.root = root
697 self.root = root
708
698
709 def __call__(self, path):
699 def __call__(self, path):
710 if path in self.audited:
700 if path in self.audited:
711 return
701 return
712 normpath = os.path.normcase(path)
702 normpath = os.path.normcase(path)
713 parts = splitpath(normpath)
703 parts = splitpath(normpath)
714 if (os.path.splitdrive(path)[0]
704 if (os.path.splitdrive(path)[0]
715 or parts[0].lower() in ('.hg', '.hg.', '')
705 or parts[0].lower() in ('.hg', '.hg.', '')
716 or os.pardir in parts):
706 or os.pardir in parts):
717 raise Abort(_("path contains illegal component: %s") % path)
707 raise Abort(_("path contains illegal component: %s") % path)
718 if '.hg' in path.lower():
708 if '.hg' in path.lower():
719 lparts = [p.lower() for p in parts]
709 lparts = [p.lower() for p in parts]
720 for p in '.hg', '.hg.':
710 for p in '.hg', '.hg.':
721 if p in lparts[1:]:
711 if p in lparts[1:]:
722 pos = lparts.index(p)
712 pos = lparts.index(p)
723 base = os.path.join(*parts[:pos])
713 base = os.path.join(*parts[:pos])
724 raise Abort(_('path %r is inside repo %r') % (path, base))
714 raise Abort(_('path %r is inside repo %r') % (path, base))
725 def check(prefix):
715 def check(prefix):
726 curpath = os.path.join(self.root, prefix)
716 curpath = os.path.join(self.root, prefix)
727 try:
717 try:
728 st = os.lstat(curpath)
718 st = os.lstat(curpath)
729 except OSError, err:
719 except OSError, err:
730 # EINVAL can be raised as invalid path syntax under win32.
720 # EINVAL can be raised as invalid path syntax under win32.
731 # They must be ignored for patterns can be checked too.
721 # They must be ignored for patterns can be checked too.
732 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
722 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
733 raise
723 raise
734 else:
724 else:
735 if stat.S_ISLNK(st.st_mode):
725 if stat.S_ISLNK(st.st_mode):
736 raise Abort(_('path %r traverses symbolic link %r') %
726 raise Abort(_('path %r traverses symbolic link %r') %
737 (path, prefix))
727 (path, prefix))
738 elif (stat.S_ISDIR(st.st_mode) and
728 elif (stat.S_ISDIR(st.st_mode) and
739 os.path.isdir(os.path.join(curpath, '.hg'))):
729 os.path.isdir(os.path.join(curpath, '.hg'))):
740 raise Abort(_('path %r is inside repo %r') %
730 raise Abort(_('path %r is inside repo %r') %
741 (path, prefix))
731 (path, prefix))
742 parts.pop()
732 parts.pop()
743 prefixes = []
733 prefixes = []
744 for n in range(len(parts)):
734 for n in range(len(parts)):
745 prefix = os.sep.join(parts)
735 prefix = os.sep.join(parts)
746 if prefix in self.auditeddir:
736 if prefix in self.auditeddir:
747 break
737 break
748 check(prefix)
738 check(prefix)
749 prefixes.append(prefix)
739 prefixes.append(prefix)
750 parts.pop()
740 parts.pop()
751
741
752 self.audited.add(path)
742 self.audited.add(path)
753 # only add prefixes to the cache after checking everything: we don't
743 # only add prefixes to the cache after checking everything: we don't
754 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
744 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
755 self.auditeddir.update(prefixes)
745 self.auditeddir.update(prefixes)
756
746
757 def nlinks(pathname):
747 def nlinks(pathname):
758 """Return number of hardlinks for the given file."""
748 """Return number of hardlinks for the given file."""
759 return os.lstat(pathname).st_nlink
749 return os.lstat(pathname).st_nlink
760
750
761 if hasattr(os, 'link'):
751 if hasattr(os, 'link'):
762 os_link = os.link
752 os_link = os.link
763 else:
753 else:
764 def os_link(src, dst):
754 def os_link(src, dst):
765 raise OSError(0, _("Hardlinks not supported"))
755 raise OSError(0, _("Hardlinks not supported"))
766
756
767 def lookup_reg(key, name=None, scope=None):
757 def lookup_reg(key, name=None, scope=None):
768 return None
758 return None
769
759
770 if os.name == 'nt':
760 if os.name == 'nt':
771 from windows import *
761 from windows import *
772 def expand_glob(pats):
762 def expand_glob(pats):
773 '''On Windows, expand the implicit globs in a list of patterns'''
763 '''On Windows, expand the implicit globs in a list of patterns'''
774 ret = []
764 ret = []
775 for p in pats:
765 for p in pats:
776 kind, name = patkind(p, None)
766 kind, name = patkind(p, None)
777 if kind is None:
767 if kind is None:
778 globbed = glob.glob(name)
768 globbed = glob.glob(name)
779 if globbed:
769 if globbed:
780 ret.extend(globbed)
770 ret.extend(globbed)
781 continue
771 continue
782 # if we couldn't expand the glob, just keep it around
772 # if we couldn't expand the glob, just keep it around
783 ret.append(p)
773 ret.append(p)
784 return ret
774 return ret
785 else:
775 else:
786 from posix import *
776 from posix import *
787
777
788 def makelock(info, pathname):
778 def makelock(info, pathname):
789 try:
779 try:
790 return os.symlink(info, pathname)
780 return os.symlink(info, pathname)
791 except OSError, why:
781 except OSError, why:
792 if why.errno == errno.EEXIST:
782 if why.errno == errno.EEXIST:
793 raise
783 raise
794 except AttributeError: # no symlink in os
784 except AttributeError: # no symlink in os
795 pass
785 pass
796
786
797 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
787 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
798 os.write(ld, info)
788 os.write(ld, info)
799 os.close(ld)
789 os.close(ld)
800
790
801 def readlock(pathname):
791 def readlock(pathname):
802 try:
792 try:
803 return os.readlink(pathname)
793 return os.readlink(pathname)
804 except OSError, why:
794 except OSError, why:
805 if why.errno not in (errno.EINVAL, errno.ENOSYS):
795 if why.errno not in (errno.EINVAL, errno.ENOSYS):
806 raise
796 raise
807 except AttributeError: # no symlink in os
797 except AttributeError: # no symlink in os
808 pass
798 pass
809 return posixfile(pathname).read()
799 return posixfile(pathname).read()
810
800
811 def fstat(fp):
801 def fstat(fp):
812 '''stat file object that may not have fileno method.'''
802 '''stat file object that may not have fileno method.'''
813 try:
803 try:
814 return os.fstat(fp.fileno())
804 return os.fstat(fp.fileno())
815 except AttributeError:
805 except AttributeError:
816 return os.stat(fp.name)
806 return os.stat(fp.name)
817
807
818 # File system features
808 # File system features
819
809
820 def checkcase(path):
810 def checkcase(path):
821 """
811 """
822 Check whether the given path is on a case-sensitive filesystem
812 Check whether the given path is on a case-sensitive filesystem
823
813
824 Requires a path (like /foo/.hg) ending with a foldable final
814 Requires a path (like /foo/.hg) ending with a foldable final
825 directory component.
815 directory component.
826 """
816 """
827 s1 = os.stat(path)
817 s1 = os.stat(path)
828 d, b = os.path.split(path)
818 d, b = os.path.split(path)
829 p2 = os.path.join(d, b.upper())
819 p2 = os.path.join(d, b.upper())
830 if path == p2:
820 if path == p2:
831 p2 = os.path.join(d, b.lower())
821 p2 = os.path.join(d, b.lower())
832 try:
822 try:
833 s2 = os.stat(p2)
823 s2 = os.stat(p2)
834 if s2 == s1:
824 if s2 == s1:
835 return False
825 return False
836 return True
826 return True
837 except:
827 except:
838 return True
828 return True
839
829
840 _fspathcache = {}
830 _fspathcache = {}
841 def fspath(name, root):
831 def fspath(name, root):
842 '''Get name in the case stored in the filesystem
832 '''Get name in the case stored in the filesystem
843
833
844 The name is either relative to root, or it is an absolute path starting
834 The name is either relative to root, or it is an absolute path starting
845 with root. Note that this function is unnecessary, and should not be
835 with root. Note that this function is unnecessary, and should not be
846 called, for case-sensitive filesystems (simply because it's expensive).
836 called, for case-sensitive filesystems (simply because it's expensive).
847 '''
837 '''
848 # If name is absolute, make it relative
838 # If name is absolute, make it relative
849 if name.lower().startswith(root.lower()):
839 if name.lower().startswith(root.lower()):
850 l = len(root)
840 l = len(root)
851 if name[l] == os.sep or name[l] == os.altsep:
841 if name[l] == os.sep or name[l] == os.altsep:
852 l = l + 1
842 l = l + 1
853 name = name[l:]
843 name = name[l:]
854
844
855 if not os.path.exists(os.path.join(root, name)):
845 if not os.path.exists(os.path.join(root, name)):
856 return None
846 return None
857
847
858 seps = os.sep
848 seps = os.sep
859 if os.altsep:
849 if os.altsep:
860 seps = seps + os.altsep
850 seps = seps + os.altsep
861 # Protect backslashes. This gets silly very quickly.
851 # Protect backslashes. This gets silly very quickly.
862 seps.replace('\\','\\\\')
852 seps.replace('\\','\\\\')
863 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
853 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
864 dir = os.path.normcase(os.path.normpath(root))
854 dir = os.path.normcase(os.path.normpath(root))
865 result = []
855 result = []
866 for part, sep in pattern.findall(name):
856 for part, sep in pattern.findall(name):
867 if sep:
857 if sep:
868 result.append(sep)
858 result.append(sep)
869 continue
859 continue
870
860
871 if dir not in _fspathcache:
861 if dir not in _fspathcache:
872 _fspathcache[dir] = os.listdir(dir)
862 _fspathcache[dir] = os.listdir(dir)
873 contents = _fspathcache[dir]
863 contents = _fspathcache[dir]
874
864
875 lpart = part.lower()
865 lpart = part.lower()
876 for n in contents:
866 for n in contents:
877 if n.lower() == lpart:
867 if n.lower() == lpart:
878 result.append(n)
868 result.append(n)
879 break
869 break
880 else:
870 else:
881 # Cannot happen, as the file exists!
871 # Cannot happen, as the file exists!
882 result.append(part)
872 result.append(part)
883 dir = os.path.join(dir, lpart)
873 dir = os.path.join(dir, lpart)
884
874
885 return ''.join(result)
875 return ''.join(result)
886
876
887 def checkexec(path):
877 def checkexec(path):
888 """
878 """
889 Check whether the given path is on a filesystem with UNIX-like exec flags
879 Check whether the given path is on a filesystem with UNIX-like exec flags
890
880
891 Requires a directory (like /foo/.hg)
881 Requires a directory (like /foo/.hg)
892 """
882 """
893
883
894 # VFAT on some Linux versions can flip mode but it doesn't persist
884 # VFAT on some Linux versions can flip mode but it doesn't persist
895 # a FS remount. Frequently we can detect it if files are created
885 # a FS remount. Frequently we can detect it if files are created
896 # with exec bit on.
886 # with exec bit on.
897
887
898 try:
888 try:
899 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
889 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
900 fh, fn = tempfile.mkstemp("", "", path)
890 fh, fn = tempfile.mkstemp("", "", path)
901 try:
891 try:
902 os.close(fh)
892 os.close(fh)
903 m = os.stat(fn).st_mode & 0777
893 m = os.stat(fn).st_mode & 0777
904 new_file_has_exec = m & EXECFLAGS
894 new_file_has_exec = m & EXECFLAGS
905 os.chmod(fn, m ^ EXECFLAGS)
895 os.chmod(fn, m ^ EXECFLAGS)
906 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
896 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
907 finally:
897 finally:
908 os.unlink(fn)
898 os.unlink(fn)
909 except (IOError, OSError):
899 except (IOError, OSError):
910 # we don't care, the user probably won't be able to commit anyway
900 # we don't care, the user probably won't be able to commit anyway
911 return False
901 return False
912 return not (new_file_has_exec or exec_flags_cannot_flip)
902 return not (new_file_has_exec or exec_flags_cannot_flip)
913
903
914 def checklink(path):
904 def checklink(path):
915 """check whether the given path is on a symlink-capable filesystem"""
905 """check whether the given path is on a symlink-capable filesystem"""
916 # mktemp is not racy because symlink creation will fail if the
906 # mktemp is not racy because symlink creation will fail if the
917 # file already exists
907 # file already exists
918 name = tempfile.mktemp(dir=path)
908 name = tempfile.mktemp(dir=path)
919 try:
909 try:
920 os.symlink(".", name)
910 os.symlink(".", name)
921 os.unlink(name)
911 os.unlink(name)
922 return True
912 return True
923 except (OSError, AttributeError):
913 except (OSError, AttributeError):
924 return False
914 return False
925
915
926 def needbinarypatch():
916 def needbinarypatch():
927 """return True if patches should be applied in binary mode by default."""
917 """return True if patches should be applied in binary mode by default."""
928 return os.name == 'nt'
918 return os.name == 'nt'
929
919
930 def endswithsep(path):
920 def endswithsep(path):
931 '''Check path ends with os.sep or os.altsep.'''
921 '''Check path ends with os.sep or os.altsep.'''
932 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
922 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
933
923
934 def splitpath(path):
924 def splitpath(path):
935 '''Split path by os.sep.
925 '''Split path by os.sep.
936 Note that this function does not use os.altsep because this is
926 Note that this function does not use os.altsep because this is
937 an alternative of simple "xxx.split(os.sep)".
927 an alternative of simple "xxx.split(os.sep)".
938 It is recommended to use os.path.normpath() before using this
928 It is recommended to use os.path.normpath() before using this
939 function if need.'''
929 function if need.'''
940 return path.split(os.sep)
930 return path.split(os.sep)
941
931
942 def gui():
932 def gui():
943 '''Are we running in a GUI?'''
933 '''Are we running in a GUI?'''
944 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
934 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
945
935
946 def mktempcopy(name, emptyok=False, createmode=None):
936 def mktempcopy(name, emptyok=False, createmode=None):
947 """Create a temporary file with the same contents from name
937 """Create a temporary file with the same contents from name
948
938
949 The permission bits are copied from the original file.
939 The permission bits are copied from the original file.
950
940
951 If the temporary file is going to be truncated immediately, you
941 If the temporary file is going to be truncated immediately, you
952 can use emptyok=True as an optimization.
942 can use emptyok=True as an optimization.
953
943
954 Returns the name of the temporary file.
944 Returns the name of the temporary file.
955 """
945 """
956 d, fn = os.path.split(name)
946 d, fn = os.path.split(name)
957 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
947 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
958 os.close(fd)
948 os.close(fd)
959 # Temporary files are created with mode 0600, which is usually not
949 # Temporary files are created with mode 0600, which is usually not
960 # what we want. If the original file already exists, just copy
950 # what we want. If the original file already exists, just copy
961 # its mode. Otherwise, manually obey umask.
951 # its mode. Otherwise, manually obey umask.
962 try:
952 try:
963 st_mode = os.lstat(name).st_mode & 0777
953 st_mode = os.lstat(name).st_mode & 0777
964 except OSError, inst:
954 except OSError, inst:
965 if inst.errno != errno.ENOENT:
955 if inst.errno != errno.ENOENT:
966 raise
956 raise
967 st_mode = createmode
957 st_mode = createmode
968 if st_mode is None:
958 if st_mode is None:
969 st_mode = ~umask
959 st_mode = ~umask
970 st_mode &= 0666
960 st_mode &= 0666
971 os.chmod(temp, st_mode)
961 os.chmod(temp, st_mode)
972 if emptyok:
962 if emptyok:
973 return temp
963 return temp
974 try:
964 try:
975 try:
965 try:
976 ifp = posixfile(name, "rb")
966 ifp = posixfile(name, "rb")
977 except IOError, inst:
967 except IOError, inst:
978 if inst.errno == errno.ENOENT:
968 if inst.errno == errno.ENOENT:
979 return temp
969 return temp
980 if not getattr(inst, 'filename', None):
970 if not getattr(inst, 'filename', None):
981 inst.filename = name
971 inst.filename = name
982 raise
972 raise
983 ofp = posixfile(temp, "wb")
973 ofp = posixfile(temp, "wb")
984 for chunk in filechunkiter(ifp):
974 for chunk in filechunkiter(ifp):
985 ofp.write(chunk)
975 ofp.write(chunk)
986 ifp.close()
976 ifp.close()
987 ofp.close()
977 ofp.close()
988 except:
978 except:
989 try: os.unlink(temp)
979 try: os.unlink(temp)
990 except: pass
980 except: pass
991 raise
981 raise
992 return temp
982 return temp
993
983
994 class atomictempfile(posixfile):
984 class atomictempfile(posixfile):
995 """file-like object that atomically updates a file
985 """file-like object that atomically updates a file
996
986
997 All writes will be redirected to a temporary copy of the original
987 All writes will be redirected to a temporary copy of the original
998 file. When rename is called, the copy is renamed to the original
988 file. When rename is called, the copy is renamed to the original
999 name, making the changes visible.
989 name, making the changes visible.
1000 """
990 """
1001 def __init__(self, name, mode, createmode):
991 def __init__(self, name, mode, createmode):
1002 self.__name = name
992 self.__name = name
1003 self.temp = mktempcopy(name, emptyok=('w' in mode),
993 self.temp = mktempcopy(name, emptyok=('w' in mode),
1004 createmode=createmode)
994 createmode=createmode)
1005 posixfile.__init__(self, self.temp, mode)
995 posixfile.__init__(self, self.temp, mode)
1006
996
1007 def rename(self):
997 def rename(self):
1008 if not self.closed:
998 if not self.closed:
1009 posixfile.close(self)
999 posixfile.close(self)
1010 rename(self.temp, localpath(self.__name))
1000 rename(self.temp, localpath(self.__name))
1011
1001
1012 def __del__(self):
1002 def __del__(self):
1013 if not self.closed:
1003 if not self.closed:
1014 try:
1004 try:
1015 os.unlink(self.temp)
1005 os.unlink(self.temp)
1016 except: pass
1006 except: pass
1017 posixfile.close(self)
1007 posixfile.close(self)
1018
1008
1019 def makedirs(name, mode=None):
1009 def makedirs(name, mode=None):
1020 """recursive directory creation with parent mode inheritance"""
1010 """recursive directory creation with parent mode inheritance"""
1021 try:
1011 try:
1022 os.mkdir(name)
1012 os.mkdir(name)
1023 if mode is not None:
1013 if mode is not None:
1024 os.chmod(name, mode)
1014 os.chmod(name, mode)
1025 return
1015 return
1026 except OSError, err:
1016 except OSError, err:
1027 if err.errno == errno.EEXIST:
1017 if err.errno == errno.EEXIST:
1028 return
1018 return
1029 if err.errno != errno.ENOENT:
1019 if err.errno != errno.ENOENT:
1030 raise
1020 raise
1031 parent = os.path.abspath(os.path.dirname(name))
1021 parent = os.path.abspath(os.path.dirname(name))
1032 makedirs(parent, mode)
1022 makedirs(parent, mode)
1033 makedirs(name, mode)
1023 makedirs(name, mode)
1034
1024
1035 class opener(object):
1025 class opener(object):
1036 """Open files relative to a base directory
1026 """Open files relative to a base directory
1037
1027
1038 This class is used to hide the details of COW semantics and
1028 This class is used to hide the details of COW semantics and
1039 remote file access from higher level code.
1029 remote file access from higher level code.
1040 """
1030 """
1041 def __init__(self, base, audit=True):
1031 def __init__(self, base, audit=True):
1042 self.base = base
1032 self.base = base
1043 if audit:
1033 if audit:
1044 self.audit_path = path_auditor(base)
1034 self.audit_path = path_auditor(base)
1045 else:
1035 else:
1046 self.audit_path = always
1036 self.audit_path = always
1047 self.createmode = None
1037 self.createmode = None
1048
1038
1049 def __getattr__(self, name):
1039 def __getattr__(self, name):
1050 if name == '_can_symlink':
1040 if name == '_can_symlink':
1051 self._can_symlink = checklink(self.base)
1041 self._can_symlink = checklink(self.base)
1052 return self._can_symlink
1042 return self._can_symlink
1053 raise AttributeError(name)
1043 raise AttributeError(name)
1054
1044
1055 def _fixfilemode(self, name):
1045 def _fixfilemode(self, name):
1056 if self.createmode is None:
1046 if self.createmode is None:
1057 return
1047 return
1058 os.chmod(name, self.createmode & 0666)
1048 os.chmod(name, self.createmode & 0666)
1059
1049
1060 def __call__(self, path, mode="r", text=False, atomictemp=False):
1050 def __call__(self, path, mode="r", text=False, atomictemp=False):
1061 self.audit_path(path)
1051 self.audit_path(path)
1062 f = os.path.join(self.base, path)
1052 f = os.path.join(self.base, path)
1063
1053
1064 if not text and "b" not in mode:
1054 if not text and "b" not in mode:
1065 mode += "b" # for that other OS
1055 mode += "b" # for that other OS
1066
1056
1067 nlink = -1
1057 nlink = -1
1068 if mode not in ("r", "rb"):
1058 if mode not in ("r", "rb"):
1069 try:
1059 try:
1070 nlink = nlinks(f)
1060 nlink = nlinks(f)
1071 except OSError:
1061 except OSError:
1072 nlink = 0
1062 nlink = 0
1073 d = os.path.dirname(f)
1063 d = os.path.dirname(f)
1074 if not os.path.isdir(d):
1064 if not os.path.isdir(d):
1075 makedirs(d, self.createmode)
1065 makedirs(d, self.createmode)
1076 if atomictemp:
1066 if atomictemp:
1077 return atomictempfile(f, mode, self.createmode)
1067 return atomictempfile(f, mode, self.createmode)
1078 if nlink > 1:
1068 if nlink > 1:
1079 rename(mktempcopy(f), f)
1069 rename(mktempcopy(f), f)
1080 fp = posixfile(f, mode)
1070 fp = posixfile(f, mode)
1081 if nlink == 0:
1071 if nlink == 0:
1082 self._fixfilemode(f)
1072 self._fixfilemode(f)
1083 return fp
1073 return fp
1084
1074
1085 def symlink(self, src, dst):
1075 def symlink(self, src, dst):
1086 self.audit_path(dst)
1076 self.audit_path(dst)
1087 linkname = os.path.join(self.base, dst)
1077 linkname = os.path.join(self.base, dst)
1088 try:
1078 try:
1089 os.unlink(linkname)
1079 os.unlink(linkname)
1090 except OSError:
1080 except OSError:
1091 pass
1081 pass
1092
1082
1093 dirname = os.path.dirname(linkname)
1083 dirname = os.path.dirname(linkname)
1094 if not os.path.exists(dirname):
1084 if not os.path.exists(dirname):
1095 makedirs(dirname, self.createmode)
1085 makedirs(dirname, self.createmode)
1096
1086
1097 if self._can_symlink:
1087 if self._can_symlink:
1098 try:
1088 try:
1099 os.symlink(src, linkname)
1089 os.symlink(src, linkname)
1100 except OSError, err:
1090 except OSError, err:
1101 raise OSError(err.errno, _('could not symlink to %r: %s') %
1091 raise OSError(err.errno, _('could not symlink to %r: %s') %
1102 (src, err.strerror), linkname)
1092 (src, err.strerror), linkname)
1103 else:
1093 else:
1104 f = self(dst, "w")
1094 f = self(dst, "w")
1105 f.write(src)
1095 f.write(src)
1106 f.close()
1096 f.close()
1107 self._fixfilemode(dst)
1097 self._fixfilemode(dst)
1108
1098
1109 class chunkbuffer(object):
1099 class chunkbuffer(object):
1110 """Allow arbitrary sized chunks of data to be efficiently read from an
1100 """Allow arbitrary sized chunks of data to be efficiently read from an
1111 iterator over chunks of arbitrary size."""
1101 iterator over chunks of arbitrary size."""
1112
1102
1113 def __init__(self, in_iter):
1103 def __init__(self, in_iter):
1114 """in_iter is the iterator that's iterating over the input chunks.
1104 """in_iter is the iterator that's iterating over the input chunks.
1115 targetsize is how big a buffer to try to maintain."""
1105 targetsize is how big a buffer to try to maintain."""
1116 self.iter = iter(in_iter)
1106 self.iter = iter(in_iter)
1117 self.buf = ''
1107 self.buf = ''
1118 self.targetsize = 2**16
1108 self.targetsize = 2**16
1119
1109
1120 def read(self, l):
1110 def read(self, l):
1121 """Read L bytes of data from the iterator of chunks of data.
1111 """Read L bytes of data from the iterator of chunks of data.
1122 Returns less than L bytes if the iterator runs dry."""
1112 Returns less than L bytes if the iterator runs dry."""
1123 if l > len(self.buf) and self.iter:
1113 if l > len(self.buf) and self.iter:
1124 # Clamp to a multiple of self.targetsize
1114 # Clamp to a multiple of self.targetsize
1125 targetsize = max(l, self.targetsize)
1115 targetsize = max(l, self.targetsize)
1126 collector = cStringIO.StringIO()
1116 collector = cStringIO.StringIO()
1127 collector.write(self.buf)
1117 collector.write(self.buf)
1128 collected = len(self.buf)
1118 collected = len(self.buf)
1129 for chunk in self.iter:
1119 for chunk in self.iter:
1130 collector.write(chunk)
1120 collector.write(chunk)
1131 collected += len(chunk)
1121 collected += len(chunk)
1132 if collected >= targetsize:
1122 if collected >= targetsize:
1133 break
1123 break
1134 if collected < targetsize:
1124 if collected < targetsize:
1135 self.iter = False
1125 self.iter = False
1136 self.buf = collector.getvalue()
1126 self.buf = collector.getvalue()
1137 if len(self.buf) == l:
1127 if len(self.buf) == l:
1138 s, self.buf = str(self.buf), ''
1128 s, self.buf = str(self.buf), ''
1139 else:
1129 else:
1140 s, self.buf = self.buf[:l], buffer(self.buf, l)
1130 s, self.buf = self.buf[:l], buffer(self.buf, l)
1141 return s
1131 return s
1142
1132
1143 def filechunkiter(f, size=65536, limit=None):
1133 def filechunkiter(f, size=65536, limit=None):
1144 """Create a generator that produces the data in the file size
1134 """Create a generator that produces the data in the file size
1145 (default 65536) bytes at a time, up to optional limit (default is
1135 (default 65536) bytes at a time, up to optional limit (default is
1146 to read all data). Chunks may be less than size bytes if the
1136 to read all data). Chunks may be less than size bytes if the
1147 chunk is the last chunk in the file, or the file is a socket or
1137 chunk is the last chunk in the file, or the file is a socket or
1148 some other type of file that sometimes reads less data than is
1138 some other type of file that sometimes reads less data than is
1149 requested."""
1139 requested."""
1150 assert size >= 0
1140 assert size >= 0
1151 assert limit is None or limit >= 0
1141 assert limit is None or limit >= 0
1152 while True:
1142 while True:
1153 if limit is None: nbytes = size
1143 if limit is None: nbytes = size
1154 else: nbytes = min(limit, size)
1144 else: nbytes = min(limit, size)
1155 s = nbytes and f.read(nbytes)
1145 s = nbytes and f.read(nbytes)
1156 if not s: break
1146 if not s: break
1157 if limit: limit -= len(s)
1147 if limit: limit -= len(s)
1158 yield s
1148 yield s
1159
1149
1160 def makedate():
1150 def makedate():
1161 lt = time.localtime()
1151 lt = time.localtime()
1162 if lt[8] == 1 and time.daylight:
1152 if lt[8] == 1 and time.daylight:
1163 tz = time.altzone
1153 tz = time.altzone
1164 else:
1154 else:
1165 tz = time.timezone
1155 tz = time.timezone
1166 return time.mktime(lt), tz
1156 return time.mktime(lt), tz
1167
1157
1168 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1158 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1169 """represent a (unixtime, offset) tuple as a localized time.
1159 """represent a (unixtime, offset) tuple as a localized time.
1170 unixtime is seconds since the epoch, and offset is the time zone's
1160 unixtime is seconds since the epoch, and offset is the time zone's
1171 number of seconds away from UTC. if timezone is false, do not
1161 number of seconds away from UTC. if timezone is false, do not
1172 append time zone to string."""
1162 append time zone to string."""
1173 t, tz = date or makedate()
1163 t, tz = date or makedate()
1174 if "%1" in format or "%2" in format:
1164 if "%1" in format or "%2" in format:
1175 sign = (tz > 0) and "-" or "+"
1165 sign = (tz > 0) and "-" or "+"
1176 minutes = abs(tz) / 60
1166 minutes = abs(tz) / 60
1177 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1167 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1178 format = format.replace("%2", "%02d" % (minutes % 60))
1168 format = format.replace("%2", "%02d" % (minutes % 60))
1179 s = time.strftime(format, time.gmtime(float(t) - tz))
1169 s = time.strftime(format, time.gmtime(float(t) - tz))
1180 return s
1170 return s
1181
1171
1182 def shortdate(date=None):
1172 def shortdate(date=None):
1183 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1173 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1184 return datestr(date, format='%Y-%m-%d')
1174 return datestr(date, format='%Y-%m-%d')
1185
1175
1186 def strdate(string, format, defaults=[]):
1176 def strdate(string, format, defaults=[]):
1187 """parse a localized time string and return a (unixtime, offset) tuple.
1177 """parse a localized time string and return a (unixtime, offset) tuple.
1188 if the string cannot be parsed, ValueError is raised."""
1178 if the string cannot be parsed, ValueError is raised."""
1189 def timezone(string):
1179 def timezone(string):
1190 tz = string.split()[-1]
1180 tz = string.split()[-1]
1191 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1181 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1192 sign = (tz[0] == "+") and 1 or -1
1182 sign = (tz[0] == "+") and 1 or -1
1193 hours = int(tz[1:3])
1183 hours = int(tz[1:3])
1194 minutes = int(tz[3:5])
1184 minutes = int(tz[3:5])
1195 return -sign * (hours * 60 + minutes) * 60
1185 return -sign * (hours * 60 + minutes) * 60
1196 if tz == "GMT" or tz == "UTC":
1186 if tz == "GMT" or tz == "UTC":
1197 return 0
1187 return 0
1198 return None
1188 return None
1199
1189
1200 # NOTE: unixtime = localunixtime + offset
1190 # NOTE: unixtime = localunixtime + offset
1201 offset, date = timezone(string), string
1191 offset, date = timezone(string), string
1202 if offset != None:
1192 if offset != None:
1203 date = " ".join(string.split()[:-1])
1193 date = " ".join(string.split()[:-1])
1204
1194
1205 # add missing elements from defaults
1195 # add missing elements from defaults
1206 for part in defaults:
1196 for part in defaults:
1207 found = [True for p in part if ("%"+p) in format]
1197 found = [True for p in part if ("%"+p) in format]
1208 if not found:
1198 if not found:
1209 date += "@" + defaults[part]
1199 date += "@" + defaults[part]
1210 format += "@%" + part[0]
1200 format += "@%" + part[0]
1211
1201
1212 timetuple = time.strptime(date, format)
1202 timetuple = time.strptime(date, format)
1213 localunixtime = int(calendar.timegm(timetuple))
1203 localunixtime = int(calendar.timegm(timetuple))
1214 if offset is None:
1204 if offset is None:
1215 # local timezone
1205 # local timezone
1216 unixtime = int(time.mktime(timetuple))
1206 unixtime = int(time.mktime(timetuple))
1217 offset = unixtime - localunixtime
1207 offset = unixtime - localunixtime
1218 else:
1208 else:
1219 unixtime = localunixtime + offset
1209 unixtime = localunixtime + offset
1220 return unixtime, offset
1210 return unixtime, offset
1221
1211
1222 def parsedate(date, formats=None, defaults=None):
1212 def parsedate(date, formats=None, defaults=None):
1223 """parse a localized date/time string and return a (unixtime, offset) tuple.
1213 """parse a localized date/time string and return a (unixtime, offset) tuple.
1224
1214
1225 The date may be a "unixtime offset" string or in one of the specified
1215 The date may be a "unixtime offset" string or in one of the specified
1226 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1216 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1227 """
1217 """
1228 if not date:
1218 if not date:
1229 return 0, 0
1219 return 0, 0
1230 if isinstance(date, tuple) and len(date) == 2:
1220 if isinstance(date, tuple) and len(date) == 2:
1231 return date
1221 return date
1232 if not formats:
1222 if not formats:
1233 formats = defaultdateformats
1223 formats = defaultdateformats
1234 date = date.strip()
1224 date = date.strip()
1235 try:
1225 try:
1236 when, offset = map(int, date.split(' '))
1226 when, offset = map(int, date.split(' '))
1237 except ValueError:
1227 except ValueError:
1238 # fill out defaults
1228 # fill out defaults
1239 if not defaults:
1229 if not defaults:
1240 defaults = {}
1230 defaults = {}
1241 now = makedate()
1231 now = makedate()
1242 for part in "d mb yY HI M S".split():
1232 for part in "d mb yY HI M S".split():
1243 if part not in defaults:
1233 if part not in defaults:
1244 if part[0] in "HMS":
1234 if part[0] in "HMS":
1245 defaults[part] = "00"
1235 defaults[part] = "00"
1246 else:
1236 else:
1247 defaults[part] = datestr(now, "%" + part[0])
1237 defaults[part] = datestr(now, "%" + part[0])
1248
1238
1249 for format in formats:
1239 for format in formats:
1250 try:
1240 try:
1251 when, offset = strdate(date, format, defaults)
1241 when, offset = strdate(date, format, defaults)
1252 except (ValueError, OverflowError):
1242 except (ValueError, OverflowError):
1253 pass
1243 pass
1254 else:
1244 else:
1255 break
1245 break
1256 else:
1246 else:
1257 raise Abort(_('invalid date: %r ') % date)
1247 raise Abort(_('invalid date: %r ') % date)
1258 # validate explicit (probably user-specified) date and
1248 # validate explicit (probably user-specified) date and
1259 # time zone offset. values must fit in signed 32 bits for
1249 # time zone offset. values must fit in signed 32 bits for
1260 # current 32-bit linux runtimes. timezones go from UTC-12
1250 # current 32-bit linux runtimes. timezones go from UTC-12
1261 # to UTC+14
1251 # to UTC+14
1262 if abs(when) > 0x7fffffff:
1252 if abs(when) > 0x7fffffff:
1263 raise Abort(_('date exceeds 32 bits: %d') % when)
1253 raise Abort(_('date exceeds 32 bits: %d') % when)
1264 if offset < -50400 or offset > 43200:
1254 if offset < -50400 or offset > 43200:
1265 raise Abort(_('impossible time zone offset: %d') % offset)
1255 raise Abort(_('impossible time zone offset: %d') % offset)
1266 return when, offset
1256 return when, offset
1267
1257
1268 def matchdate(date):
1258 def matchdate(date):
1269 """Return a function that matches a given date match specifier
1259 """Return a function that matches a given date match specifier
1270
1260
1271 Formats include:
1261 Formats include:
1272
1262
1273 '{date}' match a given date to the accuracy provided
1263 '{date}' match a given date to the accuracy provided
1274
1264
1275 '<{date}' on or before a given date
1265 '<{date}' on or before a given date
1276
1266
1277 '>{date}' on or after a given date
1267 '>{date}' on or after a given date
1278
1268
1279 """
1269 """
1280
1270
1281 def lower(date):
1271 def lower(date):
1282 d = dict(mb="1", d="1")
1272 d = dict(mb="1", d="1")
1283 return parsedate(date, extendeddateformats, d)[0]
1273 return parsedate(date, extendeddateformats, d)[0]
1284
1274
1285 def upper(date):
1275 def upper(date):
1286 d = dict(mb="12", HI="23", M="59", S="59")
1276 d = dict(mb="12", HI="23", M="59", S="59")
1287 for days in "31 30 29".split():
1277 for days in "31 30 29".split():
1288 try:
1278 try:
1289 d["d"] = days
1279 d["d"] = days
1290 return parsedate(date, extendeddateformats, d)[0]
1280 return parsedate(date, extendeddateformats, d)[0]
1291 except:
1281 except:
1292 pass
1282 pass
1293 d["d"] = "28"
1283 d["d"] = "28"
1294 return parsedate(date, extendeddateformats, d)[0]
1284 return parsedate(date, extendeddateformats, d)[0]
1295
1285
1296 date = date.strip()
1286 date = date.strip()
1297 if date[0] == "<":
1287 if date[0] == "<":
1298 when = upper(date[1:])
1288 when = upper(date[1:])
1299 return lambda x: x <= when
1289 return lambda x: x <= when
1300 elif date[0] == ">":
1290 elif date[0] == ">":
1301 when = lower(date[1:])
1291 when = lower(date[1:])
1302 return lambda x: x >= when
1292 return lambda x: x >= when
1303 elif date[0] == "-":
1293 elif date[0] == "-":
1304 try:
1294 try:
1305 days = int(date[1:])
1295 days = int(date[1:])
1306 except ValueError:
1296 except ValueError:
1307 raise Abort(_("invalid day spec: %s") % date[1:])
1297 raise Abort(_("invalid day spec: %s") % date[1:])
1308 when = makedate()[0] - days * 3600 * 24
1298 when = makedate()[0] - days * 3600 * 24
1309 return lambda x: x >= when
1299 return lambda x: x >= when
1310 elif " to " in date:
1300 elif " to " in date:
1311 a, b = date.split(" to ")
1301 a, b = date.split(" to ")
1312 start, stop = lower(a), upper(b)
1302 start, stop = lower(a), upper(b)
1313 return lambda x: x >= start and x <= stop
1303 return lambda x: x >= start and x <= stop
1314 else:
1304 else:
1315 start, stop = lower(date), upper(date)
1305 start, stop = lower(date), upper(date)
1316 return lambda x: x >= start and x <= stop
1306 return lambda x: x >= start and x <= stop
1317
1307
1318 def shortuser(user):
1308 def shortuser(user):
1319 """Return a short representation of a user name or email address."""
1309 """Return a short representation of a user name or email address."""
1320 f = user.find('@')
1310 f = user.find('@')
1321 if f >= 0:
1311 if f >= 0:
1322 user = user[:f]
1312 user = user[:f]
1323 f = user.find('<')
1313 f = user.find('<')
1324 if f >= 0:
1314 if f >= 0:
1325 user = user[f+1:]
1315 user = user[f+1:]
1326 f = user.find(' ')
1316 f = user.find(' ')
1327 if f >= 0:
1317 if f >= 0:
1328 user = user[:f]
1318 user = user[:f]
1329 f = user.find('.')
1319 f = user.find('.')
1330 if f >= 0:
1320 if f >= 0:
1331 user = user[:f]
1321 user = user[:f]
1332 return user
1322 return user
1333
1323
1334 def email(author):
1324 def email(author):
1335 '''get email of author.'''
1325 '''get email of author.'''
1336 r = author.find('>')
1326 r = author.find('>')
1337 if r == -1: r = None
1327 if r == -1: r = None
1338 return author[author.find('<')+1:r]
1328 return author[author.find('<')+1:r]
1339
1329
1340 def ellipsis(text, maxlength=400):
1330 def ellipsis(text, maxlength=400):
1341 """Trim string to at most maxlength (default: 400) characters."""
1331 """Trim string to at most maxlength (default: 400) characters."""
1342 if len(text) <= maxlength:
1332 if len(text) <= maxlength:
1343 return text
1333 return text
1344 else:
1334 else:
1345 return "%s..." % (text[:maxlength-3])
1335 return "%s..." % (text[:maxlength-3])
1346
1336
1347 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1337 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1348 '''yield every hg repository under path, recursively.'''
1338 '''yield every hg repository under path, recursively.'''
1349 def errhandler(err):
1339 def errhandler(err):
1350 if err.filename == path:
1340 if err.filename == path:
1351 raise err
1341 raise err
1352 if followsym and hasattr(os.path, 'samestat'):
1342 if followsym and hasattr(os.path, 'samestat'):
1353 def _add_dir_if_not_there(dirlst, dirname):
1343 def _add_dir_if_not_there(dirlst, dirname):
1354 match = False
1344 match = False
1355 samestat = os.path.samestat
1345 samestat = os.path.samestat
1356 dirstat = os.stat(dirname)
1346 dirstat = os.stat(dirname)
1357 for lstdirstat in dirlst:
1347 for lstdirstat in dirlst:
1358 if samestat(dirstat, lstdirstat):
1348 if samestat(dirstat, lstdirstat):
1359 match = True
1349 match = True
1360 break
1350 break
1361 if not match:
1351 if not match:
1362 dirlst.append(dirstat)
1352 dirlst.append(dirstat)
1363 return not match
1353 return not match
1364 else:
1354 else:
1365 followsym = False
1355 followsym = False
1366
1356
1367 if (seen_dirs is None) and followsym:
1357 if (seen_dirs is None) and followsym:
1368 seen_dirs = []
1358 seen_dirs = []
1369 _add_dir_if_not_there(seen_dirs, path)
1359 _add_dir_if_not_there(seen_dirs, path)
1370 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1360 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1371 if '.hg' in dirs:
1361 if '.hg' in dirs:
1372 yield root # found a repository
1362 yield root # found a repository
1373 qroot = os.path.join(root, '.hg', 'patches')
1363 qroot = os.path.join(root, '.hg', 'patches')
1374 if os.path.isdir(os.path.join(qroot, '.hg')):
1364 if os.path.isdir(os.path.join(qroot, '.hg')):
1375 yield qroot # we have a patch queue repo here
1365 yield qroot # we have a patch queue repo here
1376 if recurse:
1366 if recurse:
1377 # avoid recursing inside the .hg directory
1367 # avoid recursing inside the .hg directory
1378 dirs.remove('.hg')
1368 dirs.remove('.hg')
1379 else:
1369 else:
1380 dirs[:] = [] # don't descend further
1370 dirs[:] = [] # don't descend further
1381 elif followsym:
1371 elif followsym:
1382 newdirs = []
1372 newdirs = []
1383 for d in dirs:
1373 for d in dirs:
1384 fname = os.path.join(root, d)
1374 fname = os.path.join(root, d)
1385 if _add_dir_if_not_there(seen_dirs, fname):
1375 if _add_dir_if_not_there(seen_dirs, fname):
1386 if os.path.islink(fname):
1376 if os.path.islink(fname):
1387 for hgname in walkrepos(fname, True, seen_dirs):
1377 for hgname in walkrepos(fname, True, seen_dirs):
1388 yield hgname
1378 yield hgname
1389 else:
1379 else:
1390 newdirs.append(d)
1380 newdirs.append(d)
1391 dirs[:] = newdirs
1381 dirs[:] = newdirs
1392
1382
1393 _rcpath = None
1383 _rcpath = None
1394
1384
1395 def os_rcpath():
1385 def os_rcpath():
1396 '''return default os-specific hgrc search path'''
1386 '''return default os-specific hgrc search path'''
1397 path = system_rcpath()
1387 path = system_rcpath()
1398 path.extend(user_rcpath())
1388 path.extend(user_rcpath())
1399 path = [os.path.normpath(f) for f in path]
1389 path = [os.path.normpath(f) for f in path]
1400 return path
1390 return path
1401
1391
1402 def rcpath():
1392 def rcpath():
1403 '''return hgrc search path. if env var HGRCPATH is set, use it.
1393 '''return hgrc search path. if env var HGRCPATH is set, use it.
1404 for each item in path, if directory, use files ending in .rc,
1394 for each item in path, if directory, use files ending in .rc,
1405 else use item.
1395 else use item.
1406 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1396 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1407 if no HGRCPATH, use default os-specific path.'''
1397 if no HGRCPATH, use default os-specific path.'''
1408 global _rcpath
1398 global _rcpath
1409 if _rcpath is None:
1399 if _rcpath is None:
1410 if 'HGRCPATH' in os.environ:
1400 if 'HGRCPATH' in os.environ:
1411 _rcpath = []
1401 _rcpath = []
1412 for p in os.environ['HGRCPATH'].split(os.pathsep):
1402 for p in os.environ['HGRCPATH'].split(os.pathsep):
1413 if not p: continue
1403 if not p: continue
1414 if os.path.isdir(p):
1404 if os.path.isdir(p):
1415 for f, kind in osutil.listdir(p):
1405 for f, kind in osutil.listdir(p):
1416 if f.endswith('.rc'):
1406 if f.endswith('.rc'):
1417 _rcpath.append(os.path.join(p, f))
1407 _rcpath.append(os.path.join(p, f))
1418 else:
1408 else:
1419 _rcpath.append(p)
1409 _rcpath.append(p)
1420 else:
1410 else:
1421 _rcpath = os_rcpath()
1411 _rcpath = os_rcpath()
1422 return _rcpath
1412 return _rcpath
1423
1413
1424 def bytecount(nbytes):
1414 def bytecount(nbytes):
1425 '''return byte count formatted as readable string, with units'''
1415 '''return byte count formatted as readable string, with units'''
1426
1416
1427 units = (
1417 units = (
1428 (100, 1<<30, _('%.0f GB')),
1418 (100, 1<<30, _('%.0f GB')),
1429 (10, 1<<30, _('%.1f GB')),
1419 (10, 1<<30, _('%.1f GB')),
1430 (1, 1<<30, _('%.2f GB')),
1420 (1, 1<<30, _('%.2f GB')),
1431 (100, 1<<20, _('%.0f MB')),
1421 (100, 1<<20, _('%.0f MB')),
1432 (10, 1<<20, _('%.1f MB')),
1422 (10, 1<<20, _('%.1f MB')),
1433 (1, 1<<20, _('%.2f MB')),
1423 (1, 1<<20, _('%.2f MB')),
1434 (100, 1<<10, _('%.0f KB')),
1424 (100, 1<<10, _('%.0f KB')),
1435 (10, 1<<10, _('%.1f KB')),
1425 (10, 1<<10, _('%.1f KB')),
1436 (1, 1<<10, _('%.2f KB')),
1426 (1, 1<<10, _('%.2f KB')),
1437 (1, 1, _('%.0f bytes')),
1427 (1, 1, _('%.0f bytes')),
1438 )
1428 )
1439
1429
1440 for multiplier, divisor, format in units:
1430 for multiplier, divisor, format in units:
1441 if nbytes >= divisor * multiplier:
1431 if nbytes >= divisor * multiplier:
1442 return format % (nbytes / float(divisor))
1432 return format % (nbytes / float(divisor))
1443 return units[-1][2] % nbytes
1433 return units[-1][2] % nbytes
1444
1434
1445 def drop_scheme(scheme, path):
1435 def drop_scheme(scheme, path):
1446 sc = scheme + ':'
1436 sc = scheme + ':'
1447 if path.startswith(sc):
1437 if path.startswith(sc):
1448 path = path[len(sc):]
1438 path = path[len(sc):]
1449 if path.startswith('//'):
1439 if path.startswith('//'):
1450 path = path[2:]
1440 path = path[2:]
1451 return path
1441 return path
1452
1442
1453 def uirepr(s):
1443 def uirepr(s):
1454 # Avoid double backslash in Windows path repr()
1444 # Avoid double backslash in Windows path repr()
1455 return repr(s).replace('\\\\', '\\')
1445 return repr(s).replace('\\\\', '\\')
1456
1446
1457 def termwidth():
1447 def termwidth():
1458 if 'COLUMNS' in os.environ:
1448 if 'COLUMNS' in os.environ:
1459 try:
1449 try:
1460 return int(os.environ['COLUMNS'])
1450 return int(os.environ['COLUMNS'])
1461 except ValueError:
1451 except ValueError:
1462 pass
1452 pass
1463 try:
1453 try:
1464 import termios, array, fcntl
1454 import termios, array, fcntl
1465 for dev in (sys.stdout, sys.stdin):
1455 for dev in (sys.stdout, sys.stdin):
1466 try:
1456 try:
1467 fd = dev.fileno()
1457 fd = dev.fileno()
1468 if not os.isatty(fd):
1458 if not os.isatty(fd):
1469 continue
1459 continue
1470 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1460 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1471 return array.array('h', arri)[1]
1461 return array.array('h', arri)[1]
1472 except ValueError:
1462 except ValueError:
1473 pass
1463 pass
1474 except ImportError:
1464 except ImportError:
1475 pass
1465 pass
1476 return 80
1466 return 80
1477
1467
1478 def iterlines(iterator):
1468 def iterlines(iterator):
1479 for chunk in iterator:
1469 for chunk in iterator:
1480 for line in chunk.splitlines():
1470 for line in chunk.splitlines():
1481 yield line
1471 yield line
@@ -1,118 +1,121 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 mkdir test
3 mkdir test
4 cd test
4 cd test
5 hg init
5 hg init
6 echo foo>foo
6 echo foo>foo
7 hg commit -Am 1 -d '1 0'
7 hg commit -Am 1 -d '1 0'
8 echo bar>bar
8 echo bar>bar
9 hg commit -Am 2 -d '2 0'
9 hg commit -Am 2 -d '2 0'
10 mkdir baz
10 mkdir baz
11 echo bletch>baz/bletch
11 echo bletch>baz/bletch
12 hg commit -Am 3 -d '1000000000 0'
12 hg commit -Am 3 -d '1000000000 0'
13 echo "[web]" >> .hg/hgrc
13 echo "[web]" >> .hg/hgrc
14 echo "name = test-archive" >> .hg/hgrc
14 echo "name = test-archive" >> .hg/hgrc
15 cp .hg/hgrc .hg/hgrc-base
15 cp .hg/hgrc .hg/hgrc-base
16
16
17 # check http return codes
17 # check http return codes
18 test_archtype() {
18 test_archtype() {
19 echo "allow_archive = $1" >> .hg/hgrc
19 echo "allow_archive = $1" >> .hg/hgrc
20 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
20 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
21 cat hg.pid >> $DAEMON_PIDS
21 cat hg.pid >> $DAEMON_PIDS
22 echo % $1 allowed should give 200
22 echo % $1 allowed should give 200
23 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$2" | head -n 1
23 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$2" | head -n 1
24 echo % $3 and $4 disallowed should both give 403
24 echo % $3 and $4 disallowed should both give 403
25 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$3" | head -n 1
25 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$3" | head -n 1
26 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$4" | head -n 1
26 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$4" | head -n 1
27 "$TESTDIR/killdaemons.py"
27 "$TESTDIR/killdaemons.py"
28 cat errors.log
28 cat errors.log
29 cp .hg/hgrc-base .hg/hgrc
29 cp .hg/hgrc-base .hg/hgrc
30 }
30 }
31
31
32 echo
32 echo
33 test_archtype gz tar.gz tar.bz2 zip
33 test_archtype gz tar.gz tar.bz2 zip
34 test_archtype bz2 tar.bz2 zip tar.gz
34 test_archtype bz2 tar.bz2 zip tar.gz
35 test_archtype zip zip tar.gz tar.bz2
35 test_archtype zip zip tar.gz tar.bz2
36
36
37 echo "allow_archive = gz bz2 zip" >> .hg/hgrc
37 echo "allow_archive = gz bz2 zip" >> .hg/hgrc
38 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
38 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
39 cat hg.pid >> $DAEMON_PIDS
39 cat hg.pid >> $DAEMON_PIDS
40
40
41 echo % invalid arch type should give 404
41 echo % invalid arch type should give 404
42 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.invalid" | head -n 1
42 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.invalid" | head -n 1
43 echo
43 echo
44
44
45 TIP=`hg id -v | cut -f1 -d' '`
45 TIP=`hg id -v | cut -f1 -d' '`
46 QTIP=`hg id -q`
46 QTIP=`hg id -q`
47 cat > getarchive.py <<EOF
47 cat > getarchive.py <<EOF
48 import os, sys, urllib2
48 import os, sys, urllib2
49 try:
49 try:
50 # Set stdout to binary mode for win32 platforms
50 # Set stdout to binary mode for win32 platforms
51 import msvcrt
51 import msvcrt
52 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
52 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
53 except ImportError:
53 except ImportError:
54 pass
54 pass
55
55
56 node, archive = sys.argv[1:]
56 node, archive = sys.argv[1:]
57 f = urllib2.urlopen('http://127.0.0.1:%s/?cmd=archive;node=%s;type=%s'
57 f = urllib2.urlopen('http://127.0.0.1:%s/?cmd=archive;node=%s;type=%s'
58 % (os.environ['HGPORT'], node, archive))
58 % (os.environ['HGPORT'], node, archive))
59 sys.stdout.write(f.read())
59 sys.stdout.write(f.read())
60 EOF
60 EOF
61 http_proxy= python getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
61 http_proxy= python getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
62 http_proxy= python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
62 http_proxy= python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
63 http_proxy= python getarchive.py "$TIP" zip > archive.zip
63 http_proxy= python getarchive.py "$TIP" zip > archive.zip
64 unzip -t archive.zip | sed "s/$QTIP/TIP/"
64 unzip -t archive.zip | sed "s/$QTIP/TIP/"
65
65
66 "$TESTDIR/killdaemons.py"
66 "$TESTDIR/killdaemons.py"
67
67
68 hg archive -t tar test.tar
68 hg archive -t tar test.tar
69 tar tf test.tar
69 tar tf test.tar
70
70
71 hg archive -t tbz2 -X baz test.tar.bz2
71 hg archive -t tbz2 -X baz test.tar.bz2
72 bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
72 bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
73
73
74 hg archive -t tgz -p %b-%h test-%h.tar.gz
74 hg archive -t tgz -p %b-%h test-%h.tar.gz
75 gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
75 gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
76
76
77 cat > md5comp.py <<EOF
77 cat > md5comp.py <<EOF
78 from mercurial.util import md5
78 try:
79 from hashlib import md5
80 except ImportError:
81 from md5 import md5
79 import sys
82 import sys
80 f1, f2 = sys.argv[1:3]
83 f1, f2 = sys.argv[1:3]
81 h1 = md5(file(f1, 'rb').read()).hexdigest()
84 h1 = md5(file(f1, 'rb').read()).hexdigest()
82 h2 = md5(file(f2, 'rb').read()).hexdigest()
85 h2 = md5(file(f2, 'rb').read()).hexdigest()
83 print h1 == h2 or "md5 differ: " + repr((h1, h2))
86 print h1 == h2 or "md5 differ: " + repr((h1, h2))
84 EOF
87 EOF
85
88
86 # archive name is stored in the archive, so create similar
89 # archive name is stored in the archive, so create similar
87 # archives and rename them afterwards.
90 # archives and rename them afterwards.
88 hg archive -t tgz tip.tar.gz
91 hg archive -t tgz tip.tar.gz
89 mv tip.tar.gz tip1.tar.gz
92 mv tip.tar.gz tip1.tar.gz
90 sleep 1
93 sleep 1
91 hg archive -t tgz tip.tar.gz
94 hg archive -t tgz tip.tar.gz
92 mv tip.tar.gz tip2.tar.gz
95 mv tip.tar.gz tip2.tar.gz
93 python md5comp.py tip1.tar.gz tip2.tar.gz
96 python md5comp.py tip1.tar.gz tip2.tar.gz
94
97
95 hg archive -t zip -p /illegal test.zip
98 hg archive -t zip -p /illegal test.zip
96 hg archive -t zip -p very/../bad test.zip
99 hg archive -t zip -p very/../bad test.zip
97
100
98 hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
101 hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
99 unzip -t test.zip
102 unzip -t test.zip
100
103
101 hg archive -t tar - | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
104 hg archive -t tar - | tar tf - 2>/dev/null | sed "s/$QTIP/TIP/"
102
105
103 hg archive -r 0 -t tar rev-%r.tar
106 hg archive -r 0 -t tar rev-%r.tar
104 if [ -f rev-0.tar ]; then
107 if [ -f rev-0.tar ]; then
105 echo 'rev-0.tar created'
108 echo 'rev-0.tar created'
106 fi
109 fi
107
110
108 hg archive -t bogus test.bogus
111 hg archive -t bogus test.bogus
109
112
110 echo % server errors
113 echo % server errors
111 cat errors.log
114 cat errors.log
112
115
113 echo '% empty repo'
116 echo '% empty repo'
114 hg init ../empty
117 hg init ../empty
115 cd ../empty
118 cd ../empty
116 hg archive ../test-empty
119 hg archive ../test-empty
117
120
118 exit 0
121 exit 0
General Comments 0
You need to be logged in to leave comments. Login now