##// END OF EJS Templates
http: handle push of bundles > 2 GB again (issue3017)...
Mads Kiilerich -
r15152:94b200a1 stable
parent child Browse files
Show More
@@ -1,282 +1,280 b''
1 # httpconnection.py - urllib2 handler for new http support
1 # httpconnection.py - urllib2 handler for new http support
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 # Copyright 2011 Google, Inc.
6 # Copyright 2011 Google, Inc.
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10 import logging
10 import logging
11 import socket
11 import socket
12 import urllib
12 import urllib
13 import urllib2
13 import urllib2
14 import os
14 import os
15
15
16 from mercurial import httpclient
16 from mercurial import httpclient
17 from mercurial import sslutil
17 from mercurial import sslutil
18 from mercurial import util
18 from mercurial import util
19 from mercurial.i18n import _
19 from mercurial.i18n import _
20
20
21 # moved here from url.py to avoid a cycle
21 # moved here from url.py to avoid a cycle
22 class httpsendfile(object):
22 class httpsendfile(object):
23 """This is a wrapper around the objects returned by python's "open".
23 """This is a wrapper around the objects returned by python's "open".
24
24
25 Its purpose is to send file-like objects via HTTP and, to do so, it
25 Its purpose is to send file-like objects via HTTP.
26 defines a __len__ attribute to feed the Content-Length header.
26 It do however not define a __len__ attribute because the length
27 might be more than Py_ssize_t can handle.
27 """
28 """
28
29
29 def __init__(self, ui, *args, **kwargs):
30 def __init__(self, ui, *args, **kwargs):
30 # We can't just "self._data = open(*args, **kwargs)" here because there
31 # We can't just "self._data = open(*args, **kwargs)" here because there
31 # is an "open" function defined in this module that shadows the global
32 # is an "open" function defined in this module that shadows the global
32 # one
33 # one
33 self.ui = ui
34 self.ui = ui
34 self._data = open(*args, **kwargs)
35 self._data = open(*args, **kwargs)
35 self.seek = self._data.seek
36 self.seek = self._data.seek
36 self.close = self._data.close
37 self.close = self._data.close
37 self.write = self._data.write
38 self.write = self._data.write
38 self._len = os.fstat(self._data.fileno()).st_size
39 self.length = os.fstat(self._data.fileno()).st_size
39 self._pos = 0
40 self._pos = 0
40 self._total = self._len / 1024 * 2
41 self._total = self.length / 1024 * 2
41
42
42 def read(self, *args, **kwargs):
43 def read(self, *args, **kwargs):
43 try:
44 try:
44 ret = self._data.read(*args, **kwargs)
45 ret = self._data.read(*args, **kwargs)
45 except EOFError:
46 except EOFError:
46 self.ui.progress(_('sending'), None)
47 self.ui.progress(_('sending'), None)
47 self._pos += len(ret)
48 self._pos += len(ret)
48 # We pass double the max for total because we currently have
49 # We pass double the max for total because we currently have
49 # to send the bundle twice in the case of a server that
50 # to send the bundle twice in the case of a server that
50 # requires authentication. Since we can't know until we try
51 # requires authentication. Since we can't know until we try
51 # once whether authentication will be required, just lie to
52 # once whether authentication will be required, just lie to
52 # the user and maybe the push succeeds suddenly at 50%.
53 # the user and maybe the push succeeds suddenly at 50%.
53 self.ui.progress(_('sending'), self._pos / 1024,
54 self.ui.progress(_('sending'), self._pos / 1024,
54 unit=_('kb'), total=self._total)
55 unit=_('kb'), total=self._total)
55 return ret
56 return ret
56
57
57 def __len__(self):
58 return self._len
59
60 # moved here from url.py to avoid a cycle
58 # moved here from url.py to avoid a cycle
61 def readauthforuri(ui, uri, user):
59 def readauthforuri(ui, uri, user):
62 # Read configuration
60 # Read configuration
63 config = dict()
61 config = dict()
64 for key, val in ui.configitems('auth'):
62 for key, val in ui.configitems('auth'):
65 if '.' not in key:
63 if '.' not in key:
66 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
64 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
67 continue
65 continue
68 group, setting = key.rsplit('.', 1)
66 group, setting = key.rsplit('.', 1)
69 gdict = config.setdefault(group, dict())
67 gdict = config.setdefault(group, dict())
70 if setting in ('username', 'cert', 'key'):
68 if setting in ('username', 'cert', 'key'):
71 val = util.expandpath(val)
69 val = util.expandpath(val)
72 gdict[setting] = val
70 gdict[setting] = val
73
71
74 # Find the best match
72 # Find the best match
75 scheme, hostpath = uri.split('://', 1)
73 scheme, hostpath = uri.split('://', 1)
76 bestuser = None
74 bestuser = None
77 bestlen = 0
75 bestlen = 0
78 bestauth = None
76 bestauth = None
79 for group, auth in config.iteritems():
77 for group, auth in config.iteritems():
80 if user and user != auth.get('username', user):
78 if user and user != auth.get('username', user):
81 # If a username was set in the URI, the entry username
79 # If a username was set in the URI, the entry username
82 # must either match it or be unset
80 # must either match it or be unset
83 continue
81 continue
84 prefix = auth.get('prefix')
82 prefix = auth.get('prefix')
85 if not prefix:
83 if not prefix:
86 continue
84 continue
87 p = prefix.split('://', 1)
85 p = prefix.split('://', 1)
88 if len(p) > 1:
86 if len(p) > 1:
89 schemes, prefix = [p[0]], p[1]
87 schemes, prefix = [p[0]], p[1]
90 else:
88 else:
91 schemes = (auth.get('schemes') or 'https').split()
89 schemes = (auth.get('schemes') or 'https').split()
92 if (prefix == '*' or hostpath.startswith(prefix)) and \
90 if (prefix == '*' or hostpath.startswith(prefix)) and \
93 (len(prefix) > bestlen or (len(prefix) == bestlen and \
91 (len(prefix) > bestlen or (len(prefix) == bestlen and \
94 not bestuser and 'username' in auth)) \
92 not bestuser and 'username' in auth)) \
95 and scheme in schemes:
93 and scheme in schemes:
96 bestlen = len(prefix)
94 bestlen = len(prefix)
97 bestauth = group, auth
95 bestauth = group, auth
98 bestuser = auth.get('username')
96 bestuser = auth.get('username')
99 if user and not bestuser:
97 if user and not bestuser:
100 auth['username'] = user
98 auth['username'] = user
101 return bestauth
99 return bestauth
102
100
103 # Mercurial (at least until we can remove the old codepath) requires
101 # Mercurial (at least until we can remove the old codepath) requires
104 # that the http response object be sufficiently file-like, so we
102 # that the http response object be sufficiently file-like, so we
105 # provide a close() method here.
103 # provide a close() method here.
106 class HTTPResponse(httpclient.HTTPResponse):
104 class HTTPResponse(httpclient.HTTPResponse):
107 def close(self):
105 def close(self):
108 pass
106 pass
109
107
110 class HTTPConnection(httpclient.HTTPConnection):
108 class HTTPConnection(httpclient.HTTPConnection):
111 response_class = HTTPResponse
109 response_class = HTTPResponse
112 def request(self, method, uri, body=None, headers={}):
110 def request(self, method, uri, body=None, headers={}):
113 if isinstance(body, httpsendfile):
111 if isinstance(body, httpsendfile):
114 body.seek(0)
112 body.seek(0)
115 httpclient.HTTPConnection.request(self, method, uri, body=body,
113 httpclient.HTTPConnection.request(self, method, uri, body=body,
116 headers=headers)
114 headers=headers)
117
115
118
116
119 _configuredlogging = False
117 _configuredlogging = False
120 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
118 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
121 # Subclass BOTH of these because otherwise urllib2 "helpfully"
119 # Subclass BOTH of these because otherwise urllib2 "helpfully"
122 # reinserts them since it notices we don't include any subclasses of
120 # reinserts them since it notices we don't include any subclasses of
123 # them.
121 # them.
124 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
122 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
125 def __init__(self, ui, pwmgr):
123 def __init__(self, ui, pwmgr):
126 global _configuredlogging
124 global _configuredlogging
127 urllib2.AbstractHTTPHandler.__init__(self)
125 urllib2.AbstractHTTPHandler.__init__(self)
128 self.ui = ui
126 self.ui = ui
129 self.pwmgr = pwmgr
127 self.pwmgr = pwmgr
130 self._connections = {}
128 self._connections = {}
131 loglevel = ui.config('ui', 'http2debuglevel', default=None)
129 loglevel = ui.config('ui', 'http2debuglevel', default=None)
132 if loglevel and not _configuredlogging:
130 if loglevel and not _configuredlogging:
133 _configuredlogging = True
131 _configuredlogging = True
134 logger = logging.getLogger('mercurial.httpclient')
132 logger = logging.getLogger('mercurial.httpclient')
135 logger.setLevel(getattr(logging, loglevel.upper()))
133 logger.setLevel(getattr(logging, loglevel.upper()))
136 handler = logging.StreamHandler()
134 handler = logging.StreamHandler()
137 handler.setFormatter(logging.Formatter(LOGFMT))
135 handler.setFormatter(logging.Formatter(LOGFMT))
138 logger.addHandler(handler)
136 logger.addHandler(handler)
139
137
140 def close_all(self):
138 def close_all(self):
141 """Close and remove all connection objects being kept for reuse."""
139 """Close and remove all connection objects being kept for reuse."""
142 for openconns in self._connections.values():
140 for openconns in self._connections.values():
143 for conn in openconns:
141 for conn in openconns:
144 conn.close()
142 conn.close()
145 self._connections = {}
143 self._connections = {}
146
144
147 # shamelessly borrowed from urllib2.AbstractHTTPHandler
145 # shamelessly borrowed from urllib2.AbstractHTTPHandler
148 def do_open(self, http_class, req, use_ssl):
146 def do_open(self, http_class, req, use_ssl):
149 """Return an addinfourl object for the request, using http_class.
147 """Return an addinfourl object for the request, using http_class.
150
148
151 http_class must implement the HTTPConnection API from httplib.
149 http_class must implement the HTTPConnection API from httplib.
152 The addinfourl return value is a file-like object. It also
150 The addinfourl return value is a file-like object. It also
153 has methods and attributes including:
151 has methods and attributes including:
154 - info(): return a mimetools.Message object for the headers
152 - info(): return a mimetools.Message object for the headers
155 - geturl(): return the original request URL
153 - geturl(): return the original request URL
156 - code: HTTP status code
154 - code: HTTP status code
157 """
155 """
158 # If using a proxy, the host returned by get_host() is
156 # If using a proxy, the host returned by get_host() is
159 # actually the proxy. On Python 2.6.1, the real destination
157 # actually the proxy. On Python 2.6.1, the real destination
160 # hostname is encoded in the URI in the urllib2 request
158 # hostname is encoded in the URI in the urllib2 request
161 # object. On Python 2.6.5, it's stored in the _tunnel_host
159 # object. On Python 2.6.5, it's stored in the _tunnel_host
162 # attribute which has no accessor.
160 # attribute which has no accessor.
163 tunhost = getattr(req, '_tunnel_host', None)
161 tunhost = getattr(req, '_tunnel_host', None)
164 host = req.get_host()
162 host = req.get_host()
165 if tunhost:
163 if tunhost:
166 proxyhost = host
164 proxyhost = host
167 host = tunhost
165 host = tunhost
168 elif req.has_proxy():
166 elif req.has_proxy():
169 proxyhost = req.get_host()
167 proxyhost = req.get_host()
170 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
168 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
171 else:
169 else:
172 proxyhost = None
170 proxyhost = None
173
171
174 if proxyhost:
172 if proxyhost:
175 if ':' in proxyhost:
173 if ':' in proxyhost:
176 # Note: this means we'll explode if we try and use an
174 # Note: this means we'll explode if we try and use an
177 # IPv6 http proxy. This isn't a regression, so we
175 # IPv6 http proxy. This isn't a regression, so we
178 # won't worry about it for now.
176 # won't worry about it for now.
179 proxyhost, proxyport = proxyhost.rsplit(':', 1)
177 proxyhost, proxyport = proxyhost.rsplit(':', 1)
180 else:
178 else:
181 proxyport = 3128 # squid default
179 proxyport = 3128 # squid default
182 proxy = (proxyhost, proxyport)
180 proxy = (proxyhost, proxyport)
183 else:
181 else:
184 proxy = None
182 proxy = None
185
183
186 if not host:
184 if not host:
187 raise urllib2.URLError('no host given')
185 raise urllib2.URLError('no host given')
188
186
189 connkey = use_ssl, host, proxy
187 connkey = use_ssl, host, proxy
190 allconns = self._connections.get(connkey, [])
188 allconns = self._connections.get(connkey, [])
191 conns = [c for c in allconns if not c.busy()]
189 conns = [c for c in allconns if not c.busy()]
192 if conns:
190 if conns:
193 h = conns[0]
191 h = conns[0]
194 else:
192 else:
195 if allconns:
193 if allconns:
196 self.ui.debug('all connections for %s busy, making a new '
194 self.ui.debug('all connections for %s busy, making a new '
197 'one\n' % host)
195 'one\n' % host)
198 timeout = None
196 timeout = None
199 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
197 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
200 timeout = req.timeout
198 timeout = req.timeout
201 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
199 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
202 self._connections.setdefault(connkey, []).append(h)
200 self._connections.setdefault(connkey, []).append(h)
203
201
204 headers = dict(req.headers)
202 headers = dict(req.headers)
205 headers.update(req.unredirected_hdrs)
203 headers.update(req.unredirected_hdrs)
206 headers = dict(
204 headers = dict(
207 (name.title(), val) for name, val in headers.items())
205 (name.title(), val) for name, val in headers.items())
208 try:
206 try:
209 path = req.get_selector()
207 path = req.get_selector()
210 if '://' in path:
208 if '://' in path:
211 path = path.split('://', 1)[1].split('/', 1)[1]
209 path = path.split('://', 1)[1].split('/', 1)[1]
212 if path[0] != '/':
210 if path[0] != '/':
213 path = '/' + path
211 path = '/' + path
214 h.request(req.get_method(), path, req.data, headers)
212 h.request(req.get_method(), path, req.data, headers)
215 r = h.getresponse()
213 r = h.getresponse()
216 except socket.error, err: # XXX what error?
214 except socket.error, err: # XXX what error?
217 raise urllib2.URLError(err)
215 raise urllib2.URLError(err)
218
216
219 # Pick apart the HTTPResponse object to get the addinfourl
217 # Pick apart the HTTPResponse object to get the addinfourl
220 # object initialized properly.
218 # object initialized properly.
221 r.recv = r.read
219 r.recv = r.read
222
220
223 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
221 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
224 resp.code = r.status
222 resp.code = r.status
225 resp.msg = r.reason
223 resp.msg = r.reason
226 return resp
224 return resp
227
225
228 # httplib always uses the given host/port as the socket connect
226 # httplib always uses the given host/port as the socket connect
229 # target, and then allows full URIs in the request path, which it
227 # target, and then allows full URIs in the request path, which it
230 # then observes and treats as a signal to do proxying instead.
228 # then observes and treats as a signal to do proxying instead.
231 def http_open(self, req):
229 def http_open(self, req):
232 if req.get_full_url().startswith('https'):
230 if req.get_full_url().startswith('https'):
233 return self.https_open(req)
231 return self.https_open(req)
234 return self.do_open(HTTPConnection, req, False)
232 return self.do_open(HTTPConnection, req, False)
235
233
236 def https_open(self, req):
234 def https_open(self, req):
237 # req.get_full_url() does not contain credentials and we may
235 # req.get_full_url() does not contain credentials and we may
238 # need them to match the certificates.
236 # need them to match the certificates.
239 url = req.get_full_url()
237 url = req.get_full_url()
240 user, password = self.pwmgr.find_stored_password(url)
238 user, password = self.pwmgr.find_stored_password(url)
241 res = readauthforuri(self.ui, url, user)
239 res = readauthforuri(self.ui, url, user)
242 if res:
240 if res:
243 group, auth = res
241 group, auth = res
244 self.auth = auth
242 self.auth = auth
245 self.ui.debug("using auth.%s.* for authentication\n" % group)
243 self.ui.debug("using auth.%s.* for authentication\n" % group)
246 else:
244 else:
247 self.auth = None
245 self.auth = None
248 return self.do_open(self._makesslconnection, req, True)
246 return self.do_open(self._makesslconnection, req, True)
249
247
250 def _makesslconnection(self, host, port=443, *args, **kwargs):
248 def _makesslconnection(self, host, port=443, *args, **kwargs):
251 keyfile = None
249 keyfile = None
252 certfile = None
250 certfile = None
253
251
254 if args: # key_file
252 if args: # key_file
255 keyfile = args.pop(0)
253 keyfile = args.pop(0)
256 if args: # cert_file
254 if args: # cert_file
257 certfile = args.pop(0)
255 certfile = args.pop(0)
258
256
259 # if the user has specified different key/cert files in
257 # if the user has specified different key/cert files in
260 # hgrc, we prefer these
258 # hgrc, we prefer these
261 if self.auth and 'key' in self.auth and 'cert' in self.auth:
259 if self.auth and 'key' in self.auth and 'cert' in self.auth:
262 keyfile = self.auth['key']
260 keyfile = self.auth['key']
263 certfile = self.auth['cert']
261 certfile = self.auth['cert']
264
262
265 # let host port take precedence
263 # let host port take precedence
266 if ':' in host and '[' not in host or ']:' in host:
264 if ':' in host and '[' not in host or ']:' in host:
267 host, port = host.rsplit(':', 1)
265 host, port = host.rsplit(':', 1)
268 port = int(port)
266 port = int(port)
269 if '[' in host:
267 if '[' in host:
270 host = host[1:-1]
268 host = host[1:-1]
271
269
272 if keyfile:
270 if keyfile:
273 kwargs['keyfile'] = keyfile
271 kwargs['keyfile'] = keyfile
274 if certfile:
272 if certfile:
275 kwargs['certfile'] = certfile
273 kwargs['certfile'] = certfile
276
274
277 kwargs.update(sslutil.sslkwargs(self.ui, host))
275 kwargs.update(sslutil.sslkwargs(self.ui, host))
278
276
279 con = HTTPConnection(host, port, use_ssl=True,
277 con = HTTPConnection(host, port, use_ssl=True,
280 ssl_validator=sslutil.validator(self.ui, host),
278 ssl_validator=sslutil.validator(self.ui, host),
281 **kwargs)
279 **kwargs)
282 return con
280 return con
@@ -1,242 +1,244 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import changegroup, statichttprepo, error, httpconnection, url, util, wireproto
11 import changegroup, statichttprepo, error, httpconnection, url, util, wireproto
12 import os, urllib, urllib2, zlib, httplib
12 import os, urllib, urllib2, zlib, httplib
13 import errno, socket
13 import errno, socket
14
14
15 def zgenerator(f):
15 def zgenerator(f):
16 zd = zlib.decompressobj()
16 zd = zlib.decompressobj()
17 try:
17 try:
18 for chunk in util.filechunkiter(f):
18 for chunk in util.filechunkiter(f):
19 while chunk:
19 while chunk:
20 yield zd.decompress(chunk, 2**18)
20 yield zd.decompress(chunk, 2**18)
21 chunk = zd.unconsumed_tail
21 chunk = zd.unconsumed_tail
22 except httplib.HTTPException:
22 except httplib.HTTPException:
23 raise IOError(None, _('connection ended unexpectedly'))
23 raise IOError(None, _('connection ended unexpectedly'))
24 yield zd.flush()
24 yield zd.flush()
25
25
26 class httprepository(wireproto.wirerepository):
26 class httprepository(wireproto.wirerepository):
27 def __init__(self, ui, path):
27 def __init__(self, ui, path):
28 self.path = path
28 self.path = path
29 self.caps = None
29 self.caps = None
30 self.handler = None
30 self.handler = None
31 u = util.url(path)
31 u = util.url(path)
32 if u.query or u.fragment:
32 if u.query or u.fragment:
33 raise util.Abort(_('unsupported URL component: "%s"') %
33 raise util.Abort(_('unsupported URL component: "%s"') %
34 (u.query or u.fragment))
34 (u.query or u.fragment))
35
35
36 # urllib cannot handle URLs with embedded user or passwd
36 # urllib cannot handle URLs with embedded user or passwd
37 self._url, authinfo = u.authinfo()
37 self._url, authinfo = u.authinfo()
38
38
39 self.ui = ui
39 self.ui = ui
40 self.ui.debug('using %s\n' % self._url)
40 self.ui.debug('using %s\n' % self._url)
41
41
42 self.urlopener = url.opener(ui, authinfo)
42 self.urlopener = url.opener(ui, authinfo)
43
43
44 def __del__(self):
44 def __del__(self):
45 for h in self.urlopener.handlers:
45 for h in self.urlopener.handlers:
46 h.close()
46 h.close()
47 if hasattr(h, "close_all"):
47 if hasattr(h, "close_all"):
48 h.close_all()
48 h.close_all()
49
49
50 def url(self):
50 def url(self):
51 return self.path
51 return self.path
52
52
53 # look up capabilities only when needed
53 # look up capabilities only when needed
54
54
55 def _fetchcaps(self):
55 def _fetchcaps(self):
56 self.caps = set(self._call('capabilities').split())
56 self.caps = set(self._call('capabilities').split())
57
57
58 def get_caps(self):
58 def get_caps(self):
59 if self.caps is None:
59 if self.caps is None:
60 try:
60 try:
61 self._fetchcaps()
61 self._fetchcaps()
62 except error.RepoError:
62 except error.RepoError:
63 self.caps = set()
63 self.caps = set()
64 self.ui.debug('capabilities: %s\n' %
64 self.ui.debug('capabilities: %s\n' %
65 (' '.join(self.caps or ['none'])))
65 (' '.join(self.caps or ['none'])))
66 return self.caps
66 return self.caps
67
67
68 capabilities = property(get_caps)
68 capabilities = property(get_caps)
69
69
70 def lock(self):
70 def lock(self):
71 raise util.Abort(_('operation not supported over http'))
71 raise util.Abort(_('operation not supported over http'))
72
72
73 def _callstream(self, cmd, **args):
73 def _callstream(self, cmd, **args):
74 if cmd == 'pushkey':
74 if cmd == 'pushkey':
75 args['data'] = ''
75 args['data'] = ''
76 data = args.pop('data', None)
76 data = args.pop('data', None)
77 size = 0
78 if util.safehasattr(data, 'length'):
79 size = data.length
80 elif data is not None:
81 size = len(data)
77 headers = args.pop('headers', {})
82 headers = args.pop('headers', {})
78
83
79 if data and self.ui.configbool('ui', 'usehttp2', False):
84 if size and self.ui.configbool('ui', 'usehttp2', False):
80 headers['Expect'] = '100-Continue'
85 headers['Expect'] = '100-Continue'
81 headers['X-HgHttp2'] = '1'
86 headers['X-HgHttp2'] = '1'
82
87
83 self.ui.debug("sending %s command\n" % cmd)
88 self.ui.debug("sending %s command\n" % cmd)
84 q = [('cmd', cmd)]
89 q = [('cmd', cmd)]
85 headersize = 0
90 headersize = 0
86 if len(args) > 0:
91 if len(args) > 0:
87 httpheader = self.capable('httpheader')
92 httpheader = self.capable('httpheader')
88 if httpheader:
93 if httpheader:
89 headersize = int(httpheader.split(',')[0])
94 headersize = int(httpheader.split(',')[0])
90 if headersize > 0:
95 if headersize > 0:
91 # The headers can typically carry more data than the URL.
96 # The headers can typically carry more data than the URL.
92 encargs = urllib.urlencode(sorted(args.items()))
97 encargs = urllib.urlencode(sorted(args.items()))
93 headerfmt = 'X-HgArg-%s'
98 headerfmt = 'X-HgArg-%s'
94 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
99 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
95 headernum = 0
100 headernum = 0
96 for i in xrange(0, len(encargs), contentlen):
101 for i in xrange(0, len(encargs), contentlen):
97 headernum += 1
102 headernum += 1
98 header = headerfmt % str(headernum)
103 header = headerfmt % str(headernum)
99 headers[header] = encargs[i:i + contentlen]
104 headers[header] = encargs[i:i + contentlen]
100 varyheaders = [headerfmt % str(h) for h in range(1, headernum + 1)]
105 varyheaders = [headerfmt % str(h) for h in range(1, headernum + 1)]
101 headers['Vary'] = ','.join(varyheaders)
106 headers['Vary'] = ','.join(varyheaders)
102 else:
107 else:
103 q += sorted(args.items())
108 q += sorted(args.items())
104 qs = '?%s' % urllib.urlencode(q)
109 qs = '?%s' % urllib.urlencode(q)
105 cu = "%s%s" % (self._url, qs)
110 cu = "%s%s" % (self._url, qs)
106 req = urllib2.Request(cu, data, headers)
111 req = urllib2.Request(cu, data, headers)
107 if data is not None:
112 if data is not None:
108 # len(data) is broken if data doesn't fit into Py_ssize_t
109 # add the header ourself to avoid OverflowError
110 size = data.__len__()
111 self.ui.debug("sending %s bytes\n" % size)
113 self.ui.debug("sending %s bytes\n" % size)
112 req.add_unredirected_header('Content-Length', '%d' % size)
114 req.add_unredirected_header('Content-Length', '%d' % size)
113 try:
115 try:
114 resp = self.urlopener.open(req)
116 resp = self.urlopener.open(req)
115 except urllib2.HTTPError, inst:
117 except urllib2.HTTPError, inst:
116 if inst.code == 401:
118 if inst.code == 401:
117 raise util.Abort(_('authorization failed'))
119 raise util.Abort(_('authorization failed'))
118 raise
120 raise
119 except httplib.HTTPException, inst:
121 except httplib.HTTPException, inst:
120 self.ui.debug('http error while sending %s command\n' % cmd)
122 self.ui.debug('http error while sending %s command\n' % cmd)
121 self.ui.traceback()
123 self.ui.traceback()
122 raise IOError(None, inst)
124 raise IOError(None, inst)
123 except IndexError:
125 except IndexError:
124 # this only happens with Python 2.3, later versions raise URLError
126 # this only happens with Python 2.3, later versions raise URLError
125 raise util.Abort(_('http error, possibly caused by proxy setting'))
127 raise util.Abort(_('http error, possibly caused by proxy setting'))
126 # record the url we got redirected to
128 # record the url we got redirected to
127 resp_url = resp.geturl()
129 resp_url = resp.geturl()
128 if resp_url.endswith(qs):
130 if resp_url.endswith(qs):
129 resp_url = resp_url[:-len(qs)]
131 resp_url = resp_url[:-len(qs)]
130 if self._url.rstrip('/') != resp_url.rstrip('/'):
132 if self._url.rstrip('/') != resp_url.rstrip('/'):
131 if not self.ui.quiet:
133 if not self.ui.quiet:
132 self.ui.warn(_('real URL is %s\n') % resp_url)
134 self.ui.warn(_('real URL is %s\n') % resp_url)
133 self._url = resp_url
135 self._url = resp_url
134 try:
136 try:
135 proto = resp.getheader('content-type')
137 proto = resp.getheader('content-type')
136 except AttributeError:
138 except AttributeError:
137 proto = resp.headers.get('content-type', '')
139 proto = resp.headers.get('content-type', '')
138
140
139 safeurl = util.hidepassword(self._url)
141 safeurl = util.hidepassword(self._url)
140 # accept old "text/plain" and "application/hg-changegroup" for now
142 # accept old "text/plain" and "application/hg-changegroup" for now
141 if not (proto.startswith('application/mercurial-') or
143 if not (proto.startswith('application/mercurial-') or
142 proto.startswith('text/plain') or
144 proto.startswith('text/plain') or
143 proto.startswith('application/hg-changegroup')):
145 proto.startswith('application/hg-changegroup')):
144 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
146 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
145 raise error.RepoError(
147 raise error.RepoError(
146 _("'%s' does not appear to be an hg repository:\n"
148 _("'%s' does not appear to be an hg repository:\n"
147 "---%%<--- (%s)\n%s\n---%%<---\n")
149 "---%%<--- (%s)\n%s\n---%%<---\n")
148 % (safeurl, proto or 'no content-type', resp.read()))
150 % (safeurl, proto or 'no content-type', resp.read()))
149
151
150 if proto.startswith('application/mercurial-'):
152 if proto.startswith('application/mercurial-'):
151 try:
153 try:
152 version = proto.split('-', 1)[1]
154 version = proto.split('-', 1)[1]
153 version_info = tuple([int(n) for n in version.split('.')])
155 version_info = tuple([int(n) for n in version.split('.')])
154 except ValueError:
156 except ValueError:
155 raise error.RepoError(_("'%s' sent a broken Content-Type "
157 raise error.RepoError(_("'%s' sent a broken Content-Type "
156 "header (%s)") % (safeurl, proto))
158 "header (%s)") % (safeurl, proto))
157 if version_info > (0, 1):
159 if version_info > (0, 1):
158 raise error.RepoError(_("'%s' uses newer protocol %s") %
160 raise error.RepoError(_("'%s' uses newer protocol %s") %
159 (safeurl, version))
161 (safeurl, version))
160
162
161 return resp
163 return resp
162
164
163 def _call(self, cmd, **args):
165 def _call(self, cmd, **args):
164 fp = self._callstream(cmd, **args)
166 fp = self._callstream(cmd, **args)
165 try:
167 try:
166 return fp.read()
168 return fp.read()
167 finally:
169 finally:
168 # if using keepalive, allow connection to be reused
170 # if using keepalive, allow connection to be reused
169 fp.close()
171 fp.close()
170
172
171 def _callpush(self, cmd, cg, **args):
173 def _callpush(self, cmd, cg, **args):
172 # have to stream bundle to a temp file because we do not have
174 # have to stream bundle to a temp file because we do not have
173 # http 1.1 chunked transfer.
175 # http 1.1 chunked transfer.
174
176
175 types = self.capable('unbundle')
177 types = self.capable('unbundle')
176 try:
178 try:
177 types = types.split(',')
179 types = types.split(',')
178 except AttributeError:
180 except AttributeError:
179 # servers older than d1b16a746db6 will send 'unbundle' as a
181 # servers older than d1b16a746db6 will send 'unbundle' as a
180 # boolean capability. They only support headerless/uncompressed
182 # boolean capability. They only support headerless/uncompressed
181 # bundles.
183 # bundles.
182 types = [""]
184 types = [""]
183 for x in types:
185 for x in types:
184 if x in changegroup.bundletypes:
186 if x in changegroup.bundletypes:
185 type = x
187 type = x
186 break
188 break
187
189
188 tempname = changegroup.writebundle(cg, None, type)
190 tempname = changegroup.writebundle(cg, None, type)
189 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
191 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
190 headers = {'Content-Type': 'application/mercurial-0.1'}
192 headers = {'Content-Type': 'application/mercurial-0.1'}
191
193
192 try:
194 try:
193 try:
195 try:
194 r = self._call(cmd, data=fp, headers=headers, **args)
196 r = self._call(cmd, data=fp, headers=headers, **args)
195 vals = r.split('\n', 1)
197 vals = r.split('\n', 1)
196 if len(vals) < 2:
198 if len(vals) < 2:
197 raise error.ResponseError(_("unexpected response:"), r)
199 raise error.ResponseError(_("unexpected response:"), r)
198 return vals
200 return vals
199 except socket.error, err:
201 except socket.error, err:
200 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
202 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
201 raise util.Abort(_('push failed: %s') % err.args[1])
203 raise util.Abort(_('push failed: %s') % err.args[1])
202 raise util.Abort(err.args[1])
204 raise util.Abort(err.args[1])
203 finally:
205 finally:
204 fp.close()
206 fp.close()
205 os.unlink(tempname)
207 os.unlink(tempname)
206
208
207 def _abort(self, exception):
209 def _abort(self, exception):
208 raise exception
210 raise exception
209
211
210 def _decompress(self, stream):
212 def _decompress(self, stream):
211 return util.chunkbuffer(zgenerator(stream))
213 return util.chunkbuffer(zgenerator(stream))
212
214
213 class httpsrepository(httprepository):
215 class httpsrepository(httprepository):
214 def __init__(self, ui, path):
216 def __init__(self, ui, path):
215 if not url.has_https:
217 if not url.has_https:
216 raise util.Abort(_('Python support for SSL and HTTPS '
218 raise util.Abort(_('Python support for SSL and HTTPS '
217 'is not installed'))
219 'is not installed'))
218 httprepository.__init__(self, ui, path)
220 httprepository.__init__(self, ui, path)
219
221
220 def instance(ui, path, create):
222 def instance(ui, path, create):
221 if create:
223 if create:
222 raise util.Abort(_('cannot create new http repository'))
224 raise util.Abort(_('cannot create new http repository'))
223 try:
225 try:
224 if path.startswith('https:'):
226 if path.startswith('https:'):
225 inst = httpsrepository(ui, path)
227 inst = httpsrepository(ui, path)
226 else:
228 else:
227 inst = httprepository(ui, path)
229 inst = httprepository(ui, path)
228 try:
230 try:
229 # Try to do useful work when checking compatibility.
231 # Try to do useful work when checking compatibility.
230 # Usually saves a roundtrip since we want the caps anyway.
232 # Usually saves a roundtrip since we want the caps anyway.
231 inst._fetchcaps()
233 inst._fetchcaps()
232 except error.RepoError:
234 except error.RepoError:
233 # No luck, try older compatibility check.
235 # No luck, try older compatibility check.
234 inst.between([(nullid, nullid)])
236 inst.between([(nullid, nullid)])
235 return inst
237 return inst
236 except error.RepoError, httpexception:
238 except error.RepoError, httpexception:
237 try:
239 try:
238 r = statichttprepo.instance(ui, "static-" + path, create)
240 r = statichttprepo.instance(ui, "static-" + path, create)
239 ui.note('(falling back to static-http)\n')
241 ui.note('(falling back to static-http)\n')
240 return r
242 return r
241 except error.RepoError:
243 except error.RepoError:
242 raise httpexception # use the original http RepoError instead
244 raise httpexception # use the original http RepoError instead
@@ -1,1696 +1,1700 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 or any later version.
8 # GNU General Public License version 2 or any later version.
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 error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, signal
19 import os, time, calendar, textwrap, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 _notset = object()
28 def safehasattr(thing, attr):
29 return getattr(thing, attr, _notset) is not _notset
30
27 def _fastsha1(s):
31 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
32 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
33 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
34 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
35 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
36 from hashlib import sha1 as _sha1
33 else:
37 else:
34 from sha import sha as _sha1
38 from sha import sha as _sha1
35 global _fastsha1, sha1
39 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
40 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
41 return _sha1(s)
38
42
39 import __builtin__
43 import __builtin__
40
44
41 if sys.version_info[0] < 3:
45 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
46 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
47 return sliceable[offset:]
44 else:
48 else:
45 def fakebuffer(sliceable, offset=0):
49 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
50 return memoryview(sliceable)[offset:]
47 try:
51 try:
48 buffer
52 buffer
49 except NameError:
53 except NameError:
50 __builtin__.buffer = fakebuffer
54 __builtin__.buffer = fakebuffer
51
55
52 import subprocess
56 import subprocess
53 closefds = os.name == 'posix'
57 closefds = os.name == 'posix'
54
58
55 def popen2(cmd, env=None, newlines=False):
59 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
60 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
61 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
62 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
63 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
64 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
66 universal_newlines=newlines,
63 env=env)
67 env=env)
64 return p.stdin, p.stdout
68 return p.stdin, p.stdout
65
69
66 def popen3(cmd, env=None, newlines=False):
70 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
71 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
72 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
73 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
74 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
75 universal_newlines=newlines,
72 env=env)
76 env=env)
73 return p.stdin, p.stdout, p.stderr
77 return p.stdin, p.stdout, p.stderr
74
78
75 def version():
79 def version():
76 """Return version information if available."""
80 """Return version information if available."""
77 try:
81 try:
78 import __version__
82 import __version__
79 return __version__.version
83 return __version__.version
80 except ImportError:
84 except ImportError:
81 return 'unknown'
85 return 'unknown'
82
86
83 # used by parsedate
87 # used by parsedate
84 defaultdateformats = (
88 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
89 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
90 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
91 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
92 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
93 '%Y-%m-%d',
90 '%m-%d',
94 '%m-%d',
91 '%m/%d',
95 '%m/%d',
92 '%m/%d/%y',
96 '%m/%d/%y',
93 '%m/%d/%Y',
97 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
98 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
99 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
100 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
101 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
102 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
103 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
104 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
105 '%b %d %H:%M',
102 '%b %d %I:%M%p',
106 '%b %d %I:%M%p',
103 '%b %d %Y',
107 '%b %d %Y',
104 '%b %d',
108 '%b %d',
105 '%H:%M:%S',
109 '%H:%M:%S',
106 '%I:%M:%S%p',
110 '%I:%M:%S%p',
107 '%H:%M',
111 '%H:%M',
108 '%I:%M%p',
112 '%I:%M%p',
109 )
113 )
110
114
111 extendeddateformats = defaultdateformats + (
115 extendeddateformats = defaultdateformats + (
112 "%Y",
116 "%Y",
113 "%Y-%m",
117 "%Y-%m",
114 "%b",
118 "%b",
115 "%b %Y",
119 "%b %Y",
116 )
120 )
117
121
118 def cachefunc(func):
122 def cachefunc(func):
119 '''cache the result of function calls'''
123 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
124 # XXX doesn't handle keywords args
121 cache = {}
125 cache = {}
122 if func.func_code.co_argcount == 1:
126 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
127 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
128 # we don't need to pack/unpack the list
125 def f(arg):
129 def f(arg):
126 if arg not in cache:
130 if arg not in cache:
127 cache[arg] = func(arg)
131 cache[arg] = func(arg)
128 return cache[arg]
132 return cache[arg]
129 else:
133 else:
130 def f(*args):
134 def f(*args):
131 if args not in cache:
135 if args not in cache:
132 cache[args] = func(*args)
136 cache[args] = func(*args)
133 return cache[args]
137 return cache[args]
134
138
135 return f
139 return f
136
140
137 def lrucachefunc(func):
141 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
142 '''cache most recent results of function calls'''
139 cache = {}
143 cache = {}
140 order = []
144 order = []
141 if func.func_code.co_argcount == 1:
145 if func.func_code.co_argcount == 1:
142 def f(arg):
146 def f(arg):
143 if arg not in cache:
147 if arg not in cache:
144 if len(cache) > 20:
148 if len(cache) > 20:
145 del cache[order.pop(0)]
149 del cache[order.pop(0)]
146 cache[arg] = func(arg)
150 cache[arg] = func(arg)
147 else:
151 else:
148 order.remove(arg)
152 order.remove(arg)
149 order.append(arg)
153 order.append(arg)
150 return cache[arg]
154 return cache[arg]
151 else:
155 else:
152 def f(*args):
156 def f(*args):
153 if args not in cache:
157 if args not in cache:
154 if len(cache) > 20:
158 if len(cache) > 20:
155 del cache[order.pop(0)]
159 del cache[order.pop(0)]
156 cache[args] = func(*args)
160 cache[args] = func(*args)
157 else:
161 else:
158 order.remove(args)
162 order.remove(args)
159 order.append(args)
163 order.append(args)
160 return cache[args]
164 return cache[args]
161
165
162 return f
166 return f
163
167
164 class propertycache(object):
168 class propertycache(object):
165 def __init__(self, func):
169 def __init__(self, func):
166 self.func = func
170 self.func = func
167 self.name = func.__name__
171 self.name = func.__name__
168 def __get__(self, obj, type=None):
172 def __get__(self, obj, type=None):
169 result = self.func(obj)
173 result = self.func(obj)
170 setattr(obj, self.name, result)
174 setattr(obj, self.name, result)
171 return result
175 return result
172
176
173 def pipefilter(s, cmd):
177 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
178 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
179 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
180 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
181 pout, perr = p.communicate(s)
178 return pout
182 return pout
179
183
180 def tempfilter(s, cmd):
184 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
185 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
186 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
187 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
188 the temporary files generated.'''
185 inname, outname = None, None
189 inname, outname = None, None
186 try:
190 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
191 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
192 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
193 fp.write(s)
190 fp.close()
194 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
195 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
196 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
197 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
198 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
199 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
200 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
201 code = 0
198 if code:
202 if code:
199 raise Abort(_("command '%s' failed: %s") %
203 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explainexit(code)))
204 (cmd, explainexit(code)))
201 fp = open(outname, 'rb')
205 fp = open(outname, 'rb')
202 r = fp.read()
206 r = fp.read()
203 fp.close()
207 fp.close()
204 return r
208 return r
205 finally:
209 finally:
206 try:
210 try:
207 if inname:
211 if inname:
208 os.unlink(inname)
212 os.unlink(inname)
209 except OSError:
213 except OSError:
210 pass
214 pass
211 try:
215 try:
212 if outname:
216 if outname:
213 os.unlink(outname)
217 os.unlink(outname)
214 except OSError:
218 except OSError:
215 pass
219 pass
216
220
217 filtertable = {
221 filtertable = {
218 'tempfile:': tempfilter,
222 'tempfile:': tempfilter,
219 'pipe:': pipefilter,
223 'pipe:': pipefilter,
220 }
224 }
221
225
222 def filter(s, cmd):
226 def filter(s, cmd):
223 "filter a string through a command that transforms its input to its output"
227 "filter a string through a command that transforms its input to its output"
224 for name, fn in filtertable.iteritems():
228 for name, fn in filtertable.iteritems():
225 if cmd.startswith(name):
229 if cmd.startswith(name):
226 return fn(s, cmd[len(name):].lstrip())
230 return fn(s, cmd[len(name):].lstrip())
227 return pipefilter(s, cmd)
231 return pipefilter(s, cmd)
228
232
229 def binary(s):
233 def binary(s):
230 """return true if a string is binary data"""
234 """return true if a string is binary data"""
231 return bool(s and '\0' in s)
235 return bool(s and '\0' in s)
232
236
233 def increasingchunks(source, min=1024, max=65536):
237 def increasingchunks(source, min=1024, max=65536):
234 '''return no less than min bytes per chunk while data remains,
238 '''return no less than min bytes per chunk while data remains,
235 doubling min after each chunk until it reaches max'''
239 doubling min after each chunk until it reaches max'''
236 def log2(x):
240 def log2(x):
237 if not x:
241 if not x:
238 return 0
242 return 0
239 i = 0
243 i = 0
240 while x:
244 while x:
241 x >>= 1
245 x >>= 1
242 i += 1
246 i += 1
243 return i - 1
247 return i - 1
244
248
245 buf = []
249 buf = []
246 blen = 0
250 blen = 0
247 for chunk in source:
251 for chunk in source:
248 buf.append(chunk)
252 buf.append(chunk)
249 blen += len(chunk)
253 blen += len(chunk)
250 if blen >= min:
254 if blen >= min:
251 if min < max:
255 if min < max:
252 min = min << 1
256 min = min << 1
253 nmin = 1 << log2(blen)
257 nmin = 1 << log2(blen)
254 if nmin > min:
258 if nmin > min:
255 min = nmin
259 min = nmin
256 if min > max:
260 if min > max:
257 min = max
261 min = max
258 yield ''.join(buf)
262 yield ''.join(buf)
259 blen = 0
263 blen = 0
260 buf = []
264 buf = []
261 if buf:
265 if buf:
262 yield ''.join(buf)
266 yield ''.join(buf)
263
267
264 Abort = error.Abort
268 Abort = error.Abort
265
269
266 def always(fn):
270 def always(fn):
267 return True
271 return True
268
272
269 def never(fn):
273 def never(fn):
270 return False
274 return False
271
275
272 def pathto(root, n1, n2):
276 def pathto(root, n1, n2):
273 '''return the relative path from one place to another.
277 '''return the relative path from one place to another.
274 root should use os.sep to separate directories
278 root should use os.sep to separate directories
275 n1 should use os.sep to separate directories
279 n1 should use os.sep to separate directories
276 n2 should use "/" to separate directories
280 n2 should use "/" to separate directories
277 returns an os.sep-separated path.
281 returns an os.sep-separated path.
278
282
279 If n1 is a relative path, it's assumed it's
283 If n1 is a relative path, it's assumed it's
280 relative to root.
284 relative to root.
281 n2 should always be relative to root.
285 n2 should always be relative to root.
282 '''
286 '''
283 if not n1:
287 if not n1:
284 return localpath(n2)
288 return localpath(n2)
285 if os.path.isabs(n1):
289 if os.path.isabs(n1):
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
290 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 return os.path.join(root, localpath(n2))
291 return os.path.join(root, localpath(n2))
288 n2 = '/'.join((pconvert(root), n2))
292 n2 = '/'.join((pconvert(root), n2))
289 a, b = splitpath(n1), n2.split('/')
293 a, b = splitpath(n1), n2.split('/')
290 a.reverse()
294 a.reverse()
291 b.reverse()
295 b.reverse()
292 while a and b and a[-1] == b[-1]:
296 while a and b and a[-1] == b[-1]:
293 a.pop()
297 a.pop()
294 b.pop()
298 b.pop()
295 b.reverse()
299 b.reverse()
296 return os.sep.join((['..'] * len(a)) + b) or '.'
300 return os.sep.join((['..'] * len(a)) + b) or '.'
297
301
298 _hgexecutable = None
302 _hgexecutable = None
299
303
300 def mainfrozen():
304 def mainfrozen():
301 """return True if we are a frozen executable.
305 """return True if we are a frozen executable.
302
306
303 The code supports py2exe (most common, Windows only) and tools/freeze
307 The code supports py2exe (most common, Windows only) and tools/freeze
304 (portable, not much used).
308 (portable, not much used).
305 """
309 """
306 return (hasattr(sys, "frozen") or # new py2exe
310 return (hasattr(sys, "frozen") or # new py2exe
307 hasattr(sys, "importers") or # old py2exe
311 hasattr(sys, "importers") or # old py2exe
308 imp.is_frozen("__main__")) # tools/freeze
312 imp.is_frozen("__main__")) # tools/freeze
309
313
310 def hgexecutable():
314 def hgexecutable():
311 """return location of the 'hg' executable.
315 """return location of the 'hg' executable.
312
316
313 Defaults to $HG or 'hg' in the search path.
317 Defaults to $HG or 'hg' in the search path.
314 """
318 """
315 if _hgexecutable is None:
319 if _hgexecutable is None:
316 hg = os.environ.get('HG')
320 hg = os.environ.get('HG')
317 if hg:
321 if hg:
318 _sethgexecutable(hg)
322 _sethgexecutable(hg)
319 elif mainfrozen():
323 elif mainfrozen():
320 _sethgexecutable(sys.executable)
324 _sethgexecutable(sys.executable)
321 else:
325 else:
322 exe = findexe('hg') or os.path.basename(sys.argv[0])
326 exe = findexe('hg') or os.path.basename(sys.argv[0])
323 _sethgexecutable(exe)
327 _sethgexecutable(exe)
324 return _hgexecutable
328 return _hgexecutable
325
329
326 def _sethgexecutable(path):
330 def _sethgexecutable(path):
327 """set location of the 'hg' executable"""
331 """set location of the 'hg' executable"""
328 global _hgexecutable
332 global _hgexecutable
329 _hgexecutable = path
333 _hgexecutable = path
330
334
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
335 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
332 '''enhanced shell command execution.
336 '''enhanced shell command execution.
333 run with environment maybe modified, maybe in different dir.
337 run with environment maybe modified, maybe in different dir.
334
338
335 if command fails and onerr is None, return status. if ui object,
339 if command fails and onerr is None, return status. if ui object,
336 print error message and return status, else raise onerr object as
340 print error message and return status, else raise onerr object as
337 exception.
341 exception.
338
342
339 if out is specified, it is assumed to be a file-like object that has a
343 if out is specified, it is assumed to be a file-like object that has a
340 write() method. stdout and stderr will be redirected to out.'''
344 write() method. stdout and stderr will be redirected to out.'''
341 try:
345 try:
342 sys.stdout.flush()
346 sys.stdout.flush()
343 except Exception:
347 except Exception:
344 pass
348 pass
345 def py2shell(val):
349 def py2shell(val):
346 'convert python object into string that is useful to shell'
350 'convert python object into string that is useful to shell'
347 if val is None or val is False:
351 if val is None or val is False:
348 return '0'
352 return '0'
349 if val is True:
353 if val is True:
350 return '1'
354 return '1'
351 return str(val)
355 return str(val)
352 origcmd = cmd
356 origcmd = cmd
353 cmd = quotecommand(cmd)
357 cmd = quotecommand(cmd)
354 env = dict(os.environ)
358 env = dict(os.environ)
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
359 env.update((k, py2shell(v)) for k, v in environ.iteritems())
356 env['HG'] = hgexecutable()
360 env['HG'] = hgexecutable()
357 if out is None or out == sys.__stdout__:
361 if out is None or out == sys.__stdout__:
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
362 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
359 env=env, cwd=cwd)
363 env=env, cwd=cwd)
360 else:
364 else:
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
365 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
366 env=env, cwd=cwd, stdout=subprocess.PIPE,
363 stderr=subprocess.STDOUT)
367 stderr=subprocess.STDOUT)
364 for line in proc.stdout:
368 for line in proc.stdout:
365 out.write(line)
369 out.write(line)
366 proc.wait()
370 proc.wait()
367 rc = proc.returncode
371 rc = proc.returncode
368 if sys.platform == 'OpenVMS' and rc & 1:
372 if sys.platform == 'OpenVMS' and rc & 1:
369 rc = 0
373 rc = 0
370 if rc and onerr:
374 if rc and onerr:
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
375 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 explainexit(rc)[0])
376 explainexit(rc)[0])
373 if errprefix:
377 if errprefix:
374 errmsg = '%s: %s' % (errprefix, errmsg)
378 errmsg = '%s: %s' % (errprefix, errmsg)
375 try:
379 try:
376 onerr.warn(errmsg + '\n')
380 onerr.warn(errmsg + '\n')
377 except AttributeError:
381 except AttributeError:
378 raise onerr(errmsg)
382 raise onerr(errmsg)
379 return rc
383 return rc
380
384
381 def checksignature(func):
385 def checksignature(func):
382 '''wrap a function with code to check for calling errors'''
386 '''wrap a function with code to check for calling errors'''
383 def check(*args, **kwargs):
387 def check(*args, **kwargs):
384 try:
388 try:
385 return func(*args, **kwargs)
389 return func(*args, **kwargs)
386 except TypeError:
390 except TypeError:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
391 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 raise error.SignatureError
392 raise error.SignatureError
389 raise
393 raise
390
394
391 return check
395 return check
392
396
393 def makedir(path, notindexed):
397 def makedir(path, notindexed):
394 os.mkdir(path)
398 os.mkdir(path)
395
399
396 def unlinkpath(f):
400 def unlinkpath(f):
397 """unlink and remove the directory if it is empty"""
401 """unlink and remove the directory if it is empty"""
398 os.unlink(f)
402 os.unlink(f)
399 # try removing directories that might now be empty
403 # try removing directories that might now be empty
400 try:
404 try:
401 os.removedirs(os.path.dirname(f))
405 os.removedirs(os.path.dirname(f))
402 except OSError:
406 except OSError:
403 pass
407 pass
404
408
405 def copyfile(src, dest):
409 def copyfile(src, dest):
406 "copy a file, preserving mode and atime/mtime"
410 "copy a file, preserving mode and atime/mtime"
407 if os.path.islink(src):
411 if os.path.islink(src):
408 try:
412 try:
409 os.unlink(dest)
413 os.unlink(dest)
410 except OSError:
414 except OSError:
411 pass
415 pass
412 os.symlink(os.readlink(src), dest)
416 os.symlink(os.readlink(src), dest)
413 else:
417 else:
414 try:
418 try:
415 shutil.copyfile(src, dest)
419 shutil.copyfile(src, dest)
416 shutil.copymode(src, dest)
420 shutil.copymode(src, dest)
417 except shutil.Error, inst:
421 except shutil.Error, inst:
418 raise Abort(str(inst))
422 raise Abort(str(inst))
419
423
420 def copyfiles(src, dst, hardlink=None):
424 def copyfiles(src, dst, hardlink=None):
421 """Copy a directory tree using hardlinks if possible"""
425 """Copy a directory tree using hardlinks if possible"""
422
426
423 if hardlink is None:
427 if hardlink is None:
424 hardlink = (os.stat(src).st_dev ==
428 hardlink = (os.stat(src).st_dev ==
425 os.stat(os.path.dirname(dst)).st_dev)
429 os.stat(os.path.dirname(dst)).st_dev)
426
430
427 num = 0
431 num = 0
428 if os.path.isdir(src):
432 if os.path.isdir(src):
429 os.mkdir(dst)
433 os.mkdir(dst)
430 for name, kind in osutil.listdir(src):
434 for name, kind in osutil.listdir(src):
431 srcname = os.path.join(src, name)
435 srcname = os.path.join(src, name)
432 dstname = os.path.join(dst, name)
436 dstname = os.path.join(dst, name)
433 hardlink, n = copyfiles(srcname, dstname, hardlink)
437 hardlink, n = copyfiles(srcname, dstname, hardlink)
434 num += n
438 num += n
435 else:
439 else:
436 if hardlink:
440 if hardlink:
437 try:
441 try:
438 oslink(src, dst)
442 oslink(src, dst)
439 except (IOError, OSError):
443 except (IOError, OSError):
440 hardlink = False
444 hardlink = False
441 shutil.copy(src, dst)
445 shutil.copy(src, dst)
442 else:
446 else:
443 shutil.copy(src, dst)
447 shutil.copy(src, dst)
444 num += 1
448 num += 1
445
449
446 return hardlink, num
450 return hardlink, num
447
451
448 _winreservednames = '''con prn aux nul
452 _winreservednames = '''con prn aux nul
449 com1 com2 com3 com4 com5 com6 com7 com8 com9
453 com1 com2 com3 com4 com5 com6 com7 com8 com9
450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
454 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
451 _winreservedchars = ':*?"<>|'
455 _winreservedchars = ':*?"<>|'
452 def checkwinfilename(path):
456 def checkwinfilename(path):
453 '''Check that the base-relative path is a valid filename on Windows.
457 '''Check that the base-relative path is a valid filename on Windows.
454 Returns None if the path is ok, or a UI string describing the problem.
458 Returns None if the path is ok, or a UI string describing the problem.
455
459
456 >>> checkwinfilename("just/a/normal/path")
460 >>> checkwinfilename("just/a/normal/path")
457 >>> checkwinfilename("foo/bar/con.xml")
461 >>> checkwinfilename("foo/bar/con.xml")
458 "filename contains 'con', which is reserved on Windows"
462 "filename contains 'con', which is reserved on Windows"
459 >>> checkwinfilename("foo/con.xml/bar")
463 >>> checkwinfilename("foo/con.xml/bar")
460 "filename contains 'con', which is reserved on Windows"
464 "filename contains 'con', which is reserved on Windows"
461 >>> checkwinfilename("foo/bar/xml.con")
465 >>> checkwinfilename("foo/bar/xml.con")
462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
466 >>> checkwinfilename("foo/bar/AUX/bla.txt")
463 "filename contains 'AUX', which is reserved on Windows"
467 "filename contains 'AUX', which is reserved on Windows"
464 >>> checkwinfilename("foo/bar/bla:.txt")
468 >>> checkwinfilename("foo/bar/bla:.txt")
465 "filename contains ':', which is reserved on Windows"
469 "filename contains ':', which is reserved on Windows"
466 >>> checkwinfilename("foo/bar/b\07la.txt")
470 >>> checkwinfilename("foo/bar/b\07la.txt")
467 "filename contains '\\\\x07', which is invalid on Windows"
471 "filename contains '\\\\x07', which is invalid on Windows"
468 >>> checkwinfilename("foo/bar/bla ")
472 >>> checkwinfilename("foo/bar/bla ")
469 "filename ends with ' ', which is not allowed on Windows"
473 "filename ends with ' ', which is not allowed on Windows"
470 '''
474 '''
471 for n in path.replace('\\', '/').split('/'):
475 for n in path.replace('\\', '/').split('/'):
472 if not n:
476 if not n:
473 continue
477 continue
474 for c in n:
478 for c in n:
475 if c in _winreservedchars:
479 if c in _winreservedchars:
476 return _("filename contains '%s', which is reserved "
480 return _("filename contains '%s', which is reserved "
477 "on Windows") % c
481 "on Windows") % c
478 if ord(c) <= 31:
482 if ord(c) <= 31:
479 return _("filename contains %r, which is invalid "
483 return _("filename contains %r, which is invalid "
480 "on Windows") % c
484 "on Windows") % c
481 base = n.split('.')[0]
485 base = n.split('.')[0]
482 if base and base.lower() in _winreservednames:
486 if base and base.lower() in _winreservednames:
483 return _("filename contains '%s', which is reserved "
487 return _("filename contains '%s', which is reserved "
484 "on Windows") % base
488 "on Windows") % base
485 t = n[-1]
489 t = n[-1]
486 if t in '. ':
490 if t in '. ':
487 return _("filename ends with '%s', which is not allowed "
491 return _("filename ends with '%s', which is not allowed "
488 "on Windows") % t
492 "on Windows") % t
489
493
490 def lookupreg(key, name=None, scope=None):
494 def lookupreg(key, name=None, scope=None):
491 return None
495 return None
492
496
493 def hidewindow():
497 def hidewindow():
494 """Hide current shell window.
498 """Hide current shell window.
495
499
496 Used to hide the window opened when starting asynchronous
500 Used to hide the window opened when starting asynchronous
497 child process under Windows, unneeded on other systems.
501 child process under Windows, unneeded on other systems.
498 """
502 """
499 pass
503 pass
500
504
501 if os.name == 'nt':
505 if os.name == 'nt':
502 checkosfilename = checkwinfilename
506 checkosfilename = checkwinfilename
503 from windows import *
507 from windows import *
504 else:
508 else:
505 from posix import *
509 from posix import *
506
510
507 def makelock(info, pathname):
511 def makelock(info, pathname):
508 try:
512 try:
509 return os.symlink(info, pathname)
513 return os.symlink(info, pathname)
510 except OSError, why:
514 except OSError, why:
511 if why.errno == errno.EEXIST:
515 if why.errno == errno.EEXIST:
512 raise
516 raise
513 except AttributeError: # no symlink in os
517 except AttributeError: # no symlink in os
514 pass
518 pass
515
519
516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
520 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
517 os.write(ld, info)
521 os.write(ld, info)
518 os.close(ld)
522 os.close(ld)
519
523
520 def readlock(pathname):
524 def readlock(pathname):
521 try:
525 try:
522 return os.readlink(pathname)
526 return os.readlink(pathname)
523 except OSError, why:
527 except OSError, why:
524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
528 if why.errno not in (errno.EINVAL, errno.ENOSYS):
525 raise
529 raise
526 except AttributeError: # no symlink in os
530 except AttributeError: # no symlink in os
527 pass
531 pass
528 fp = posixfile(pathname)
532 fp = posixfile(pathname)
529 r = fp.read()
533 r = fp.read()
530 fp.close()
534 fp.close()
531 return r
535 return r
532
536
533 def fstat(fp):
537 def fstat(fp):
534 '''stat file object that may not have fileno method.'''
538 '''stat file object that may not have fileno method.'''
535 try:
539 try:
536 return os.fstat(fp.fileno())
540 return os.fstat(fp.fileno())
537 except AttributeError:
541 except AttributeError:
538 return os.stat(fp.name)
542 return os.stat(fp.name)
539
543
540 # File system features
544 # File system features
541
545
542 def checkcase(path):
546 def checkcase(path):
543 """
547 """
544 Check whether the given path is on a case-sensitive filesystem
548 Check whether the given path is on a case-sensitive filesystem
545
549
546 Requires a path (like /foo/.hg) ending with a foldable final
550 Requires a path (like /foo/.hg) ending with a foldable final
547 directory component.
551 directory component.
548 """
552 """
549 s1 = os.stat(path)
553 s1 = os.stat(path)
550 d, b = os.path.split(path)
554 d, b = os.path.split(path)
551 p2 = os.path.join(d, b.upper())
555 p2 = os.path.join(d, b.upper())
552 if path == p2:
556 if path == p2:
553 p2 = os.path.join(d, b.lower())
557 p2 = os.path.join(d, b.lower())
554 try:
558 try:
555 s2 = os.stat(p2)
559 s2 = os.stat(p2)
556 if s2 == s1:
560 if s2 == s1:
557 return False
561 return False
558 return True
562 return True
559 except OSError:
563 except OSError:
560 return True
564 return True
561
565
562 _fspathcache = {}
566 _fspathcache = {}
563 def fspath(name, root):
567 def fspath(name, root):
564 '''Get name in the case stored in the filesystem
568 '''Get name in the case stored in the filesystem
565
569
566 The name is either relative to root, or it is an absolute path starting
570 The name is either relative to root, or it is an absolute path starting
567 with root. Note that this function is unnecessary, and should not be
571 with root. Note that this function is unnecessary, and should not be
568 called, for case-sensitive filesystems (simply because it's expensive).
572 called, for case-sensitive filesystems (simply because it's expensive).
569 '''
573 '''
570 # If name is absolute, make it relative
574 # If name is absolute, make it relative
571 if name.lower().startswith(root.lower()):
575 if name.lower().startswith(root.lower()):
572 l = len(root)
576 l = len(root)
573 if name[l] == os.sep or name[l] == os.altsep:
577 if name[l] == os.sep or name[l] == os.altsep:
574 l = l + 1
578 l = l + 1
575 name = name[l:]
579 name = name[l:]
576
580
577 if not os.path.lexists(os.path.join(root, name)):
581 if not os.path.lexists(os.path.join(root, name)):
578 return None
582 return None
579
583
580 seps = os.sep
584 seps = os.sep
581 if os.altsep:
585 if os.altsep:
582 seps = seps + os.altsep
586 seps = seps + os.altsep
583 # Protect backslashes. This gets silly very quickly.
587 # Protect backslashes. This gets silly very quickly.
584 seps.replace('\\','\\\\')
588 seps.replace('\\','\\\\')
585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
589 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
586 dir = os.path.normcase(os.path.normpath(root))
590 dir = os.path.normcase(os.path.normpath(root))
587 result = []
591 result = []
588 for part, sep in pattern.findall(name):
592 for part, sep in pattern.findall(name):
589 if sep:
593 if sep:
590 result.append(sep)
594 result.append(sep)
591 continue
595 continue
592
596
593 if dir not in _fspathcache:
597 if dir not in _fspathcache:
594 _fspathcache[dir] = os.listdir(dir)
598 _fspathcache[dir] = os.listdir(dir)
595 contents = _fspathcache[dir]
599 contents = _fspathcache[dir]
596
600
597 lpart = part.lower()
601 lpart = part.lower()
598 lenp = len(part)
602 lenp = len(part)
599 for n in contents:
603 for n in contents:
600 if lenp == len(n) and n.lower() == lpart:
604 if lenp == len(n) and n.lower() == lpart:
601 result.append(n)
605 result.append(n)
602 break
606 break
603 else:
607 else:
604 # Cannot happen, as the file exists!
608 # Cannot happen, as the file exists!
605 result.append(part)
609 result.append(part)
606 dir = os.path.join(dir, lpart)
610 dir = os.path.join(dir, lpart)
607
611
608 return ''.join(result)
612 return ''.join(result)
609
613
610 def checknlink(testfile):
614 def checknlink(testfile):
611 '''check whether hardlink count reporting works properly'''
615 '''check whether hardlink count reporting works properly'''
612
616
613 # testfile may be open, so we need a separate file for checking to
617 # testfile may be open, so we need a separate file for checking to
614 # work around issue2543 (or testfile may get lost on Samba shares)
618 # work around issue2543 (or testfile may get lost on Samba shares)
615 f1 = testfile + ".hgtmp1"
619 f1 = testfile + ".hgtmp1"
616 if os.path.lexists(f1):
620 if os.path.lexists(f1):
617 return False
621 return False
618 try:
622 try:
619 posixfile(f1, 'w').close()
623 posixfile(f1, 'w').close()
620 except IOError:
624 except IOError:
621 return False
625 return False
622
626
623 f2 = testfile + ".hgtmp2"
627 f2 = testfile + ".hgtmp2"
624 fd = None
628 fd = None
625 try:
629 try:
626 try:
630 try:
627 oslink(f1, f2)
631 oslink(f1, f2)
628 except OSError:
632 except OSError:
629 return False
633 return False
630
634
631 # nlinks() may behave differently for files on Windows shares if
635 # nlinks() may behave differently for files on Windows shares if
632 # the file is open.
636 # the file is open.
633 fd = posixfile(f2)
637 fd = posixfile(f2)
634 return nlinks(f2) > 1
638 return nlinks(f2) > 1
635 finally:
639 finally:
636 if fd is not None:
640 if fd is not None:
637 fd.close()
641 fd.close()
638 for f in (f1, f2):
642 for f in (f1, f2):
639 try:
643 try:
640 os.unlink(f)
644 os.unlink(f)
641 except OSError:
645 except OSError:
642 pass
646 pass
643
647
644 return False
648 return False
645
649
646 def endswithsep(path):
650 def endswithsep(path):
647 '''Check path ends with os.sep or os.altsep.'''
651 '''Check path ends with os.sep or os.altsep.'''
648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
652 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
649
653
650 def splitpath(path):
654 def splitpath(path):
651 '''Split path by os.sep.
655 '''Split path by os.sep.
652 Note that this function does not use os.altsep because this is
656 Note that this function does not use os.altsep because this is
653 an alternative of simple "xxx.split(os.sep)".
657 an alternative of simple "xxx.split(os.sep)".
654 It is recommended to use os.path.normpath() before using this
658 It is recommended to use os.path.normpath() before using this
655 function if need.'''
659 function if need.'''
656 return path.split(os.sep)
660 return path.split(os.sep)
657
661
658 def gui():
662 def gui():
659 '''Are we running in a GUI?'''
663 '''Are we running in a GUI?'''
660 if sys.platform == 'darwin':
664 if sys.platform == 'darwin':
661 if 'SSH_CONNECTION' in os.environ:
665 if 'SSH_CONNECTION' in os.environ:
662 # handle SSH access to a box where the user is logged in
666 # handle SSH access to a box where the user is logged in
663 return False
667 return False
664 elif getattr(osutil, 'isgui', None):
668 elif getattr(osutil, 'isgui', None):
665 # check if a CoreGraphics session is available
669 # check if a CoreGraphics session is available
666 return osutil.isgui()
670 return osutil.isgui()
667 else:
671 else:
668 # pure build; use a safe default
672 # pure build; use a safe default
669 return True
673 return True
670 else:
674 else:
671 return os.name == "nt" or os.environ.get("DISPLAY")
675 return os.name == "nt" or os.environ.get("DISPLAY")
672
676
673 def mktempcopy(name, emptyok=False, createmode=None):
677 def mktempcopy(name, emptyok=False, createmode=None):
674 """Create a temporary file with the same contents from name
678 """Create a temporary file with the same contents from name
675
679
676 The permission bits are copied from the original file.
680 The permission bits are copied from the original file.
677
681
678 If the temporary file is going to be truncated immediately, you
682 If the temporary file is going to be truncated immediately, you
679 can use emptyok=True as an optimization.
683 can use emptyok=True as an optimization.
680
684
681 Returns the name of the temporary file.
685 Returns the name of the temporary file.
682 """
686 """
683 d, fn = os.path.split(name)
687 d, fn = os.path.split(name)
684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
688 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
685 os.close(fd)
689 os.close(fd)
686 # Temporary files are created with mode 0600, which is usually not
690 # Temporary files are created with mode 0600, which is usually not
687 # what we want. If the original file already exists, just copy
691 # what we want. If the original file already exists, just copy
688 # its mode. Otherwise, manually obey umask.
692 # its mode. Otherwise, manually obey umask.
689 try:
693 try:
690 st_mode = os.lstat(name).st_mode & 0777
694 st_mode = os.lstat(name).st_mode & 0777
691 except OSError, inst:
695 except OSError, inst:
692 if inst.errno != errno.ENOENT:
696 if inst.errno != errno.ENOENT:
693 raise
697 raise
694 st_mode = createmode
698 st_mode = createmode
695 if st_mode is None:
699 if st_mode is None:
696 st_mode = ~umask
700 st_mode = ~umask
697 st_mode &= 0666
701 st_mode &= 0666
698 os.chmod(temp, st_mode)
702 os.chmod(temp, st_mode)
699 if emptyok:
703 if emptyok:
700 return temp
704 return temp
701 try:
705 try:
702 try:
706 try:
703 ifp = posixfile(name, "rb")
707 ifp = posixfile(name, "rb")
704 except IOError, inst:
708 except IOError, inst:
705 if inst.errno == errno.ENOENT:
709 if inst.errno == errno.ENOENT:
706 return temp
710 return temp
707 if not getattr(inst, 'filename', None):
711 if not getattr(inst, 'filename', None):
708 inst.filename = name
712 inst.filename = name
709 raise
713 raise
710 ofp = posixfile(temp, "wb")
714 ofp = posixfile(temp, "wb")
711 for chunk in filechunkiter(ifp):
715 for chunk in filechunkiter(ifp):
712 ofp.write(chunk)
716 ofp.write(chunk)
713 ifp.close()
717 ifp.close()
714 ofp.close()
718 ofp.close()
715 except:
719 except:
716 try: os.unlink(temp)
720 try: os.unlink(temp)
717 except: pass
721 except: pass
718 raise
722 raise
719 return temp
723 return temp
720
724
721 class atomictempfile(object):
725 class atomictempfile(object):
722 '''writeable file object that atomically updates a file
726 '''writeable file object that atomically updates a file
723
727
724 All writes will go to a temporary copy of the original file. Call
728 All writes will go to a temporary copy of the original file. Call
725 rename() when you are done writing, and atomictempfile will rename
729 rename() when you are done writing, and atomictempfile will rename
726 the temporary copy to the original name, making the changes visible.
730 the temporary copy to the original name, making the changes visible.
727
731
728 Unlike other file-like objects, close() discards your writes by
732 Unlike other file-like objects, close() discards your writes by
729 simply deleting the temporary file.
733 simply deleting the temporary file.
730 '''
734 '''
731 def __init__(self, name, mode='w+b', createmode=None):
735 def __init__(self, name, mode='w+b', createmode=None):
732 self.__name = name # permanent name
736 self.__name = name # permanent name
733 self._tempname = mktempcopy(name, emptyok=('w' in mode),
737 self._tempname = mktempcopy(name, emptyok=('w' in mode),
734 createmode=createmode)
738 createmode=createmode)
735 self._fp = posixfile(self._tempname, mode)
739 self._fp = posixfile(self._tempname, mode)
736
740
737 # delegated methods
741 # delegated methods
738 self.write = self._fp.write
742 self.write = self._fp.write
739 self.fileno = self._fp.fileno
743 self.fileno = self._fp.fileno
740
744
741 def rename(self):
745 def rename(self):
742 if not self._fp.closed:
746 if not self._fp.closed:
743 self._fp.close()
747 self._fp.close()
744 rename(self._tempname, localpath(self.__name))
748 rename(self._tempname, localpath(self.__name))
745
749
746 def close(self):
750 def close(self):
747 if not self._fp.closed:
751 if not self._fp.closed:
748 try:
752 try:
749 os.unlink(self._tempname)
753 os.unlink(self._tempname)
750 except OSError:
754 except OSError:
751 pass
755 pass
752 self._fp.close()
756 self._fp.close()
753
757
754 def __del__(self):
758 def __del__(self):
755 if hasattr(self, '_fp'): # constructor actually did something
759 if hasattr(self, '_fp'): # constructor actually did something
756 self.close()
760 self.close()
757
761
758 def makedirs(name, mode=None):
762 def makedirs(name, mode=None):
759 """recursive directory creation with parent mode inheritance"""
763 """recursive directory creation with parent mode inheritance"""
760 parent = os.path.abspath(os.path.dirname(name))
764 parent = os.path.abspath(os.path.dirname(name))
761 try:
765 try:
762 os.mkdir(name)
766 os.mkdir(name)
763 if mode is not None:
767 if mode is not None:
764 os.chmod(name, mode)
768 os.chmod(name, mode)
765 return
769 return
766 except OSError, err:
770 except OSError, err:
767 if err.errno == errno.EEXIST:
771 if err.errno == errno.EEXIST:
768 return
772 return
769 if not name or parent == name or err.errno != errno.ENOENT:
773 if not name or parent == name or err.errno != errno.ENOENT:
770 raise
774 raise
771 makedirs(parent, mode)
775 makedirs(parent, mode)
772 makedirs(name, mode)
776 makedirs(name, mode)
773
777
774 def readfile(path):
778 def readfile(path):
775 fp = open(path, 'rb')
779 fp = open(path, 'rb')
776 try:
780 try:
777 return fp.read()
781 return fp.read()
778 finally:
782 finally:
779 fp.close()
783 fp.close()
780
784
781 def writefile(path, text):
785 def writefile(path, text):
782 fp = open(path, 'wb')
786 fp = open(path, 'wb')
783 try:
787 try:
784 fp.write(text)
788 fp.write(text)
785 finally:
789 finally:
786 fp.close()
790 fp.close()
787
791
788 def appendfile(path, text):
792 def appendfile(path, text):
789 fp = open(path, 'ab')
793 fp = open(path, 'ab')
790 try:
794 try:
791 fp.write(text)
795 fp.write(text)
792 finally:
796 finally:
793 fp.close()
797 fp.close()
794
798
795 class chunkbuffer(object):
799 class chunkbuffer(object):
796 """Allow arbitrary sized chunks of data to be efficiently read from an
800 """Allow arbitrary sized chunks of data to be efficiently read from an
797 iterator over chunks of arbitrary size."""
801 iterator over chunks of arbitrary size."""
798
802
799 def __init__(self, in_iter):
803 def __init__(self, in_iter):
800 """in_iter is the iterator that's iterating over the input chunks.
804 """in_iter is the iterator that's iterating over the input chunks.
801 targetsize is how big a buffer to try to maintain."""
805 targetsize is how big a buffer to try to maintain."""
802 def splitbig(chunks):
806 def splitbig(chunks):
803 for chunk in chunks:
807 for chunk in chunks:
804 if len(chunk) > 2**20:
808 if len(chunk) > 2**20:
805 pos = 0
809 pos = 0
806 while pos < len(chunk):
810 while pos < len(chunk):
807 end = pos + 2 ** 18
811 end = pos + 2 ** 18
808 yield chunk[pos:end]
812 yield chunk[pos:end]
809 pos = end
813 pos = end
810 else:
814 else:
811 yield chunk
815 yield chunk
812 self.iter = splitbig(in_iter)
816 self.iter = splitbig(in_iter)
813 self._queue = []
817 self._queue = []
814
818
815 def read(self, l):
819 def read(self, l):
816 """Read L bytes of data from the iterator of chunks of data.
820 """Read L bytes of data from the iterator of chunks of data.
817 Returns less than L bytes if the iterator runs dry."""
821 Returns less than L bytes if the iterator runs dry."""
818 left = l
822 left = l
819 buf = ''
823 buf = ''
820 queue = self._queue
824 queue = self._queue
821 while left > 0:
825 while left > 0:
822 # refill the queue
826 # refill the queue
823 if not queue:
827 if not queue:
824 target = 2**18
828 target = 2**18
825 for chunk in self.iter:
829 for chunk in self.iter:
826 queue.append(chunk)
830 queue.append(chunk)
827 target -= len(chunk)
831 target -= len(chunk)
828 if target <= 0:
832 if target <= 0:
829 break
833 break
830 if not queue:
834 if not queue:
831 break
835 break
832
836
833 chunk = queue.pop(0)
837 chunk = queue.pop(0)
834 left -= len(chunk)
838 left -= len(chunk)
835 if left < 0:
839 if left < 0:
836 queue.insert(0, chunk[left:])
840 queue.insert(0, chunk[left:])
837 buf += chunk[:left]
841 buf += chunk[:left]
838 else:
842 else:
839 buf += chunk
843 buf += chunk
840
844
841 return buf
845 return buf
842
846
843 def filechunkiter(f, size=65536, limit=None):
847 def filechunkiter(f, size=65536, limit=None):
844 """Create a generator that produces the data in the file size
848 """Create a generator that produces the data in the file size
845 (default 65536) bytes at a time, up to optional limit (default is
849 (default 65536) bytes at a time, up to optional limit (default is
846 to read all data). Chunks may be less than size bytes if the
850 to read all data). Chunks may be less than size bytes if the
847 chunk is the last chunk in the file, or the file is a socket or
851 chunk is the last chunk in the file, or the file is a socket or
848 some other type of file that sometimes reads less data than is
852 some other type of file that sometimes reads less data than is
849 requested."""
853 requested."""
850 assert size >= 0
854 assert size >= 0
851 assert limit is None or limit >= 0
855 assert limit is None or limit >= 0
852 while True:
856 while True:
853 if limit is None:
857 if limit is None:
854 nbytes = size
858 nbytes = size
855 else:
859 else:
856 nbytes = min(limit, size)
860 nbytes = min(limit, size)
857 s = nbytes and f.read(nbytes)
861 s = nbytes and f.read(nbytes)
858 if not s:
862 if not s:
859 break
863 break
860 if limit:
864 if limit:
861 limit -= len(s)
865 limit -= len(s)
862 yield s
866 yield s
863
867
864 def makedate():
868 def makedate():
865 lt = time.localtime()
869 lt = time.localtime()
866 if lt[8] == 1 and time.daylight:
870 if lt[8] == 1 and time.daylight:
867 tz = time.altzone
871 tz = time.altzone
868 else:
872 else:
869 tz = time.timezone
873 tz = time.timezone
870 t = time.mktime(lt)
874 t = time.mktime(lt)
871 if t < 0:
875 if t < 0:
872 hint = _("check your clock")
876 hint = _("check your clock")
873 raise Abort(_("negative timestamp: %d") % t, hint=hint)
877 raise Abort(_("negative timestamp: %d") % t, hint=hint)
874 return t, tz
878 return t, tz
875
879
876 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
880 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
877 """represent a (unixtime, offset) tuple as a localized time.
881 """represent a (unixtime, offset) tuple as a localized time.
878 unixtime is seconds since the epoch, and offset is the time zone's
882 unixtime is seconds since the epoch, and offset is the time zone's
879 number of seconds away from UTC. if timezone is false, do not
883 number of seconds away from UTC. if timezone is false, do not
880 append time zone to string."""
884 append time zone to string."""
881 t, tz = date or makedate()
885 t, tz = date or makedate()
882 if t < 0:
886 if t < 0:
883 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
887 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
884 tz = 0
888 tz = 0
885 if "%1" in format or "%2" in format:
889 if "%1" in format or "%2" in format:
886 sign = (tz > 0) and "-" or "+"
890 sign = (tz > 0) and "-" or "+"
887 minutes = abs(tz) // 60
891 minutes = abs(tz) // 60
888 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
892 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
889 format = format.replace("%2", "%02d" % (minutes % 60))
893 format = format.replace("%2", "%02d" % (minutes % 60))
890 s = time.strftime(format, time.gmtime(float(t) - tz))
894 s = time.strftime(format, time.gmtime(float(t) - tz))
891 return s
895 return s
892
896
893 def shortdate(date=None):
897 def shortdate(date=None):
894 """turn (timestamp, tzoff) tuple into iso 8631 date."""
898 """turn (timestamp, tzoff) tuple into iso 8631 date."""
895 return datestr(date, format='%Y-%m-%d')
899 return datestr(date, format='%Y-%m-%d')
896
900
897 def strdate(string, format, defaults=[]):
901 def strdate(string, format, defaults=[]):
898 """parse a localized time string and return a (unixtime, offset) tuple.
902 """parse a localized time string and return a (unixtime, offset) tuple.
899 if the string cannot be parsed, ValueError is raised."""
903 if the string cannot be parsed, ValueError is raised."""
900 def timezone(string):
904 def timezone(string):
901 tz = string.split()[-1]
905 tz = string.split()[-1]
902 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
906 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
903 sign = (tz[0] == "+") and 1 or -1
907 sign = (tz[0] == "+") and 1 or -1
904 hours = int(tz[1:3])
908 hours = int(tz[1:3])
905 minutes = int(tz[3:5])
909 minutes = int(tz[3:5])
906 return -sign * (hours * 60 + minutes) * 60
910 return -sign * (hours * 60 + minutes) * 60
907 if tz == "GMT" or tz == "UTC":
911 if tz == "GMT" or tz == "UTC":
908 return 0
912 return 0
909 return None
913 return None
910
914
911 # NOTE: unixtime = localunixtime + offset
915 # NOTE: unixtime = localunixtime + offset
912 offset, date = timezone(string), string
916 offset, date = timezone(string), string
913 if offset is not None:
917 if offset is not None:
914 date = " ".join(string.split()[:-1])
918 date = " ".join(string.split()[:-1])
915
919
916 # add missing elements from defaults
920 # add missing elements from defaults
917 usenow = False # default to using biased defaults
921 usenow = False # default to using biased defaults
918 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
922 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
919 found = [True for p in part if ("%"+p) in format]
923 found = [True for p in part if ("%"+p) in format]
920 if not found:
924 if not found:
921 date += "@" + defaults[part][usenow]
925 date += "@" + defaults[part][usenow]
922 format += "@%" + part[0]
926 format += "@%" + part[0]
923 else:
927 else:
924 # We've found a specific time element, less specific time
928 # We've found a specific time element, less specific time
925 # elements are relative to today
929 # elements are relative to today
926 usenow = True
930 usenow = True
927
931
928 timetuple = time.strptime(date, format)
932 timetuple = time.strptime(date, format)
929 localunixtime = int(calendar.timegm(timetuple))
933 localunixtime = int(calendar.timegm(timetuple))
930 if offset is None:
934 if offset is None:
931 # local timezone
935 # local timezone
932 unixtime = int(time.mktime(timetuple))
936 unixtime = int(time.mktime(timetuple))
933 offset = unixtime - localunixtime
937 offset = unixtime - localunixtime
934 else:
938 else:
935 unixtime = localunixtime + offset
939 unixtime = localunixtime + offset
936 return unixtime, offset
940 return unixtime, offset
937
941
938 def parsedate(date, formats=None, bias={}):
942 def parsedate(date, formats=None, bias={}):
939 """parse a localized date/time and return a (unixtime, offset) tuple.
943 """parse a localized date/time and return a (unixtime, offset) tuple.
940
944
941 The date may be a "unixtime offset" string or in one of the specified
945 The date may be a "unixtime offset" string or in one of the specified
942 formats. If the date already is a (unixtime, offset) tuple, it is returned.
946 formats. If the date already is a (unixtime, offset) tuple, it is returned.
943 """
947 """
944 if not date:
948 if not date:
945 return 0, 0
949 return 0, 0
946 if isinstance(date, tuple) and len(date) == 2:
950 if isinstance(date, tuple) and len(date) == 2:
947 return date
951 return date
948 if not formats:
952 if not formats:
949 formats = defaultdateformats
953 formats = defaultdateformats
950 date = date.strip()
954 date = date.strip()
951 try:
955 try:
952 when, offset = map(int, date.split(' '))
956 when, offset = map(int, date.split(' '))
953 except ValueError:
957 except ValueError:
954 # fill out defaults
958 # fill out defaults
955 now = makedate()
959 now = makedate()
956 defaults = {}
960 defaults = {}
957 for part in ("d", "mb", "yY", "HI", "M", "S"):
961 for part in ("d", "mb", "yY", "HI", "M", "S"):
958 # this piece is for rounding the specific end of unknowns
962 # this piece is for rounding the specific end of unknowns
959 b = bias.get(part)
963 b = bias.get(part)
960 if b is None:
964 if b is None:
961 if part[0] in "HMS":
965 if part[0] in "HMS":
962 b = "00"
966 b = "00"
963 else:
967 else:
964 b = "0"
968 b = "0"
965
969
966 # this piece is for matching the generic end to today's date
970 # this piece is for matching the generic end to today's date
967 n = datestr(now, "%" + part[0])
971 n = datestr(now, "%" + part[0])
968
972
969 defaults[part] = (b, n)
973 defaults[part] = (b, n)
970
974
971 for format in formats:
975 for format in formats:
972 try:
976 try:
973 when, offset = strdate(date, format, defaults)
977 when, offset = strdate(date, format, defaults)
974 except (ValueError, OverflowError):
978 except (ValueError, OverflowError):
975 pass
979 pass
976 else:
980 else:
977 break
981 break
978 else:
982 else:
979 raise Abort(_('invalid date: %r') % date)
983 raise Abort(_('invalid date: %r') % date)
980 # validate explicit (probably user-specified) date and
984 # validate explicit (probably user-specified) date and
981 # time zone offset. values must fit in signed 32 bits for
985 # time zone offset. values must fit in signed 32 bits for
982 # current 32-bit linux runtimes. timezones go from UTC-12
986 # current 32-bit linux runtimes. timezones go from UTC-12
983 # to UTC+14
987 # to UTC+14
984 if abs(when) > 0x7fffffff:
988 if abs(when) > 0x7fffffff:
985 raise Abort(_('date exceeds 32 bits: %d') % when)
989 raise Abort(_('date exceeds 32 bits: %d') % when)
986 if when < 0:
990 if when < 0:
987 raise Abort(_('negative date value: %d') % when)
991 raise Abort(_('negative date value: %d') % when)
988 if offset < -50400 or offset > 43200:
992 if offset < -50400 or offset > 43200:
989 raise Abort(_('impossible time zone offset: %d') % offset)
993 raise Abort(_('impossible time zone offset: %d') % offset)
990 return when, offset
994 return when, offset
991
995
992 def matchdate(date):
996 def matchdate(date):
993 """Return a function that matches a given date match specifier
997 """Return a function that matches a given date match specifier
994
998
995 Formats include:
999 Formats include:
996
1000
997 '{date}' match a given date to the accuracy provided
1001 '{date}' match a given date to the accuracy provided
998
1002
999 '<{date}' on or before a given date
1003 '<{date}' on or before a given date
1000
1004
1001 '>{date}' on or after a given date
1005 '>{date}' on or after a given date
1002
1006
1003 >>> p1 = parsedate("10:29:59")
1007 >>> p1 = parsedate("10:29:59")
1004 >>> p2 = parsedate("10:30:00")
1008 >>> p2 = parsedate("10:30:00")
1005 >>> p3 = parsedate("10:30:59")
1009 >>> p3 = parsedate("10:30:59")
1006 >>> p4 = parsedate("10:31:00")
1010 >>> p4 = parsedate("10:31:00")
1007 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1011 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1008 >>> f = matchdate("10:30")
1012 >>> f = matchdate("10:30")
1009 >>> f(p1[0])
1013 >>> f(p1[0])
1010 False
1014 False
1011 >>> f(p2[0])
1015 >>> f(p2[0])
1012 True
1016 True
1013 >>> f(p3[0])
1017 >>> f(p3[0])
1014 True
1018 True
1015 >>> f(p4[0])
1019 >>> f(p4[0])
1016 False
1020 False
1017 >>> f(p5[0])
1021 >>> f(p5[0])
1018 False
1022 False
1019 """
1023 """
1020
1024
1021 def lower(date):
1025 def lower(date):
1022 d = dict(mb="1", d="1")
1026 d = dict(mb="1", d="1")
1023 return parsedate(date, extendeddateformats, d)[0]
1027 return parsedate(date, extendeddateformats, d)[0]
1024
1028
1025 def upper(date):
1029 def upper(date):
1026 d = dict(mb="12", HI="23", M="59", S="59")
1030 d = dict(mb="12", HI="23", M="59", S="59")
1027 for days in ("31", "30", "29"):
1031 for days in ("31", "30", "29"):
1028 try:
1032 try:
1029 d["d"] = days
1033 d["d"] = days
1030 return parsedate(date, extendeddateformats, d)[0]
1034 return parsedate(date, extendeddateformats, d)[0]
1031 except:
1035 except:
1032 pass
1036 pass
1033 d["d"] = "28"
1037 d["d"] = "28"
1034 return parsedate(date, extendeddateformats, d)[0]
1038 return parsedate(date, extendeddateformats, d)[0]
1035
1039
1036 date = date.strip()
1040 date = date.strip()
1037
1041
1038 if not date:
1042 if not date:
1039 raise Abort(_("dates cannot consist entirely of whitespace"))
1043 raise Abort(_("dates cannot consist entirely of whitespace"))
1040 elif date[0] == "<":
1044 elif date[0] == "<":
1041 if not date[1:]:
1045 if not date[1:]:
1042 raise Abort(_("invalid day spec, use '<DATE'"))
1046 raise Abort(_("invalid day spec, use '<DATE'"))
1043 when = upper(date[1:])
1047 when = upper(date[1:])
1044 return lambda x: x <= when
1048 return lambda x: x <= when
1045 elif date[0] == ">":
1049 elif date[0] == ">":
1046 if not date[1:]:
1050 if not date[1:]:
1047 raise Abort(_("invalid day spec, use '>DATE'"))
1051 raise Abort(_("invalid day spec, use '>DATE'"))
1048 when = lower(date[1:])
1052 when = lower(date[1:])
1049 return lambda x: x >= when
1053 return lambda x: x >= when
1050 elif date[0] == "-":
1054 elif date[0] == "-":
1051 try:
1055 try:
1052 days = int(date[1:])
1056 days = int(date[1:])
1053 except ValueError:
1057 except ValueError:
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1058 raise Abort(_("invalid day spec: %s") % date[1:])
1055 if days < 0:
1059 if days < 0:
1056 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1060 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1057 % date[1:])
1061 % date[1:])
1058 when = makedate()[0] - days * 3600 * 24
1062 when = makedate()[0] - days * 3600 * 24
1059 return lambda x: x >= when
1063 return lambda x: x >= when
1060 elif " to " in date:
1064 elif " to " in date:
1061 a, b = date.split(" to ")
1065 a, b = date.split(" to ")
1062 start, stop = lower(a), upper(b)
1066 start, stop = lower(a), upper(b)
1063 return lambda x: x >= start and x <= stop
1067 return lambda x: x >= start and x <= stop
1064 else:
1068 else:
1065 start, stop = lower(date), upper(date)
1069 start, stop = lower(date), upper(date)
1066 return lambda x: x >= start and x <= stop
1070 return lambda x: x >= start and x <= stop
1067
1071
1068 def shortuser(user):
1072 def shortuser(user):
1069 """Return a short representation of a user name or email address."""
1073 """Return a short representation of a user name or email address."""
1070 f = user.find('@')
1074 f = user.find('@')
1071 if f >= 0:
1075 if f >= 0:
1072 user = user[:f]
1076 user = user[:f]
1073 f = user.find('<')
1077 f = user.find('<')
1074 if f >= 0:
1078 if f >= 0:
1075 user = user[f + 1:]
1079 user = user[f + 1:]
1076 f = user.find(' ')
1080 f = user.find(' ')
1077 if f >= 0:
1081 if f >= 0:
1078 user = user[:f]
1082 user = user[:f]
1079 f = user.find('.')
1083 f = user.find('.')
1080 if f >= 0:
1084 if f >= 0:
1081 user = user[:f]
1085 user = user[:f]
1082 return user
1086 return user
1083
1087
1084 def email(author):
1088 def email(author):
1085 '''get email of author.'''
1089 '''get email of author.'''
1086 r = author.find('>')
1090 r = author.find('>')
1087 if r == -1:
1091 if r == -1:
1088 r = None
1092 r = None
1089 return author[author.find('<') + 1:r]
1093 return author[author.find('<') + 1:r]
1090
1094
1091 def _ellipsis(text, maxlength):
1095 def _ellipsis(text, maxlength):
1092 if len(text) <= maxlength:
1096 if len(text) <= maxlength:
1093 return text, False
1097 return text, False
1094 else:
1098 else:
1095 return "%s..." % (text[:maxlength - 3]), True
1099 return "%s..." % (text[:maxlength - 3]), True
1096
1100
1097 def ellipsis(text, maxlength=400):
1101 def ellipsis(text, maxlength=400):
1098 """Trim string to at most maxlength (default: 400) characters."""
1102 """Trim string to at most maxlength (default: 400) characters."""
1099 try:
1103 try:
1100 # use unicode not to split at intermediate multi-byte sequence
1104 # use unicode not to split at intermediate multi-byte sequence
1101 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1105 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1102 maxlength)
1106 maxlength)
1103 if not truncated:
1107 if not truncated:
1104 return text
1108 return text
1105 return utext.encode(encoding.encoding)
1109 return utext.encode(encoding.encoding)
1106 except (UnicodeDecodeError, UnicodeEncodeError):
1110 except (UnicodeDecodeError, UnicodeEncodeError):
1107 return _ellipsis(text, maxlength)[0]
1111 return _ellipsis(text, maxlength)[0]
1108
1112
1109 def bytecount(nbytes):
1113 def bytecount(nbytes):
1110 '''return byte count formatted as readable string, with units'''
1114 '''return byte count formatted as readable string, with units'''
1111
1115
1112 units = (
1116 units = (
1113 (100, 1 << 30, _('%.0f GB')),
1117 (100, 1 << 30, _('%.0f GB')),
1114 (10, 1 << 30, _('%.1f GB')),
1118 (10, 1 << 30, _('%.1f GB')),
1115 (1, 1 << 30, _('%.2f GB')),
1119 (1, 1 << 30, _('%.2f GB')),
1116 (100, 1 << 20, _('%.0f MB')),
1120 (100, 1 << 20, _('%.0f MB')),
1117 (10, 1 << 20, _('%.1f MB')),
1121 (10, 1 << 20, _('%.1f MB')),
1118 (1, 1 << 20, _('%.2f MB')),
1122 (1, 1 << 20, _('%.2f MB')),
1119 (100, 1 << 10, _('%.0f KB')),
1123 (100, 1 << 10, _('%.0f KB')),
1120 (10, 1 << 10, _('%.1f KB')),
1124 (10, 1 << 10, _('%.1f KB')),
1121 (1, 1 << 10, _('%.2f KB')),
1125 (1, 1 << 10, _('%.2f KB')),
1122 (1, 1, _('%.0f bytes')),
1126 (1, 1, _('%.0f bytes')),
1123 )
1127 )
1124
1128
1125 for multiplier, divisor, format in units:
1129 for multiplier, divisor, format in units:
1126 if nbytes >= divisor * multiplier:
1130 if nbytes >= divisor * multiplier:
1127 return format % (nbytes / float(divisor))
1131 return format % (nbytes / float(divisor))
1128 return units[-1][2] % nbytes
1132 return units[-1][2] % nbytes
1129
1133
1130 def uirepr(s):
1134 def uirepr(s):
1131 # Avoid double backslash in Windows path repr()
1135 # Avoid double backslash in Windows path repr()
1132 return repr(s).replace('\\\\', '\\')
1136 return repr(s).replace('\\\\', '\\')
1133
1137
1134 # delay import of textwrap
1138 # delay import of textwrap
1135 def MBTextWrapper(**kwargs):
1139 def MBTextWrapper(**kwargs):
1136 class tw(textwrap.TextWrapper):
1140 class tw(textwrap.TextWrapper):
1137 """
1141 """
1138 Extend TextWrapper for width-awareness.
1142 Extend TextWrapper for width-awareness.
1139
1143
1140 Neither number of 'bytes' in any encoding nor 'characters' is
1144 Neither number of 'bytes' in any encoding nor 'characters' is
1141 appropriate to calculate terminal columns for specified string.
1145 appropriate to calculate terminal columns for specified string.
1142
1146
1143 Original TextWrapper implementation uses built-in 'len()' directly,
1147 Original TextWrapper implementation uses built-in 'len()' directly,
1144 so overriding is needed to use width information of each characters.
1148 so overriding is needed to use width information of each characters.
1145
1149
1146 In addition, characters classified into 'ambiguous' width are
1150 In addition, characters classified into 'ambiguous' width are
1147 treated as wide in east asian area, but as narrow in other.
1151 treated as wide in east asian area, but as narrow in other.
1148
1152
1149 This requires use decision to determine width of such characters.
1153 This requires use decision to determine width of such characters.
1150 """
1154 """
1151 def __init__(self, **kwargs):
1155 def __init__(self, **kwargs):
1152 textwrap.TextWrapper.__init__(self, **kwargs)
1156 textwrap.TextWrapper.__init__(self, **kwargs)
1153
1157
1154 # for compatibility between 2.4 and 2.6
1158 # for compatibility between 2.4 and 2.6
1155 if getattr(self, 'drop_whitespace', None) is None:
1159 if getattr(self, 'drop_whitespace', None) is None:
1156 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1160 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1157
1161
1158 def _cutdown(self, ucstr, space_left):
1162 def _cutdown(self, ucstr, space_left):
1159 l = 0
1163 l = 0
1160 colwidth = encoding.ucolwidth
1164 colwidth = encoding.ucolwidth
1161 for i in xrange(len(ucstr)):
1165 for i in xrange(len(ucstr)):
1162 l += colwidth(ucstr[i])
1166 l += colwidth(ucstr[i])
1163 if space_left < l:
1167 if space_left < l:
1164 return (ucstr[:i], ucstr[i:])
1168 return (ucstr[:i], ucstr[i:])
1165 return ucstr, ''
1169 return ucstr, ''
1166
1170
1167 # overriding of base class
1171 # overriding of base class
1168 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1172 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1169 space_left = max(width - cur_len, 1)
1173 space_left = max(width - cur_len, 1)
1170
1174
1171 if self.break_long_words:
1175 if self.break_long_words:
1172 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1176 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1173 cur_line.append(cut)
1177 cur_line.append(cut)
1174 reversed_chunks[-1] = res
1178 reversed_chunks[-1] = res
1175 elif not cur_line:
1179 elif not cur_line:
1176 cur_line.append(reversed_chunks.pop())
1180 cur_line.append(reversed_chunks.pop())
1177
1181
1178 # this overriding code is imported from TextWrapper of python 2.6
1182 # this overriding code is imported from TextWrapper of python 2.6
1179 # to calculate columns of string by 'encoding.ucolwidth()'
1183 # to calculate columns of string by 'encoding.ucolwidth()'
1180 def _wrap_chunks(self, chunks):
1184 def _wrap_chunks(self, chunks):
1181 colwidth = encoding.ucolwidth
1185 colwidth = encoding.ucolwidth
1182
1186
1183 lines = []
1187 lines = []
1184 if self.width <= 0:
1188 if self.width <= 0:
1185 raise ValueError("invalid width %r (must be > 0)" % self.width)
1189 raise ValueError("invalid width %r (must be > 0)" % self.width)
1186
1190
1187 # Arrange in reverse order so items can be efficiently popped
1191 # Arrange in reverse order so items can be efficiently popped
1188 # from a stack of chucks.
1192 # from a stack of chucks.
1189 chunks.reverse()
1193 chunks.reverse()
1190
1194
1191 while chunks:
1195 while chunks:
1192
1196
1193 # Start the list of chunks that will make up the current line.
1197 # Start the list of chunks that will make up the current line.
1194 # cur_len is just the length of all the chunks in cur_line.
1198 # cur_len is just the length of all the chunks in cur_line.
1195 cur_line = []
1199 cur_line = []
1196 cur_len = 0
1200 cur_len = 0
1197
1201
1198 # Figure out which static string will prefix this line.
1202 # Figure out which static string will prefix this line.
1199 if lines:
1203 if lines:
1200 indent = self.subsequent_indent
1204 indent = self.subsequent_indent
1201 else:
1205 else:
1202 indent = self.initial_indent
1206 indent = self.initial_indent
1203
1207
1204 # Maximum width for this line.
1208 # Maximum width for this line.
1205 width = self.width - len(indent)
1209 width = self.width - len(indent)
1206
1210
1207 # First chunk on line is whitespace -- drop it, unless this
1211 # First chunk on line is whitespace -- drop it, unless this
1208 # is the very beginning of the text (ie. no lines started yet).
1212 # is the very beginning of the text (ie. no lines started yet).
1209 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1213 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1210 del chunks[-1]
1214 del chunks[-1]
1211
1215
1212 while chunks:
1216 while chunks:
1213 l = colwidth(chunks[-1])
1217 l = colwidth(chunks[-1])
1214
1218
1215 # Can at least squeeze this chunk onto the current line.
1219 # Can at least squeeze this chunk onto the current line.
1216 if cur_len + l <= width:
1220 if cur_len + l <= width:
1217 cur_line.append(chunks.pop())
1221 cur_line.append(chunks.pop())
1218 cur_len += l
1222 cur_len += l
1219
1223
1220 # Nope, this line is full.
1224 # Nope, this line is full.
1221 else:
1225 else:
1222 break
1226 break
1223
1227
1224 # The current line is full, and the next chunk is too big to
1228 # The current line is full, and the next chunk is too big to
1225 # fit on *any* line (not just this one).
1229 # fit on *any* line (not just this one).
1226 if chunks and colwidth(chunks[-1]) > width:
1230 if chunks and colwidth(chunks[-1]) > width:
1227 self._handle_long_word(chunks, cur_line, cur_len, width)
1231 self._handle_long_word(chunks, cur_line, cur_len, width)
1228
1232
1229 # If the last chunk on this line is all whitespace, drop it.
1233 # If the last chunk on this line is all whitespace, drop it.
1230 if (self.drop_whitespace and
1234 if (self.drop_whitespace and
1231 cur_line and cur_line[-1].strip() == ''):
1235 cur_line and cur_line[-1].strip() == ''):
1232 del cur_line[-1]
1236 del cur_line[-1]
1233
1237
1234 # Convert current line back to a string and store it in list
1238 # Convert current line back to a string and store it in list
1235 # of all lines (return value).
1239 # of all lines (return value).
1236 if cur_line:
1240 if cur_line:
1237 lines.append(indent + ''.join(cur_line))
1241 lines.append(indent + ''.join(cur_line))
1238
1242
1239 return lines
1243 return lines
1240
1244
1241 global MBTextWrapper
1245 global MBTextWrapper
1242 MBTextWrapper = tw
1246 MBTextWrapper = tw
1243 return tw(**kwargs)
1247 return tw(**kwargs)
1244
1248
1245 def wrap(line, width, initindent='', hangindent=''):
1249 def wrap(line, width, initindent='', hangindent=''):
1246 maxindent = max(len(hangindent), len(initindent))
1250 maxindent = max(len(hangindent), len(initindent))
1247 if width <= maxindent:
1251 if width <= maxindent:
1248 # adjust for weird terminal size
1252 # adjust for weird terminal size
1249 width = max(78, maxindent + 1)
1253 width = max(78, maxindent + 1)
1250 line = line.decode(encoding.encoding, encoding.encodingmode)
1254 line = line.decode(encoding.encoding, encoding.encodingmode)
1251 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1255 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1252 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1256 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1253 wrapper = MBTextWrapper(width=width,
1257 wrapper = MBTextWrapper(width=width,
1254 initial_indent=initindent,
1258 initial_indent=initindent,
1255 subsequent_indent=hangindent)
1259 subsequent_indent=hangindent)
1256 return wrapper.fill(line).encode(encoding.encoding)
1260 return wrapper.fill(line).encode(encoding.encoding)
1257
1261
1258 def iterlines(iterator):
1262 def iterlines(iterator):
1259 for chunk in iterator:
1263 for chunk in iterator:
1260 for line in chunk.splitlines():
1264 for line in chunk.splitlines():
1261 yield line
1265 yield line
1262
1266
1263 def expandpath(path):
1267 def expandpath(path):
1264 return os.path.expanduser(os.path.expandvars(path))
1268 return os.path.expanduser(os.path.expandvars(path))
1265
1269
1266 def hgcmd():
1270 def hgcmd():
1267 """Return the command used to execute current hg
1271 """Return the command used to execute current hg
1268
1272
1269 This is different from hgexecutable() because on Windows we want
1273 This is different from hgexecutable() because on Windows we want
1270 to avoid things opening new shell windows like batch files, so we
1274 to avoid things opening new shell windows like batch files, so we
1271 get either the python call or current executable.
1275 get either the python call or current executable.
1272 """
1276 """
1273 if mainfrozen():
1277 if mainfrozen():
1274 return [sys.executable]
1278 return [sys.executable]
1275 return gethgcmd()
1279 return gethgcmd()
1276
1280
1277 def rundetached(args, condfn):
1281 def rundetached(args, condfn):
1278 """Execute the argument list in a detached process.
1282 """Execute the argument list in a detached process.
1279
1283
1280 condfn is a callable which is called repeatedly and should return
1284 condfn is a callable which is called repeatedly and should return
1281 True once the child process is known to have started successfully.
1285 True once the child process is known to have started successfully.
1282 At this point, the child process PID is returned. If the child
1286 At this point, the child process PID is returned. If the child
1283 process fails to start or finishes before condfn() evaluates to
1287 process fails to start or finishes before condfn() evaluates to
1284 True, return -1.
1288 True, return -1.
1285 """
1289 """
1286 # Windows case is easier because the child process is either
1290 # Windows case is easier because the child process is either
1287 # successfully starting and validating the condition or exiting
1291 # successfully starting and validating the condition or exiting
1288 # on failure. We just poll on its PID. On Unix, if the child
1292 # on failure. We just poll on its PID. On Unix, if the child
1289 # process fails to start, it will be left in a zombie state until
1293 # process fails to start, it will be left in a zombie state until
1290 # the parent wait on it, which we cannot do since we expect a long
1294 # the parent wait on it, which we cannot do since we expect a long
1291 # running process on success. Instead we listen for SIGCHLD telling
1295 # running process on success. Instead we listen for SIGCHLD telling
1292 # us our child process terminated.
1296 # us our child process terminated.
1293 terminated = set()
1297 terminated = set()
1294 def handler(signum, frame):
1298 def handler(signum, frame):
1295 terminated.add(os.wait())
1299 terminated.add(os.wait())
1296 prevhandler = None
1300 prevhandler = None
1297 if hasattr(signal, 'SIGCHLD'):
1301 if hasattr(signal, 'SIGCHLD'):
1298 prevhandler = signal.signal(signal.SIGCHLD, handler)
1302 prevhandler = signal.signal(signal.SIGCHLD, handler)
1299 try:
1303 try:
1300 pid = spawndetached(args)
1304 pid = spawndetached(args)
1301 while not condfn():
1305 while not condfn():
1302 if ((pid in terminated or not testpid(pid))
1306 if ((pid in terminated or not testpid(pid))
1303 and not condfn()):
1307 and not condfn()):
1304 return -1
1308 return -1
1305 time.sleep(0.1)
1309 time.sleep(0.1)
1306 return pid
1310 return pid
1307 finally:
1311 finally:
1308 if prevhandler is not None:
1312 if prevhandler is not None:
1309 signal.signal(signal.SIGCHLD, prevhandler)
1313 signal.signal(signal.SIGCHLD, prevhandler)
1310
1314
1311 try:
1315 try:
1312 any, all = any, all
1316 any, all = any, all
1313 except NameError:
1317 except NameError:
1314 def any(iterable):
1318 def any(iterable):
1315 for i in iterable:
1319 for i in iterable:
1316 if i:
1320 if i:
1317 return True
1321 return True
1318 return False
1322 return False
1319
1323
1320 def all(iterable):
1324 def all(iterable):
1321 for i in iterable:
1325 for i in iterable:
1322 if not i:
1326 if not i:
1323 return False
1327 return False
1324 return True
1328 return True
1325
1329
1326 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1330 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1327 """Return the result of interpolating items in the mapping into string s.
1331 """Return the result of interpolating items in the mapping into string s.
1328
1332
1329 prefix is a single character string, or a two character string with
1333 prefix is a single character string, or a two character string with
1330 a backslash as the first character if the prefix needs to be escaped in
1334 a backslash as the first character if the prefix needs to be escaped in
1331 a regular expression.
1335 a regular expression.
1332
1336
1333 fn is an optional function that will be applied to the replacement text
1337 fn is an optional function that will be applied to the replacement text
1334 just before replacement.
1338 just before replacement.
1335
1339
1336 escape_prefix is an optional flag that allows using doubled prefix for
1340 escape_prefix is an optional flag that allows using doubled prefix for
1337 its escaping.
1341 its escaping.
1338 """
1342 """
1339 fn = fn or (lambda s: s)
1343 fn = fn or (lambda s: s)
1340 patterns = '|'.join(mapping.keys())
1344 patterns = '|'.join(mapping.keys())
1341 if escape_prefix:
1345 if escape_prefix:
1342 patterns += '|' + prefix
1346 patterns += '|' + prefix
1343 if len(prefix) > 1:
1347 if len(prefix) > 1:
1344 prefix_char = prefix[1:]
1348 prefix_char = prefix[1:]
1345 else:
1349 else:
1346 prefix_char = prefix
1350 prefix_char = prefix
1347 mapping[prefix_char] = prefix_char
1351 mapping[prefix_char] = prefix_char
1348 r = re.compile(r'%s(%s)' % (prefix, patterns))
1352 r = re.compile(r'%s(%s)' % (prefix, patterns))
1349 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1353 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1350
1354
1351 def getport(port):
1355 def getport(port):
1352 """Return the port for a given network service.
1356 """Return the port for a given network service.
1353
1357
1354 If port is an integer, it's returned as is. If it's a string, it's
1358 If port is an integer, it's returned as is. If it's a string, it's
1355 looked up using socket.getservbyname(). If there's no matching
1359 looked up using socket.getservbyname(). If there's no matching
1356 service, util.Abort is raised.
1360 service, util.Abort is raised.
1357 """
1361 """
1358 try:
1362 try:
1359 return int(port)
1363 return int(port)
1360 except ValueError:
1364 except ValueError:
1361 pass
1365 pass
1362
1366
1363 try:
1367 try:
1364 return socket.getservbyname(port)
1368 return socket.getservbyname(port)
1365 except socket.error:
1369 except socket.error:
1366 raise Abort(_("no port number associated with service '%s'") % port)
1370 raise Abort(_("no port number associated with service '%s'") % port)
1367
1371
1368 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1372 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1369 '0': False, 'no': False, 'false': False, 'off': False,
1373 '0': False, 'no': False, 'false': False, 'off': False,
1370 'never': False}
1374 'never': False}
1371
1375
1372 def parsebool(s):
1376 def parsebool(s):
1373 """Parse s into a boolean.
1377 """Parse s into a boolean.
1374
1378
1375 If s is not a valid boolean, returns None.
1379 If s is not a valid boolean, returns None.
1376 """
1380 """
1377 return _booleans.get(s.lower(), None)
1381 return _booleans.get(s.lower(), None)
1378
1382
1379 _hexdig = '0123456789ABCDEFabcdef'
1383 _hexdig = '0123456789ABCDEFabcdef'
1380 _hextochr = dict((a + b, chr(int(a + b, 16)))
1384 _hextochr = dict((a + b, chr(int(a + b, 16)))
1381 for a in _hexdig for b in _hexdig)
1385 for a in _hexdig for b in _hexdig)
1382
1386
1383 def _urlunquote(s):
1387 def _urlunquote(s):
1384 """unquote('abc%20def') -> 'abc def'."""
1388 """unquote('abc%20def') -> 'abc def'."""
1385 res = s.split('%')
1389 res = s.split('%')
1386 # fastpath
1390 # fastpath
1387 if len(res) == 1:
1391 if len(res) == 1:
1388 return s
1392 return s
1389 s = res[0]
1393 s = res[0]
1390 for item in res[1:]:
1394 for item in res[1:]:
1391 try:
1395 try:
1392 s += _hextochr[item[:2]] + item[2:]
1396 s += _hextochr[item[:2]] + item[2:]
1393 except KeyError:
1397 except KeyError:
1394 s += '%' + item
1398 s += '%' + item
1395 except UnicodeDecodeError:
1399 except UnicodeDecodeError:
1396 s += unichr(int(item[:2], 16)) + item[2:]
1400 s += unichr(int(item[:2], 16)) + item[2:]
1397 return s
1401 return s
1398
1402
1399 class url(object):
1403 class url(object):
1400 r"""Reliable URL parser.
1404 r"""Reliable URL parser.
1401
1405
1402 This parses URLs and provides attributes for the following
1406 This parses URLs and provides attributes for the following
1403 components:
1407 components:
1404
1408
1405 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1409 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1406
1410
1407 Missing components are set to None. The only exception is
1411 Missing components are set to None. The only exception is
1408 fragment, which is set to '' if present but empty.
1412 fragment, which is set to '' if present but empty.
1409
1413
1410 If parsefragment is False, fragment is included in query. If
1414 If parsefragment is False, fragment is included in query. If
1411 parsequery is False, query is included in path. If both are
1415 parsequery is False, query is included in path. If both are
1412 False, both fragment and query are included in path.
1416 False, both fragment and query are included in path.
1413
1417
1414 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1418 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1415
1419
1416 Note that for backward compatibility reasons, bundle URLs do not
1420 Note that for backward compatibility reasons, bundle URLs do not
1417 take host names. That means 'bundle://../' has a path of '../'.
1421 take host names. That means 'bundle://../' has a path of '../'.
1418
1422
1419 Examples:
1423 Examples:
1420
1424
1421 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1425 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1422 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1426 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1423 >>> url('ssh://[::1]:2200//home/joe/repo')
1427 >>> url('ssh://[::1]:2200//home/joe/repo')
1424 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1428 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1425 >>> url('file:///home/joe/repo')
1429 >>> url('file:///home/joe/repo')
1426 <url scheme: 'file', path: '/home/joe/repo'>
1430 <url scheme: 'file', path: '/home/joe/repo'>
1427 >>> url('file:///c:/temp/foo/')
1431 >>> url('file:///c:/temp/foo/')
1428 <url scheme: 'file', path: 'c:/temp/foo/'>
1432 <url scheme: 'file', path: 'c:/temp/foo/'>
1429 >>> url('bundle:foo')
1433 >>> url('bundle:foo')
1430 <url scheme: 'bundle', path: 'foo'>
1434 <url scheme: 'bundle', path: 'foo'>
1431 >>> url('bundle://../foo')
1435 >>> url('bundle://../foo')
1432 <url scheme: 'bundle', path: '../foo'>
1436 <url scheme: 'bundle', path: '../foo'>
1433 >>> url(r'c:\foo\bar')
1437 >>> url(r'c:\foo\bar')
1434 <url path: 'c:\\foo\\bar'>
1438 <url path: 'c:\\foo\\bar'>
1435 >>> url(r'\\blah\blah\blah')
1439 >>> url(r'\\blah\blah\blah')
1436 <url path: '\\\\blah\\blah\\blah'>
1440 <url path: '\\\\blah\\blah\\blah'>
1437 >>> url(r'\\blah\blah\blah#baz')
1441 >>> url(r'\\blah\blah\blah#baz')
1438 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1442 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1439
1443
1440 Authentication credentials:
1444 Authentication credentials:
1441
1445
1442 >>> url('ssh://joe:xyz@x/repo')
1446 >>> url('ssh://joe:xyz@x/repo')
1443 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1447 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1444 >>> url('ssh://joe@x/repo')
1448 >>> url('ssh://joe@x/repo')
1445 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1449 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1446
1450
1447 Query strings and fragments:
1451 Query strings and fragments:
1448
1452
1449 >>> url('http://host/a?b#c')
1453 >>> url('http://host/a?b#c')
1450 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1454 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1451 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1455 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1452 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1456 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1453 """
1457 """
1454
1458
1455 _safechars = "!~*'()+"
1459 _safechars = "!~*'()+"
1456 _safepchars = "/!~*'()+"
1460 _safepchars = "/!~*'()+"
1457 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1461 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1458
1462
1459 def __init__(self, path, parsequery=True, parsefragment=True):
1463 def __init__(self, path, parsequery=True, parsefragment=True):
1460 # We slowly chomp away at path until we have only the path left
1464 # We slowly chomp away at path until we have only the path left
1461 self.scheme = self.user = self.passwd = self.host = None
1465 self.scheme = self.user = self.passwd = self.host = None
1462 self.port = self.path = self.query = self.fragment = None
1466 self.port = self.path = self.query = self.fragment = None
1463 self._localpath = True
1467 self._localpath = True
1464 self._hostport = ''
1468 self._hostport = ''
1465 self._origpath = path
1469 self._origpath = path
1466
1470
1467 if parsefragment and '#' in path:
1471 if parsefragment and '#' in path:
1468 path, self.fragment = path.split('#', 1)
1472 path, self.fragment = path.split('#', 1)
1469 if not path:
1473 if not path:
1470 path = None
1474 path = None
1471
1475
1472 # special case for Windows drive letters and UNC paths
1476 # special case for Windows drive letters and UNC paths
1473 if hasdriveletter(path) or path.startswith(r'\\'):
1477 if hasdriveletter(path) or path.startswith(r'\\'):
1474 self.path = path
1478 self.path = path
1475 return
1479 return
1476
1480
1477 # For compatibility reasons, we can't handle bundle paths as
1481 # For compatibility reasons, we can't handle bundle paths as
1478 # normal URLS
1482 # normal URLS
1479 if path.startswith('bundle:'):
1483 if path.startswith('bundle:'):
1480 self.scheme = 'bundle'
1484 self.scheme = 'bundle'
1481 path = path[7:]
1485 path = path[7:]
1482 if path.startswith('//'):
1486 if path.startswith('//'):
1483 path = path[2:]
1487 path = path[2:]
1484 self.path = path
1488 self.path = path
1485 return
1489 return
1486
1490
1487 if self._matchscheme(path):
1491 if self._matchscheme(path):
1488 parts = path.split(':', 1)
1492 parts = path.split(':', 1)
1489 if parts[0]:
1493 if parts[0]:
1490 self.scheme, path = parts
1494 self.scheme, path = parts
1491 self._localpath = False
1495 self._localpath = False
1492
1496
1493 if not path:
1497 if not path:
1494 path = None
1498 path = None
1495 if self._localpath:
1499 if self._localpath:
1496 self.path = ''
1500 self.path = ''
1497 return
1501 return
1498 else:
1502 else:
1499 if self._localpath:
1503 if self._localpath:
1500 self.path = path
1504 self.path = path
1501 return
1505 return
1502
1506
1503 if parsequery and '?' in path:
1507 if parsequery and '?' in path:
1504 path, self.query = path.split('?', 1)
1508 path, self.query = path.split('?', 1)
1505 if not path:
1509 if not path:
1506 path = None
1510 path = None
1507 if not self.query:
1511 if not self.query:
1508 self.query = None
1512 self.query = None
1509
1513
1510 # // is required to specify a host/authority
1514 # // is required to specify a host/authority
1511 if path and path.startswith('//'):
1515 if path and path.startswith('//'):
1512 parts = path[2:].split('/', 1)
1516 parts = path[2:].split('/', 1)
1513 if len(parts) > 1:
1517 if len(parts) > 1:
1514 self.host, path = parts
1518 self.host, path = parts
1515 path = path
1519 path = path
1516 else:
1520 else:
1517 self.host = parts[0]
1521 self.host = parts[0]
1518 path = None
1522 path = None
1519 if not self.host:
1523 if not self.host:
1520 self.host = None
1524 self.host = None
1521 # path of file:///d is /d
1525 # path of file:///d is /d
1522 # path of file:///d:/ is d:/, not /d:/
1526 # path of file:///d:/ is d:/, not /d:/
1523 if path and not hasdriveletter(path):
1527 if path and not hasdriveletter(path):
1524 path = '/' + path
1528 path = '/' + path
1525
1529
1526 if self.host and '@' in self.host:
1530 if self.host and '@' in self.host:
1527 self.user, self.host = self.host.rsplit('@', 1)
1531 self.user, self.host = self.host.rsplit('@', 1)
1528 if ':' in self.user:
1532 if ':' in self.user:
1529 self.user, self.passwd = self.user.split(':', 1)
1533 self.user, self.passwd = self.user.split(':', 1)
1530 if not self.host:
1534 if not self.host:
1531 self.host = None
1535 self.host = None
1532
1536
1533 # Don't split on colons in IPv6 addresses without ports
1537 # Don't split on colons in IPv6 addresses without ports
1534 if (self.host and ':' in self.host and
1538 if (self.host and ':' in self.host and
1535 not (self.host.startswith('[') and self.host.endswith(']'))):
1539 not (self.host.startswith('[') and self.host.endswith(']'))):
1536 self._hostport = self.host
1540 self._hostport = self.host
1537 self.host, self.port = self.host.rsplit(':', 1)
1541 self.host, self.port = self.host.rsplit(':', 1)
1538 if not self.host:
1542 if not self.host:
1539 self.host = None
1543 self.host = None
1540
1544
1541 if (self.host and self.scheme == 'file' and
1545 if (self.host and self.scheme == 'file' and
1542 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1546 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1543 raise Abort(_('file:// URLs can only refer to localhost'))
1547 raise Abort(_('file:// URLs can only refer to localhost'))
1544
1548
1545 self.path = path
1549 self.path = path
1546
1550
1547 # leave the query string escaped
1551 # leave the query string escaped
1548 for a in ('user', 'passwd', 'host', 'port',
1552 for a in ('user', 'passwd', 'host', 'port',
1549 'path', 'fragment'):
1553 'path', 'fragment'):
1550 v = getattr(self, a)
1554 v = getattr(self, a)
1551 if v is not None:
1555 if v is not None:
1552 setattr(self, a, _urlunquote(v))
1556 setattr(self, a, _urlunquote(v))
1553
1557
1554 def __repr__(self):
1558 def __repr__(self):
1555 attrs = []
1559 attrs = []
1556 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1560 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1557 'query', 'fragment'):
1561 'query', 'fragment'):
1558 v = getattr(self, a)
1562 v = getattr(self, a)
1559 if v is not None:
1563 if v is not None:
1560 attrs.append('%s: %r' % (a, v))
1564 attrs.append('%s: %r' % (a, v))
1561 return '<url %s>' % ', '.join(attrs)
1565 return '<url %s>' % ', '.join(attrs)
1562
1566
1563 def __str__(self):
1567 def __str__(self):
1564 r"""Join the URL's components back into a URL string.
1568 r"""Join the URL's components back into a URL string.
1565
1569
1566 Examples:
1570 Examples:
1567
1571
1568 >>> str(url('http://user:pw@host:80/?foo#bar'))
1572 >>> str(url('http://user:pw@host:80/?foo#bar'))
1569 'http://user:pw@host:80/?foo#bar'
1573 'http://user:pw@host:80/?foo#bar'
1570 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1574 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1571 'http://user:pw@host:80/?foo=bar&baz=42'
1575 'http://user:pw@host:80/?foo=bar&baz=42'
1572 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1576 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1573 'http://user:pw@host:80/?foo=bar%3dbaz'
1577 'http://user:pw@host:80/?foo=bar%3dbaz'
1574 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1578 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1575 'ssh://user:pw@[::1]:2200//home/joe#'
1579 'ssh://user:pw@[::1]:2200//home/joe#'
1576 >>> str(url('http://localhost:80//'))
1580 >>> str(url('http://localhost:80//'))
1577 'http://localhost:80//'
1581 'http://localhost:80//'
1578 >>> str(url('http://localhost:80/'))
1582 >>> str(url('http://localhost:80/'))
1579 'http://localhost:80/'
1583 'http://localhost:80/'
1580 >>> str(url('http://localhost:80'))
1584 >>> str(url('http://localhost:80'))
1581 'http://localhost:80/'
1585 'http://localhost:80/'
1582 >>> str(url('bundle:foo'))
1586 >>> str(url('bundle:foo'))
1583 'bundle:foo'
1587 'bundle:foo'
1584 >>> str(url('bundle://../foo'))
1588 >>> str(url('bundle://../foo'))
1585 'bundle:../foo'
1589 'bundle:../foo'
1586 >>> str(url('path'))
1590 >>> str(url('path'))
1587 'path'
1591 'path'
1588 >>> str(url('file:///tmp/foo/bar'))
1592 >>> str(url('file:///tmp/foo/bar'))
1589 'file:///tmp/foo/bar'
1593 'file:///tmp/foo/bar'
1590 >>> print url(r'bundle:foo\bar')
1594 >>> print url(r'bundle:foo\bar')
1591 bundle:foo\bar
1595 bundle:foo\bar
1592 """
1596 """
1593 if self._localpath:
1597 if self._localpath:
1594 s = self.path
1598 s = self.path
1595 if self.scheme == 'bundle':
1599 if self.scheme == 'bundle':
1596 s = 'bundle:' + s
1600 s = 'bundle:' + s
1597 if self.fragment:
1601 if self.fragment:
1598 s += '#' + self.fragment
1602 s += '#' + self.fragment
1599 return s
1603 return s
1600
1604
1601 s = self.scheme + ':'
1605 s = self.scheme + ':'
1602 if self.user or self.passwd or self.host:
1606 if self.user or self.passwd or self.host:
1603 s += '//'
1607 s += '//'
1604 elif self.scheme and (not self.path or self.path.startswith('/')):
1608 elif self.scheme and (not self.path or self.path.startswith('/')):
1605 s += '//'
1609 s += '//'
1606 if self.user:
1610 if self.user:
1607 s += urllib.quote(self.user, safe=self._safechars)
1611 s += urllib.quote(self.user, safe=self._safechars)
1608 if self.passwd:
1612 if self.passwd:
1609 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1613 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1610 if self.user or self.passwd:
1614 if self.user or self.passwd:
1611 s += '@'
1615 s += '@'
1612 if self.host:
1616 if self.host:
1613 if not (self.host.startswith('[') and self.host.endswith(']')):
1617 if not (self.host.startswith('[') and self.host.endswith(']')):
1614 s += urllib.quote(self.host)
1618 s += urllib.quote(self.host)
1615 else:
1619 else:
1616 s += self.host
1620 s += self.host
1617 if self.port:
1621 if self.port:
1618 s += ':' + urllib.quote(self.port)
1622 s += ':' + urllib.quote(self.port)
1619 if self.host:
1623 if self.host:
1620 s += '/'
1624 s += '/'
1621 if self.path:
1625 if self.path:
1622 # TODO: similar to the query string, we should not unescape the
1626 # TODO: similar to the query string, we should not unescape the
1623 # path when we store it, the path might contain '%2f' = '/',
1627 # path when we store it, the path might contain '%2f' = '/',
1624 # which we should *not* escape.
1628 # which we should *not* escape.
1625 s += urllib.quote(self.path, safe=self._safepchars)
1629 s += urllib.quote(self.path, safe=self._safepchars)
1626 if self.query:
1630 if self.query:
1627 # we store the query in escaped form.
1631 # we store the query in escaped form.
1628 s += '?' + self.query
1632 s += '?' + self.query
1629 if self.fragment is not None:
1633 if self.fragment is not None:
1630 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1634 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1631 return s
1635 return s
1632
1636
1633 def authinfo(self):
1637 def authinfo(self):
1634 user, passwd = self.user, self.passwd
1638 user, passwd = self.user, self.passwd
1635 try:
1639 try:
1636 self.user, self.passwd = None, None
1640 self.user, self.passwd = None, None
1637 s = str(self)
1641 s = str(self)
1638 finally:
1642 finally:
1639 self.user, self.passwd = user, passwd
1643 self.user, self.passwd = user, passwd
1640 if not self.user:
1644 if not self.user:
1641 return (s, None)
1645 return (s, None)
1642 # authinfo[1] is passed to urllib2 password manager, and its URIs
1646 # authinfo[1] is passed to urllib2 password manager, and its URIs
1643 # must not contain credentials.
1647 # must not contain credentials.
1644 return (s, (None, (s, self.host),
1648 return (s, (None, (s, self.host),
1645 self.user, self.passwd or ''))
1649 self.user, self.passwd or ''))
1646
1650
1647 def isabs(self):
1651 def isabs(self):
1648 if self.scheme and self.scheme != 'file':
1652 if self.scheme and self.scheme != 'file':
1649 return True # remote URL
1653 return True # remote URL
1650 if hasdriveletter(self.path):
1654 if hasdriveletter(self.path):
1651 return True # absolute for our purposes - can't be joined()
1655 return True # absolute for our purposes - can't be joined()
1652 if self.path.startswith(r'\\'):
1656 if self.path.startswith(r'\\'):
1653 return True # Windows UNC path
1657 return True # Windows UNC path
1654 if self.path.startswith('/'):
1658 if self.path.startswith('/'):
1655 return True # POSIX-style
1659 return True # POSIX-style
1656 return False
1660 return False
1657
1661
1658 def localpath(self):
1662 def localpath(self):
1659 if self.scheme == 'file' or self.scheme == 'bundle':
1663 if self.scheme == 'file' or self.scheme == 'bundle':
1660 path = self.path or '/'
1664 path = self.path or '/'
1661 # For Windows, we need to promote hosts containing drive
1665 # For Windows, we need to promote hosts containing drive
1662 # letters to paths with drive letters.
1666 # letters to paths with drive letters.
1663 if hasdriveletter(self._hostport):
1667 if hasdriveletter(self._hostport):
1664 path = self._hostport + '/' + self.path
1668 path = self._hostport + '/' + self.path
1665 elif self.host is not None and self.path:
1669 elif self.host is not None and self.path:
1666 path = '/' + path
1670 path = '/' + path
1667 return path
1671 return path
1668 return self._origpath
1672 return self._origpath
1669
1673
1670 def hasscheme(path):
1674 def hasscheme(path):
1671 return bool(url(path).scheme)
1675 return bool(url(path).scheme)
1672
1676
1673 def hasdriveletter(path):
1677 def hasdriveletter(path):
1674 return path[1:2] == ':' and path[0:1].isalpha()
1678 return path[1:2] == ':' and path[0:1].isalpha()
1675
1679
1676 def urllocalpath(path):
1680 def urllocalpath(path):
1677 return url(path, parsequery=False, parsefragment=False).localpath()
1681 return url(path, parsequery=False, parsefragment=False).localpath()
1678
1682
1679 def hidepassword(u):
1683 def hidepassword(u):
1680 '''hide user credential in a url string'''
1684 '''hide user credential in a url string'''
1681 u = url(u)
1685 u = url(u)
1682 if u.passwd:
1686 if u.passwd:
1683 u.passwd = '***'
1687 u.passwd = '***'
1684 return str(u)
1688 return str(u)
1685
1689
1686 def removeauth(u):
1690 def removeauth(u):
1687 '''remove all authentication information from a url string'''
1691 '''remove all authentication information from a url string'''
1688 u = url(u)
1692 u = url(u)
1689 u.user = u.passwd = None
1693 u.user = u.passwd = None
1690 return str(u)
1694 return str(u)
1691
1695
1692 def isatty(fd):
1696 def isatty(fd):
1693 try:
1697 try:
1694 return fd.isatty()
1698 return fd.isatty()
1695 except AttributeError:
1699 except AttributeError:
1696 return False
1700 return False
General Comments 0
You need to be logged in to leave comments. Login now