##// END OF EJS Templates
sshpeer: store subprocess so it cleans up correctly...
Durham Goode -
r18759:9baf4330 default
parent child Browse files
Show More
@@ -1,239 +1,242 b''
1 # sshpeer.py - ssh repository proxy class for mercurial
1 # sshpeer.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 from i18n import _
9 from i18n import _
10 import util, error, wireproto
10 import util, error, wireproto
11
11
12 class remotelock(object):
12 class remotelock(object):
13 def __init__(self, repo):
13 def __init__(self, repo):
14 self.repo = repo
14 self.repo = repo
15 def release(self):
15 def release(self):
16 self.repo.unlock()
16 self.repo.unlock()
17 self.repo = None
17 self.repo = None
18 def __del__(self):
18 def __del__(self):
19 if self.repo:
19 if self.repo:
20 self.release()
20 self.release()
21
21
22 def _serverquote(s):
22 def _serverquote(s):
23 '''quote a string for the remote shell ... which we assume is sh'''
23 '''quote a string for the remote shell ... which we assume is sh'''
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
25 return s
25 return s
26 return "'%s'" % s.replace("'", "'\\''")
26 return "'%s'" % s.replace("'", "'\\''")
27
27
28 class sshpeer(wireproto.wirepeer):
28 class sshpeer(wireproto.wirepeer):
29 def __init__(self, ui, path, create=False):
29 def __init__(self, ui, path, create=False):
30 self._url = path
30 self._url = path
31 self.ui = ui
31 self.ui = ui
32 self.pipeo = self.pipei = self.pipee = None
32 self.pipeo = self.pipei = self.pipee = None
33
33
34 u = util.url(path, parsequery=False, parsefragment=False)
34 u = util.url(path, parsequery=False, parsefragment=False)
35 if u.scheme != 'ssh' or not u.host or u.path is None:
35 if u.scheme != 'ssh' or not u.host or u.path is None:
36 self._abort(error.RepoError(_("couldn't parse location %s") % path))
36 self._abort(error.RepoError(_("couldn't parse location %s") % path))
37
37
38 self.user = u.user
38 self.user = u.user
39 if u.passwd is not None:
39 if u.passwd is not None:
40 self._abort(error.RepoError(_("password in URL not supported")))
40 self._abort(error.RepoError(_("password in URL not supported")))
41 self.host = u.host
41 self.host = u.host
42 self.port = u.port
42 self.port = u.port
43 self.path = u.path or "."
43 self.path = u.path or "."
44
44
45 sshcmd = self.ui.config("ui", "ssh", "ssh")
45 sshcmd = self.ui.config("ui", "ssh", "ssh")
46 remotecmd = self.ui.config("ui", "remotecmd", "hg")
46 remotecmd = self.ui.config("ui", "remotecmd", "hg")
47
47
48 args = util.sshargs(sshcmd, self.host, self.user, self.port)
48 args = util.sshargs(sshcmd, self.host, self.user, self.port)
49
49
50 if create:
50 if create:
51 cmd = '%s %s %s' % (sshcmd, args,
51 cmd = '%s %s %s' % (sshcmd, args,
52 util.shellquote("%s init %s" %
52 util.shellquote("%s init %s" %
53 (_serverquote(remotecmd), _serverquote(self.path))))
53 (_serverquote(remotecmd), _serverquote(self.path))))
54 ui.note(_('running %s\n') % cmd)
54 ui.note(_('running %s\n') % cmd)
55 res = util.system(cmd)
55 res = util.system(cmd)
56 if res != 0:
56 if res != 0:
57 self._abort(error.RepoError(_("could not create remote repo")))
57 self._abort(error.RepoError(_("could not create remote repo")))
58
58
59 self.validate_repo(ui, sshcmd, args, remotecmd)
59 self.validate_repo(ui, sshcmd, args, remotecmd)
60
60
61 def url(self):
61 def url(self):
62 return self._url
62 return self._url
63
63
64 def validate_repo(self, ui, sshcmd, args, remotecmd):
64 def validate_repo(self, ui, sshcmd, args, remotecmd):
65 # cleanup up previous run
65 # cleanup up previous run
66 self.cleanup()
66 self.cleanup()
67
67
68 cmd = '%s %s %s' % (sshcmd, args,
68 cmd = '%s %s %s' % (sshcmd, args,
69 util.shellquote("%s -R %s serve --stdio" %
69 util.shellquote("%s -R %s serve --stdio" %
70 (_serverquote(remotecmd), _serverquote(self.path))))
70 (_serverquote(remotecmd), _serverquote(self.path))))
71 ui.note(_('running %s\n') % cmd)
71 ui.note(_('running %s\n') % cmd)
72 cmd = util.quotecommand(cmd)
72 cmd = util.quotecommand(cmd)
73 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
73
74 # while self.subprocess isn't used, having it allows the subprocess to
75 # to clean up correctly later
76 self.pipeo, self.pipei, self.pipee, self.subprocess = util.popen4(cmd)
74
77
75 # skip any noise generated by remote shell
78 # skip any noise generated by remote shell
76 self._callstream("hello")
79 self._callstream("hello")
77 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
80 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
78 lines = ["", "dummy"]
81 lines = ["", "dummy"]
79 max_noise = 500
82 max_noise = 500
80 while lines[-1] and max_noise:
83 while lines[-1] and max_noise:
81 l = r.readline()
84 l = r.readline()
82 self.readerr()
85 self.readerr()
83 if lines[-1] == "1\n" and l == "\n":
86 if lines[-1] == "1\n" and l == "\n":
84 break
87 break
85 if l:
88 if l:
86 ui.debug("remote: ", l)
89 ui.debug("remote: ", l)
87 lines.append(l)
90 lines.append(l)
88 max_noise -= 1
91 max_noise -= 1
89 else:
92 else:
90 self._abort(error.RepoError(_('no suitable response from '
93 self._abort(error.RepoError(_('no suitable response from '
91 'remote hg')))
94 'remote hg')))
92
95
93 self._caps = set()
96 self._caps = set()
94 for l in reversed(lines):
97 for l in reversed(lines):
95 if l.startswith("capabilities:"):
98 if l.startswith("capabilities:"):
96 self._caps.update(l[:-1].split(":")[1].split())
99 self._caps.update(l[:-1].split(":")[1].split())
97 break
100 break
98
101
99 def _capabilities(self):
102 def _capabilities(self):
100 return self._caps
103 return self._caps
101
104
102 def readerr(self):
105 def readerr(self):
103 while True:
106 while True:
104 size = util.fstat(self.pipee).st_size
107 size = util.fstat(self.pipee).st_size
105 if size == 0:
108 if size == 0:
106 break
109 break
107 s = self.pipee.read(size)
110 s = self.pipee.read(size)
108 if not s:
111 if not s:
109 break
112 break
110 for l in s.splitlines():
113 for l in s.splitlines():
111 self.ui.status(_("remote: "), l, '\n')
114 self.ui.status(_("remote: "), l, '\n')
112
115
113 def _abort(self, exception):
116 def _abort(self, exception):
114 self.cleanup()
117 self.cleanup()
115 raise exception
118 raise exception
116
119
117 def cleanup(self):
120 def cleanup(self):
118 if self.pipeo is None:
121 if self.pipeo is None:
119 return
122 return
120 self.pipeo.close()
123 self.pipeo.close()
121 self.pipei.close()
124 self.pipei.close()
122 try:
125 try:
123 # read the error descriptor until EOF
126 # read the error descriptor until EOF
124 for l in self.pipee:
127 for l in self.pipee:
125 self.ui.status(_("remote: "), l)
128 self.ui.status(_("remote: "), l)
126 except (IOError, ValueError):
129 except (IOError, ValueError):
127 pass
130 pass
128 self.pipee.close()
131 self.pipee.close()
129
132
130 __del__ = cleanup
133 __del__ = cleanup
131
134
132 def _callstream(self, cmd, **args):
135 def _callstream(self, cmd, **args):
133 self.ui.debug("sending %s command\n" % cmd)
136 self.ui.debug("sending %s command\n" % cmd)
134 self.pipeo.write("%s\n" % cmd)
137 self.pipeo.write("%s\n" % cmd)
135 _func, names = wireproto.commands[cmd]
138 _func, names = wireproto.commands[cmd]
136 keys = names.split()
139 keys = names.split()
137 wireargs = {}
140 wireargs = {}
138 for k in keys:
141 for k in keys:
139 if k == '*':
142 if k == '*':
140 wireargs['*'] = args
143 wireargs['*'] = args
141 break
144 break
142 else:
145 else:
143 wireargs[k] = args[k]
146 wireargs[k] = args[k]
144 del args[k]
147 del args[k]
145 for k, v in sorted(wireargs.iteritems()):
148 for k, v in sorted(wireargs.iteritems()):
146 self.pipeo.write("%s %d\n" % (k, len(v)))
149 self.pipeo.write("%s %d\n" % (k, len(v)))
147 if isinstance(v, dict):
150 if isinstance(v, dict):
148 for dk, dv in v.iteritems():
151 for dk, dv in v.iteritems():
149 self.pipeo.write("%s %d\n" % (dk, len(dv)))
152 self.pipeo.write("%s %d\n" % (dk, len(dv)))
150 self.pipeo.write(dv)
153 self.pipeo.write(dv)
151 else:
154 else:
152 self.pipeo.write(v)
155 self.pipeo.write(v)
153 self.pipeo.flush()
156 self.pipeo.flush()
154
157
155 return self.pipei
158 return self.pipei
156
159
157 def _call(self, cmd, **args):
160 def _call(self, cmd, **args):
158 self._callstream(cmd, **args)
161 self._callstream(cmd, **args)
159 return self._recv()
162 return self._recv()
160
163
161 def _callpush(self, cmd, fp, **args):
164 def _callpush(self, cmd, fp, **args):
162 r = self._call(cmd, **args)
165 r = self._call(cmd, **args)
163 if r:
166 if r:
164 return '', r
167 return '', r
165 while True:
168 while True:
166 d = fp.read(4096)
169 d = fp.read(4096)
167 if not d:
170 if not d:
168 break
171 break
169 self._send(d)
172 self._send(d)
170 self._send("", flush=True)
173 self._send("", flush=True)
171 r = self._recv()
174 r = self._recv()
172 if r:
175 if r:
173 return '', r
176 return '', r
174 return self._recv(), ''
177 return self._recv(), ''
175
178
176 def _decompress(self, stream):
179 def _decompress(self, stream):
177 return stream
180 return stream
178
181
179 def _recv(self):
182 def _recv(self):
180 l = self.pipei.readline()
183 l = self.pipei.readline()
181 if l == '\n':
184 if l == '\n':
182 err = []
185 err = []
183 while True:
186 while True:
184 line = self.pipee.readline()
187 line = self.pipee.readline()
185 if line == '-\n':
188 if line == '-\n':
186 break
189 break
187 err.extend([line])
190 err.extend([line])
188 if len(err) > 0:
191 if len(err) > 0:
189 # strip the trailing newline added to the last line server-side
192 # strip the trailing newline added to the last line server-side
190 err[-1] = err[-1][:-1]
193 err[-1] = err[-1][:-1]
191 self._abort(error.OutOfBandError(*err))
194 self._abort(error.OutOfBandError(*err))
192 self.readerr()
195 self.readerr()
193 try:
196 try:
194 l = int(l)
197 l = int(l)
195 except ValueError:
198 except ValueError:
196 self._abort(error.ResponseError(_("unexpected response:"), l))
199 self._abort(error.ResponseError(_("unexpected response:"), l))
197 return self.pipei.read(l)
200 return self.pipei.read(l)
198
201
199 def _send(self, data, flush=False):
202 def _send(self, data, flush=False):
200 self.pipeo.write("%d\n" % len(data))
203 self.pipeo.write("%d\n" % len(data))
201 if data:
204 if data:
202 self.pipeo.write(data)
205 self.pipeo.write(data)
203 if flush:
206 if flush:
204 self.pipeo.flush()
207 self.pipeo.flush()
205 self.readerr()
208 self.readerr()
206
209
207 def lock(self):
210 def lock(self):
208 self._call("lock")
211 self._call("lock")
209 return remotelock(self)
212 return remotelock(self)
210
213
211 def unlock(self):
214 def unlock(self):
212 self._call("unlock")
215 self._call("unlock")
213
216
214 def addchangegroup(self, cg, source, url, lock=None):
217 def addchangegroup(self, cg, source, url, lock=None):
215 '''Send a changegroup to the remote server. Return an integer
218 '''Send a changegroup to the remote server. Return an integer
216 similar to unbundle(). DEPRECATED, since it requires locking the
219 similar to unbundle(). DEPRECATED, since it requires locking the
217 remote.'''
220 remote.'''
218 d = self._call("addchangegroup")
221 d = self._call("addchangegroup")
219 if d:
222 if d:
220 self._abort(error.RepoError(_("push refused: %s") % d))
223 self._abort(error.RepoError(_("push refused: %s") % d))
221 while True:
224 while True:
222 d = cg.read(4096)
225 d = cg.read(4096)
223 if not d:
226 if not d:
224 break
227 break
225 self.pipeo.write(d)
228 self.pipeo.write(d)
226 self.readerr()
229 self.readerr()
227
230
228 self.pipeo.flush()
231 self.pipeo.flush()
229
232
230 self.readerr()
233 self.readerr()
231 r = self._recv()
234 r = self._recv()
232 if not r:
235 if not r:
233 return 1
236 return 1
234 try:
237 try:
235 return int(r)
238 return int(r)
236 except ValueError:
239 except ValueError:
237 self._abort(error.ResponseError(_("unexpected response:"), r))
240 self._abort(error.ResponseError(_("unexpected response:"), r))
238
241
239 instance = sshpeer
242 instance = sshpeer
@@ -1,1917 +1,1921 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific 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 specific implementations.
10 """Mercurial utility functions and platform specific 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, collections
17 import error, osutil, encoding, collections
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, datetime, calendar, textwrap, signal
19 import os, time, datetime, calendar, textwrap, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 if os.name == 'nt':
22 if os.name == 'nt':
23 import windows as platform
23 import windows as platform
24 else:
24 else:
25 import posix as platform
25 import posix as platform
26
26
27 cachestat = platform.cachestat
27 cachestat = platform.cachestat
28 checkexec = platform.checkexec
28 checkexec = platform.checkexec
29 checklink = platform.checklink
29 checklink = platform.checklink
30 copymode = platform.copymode
30 copymode = platform.copymode
31 executablepath = platform.executablepath
31 executablepath = platform.executablepath
32 expandglobs = platform.expandglobs
32 expandglobs = platform.expandglobs
33 explainexit = platform.explainexit
33 explainexit = platform.explainexit
34 findexe = platform.findexe
34 findexe = platform.findexe
35 gethgcmd = platform.gethgcmd
35 gethgcmd = platform.gethgcmd
36 getuser = platform.getuser
36 getuser = platform.getuser
37 groupmembers = platform.groupmembers
37 groupmembers = platform.groupmembers
38 groupname = platform.groupname
38 groupname = platform.groupname
39 hidewindow = platform.hidewindow
39 hidewindow = platform.hidewindow
40 isexec = platform.isexec
40 isexec = platform.isexec
41 isowner = platform.isowner
41 isowner = platform.isowner
42 localpath = platform.localpath
42 localpath = platform.localpath
43 lookupreg = platform.lookupreg
43 lookupreg = platform.lookupreg
44 makedir = platform.makedir
44 makedir = platform.makedir
45 nlinks = platform.nlinks
45 nlinks = platform.nlinks
46 normpath = platform.normpath
46 normpath = platform.normpath
47 normcase = platform.normcase
47 normcase = platform.normcase
48 openhardlinks = platform.openhardlinks
48 openhardlinks = platform.openhardlinks
49 oslink = platform.oslink
49 oslink = platform.oslink
50 parsepatchoutput = platform.parsepatchoutput
50 parsepatchoutput = platform.parsepatchoutput
51 pconvert = platform.pconvert
51 pconvert = platform.pconvert
52 popen = platform.popen
52 popen = platform.popen
53 posixfile = platform.posixfile
53 posixfile = platform.posixfile
54 quotecommand = platform.quotecommand
54 quotecommand = platform.quotecommand
55 realpath = platform.realpath
55 realpath = platform.realpath
56 rename = platform.rename
56 rename = platform.rename
57 samedevice = platform.samedevice
57 samedevice = platform.samedevice
58 samefile = platform.samefile
58 samefile = platform.samefile
59 samestat = platform.samestat
59 samestat = platform.samestat
60 setbinary = platform.setbinary
60 setbinary = platform.setbinary
61 setflags = platform.setflags
61 setflags = platform.setflags
62 setsignalhandler = platform.setsignalhandler
62 setsignalhandler = platform.setsignalhandler
63 shellquote = platform.shellquote
63 shellquote = platform.shellquote
64 spawndetached = platform.spawndetached
64 spawndetached = platform.spawndetached
65 split = platform.split
65 split = platform.split
66 sshargs = platform.sshargs
66 sshargs = platform.sshargs
67 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
67 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
68 termwidth = platform.termwidth
68 termwidth = platform.termwidth
69 testpid = platform.testpid
69 testpid = platform.testpid
70 umask = platform.umask
70 umask = platform.umask
71 unlink = platform.unlink
71 unlink = platform.unlink
72 unlinkpath = platform.unlinkpath
72 unlinkpath = platform.unlinkpath
73 username = platform.username
73 username = platform.username
74
74
75 # Python compatibility
75 # Python compatibility
76
76
77 _notset = object()
77 _notset = object()
78
78
79 def safehasattr(thing, attr):
79 def safehasattr(thing, attr):
80 return getattr(thing, attr, _notset) is not _notset
80 return getattr(thing, attr, _notset) is not _notset
81
81
82 def sha1(s=''):
82 def sha1(s=''):
83 '''
83 '''
84 Low-overhead wrapper around Python's SHA support
84 Low-overhead wrapper around Python's SHA support
85
85
86 >>> f = _fastsha1
86 >>> f = _fastsha1
87 >>> a = sha1()
87 >>> a = sha1()
88 >>> a = f()
88 >>> a = f()
89 >>> a.hexdigest()
89 >>> a.hexdigest()
90 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
90 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
91 '''
91 '''
92
92
93 return _fastsha1(s)
93 return _fastsha1(s)
94
94
95 def _fastsha1(s=''):
95 def _fastsha1(s=''):
96 # This function will import sha1 from hashlib or sha (whichever is
96 # This function will import sha1 from hashlib or sha (whichever is
97 # available) and overwrite itself with it on the first call.
97 # available) and overwrite itself with it on the first call.
98 # Subsequent calls will go directly to the imported function.
98 # Subsequent calls will go directly to the imported function.
99 if sys.version_info >= (2, 5):
99 if sys.version_info >= (2, 5):
100 from hashlib import sha1 as _sha1
100 from hashlib import sha1 as _sha1
101 else:
101 else:
102 from sha import sha as _sha1
102 from sha import sha as _sha1
103 global _fastsha1, sha1
103 global _fastsha1, sha1
104 _fastsha1 = sha1 = _sha1
104 _fastsha1 = sha1 = _sha1
105 return _sha1(s)
105 return _sha1(s)
106
106
107 try:
107 try:
108 buffer = buffer
108 buffer = buffer
109 except NameError:
109 except NameError:
110 if sys.version_info[0] < 3:
110 if sys.version_info[0] < 3:
111 def buffer(sliceable, offset=0):
111 def buffer(sliceable, offset=0):
112 return sliceable[offset:]
112 return sliceable[offset:]
113 else:
113 else:
114 def buffer(sliceable, offset=0):
114 def buffer(sliceable, offset=0):
115 return memoryview(sliceable)[offset:]
115 return memoryview(sliceable)[offset:]
116
116
117 import subprocess
117 import subprocess
118 closefds = os.name == 'posix'
118 closefds = os.name == 'posix'
119
119
120 def popen2(cmd, env=None, newlines=False):
120 def popen2(cmd, env=None, newlines=False):
121 # Setting bufsize to -1 lets the system decide the buffer size.
121 # Setting bufsize to -1 lets the system decide the buffer size.
122 # The default for bufsize is 0, meaning unbuffered. This leads to
122 # The default for bufsize is 0, meaning unbuffered. This leads to
123 # poor performance on Mac OS X: http://bugs.python.org/issue4194
123 # poor performance on Mac OS X: http://bugs.python.org/issue4194
124 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
124 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
125 close_fds=closefds,
125 close_fds=closefds,
126 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
126 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
127 universal_newlines=newlines,
127 universal_newlines=newlines,
128 env=env)
128 env=env)
129 return p.stdin, p.stdout
129 return p.stdin, p.stdout
130
130
131 def popen3(cmd, env=None, newlines=False):
131 def popen3(cmd, env=None, newlines=False):
132 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
133 return stdin, stdout, stderr
134
135 def popen4(cmd, env=None, newlines=False):
132 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
133 close_fds=closefds,
137 close_fds=closefds,
134 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
135 stderr=subprocess.PIPE,
139 stderr=subprocess.PIPE,
136 universal_newlines=newlines,
140 universal_newlines=newlines,
137 env=env)
141 env=env)
138 return p.stdin, p.stdout, p.stderr
142 return p.stdin, p.stdout, p.stderr, p
139
143
140 def version():
144 def version():
141 """Return version information if available."""
145 """Return version information if available."""
142 try:
146 try:
143 import __version__
147 import __version__
144 return __version__.version
148 return __version__.version
145 except ImportError:
149 except ImportError:
146 return 'unknown'
150 return 'unknown'
147
151
148 # used by parsedate
152 # used by parsedate
149 defaultdateformats = (
153 defaultdateformats = (
150 '%Y-%m-%d %H:%M:%S',
154 '%Y-%m-%d %H:%M:%S',
151 '%Y-%m-%d %I:%M:%S%p',
155 '%Y-%m-%d %I:%M:%S%p',
152 '%Y-%m-%d %H:%M',
156 '%Y-%m-%d %H:%M',
153 '%Y-%m-%d %I:%M%p',
157 '%Y-%m-%d %I:%M%p',
154 '%Y-%m-%d',
158 '%Y-%m-%d',
155 '%m-%d',
159 '%m-%d',
156 '%m/%d',
160 '%m/%d',
157 '%m/%d/%y',
161 '%m/%d/%y',
158 '%m/%d/%Y',
162 '%m/%d/%Y',
159 '%a %b %d %H:%M:%S %Y',
163 '%a %b %d %H:%M:%S %Y',
160 '%a %b %d %I:%M:%S%p %Y',
164 '%a %b %d %I:%M:%S%p %Y',
161 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
165 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
162 '%b %d %H:%M:%S %Y',
166 '%b %d %H:%M:%S %Y',
163 '%b %d %I:%M:%S%p %Y',
167 '%b %d %I:%M:%S%p %Y',
164 '%b %d %H:%M:%S',
168 '%b %d %H:%M:%S',
165 '%b %d %I:%M:%S%p',
169 '%b %d %I:%M:%S%p',
166 '%b %d %H:%M',
170 '%b %d %H:%M',
167 '%b %d %I:%M%p',
171 '%b %d %I:%M%p',
168 '%b %d %Y',
172 '%b %d %Y',
169 '%b %d',
173 '%b %d',
170 '%H:%M:%S',
174 '%H:%M:%S',
171 '%I:%M:%S%p',
175 '%I:%M:%S%p',
172 '%H:%M',
176 '%H:%M',
173 '%I:%M%p',
177 '%I:%M%p',
174 )
178 )
175
179
176 extendeddateformats = defaultdateformats + (
180 extendeddateformats = defaultdateformats + (
177 "%Y",
181 "%Y",
178 "%Y-%m",
182 "%Y-%m",
179 "%b",
183 "%b",
180 "%b %Y",
184 "%b %Y",
181 )
185 )
182
186
183 def cachefunc(func):
187 def cachefunc(func):
184 '''cache the result of function calls'''
188 '''cache the result of function calls'''
185 # XXX doesn't handle keywords args
189 # XXX doesn't handle keywords args
186 cache = {}
190 cache = {}
187 if func.func_code.co_argcount == 1:
191 if func.func_code.co_argcount == 1:
188 # we gain a small amount of time because
192 # we gain a small amount of time because
189 # we don't need to pack/unpack the list
193 # we don't need to pack/unpack the list
190 def f(arg):
194 def f(arg):
191 if arg not in cache:
195 if arg not in cache:
192 cache[arg] = func(arg)
196 cache[arg] = func(arg)
193 return cache[arg]
197 return cache[arg]
194 else:
198 else:
195 def f(*args):
199 def f(*args):
196 if args not in cache:
200 if args not in cache:
197 cache[args] = func(*args)
201 cache[args] = func(*args)
198 return cache[args]
202 return cache[args]
199
203
200 return f
204 return f
201
205
202 try:
206 try:
203 collections.deque.remove
207 collections.deque.remove
204 deque = collections.deque
208 deque = collections.deque
205 except AttributeError:
209 except AttributeError:
206 # python 2.4 lacks deque.remove
210 # python 2.4 lacks deque.remove
207 class deque(collections.deque):
211 class deque(collections.deque):
208 def remove(self, val):
212 def remove(self, val):
209 for i, v in enumerate(self):
213 for i, v in enumerate(self):
210 if v == val:
214 if v == val:
211 del self[i]
215 del self[i]
212 break
216 break
213
217
214 class lrucachedict(object):
218 class lrucachedict(object):
215 '''cache most recent gets from or sets to this dictionary'''
219 '''cache most recent gets from or sets to this dictionary'''
216 def __init__(self, maxsize):
220 def __init__(self, maxsize):
217 self._cache = {}
221 self._cache = {}
218 self._maxsize = maxsize
222 self._maxsize = maxsize
219 self._order = deque()
223 self._order = deque()
220
224
221 def __getitem__(self, key):
225 def __getitem__(self, key):
222 value = self._cache[key]
226 value = self._cache[key]
223 self._order.remove(key)
227 self._order.remove(key)
224 self._order.append(key)
228 self._order.append(key)
225 return value
229 return value
226
230
227 def __setitem__(self, key, value):
231 def __setitem__(self, key, value):
228 if key not in self._cache:
232 if key not in self._cache:
229 if len(self._cache) >= self._maxsize:
233 if len(self._cache) >= self._maxsize:
230 del self._cache[self._order.popleft()]
234 del self._cache[self._order.popleft()]
231 else:
235 else:
232 self._order.remove(key)
236 self._order.remove(key)
233 self._cache[key] = value
237 self._cache[key] = value
234 self._order.append(key)
238 self._order.append(key)
235
239
236 def __contains__(self, key):
240 def __contains__(self, key):
237 return key in self._cache
241 return key in self._cache
238
242
239 def lrucachefunc(func):
243 def lrucachefunc(func):
240 '''cache most recent results of function calls'''
244 '''cache most recent results of function calls'''
241 cache = {}
245 cache = {}
242 order = deque()
246 order = deque()
243 if func.func_code.co_argcount == 1:
247 if func.func_code.co_argcount == 1:
244 def f(arg):
248 def f(arg):
245 if arg not in cache:
249 if arg not in cache:
246 if len(cache) > 20:
250 if len(cache) > 20:
247 del cache[order.popleft()]
251 del cache[order.popleft()]
248 cache[arg] = func(arg)
252 cache[arg] = func(arg)
249 else:
253 else:
250 order.remove(arg)
254 order.remove(arg)
251 order.append(arg)
255 order.append(arg)
252 return cache[arg]
256 return cache[arg]
253 else:
257 else:
254 def f(*args):
258 def f(*args):
255 if args not in cache:
259 if args not in cache:
256 if len(cache) > 20:
260 if len(cache) > 20:
257 del cache[order.popleft()]
261 del cache[order.popleft()]
258 cache[args] = func(*args)
262 cache[args] = func(*args)
259 else:
263 else:
260 order.remove(args)
264 order.remove(args)
261 order.append(args)
265 order.append(args)
262 return cache[args]
266 return cache[args]
263
267
264 return f
268 return f
265
269
266 class propertycache(object):
270 class propertycache(object):
267 def __init__(self, func):
271 def __init__(self, func):
268 self.func = func
272 self.func = func
269 self.name = func.__name__
273 self.name = func.__name__
270 def __get__(self, obj, type=None):
274 def __get__(self, obj, type=None):
271 result = self.func(obj)
275 result = self.func(obj)
272 self.cachevalue(obj, result)
276 self.cachevalue(obj, result)
273 return result
277 return result
274
278
275 def cachevalue(self, obj, value):
279 def cachevalue(self, obj, value):
276 setattr(obj, self.name, value)
280 setattr(obj, self.name, value)
277
281
278 def pipefilter(s, cmd):
282 def pipefilter(s, cmd):
279 '''filter string S through command CMD, returning its output'''
283 '''filter string S through command CMD, returning its output'''
280 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
284 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
281 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
285 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
282 pout, perr = p.communicate(s)
286 pout, perr = p.communicate(s)
283 return pout
287 return pout
284
288
285 def tempfilter(s, cmd):
289 def tempfilter(s, cmd):
286 '''filter string S through a pair of temporary files with CMD.
290 '''filter string S through a pair of temporary files with CMD.
287 CMD is used as a template to create the real command to be run,
291 CMD is used as a template to create the real command to be run,
288 with the strings INFILE and OUTFILE replaced by the real names of
292 with the strings INFILE and OUTFILE replaced by the real names of
289 the temporary files generated.'''
293 the temporary files generated.'''
290 inname, outname = None, None
294 inname, outname = None, None
291 try:
295 try:
292 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
296 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
293 fp = os.fdopen(infd, 'wb')
297 fp = os.fdopen(infd, 'wb')
294 fp.write(s)
298 fp.write(s)
295 fp.close()
299 fp.close()
296 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
300 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
297 os.close(outfd)
301 os.close(outfd)
298 cmd = cmd.replace('INFILE', inname)
302 cmd = cmd.replace('INFILE', inname)
299 cmd = cmd.replace('OUTFILE', outname)
303 cmd = cmd.replace('OUTFILE', outname)
300 code = os.system(cmd)
304 code = os.system(cmd)
301 if sys.platform == 'OpenVMS' and code & 1:
305 if sys.platform == 'OpenVMS' and code & 1:
302 code = 0
306 code = 0
303 if code:
307 if code:
304 raise Abort(_("command '%s' failed: %s") %
308 raise Abort(_("command '%s' failed: %s") %
305 (cmd, explainexit(code)))
309 (cmd, explainexit(code)))
306 fp = open(outname, 'rb')
310 fp = open(outname, 'rb')
307 r = fp.read()
311 r = fp.read()
308 fp.close()
312 fp.close()
309 return r
313 return r
310 finally:
314 finally:
311 try:
315 try:
312 if inname:
316 if inname:
313 os.unlink(inname)
317 os.unlink(inname)
314 except OSError:
318 except OSError:
315 pass
319 pass
316 try:
320 try:
317 if outname:
321 if outname:
318 os.unlink(outname)
322 os.unlink(outname)
319 except OSError:
323 except OSError:
320 pass
324 pass
321
325
322 filtertable = {
326 filtertable = {
323 'tempfile:': tempfilter,
327 'tempfile:': tempfilter,
324 'pipe:': pipefilter,
328 'pipe:': pipefilter,
325 }
329 }
326
330
327 def filter(s, cmd):
331 def filter(s, cmd):
328 "filter a string through a command that transforms its input to its output"
332 "filter a string through a command that transforms its input to its output"
329 for name, fn in filtertable.iteritems():
333 for name, fn in filtertable.iteritems():
330 if cmd.startswith(name):
334 if cmd.startswith(name):
331 return fn(s, cmd[len(name):].lstrip())
335 return fn(s, cmd[len(name):].lstrip())
332 return pipefilter(s, cmd)
336 return pipefilter(s, cmd)
333
337
334 def binary(s):
338 def binary(s):
335 """return true if a string is binary data"""
339 """return true if a string is binary data"""
336 return bool(s and '\0' in s)
340 return bool(s and '\0' in s)
337
341
338 def increasingchunks(source, min=1024, max=65536):
342 def increasingchunks(source, min=1024, max=65536):
339 '''return no less than min bytes per chunk while data remains,
343 '''return no less than min bytes per chunk while data remains,
340 doubling min after each chunk until it reaches max'''
344 doubling min after each chunk until it reaches max'''
341 def log2(x):
345 def log2(x):
342 if not x:
346 if not x:
343 return 0
347 return 0
344 i = 0
348 i = 0
345 while x:
349 while x:
346 x >>= 1
350 x >>= 1
347 i += 1
351 i += 1
348 return i - 1
352 return i - 1
349
353
350 buf = []
354 buf = []
351 blen = 0
355 blen = 0
352 for chunk in source:
356 for chunk in source:
353 buf.append(chunk)
357 buf.append(chunk)
354 blen += len(chunk)
358 blen += len(chunk)
355 if blen >= min:
359 if blen >= min:
356 if min < max:
360 if min < max:
357 min = min << 1
361 min = min << 1
358 nmin = 1 << log2(blen)
362 nmin = 1 << log2(blen)
359 if nmin > min:
363 if nmin > min:
360 min = nmin
364 min = nmin
361 if min > max:
365 if min > max:
362 min = max
366 min = max
363 yield ''.join(buf)
367 yield ''.join(buf)
364 blen = 0
368 blen = 0
365 buf = []
369 buf = []
366 if buf:
370 if buf:
367 yield ''.join(buf)
371 yield ''.join(buf)
368
372
369 Abort = error.Abort
373 Abort = error.Abort
370
374
371 def always(fn):
375 def always(fn):
372 return True
376 return True
373
377
374 def never(fn):
378 def never(fn):
375 return False
379 return False
376
380
377 def pathto(root, n1, n2):
381 def pathto(root, n1, n2):
378 '''return the relative path from one place to another.
382 '''return the relative path from one place to another.
379 root should use os.sep to separate directories
383 root should use os.sep to separate directories
380 n1 should use os.sep to separate directories
384 n1 should use os.sep to separate directories
381 n2 should use "/" to separate directories
385 n2 should use "/" to separate directories
382 returns an os.sep-separated path.
386 returns an os.sep-separated path.
383
387
384 If n1 is a relative path, it's assumed it's
388 If n1 is a relative path, it's assumed it's
385 relative to root.
389 relative to root.
386 n2 should always be relative to root.
390 n2 should always be relative to root.
387 '''
391 '''
388 if not n1:
392 if not n1:
389 return localpath(n2)
393 return localpath(n2)
390 if os.path.isabs(n1):
394 if os.path.isabs(n1):
391 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
395 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
392 return os.path.join(root, localpath(n2))
396 return os.path.join(root, localpath(n2))
393 n2 = '/'.join((pconvert(root), n2))
397 n2 = '/'.join((pconvert(root), n2))
394 a, b = splitpath(n1), n2.split('/')
398 a, b = splitpath(n1), n2.split('/')
395 a.reverse()
399 a.reverse()
396 b.reverse()
400 b.reverse()
397 while a and b and a[-1] == b[-1]:
401 while a and b and a[-1] == b[-1]:
398 a.pop()
402 a.pop()
399 b.pop()
403 b.pop()
400 b.reverse()
404 b.reverse()
401 return os.sep.join((['..'] * len(a)) + b) or '.'
405 return os.sep.join((['..'] * len(a)) + b) or '.'
402
406
403 _hgexecutable = None
407 _hgexecutable = None
404
408
405 def mainfrozen():
409 def mainfrozen():
406 """return True if we are a frozen executable.
410 """return True if we are a frozen executable.
407
411
408 The code supports py2exe (most common, Windows only) and tools/freeze
412 The code supports py2exe (most common, Windows only) and tools/freeze
409 (portable, not much used).
413 (portable, not much used).
410 """
414 """
411 return (safehasattr(sys, "frozen") or # new py2exe
415 return (safehasattr(sys, "frozen") or # new py2exe
412 safehasattr(sys, "importers") or # old py2exe
416 safehasattr(sys, "importers") or # old py2exe
413 imp.is_frozen("__main__")) # tools/freeze
417 imp.is_frozen("__main__")) # tools/freeze
414
418
415 def hgexecutable():
419 def hgexecutable():
416 """return location of the 'hg' executable.
420 """return location of the 'hg' executable.
417
421
418 Defaults to $HG or 'hg' in the search path.
422 Defaults to $HG or 'hg' in the search path.
419 """
423 """
420 if _hgexecutable is None:
424 if _hgexecutable is None:
421 hg = os.environ.get('HG')
425 hg = os.environ.get('HG')
422 mainmod = sys.modules['__main__']
426 mainmod = sys.modules['__main__']
423 if hg:
427 if hg:
424 _sethgexecutable(hg)
428 _sethgexecutable(hg)
425 elif mainfrozen():
429 elif mainfrozen():
426 _sethgexecutable(sys.executable)
430 _sethgexecutable(sys.executable)
427 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
431 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
428 _sethgexecutable(mainmod.__file__)
432 _sethgexecutable(mainmod.__file__)
429 else:
433 else:
430 exe = findexe('hg') or os.path.basename(sys.argv[0])
434 exe = findexe('hg') or os.path.basename(sys.argv[0])
431 _sethgexecutable(exe)
435 _sethgexecutable(exe)
432 return _hgexecutable
436 return _hgexecutable
433
437
434 def _sethgexecutable(path):
438 def _sethgexecutable(path):
435 """set location of the 'hg' executable"""
439 """set location of the 'hg' executable"""
436 global _hgexecutable
440 global _hgexecutable
437 _hgexecutable = path
441 _hgexecutable = path
438
442
439 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
443 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
440 '''enhanced shell command execution.
444 '''enhanced shell command execution.
441 run with environment maybe modified, maybe in different dir.
445 run with environment maybe modified, maybe in different dir.
442
446
443 if command fails and onerr is None, return status. if ui object,
447 if command fails and onerr is None, return status. if ui object,
444 print error message and return status, else raise onerr object as
448 print error message and return status, else raise onerr object as
445 exception.
449 exception.
446
450
447 if out is specified, it is assumed to be a file-like object that has a
451 if out is specified, it is assumed to be a file-like object that has a
448 write() method. stdout and stderr will be redirected to out.'''
452 write() method. stdout and stderr will be redirected to out.'''
449 try:
453 try:
450 sys.stdout.flush()
454 sys.stdout.flush()
451 except Exception:
455 except Exception:
452 pass
456 pass
453 def py2shell(val):
457 def py2shell(val):
454 'convert python object into string that is useful to shell'
458 'convert python object into string that is useful to shell'
455 if val is None or val is False:
459 if val is None or val is False:
456 return '0'
460 return '0'
457 if val is True:
461 if val is True:
458 return '1'
462 return '1'
459 return str(val)
463 return str(val)
460 origcmd = cmd
464 origcmd = cmd
461 cmd = quotecommand(cmd)
465 cmd = quotecommand(cmd)
462 if sys.platform == 'plan9':
466 if sys.platform == 'plan9':
463 # subprocess kludge to work around issues in half-baked Python
467 # subprocess kludge to work around issues in half-baked Python
464 # ports, notably bichued/python:
468 # ports, notably bichued/python:
465 if not cwd is None:
469 if not cwd is None:
466 os.chdir(cwd)
470 os.chdir(cwd)
467 rc = os.system(cmd)
471 rc = os.system(cmd)
468 else:
472 else:
469 env = dict(os.environ)
473 env = dict(os.environ)
470 env.update((k, py2shell(v)) for k, v in environ.iteritems())
474 env.update((k, py2shell(v)) for k, v in environ.iteritems())
471 env['HG'] = hgexecutable()
475 env['HG'] = hgexecutable()
472 if out is None or out == sys.__stdout__:
476 if out is None or out == sys.__stdout__:
473 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
477 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
474 env=env, cwd=cwd)
478 env=env, cwd=cwd)
475 else:
479 else:
476 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
480 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
477 env=env, cwd=cwd, stdout=subprocess.PIPE,
481 env=env, cwd=cwd, stdout=subprocess.PIPE,
478 stderr=subprocess.STDOUT)
482 stderr=subprocess.STDOUT)
479 for line in proc.stdout:
483 for line in proc.stdout:
480 out.write(line)
484 out.write(line)
481 proc.wait()
485 proc.wait()
482 rc = proc.returncode
486 rc = proc.returncode
483 if sys.platform == 'OpenVMS' and rc & 1:
487 if sys.platform == 'OpenVMS' and rc & 1:
484 rc = 0
488 rc = 0
485 if rc and onerr:
489 if rc and onerr:
486 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
490 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
487 explainexit(rc)[0])
491 explainexit(rc)[0])
488 if errprefix:
492 if errprefix:
489 errmsg = '%s: %s' % (errprefix, errmsg)
493 errmsg = '%s: %s' % (errprefix, errmsg)
490 try:
494 try:
491 onerr.warn(errmsg + '\n')
495 onerr.warn(errmsg + '\n')
492 except AttributeError:
496 except AttributeError:
493 raise onerr(errmsg)
497 raise onerr(errmsg)
494 return rc
498 return rc
495
499
496 def checksignature(func):
500 def checksignature(func):
497 '''wrap a function with code to check for calling errors'''
501 '''wrap a function with code to check for calling errors'''
498 def check(*args, **kwargs):
502 def check(*args, **kwargs):
499 try:
503 try:
500 return func(*args, **kwargs)
504 return func(*args, **kwargs)
501 except TypeError:
505 except TypeError:
502 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
506 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
503 raise error.SignatureError
507 raise error.SignatureError
504 raise
508 raise
505
509
506 return check
510 return check
507
511
508 def copyfile(src, dest):
512 def copyfile(src, dest):
509 "copy a file, preserving mode and atime/mtime"
513 "copy a file, preserving mode and atime/mtime"
510 if os.path.lexists(dest):
514 if os.path.lexists(dest):
511 unlink(dest)
515 unlink(dest)
512 if os.path.islink(src):
516 if os.path.islink(src):
513 os.symlink(os.readlink(src), dest)
517 os.symlink(os.readlink(src), dest)
514 else:
518 else:
515 try:
519 try:
516 shutil.copyfile(src, dest)
520 shutil.copyfile(src, dest)
517 shutil.copymode(src, dest)
521 shutil.copymode(src, dest)
518 except shutil.Error, inst:
522 except shutil.Error, inst:
519 raise Abort(str(inst))
523 raise Abort(str(inst))
520
524
521 def copyfiles(src, dst, hardlink=None):
525 def copyfiles(src, dst, hardlink=None):
522 """Copy a directory tree using hardlinks if possible"""
526 """Copy a directory tree using hardlinks if possible"""
523
527
524 if hardlink is None:
528 if hardlink is None:
525 hardlink = (os.stat(src).st_dev ==
529 hardlink = (os.stat(src).st_dev ==
526 os.stat(os.path.dirname(dst)).st_dev)
530 os.stat(os.path.dirname(dst)).st_dev)
527
531
528 num = 0
532 num = 0
529 if os.path.isdir(src):
533 if os.path.isdir(src):
530 os.mkdir(dst)
534 os.mkdir(dst)
531 for name, kind in osutil.listdir(src):
535 for name, kind in osutil.listdir(src):
532 srcname = os.path.join(src, name)
536 srcname = os.path.join(src, name)
533 dstname = os.path.join(dst, name)
537 dstname = os.path.join(dst, name)
534 hardlink, n = copyfiles(srcname, dstname, hardlink)
538 hardlink, n = copyfiles(srcname, dstname, hardlink)
535 num += n
539 num += n
536 else:
540 else:
537 if hardlink:
541 if hardlink:
538 try:
542 try:
539 oslink(src, dst)
543 oslink(src, dst)
540 except (IOError, OSError):
544 except (IOError, OSError):
541 hardlink = False
545 hardlink = False
542 shutil.copy(src, dst)
546 shutil.copy(src, dst)
543 else:
547 else:
544 shutil.copy(src, dst)
548 shutil.copy(src, dst)
545 num += 1
549 num += 1
546
550
547 return hardlink, num
551 return hardlink, num
548
552
549 _winreservednames = '''con prn aux nul
553 _winreservednames = '''con prn aux nul
550 com1 com2 com3 com4 com5 com6 com7 com8 com9
554 com1 com2 com3 com4 com5 com6 com7 com8 com9
551 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
555 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
552 _winreservedchars = ':*?"<>|'
556 _winreservedchars = ':*?"<>|'
553 def checkwinfilename(path):
557 def checkwinfilename(path):
554 '''Check that the base-relative path is a valid filename on Windows.
558 '''Check that the base-relative path is a valid filename on Windows.
555 Returns None if the path is ok, or a UI string describing the problem.
559 Returns None if the path is ok, or a UI string describing the problem.
556
560
557 >>> checkwinfilename("just/a/normal/path")
561 >>> checkwinfilename("just/a/normal/path")
558 >>> checkwinfilename("foo/bar/con.xml")
562 >>> checkwinfilename("foo/bar/con.xml")
559 "filename contains 'con', which is reserved on Windows"
563 "filename contains 'con', which is reserved on Windows"
560 >>> checkwinfilename("foo/con.xml/bar")
564 >>> checkwinfilename("foo/con.xml/bar")
561 "filename contains 'con', which is reserved on Windows"
565 "filename contains 'con', which is reserved on Windows"
562 >>> checkwinfilename("foo/bar/xml.con")
566 >>> checkwinfilename("foo/bar/xml.con")
563 >>> checkwinfilename("foo/bar/AUX/bla.txt")
567 >>> checkwinfilename("foo/bar/AUX/bla.txt")
564 "filename contains 'AUX', which is reserved on Windows"
568 "filename contains 'AUX', which is reserved on Windows"
565 >>> checkwinfilename("foo/bar/bla:.txt")
569 >>> checkwinfilename("foo/bar/bla:.txt")
566 "filename contains ':', which is reserved on Windows"
570 "filename contains ':', which is reserved on Windows"
567 >>> checkwinfilename("foo/bar/b\07la.txt")
571 >>> checkwinfilename("foo/bar/b\07la.txt")
568 "filename contains '\\\\x07', which is invalid on Windows"
572 "filename contains '\\\\x07', which is invalid on Windows"
569 >>> checkwinfilename("foo/bar/bla ")
573 >>> checkwinfilename("foo/bar/bla ")
570 "filename ends with ' ', which is not allowed on Windows"
574 "filename ends with ' ', which is not allowed on Windows"
571 >>> checkwinfilename("../bar")
575 >>> checkwinfilename("../bar")
572 '''
576 '''
573 for n in path.replace('\\', '/').split('/'):
577 for n in path.replace('\\', '/').split('/'):
574 if not n:
578 if not n:
575 continue
579 continue
576 for c in n:
580 for c in n:
577 if c in _winreservedchars:
581 if c in _winreservedchars:
578 return _("filename contains '%s', which is reserved "
582 return _("filename contains '%s', which is reserved "
579 "on Windows") % c
583 "on Windows") % c
580 if ord(c) <= 31:
584 if ord(c) <= 31:
581 return _("filename contains %r, which is invalid "
585 return _("filename contains %r, which is invalid "
582 "on Windows") % c
586 "on Windows") % c
583 base = n.split('.')[0]
587 base = n.split('.')[0]
584 if base and base.lower() in _winreservednames:
588 if base and base.lower() in _winreservednames:
585 return _("filename contains '%s', which is reserved "
589 return _("filename contains '%s', which is reserved "
586 "on Windows") % base
590 "on Windows") % base
587 t = n[-1]
591 t = n[-1]
588 if t in '. ' and n not in '..':
592 if t in '. ' and n not in '..':
589 return _("filename ends with '%s', which is not allowed "
593 return _("filename ends with '%s', which is not allowed "
590 "on Windows") % t
594 "on Windows") % t
591
595
592 if os.name == 'nt':
596 if os.name == 'nt':
593 checkosfilename = checkwinfilename
597 checkosfilename = checkwinfilename
594 else:
598 else:
595 checkosfilename = platform.checkosfilename
599 checkosfilename = platform.checkosfilename
596
600
597 def makelock(info, pathname):
601 def makelock(info, pathname):
598 try:
602 try:
599 return os.symlink(info, pathname)
603 return os.symlink(info, pathname)
600 except OSError, why:
604 except OSError, why:
601 if why.errno == errno.EEXIST:
605 if why.errno == errno.EEXIST:
602 raise
606 raise
603 except AttributeError: # no symlink in os
607 except AttributeError: # no symlink in os
604 pass
608 pass
605
609
606 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
610 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
607 os.write(ld, info)
611 os.write(ld, info)
608 os.close(ld)
612 os.close(ld)
609
613
610 def readlock(pathname):
614 def readlock(pathname):
611 try:
615 try:
612 return os.readlink(pathname)
616 return os.readlink(pathname)
613 except OSError, why:
617 except OSError, why:
614 if why.errno not in (errno.EINVAL, errno.ENOSYS):
618 if why.errno not in (errno.EINVAL, errno.ENOSYS):
615 raise
619 raise
616 except AttributeError: # no symlink in os
620 except AttributeError: # no symlink in os
617 pass
621 pass
618 fp = posixfile(pathname)
622 fp = posixfile(pathname)
619 r = fp.read()
623 r = fp.read()
620 fp.close()
624 fp.close()
621 return r
625 return r
622
626
623 def fstat(fp):
627 def fstat(fp):
624 '''stat file object that may not have fileno method.'''
628 '''stat file object that may not have fileno method.'''
625 try:
629 try:
626 return os.fstat(fp.fileno())
630 return os.fstat(fp.fileno())
627 except AttributeError:
631 except AttributeError:
628 return os.stat(fp.name)
632 return os.stat(fp.name)
629
633
630 # File system features
634 # File system features
631
635
632 def checkcase(path):
636 def checkcase(path):
633 """
637 """
634 Check whether the given path is on a case-sensitive filesystem
638 Check whether the given path is on a case-sensitive filesystem
635
639
636 Requires a path (like /foo/.hg) ending with a foldable final
640 Requires a path (like /foo/.hg) ending with a foldable final
637 directory component.
641 directory component.
638 """
642 """
639 s1 = os.stat(path)
643 s1 = os.stat(path)
640 d, b = os.path.split(path)
644 d, b = os.path.split(path)
641 b2 = b.upper()
645 b2 = b.upper()
642 if b == b2:
646 if b == b2:
643 b2 = b.lower()
647 b2 = b.lower()
644 if b == b2:
648 if b == b2:
645 return True # no evidence against case sensitivity
649 return True # no evidence against case sensitivity
646 p2 = os.path.join(d, b2)
650 p2 = os.path.join(d, b2)
647 try:
651 try:
648 s2 = os.stat(p2)
652 s2 = os.stat(p2)
649 if s2 == s1:
653 if s2 == s1:
650 return False
654 return False
651 return True
655 return True
652 except OSError:
656 except OSError:
653 return True
657 return True
654
658
655 try:
659 try:
656 import re2
660 import re2
657 _re2 = None
661 _re2 = None
658 except ImportError:
662 except ImportError:
659 _re2 = False
663 _re2 = False
660
664
661 def compilere(pat):
665 def compilere(pat):
662 '''Compile a regular expression, using re2 if possible
666 '''Compile a regular expression, using re2 if possible
663
667
664 For best performance, use only re2-compatible regexp features.'''
668 For best performance, use only re2-compatible regexp features.'''
665 global _re2
669 global _re2
666 if _re2 is None:
670 if _re2 is None:
667 try:
671 try:
668 re2.compile
672 re2.compile
669 _re2 = True
673 _re2 = True
670 except ImportError:
674 except ImportError:
671 _re2 = False
675 _re2 = False
672 if _re2:
676 if _re2:
673 try:
677 try:
674 return re2.compile(pat)
678 return re2.compile(pat)
675 except re2.error:
679 except re2.error:
676 pass
680 pass
677 return re.compile(pat)
681 return re.compile(pat)
678
682
679 _fspathcache = {}
683 _fspathcache = {}
680 def fspath(name, root):
684 def fspath(name, root):
681 '''Get name in the case stored in the filesystem
685 '''Get name in the case stored in the filesystem
682
686
683 The name should be relative to root, and be normcase-ed for efficiency.
687 The name should be relative to root, and be normcase-ed for efficiency.
684
688
685 Note that this function is unnecessary, and should not be
689 Note that this function is unnecessary, and should not be
686 called, for case-sensitive filesystems (simply because it's expensive).
690 called, for case-sensitive filesystems (simply because it's expensive).
687
691
688 The root should be normcase-ed, too.
692 The root should be normcase-ed, too.
689 '''
693 '''
690 def find(p, contents):
694 def find(p, contents):
691 for n in contents:
695 for n in contents:
692 if normcase(n) == p:
696 if normcase(n) == p:
693 return n
697 return n
694 return None
698 return None
695
699
696 seps = os.sep
700 seps = os.sep
697 if os.altsep:
701 if os.altsep:
698 seps = seps + os.altsep
702 seps = seps + os.altsep
699 # Protect backslashes. This gets silly very quickly.
703 # Protect backslashes. This gets silly very quickly.
700 seps.replace('\\','\\\\')
704 seps.replace('\\','\\\\')
701 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
705 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
702 dir = os.path.normpath(root)
706 dir = os.path.normpath(root)
703 result = []
707 result = []
704 for part, sep in pattern.findall(name):
708 for part, sep in pattern.findall(name):
705 if sep:
709 if sep:
706 result.append(sep)
710 result.append(sep)
707 continue
711 continue
708
712
709 if dir not in _fspathcache:
713 if dir not in _fspathcache:
710 _fspathcache[dir] = os.listdir(dir)
714 _fspathcache[dir] = os.listdir(dir)
711 contents = _fspathcache[dir]
715 contents = _fspathcache[dir]
712
716
713 found = find(part, contents)
717 found = find(part, contents)
714 if not found:
718 if not found:
715 # retry "once per directory" per "dirstate.walk" which
719 # retry "once per directory" per "dirstate.walk" which
716 # may take place for each patches of "hg qpush", for example
720 # may take place for each patches of "hg qpush", for example
717 contents = os.listdir(dir)
721 contents = os.listdir(dir)
718 _fspathcache[dir] = contents
722 _fspathcache[dir] = contents
719 found = find(part, contents)
723 found = find(part, contents)
720
724
721 result.append(found or part)
725 result.append(found or part)
722 dir = os.path.join(dir, part)
726 dir = os.path.join(dir, part)
723
727
724 return ''.join(result)
728 return ''.join(result)
725
729
726 def checknlink(testfile):
730 def checknlink(testfile):
727 '''check whether hardlink count reporting works properly'''
731 '''check whether hardlink count reporting works properly'''
728
732
729 # testfile may be open, so we need a separate file for checking to
733 # testfile may be open, so we need a separate file for checking to
730 # work around issue2543 (or testfile may get lost on Samba shares)
734 # work around issue2543 (or testfile may get lost on Samba shares)
731 f1 = testfile + ".hgtmp1"
735 f1 = testfile + ".hgtmp1"
732 if os.path.lexists(f1):
736 if os.path.lexists(f1):
733 return False
737 return False
734 try:
738 try:
735 posixfile(f1, 'w').close()
739 posixfile(f1, 'w').close()
736 except IOError:
740 except IOError:
737 return False
741 return False
738
742
739 f2 = testfile + ".hgtmp2"
743 f2 = testfile + ".hgtmp2"
740 fd = None
744 fd = None
741 try:
745 try:
742 try:
746 try:
743 oslink(f1, f2)
747 oslink(f1, f2)
744 except OSError:
748 except OSError:
745 return False
749 return False
746
750
747 # nlinks() may behave differently for files on Windows shares if
751 # nlinks() may behave differently for files on Windows shares if
748 # the file is open.
752 # the file is open.
749 fd = posixfile(f2)
753 fd = posixfile(f2)
750 return nlinks(f2) > 1
754 return nlinks(f2) > 1
751 finally:
755 finally:
752 if fd is not None:
756 if fd is not None:
753 fd.close()
757 fd.close()
754 for f in (f1, f2):
758 for f in (f1, f2):
755 try:
759 try:
756 os.unlink(f)
760 os.unlink(f)
757 except OSError:
761 except OSError:
758 pass
762 pass
759
763
760 return False
764 return False
761
765
762 def endswithsep(path):
766 def endswithsep(path):
763 '''Check path ends with os.sep or os.altsep.'''
767 '''Check path ends with os.sep or os.altsep.'''
764 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
768 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
765
769
766 def splitpath(path):
770 def splitpath(path):
767 '''Split path by os.sep.
771 '''Split path by os.sep.
768 Note that this function does not use os.altsep because this is
772 Note that this function does not use os.altsep because this is
769 an alternative of simple "xxx.split(os.sep)".
773 an alternative of simple "xxx.split(os.sep)".
770 It is recommended to use os.path.normpath() before using this
774 It is recommended to use os.path.normpath() before using this
771 function if need.'''
775 function if need.'''
772 return path.split(os.sep)
776 return path.split(os.sep)
773
777
774 def gui():
778 def gui():
775 '''Are we running in a GUI?'''
779 '''Are we running in a GUI?'''
776 if sys.platform == 'darwin':
780 if sys.platform == 'darwin':
777 if 'SSH_CONNECTION' in os.environ:
781 if 'SSH_CONNECTION' in os.environ:
778 # handle SSH access to a box where the user is logged in
782 # handle SSH access to a box where the user is logged in
779 return False
783 return False
780 elif getattr(osutil, 'isgui', None):
784 elif getattr(osutil, 'isgui', None):
781 # check if a CoreGraphics session is available
785 # check if a CoreGraphics session is available
782 return osutil.isgui()
786 return osutil.isgui()
783 else:
787 else:
784 # pure build; use a safe default
788 # pure build; use a safe default
785 return True
789 return True
786 else:
790 else:
787 return os.name == "nt" or os.environ.get("DISPLAY")
791 return os.name == "nt" or os.environ.get("DISPLAY")
788
792
789 def mktempcopy(name, emptyok=False, createmode=None):
793 def mktempcopy(name, emptyok=False, createmode=None):
790 """Create a temporary file with the same contents from name
794 """Create a temporary file with the same contents from name
791
795
792 The permission bits are copied from the original file.
796 The permission bits are copied from the original file.
793
797
794 If the temporary file is going to be truncated immediately, you
798 If the temporary file is going to be truncated immediately, you
795 can use emptyok=True as an optimization.
799 can use emptyok=True as an optimization.
796
800
797 Returns the name of the temporary file.
801 Returns the name of the temporary file.
798 """
802 """
799 d, fn = os.path.split(name)
803 d, fn = os.path.split(name)
800 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
804 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
801 os.close(fd)
805 os.close(fd)
802 # Temporary files are created with mode 0600, which is usually not
806 # Temporary files are created with mode 0600, which is usually not
803 # what we want. If the original file already exists, just copy
807 # what we want. If the original file already exists, just copy
804 # its mode. Otherwise, manually obey umask.
808 # its mode. Otherwise, manually obey umask.
805 copymode(name, temp, createmode)
809 copymode(name, temp, createmode)
806 if emptyok:
810 if emptyok:
807 return temp
811 return temp
808 try:
812 try:
809 try:
813 try:
810 ifp = posixfile(name, "rb")
814 ifp = posixfile(name, "rb")
811 except IOError, inst:
815 except IOError, inst:
812 if inst.errno == errno.ENOENT:
816 if inst.errno == errno.ENOENT:
813 return temp
817 return temp
814 if not getattr(inst, 'filename', None):
818 if not getattr(inst, 'filename', None):
815 inst.filename = name
819 inst.filename = name
816 raise
820 raise
817 ofp = posixfile(temp, "wb")
821 ofp = posixfile(temp, "wb")
818 for chunk in filechunkiter(ifp):
822 for chunk in filechunkiter(ifp):
819 ofp.write(chunk)
823 ofp.write(chunk)
820 ifp.close()
824 ifp.close()
821 ofp.close()
825 ofp.close()
822 except: # re-raises
826 except: # re-raises
823 try: os.unlink(temp)
827 try: os.unlink(temp)
824 except OSError: pass
828 except OSError: pass
825 raise
829 raise
826 return temp
830 return temp
827
831
828 class atomictempfile(object):
832 class atomictempfile(object):
829 '''writable file object that atomically updates a file
833 '''writable file object that atomically updates a file
830
834
831 All writes will go to a temporary copy of the original file. Call
835 All writes will go to a temporary copy of the original file. Call
832 close() when you are done writing, and atomictempfile will rename
836 close() when you are done writing, and atomictempfile will rename
833 the temporary copy to the original name, making the changes
837 the temporary copy to the original name, making the changes
834 visible. If the object is destroyed without being closed, all your
838 visible. If the object is destroyed without being closed, all your
835 writes are discarded.
839 writes are discarded.
836 '''
840 '''
837 def __init__(self, name, mode='w+b', createmode=None):
841 def __init__(self, name, mode='w+b', createmode=None):
838 self.__name = name # permanent name
842 self.__name = name # permanent name
839 self._tempname = mktempcopy(name, emptyok=('w' in mode),
843 self._tempname = mktempcopy(name, emptyok=('w' in mode),
840 createmode=createmode)
844 createmode=createmode)
841 self._fp = posixfile(self._tempname, mode)
845 self._fp = posixfile(self._tempname, mode)
842
846
843 # delegated methods
847 # delegated methods
844 self.write = self._fp.write
848 self.write = self._fp.write
845 self.seek = self._fp.seek
849 self.seek = self._fp.seek
846 self.tell = self._fp.tell
850 self.tell = self._fp.tell
847 self.fileno = self._fp.fileno
851 self.fileno = self._fp.fileno
848
852
849 def close(self):
853 def close(self):
850 if not self._fp.closed:
854 if not self._fp.closed:
851 self._fp.close()
855 self._fp.close()
852 rename(self._tempname, localpath(self.__name))
856 rename(self._tempname, localpath(self.__name))
853
857
854 def discard(self):
858 def discard(self):
855 if not self._fp.closed:
859 if not self._fp.closed:
856 try:
860 try:
857 os.unlink(self._tempname)
861 os.unlink(self._tempname)
858 except OSError:
862 except OSError:
859 pass
863 pass
860 self._fp.close()
864 self._fp.close()
861
865
862 def __del__(self):
866 def __del__(self):
863 if safehasattr(self, '_fp'): # constructor actually did something
867 if safehasattr(self, '_fp'): # constructor actually did something
864 self.discard()
868 self.discard()
865
869
866 def makedirs(name, mode=None):
870 def makedirs(name, mode=None):
867 """recursive directory creation with parent mode inheritance"""
871 """recursive directory creation with parent mode inheritance"""
868 try:
872 try:
869 os.mkdir(name)
873 os.mkdir(name)
870 except OSError, err:
874 except OSError, err:
871 if err.errno == errno.EEXIST:
875 if err.errno == errno.EEXIST:
872 return
876 return
873 if err.errno != errno.ENOENT or not name:
877 if err.errno != errno.ENOENT or not name:
874 raise
878 raise
875 parent = os.path.dirname(os.path.abspath(name))
879 parent = os.path.dirname(os.path.abspath(name))
876 if parent == name:
880 if parent == name:
877 raise
881 raise
878 makedirs(parent, mode)
882 makedirs(parent, mode)
879 os.mkdir(name)
883 os.mkdir(name)
880 if mode is not None:
884 if mode is not None:
881 os.chmod(name, mode)
885 os.chmod(name, mode)
882
886
883 def ensuredirs(name, mode=None):
887 def ensuredirs(name, mode=None):
884 """race-safe recursive directory creation"""
888 """race-safe recursive directory creation"""
885 if os.path.isdir(name):
889 if os.path.isdir(name):
886 return
890 return
887 parent = os.path.dirname(os.path.abspath(name))
891 parent = os.path.dirname(os.path.abspath(name))
888 if parent != name:
892 if parent != name:
889 ensuredirs(parent, mode)
893 ensuredirs(parent, mode)
890 try:
894 try:
891 os.mkdir(name)
895 os.mkdir(name)
892 except OSError, err:
896 except OSError, err:
893 if err.errno == errno.EEXIST and os.path.isdir(name):
897 if err.errno == errno.EEXIST and os.path.isdir(name):
894 # someone else seems to have won a directory creation race
898 # someone else seems to have won a directory creation race
895 return
899 return
896 raise
900 raise
897 if mode is not None:
901 if mode is not None:
898 os.chmod(name, mode)
902 os.chmod(name, mode)
899
903
900 def readfile(path):
904 def readfile(path):
901 fp = open(path, 'rb')
905 fp = open(path, 'rb')
902 try:
906 try:
903 return fp.read()
907 return fp.read()
904 finally:
908 finally:
905 fp.close()
909 fp.close()
906
910
907 def writefile(path, text):
911 def writefile(path, text):
908 fp = open(path, 'wb')
912 fp = open(path, 'wb')
909 try:
913 try:
910 fp.write(text)
914 fp.write(text)
911 finally:
915 finally:
912 fp.close()
916 fp.close()
913
917
914 def appendfile(path, text):
918 def appendfile(path, text):
915 fp = open(path, 'ab')
919 fp = open(path, 'ab')
916 try:
920 try:
917 fp.write(text)
921 fp.write(text)
918 finally:
922 finally:
919 fp.close()
923 fp.close()
920
924
921 class chunkbuffer(object):
925 class chunkbuffer(object):
922 """Allow arbitrary sized chunks of data to be efficiently read from an
926 """Allow arbitrary sized chunks of data to be efficiently read from an
923 iterator over chunks of arbitrary size."""
927 iterator over chunks of arbitrary size."""
924
928
925 def __init__(self, in_iter):
929 def __init__(self, in_iter):
926 """in_iter is the iterator that's iterating over the input chunks.
930 """in_iter is the iterator that's iterating over the input chunks.
927 targetsize is how big a buffer to try to maintain."""
931 targetsize is how big a buffer to try to maintain."""
928 def splitbig(chunks):
932 def splitbig(chunks):
929 for chunk in chunks:
933 for chunk in chunks:
930 if len(chunk) > 2**20:
934 if len(chunk) > 2**20:
931 pos = 0
935 pos = 0
932 while pos < len(chunk):
936 while pos < len(chunk):
933 end = pos + 2 ** 18
937 end = pos + 2 ** 18
934 yield chunk[pos:end]
938 yield chunk[pos:end]
935 pos = end
939 pos = end
936 else:
940 else:
937 yield chunk
941 yield chunk
938 self.iter = splitbig(in_iter)
942 self.iter = splitbig(in_iter)
939 self._queue = deque()
943 self._queue = deque()
940
944
941 def read(self, l):
945 def read(self, l):
942 """Read L bytes of data from the iterator of chunks of data.
946 """Read L bytes of data from the iterator of chunks of data.
943 Returns less than L bytes if the iterator runs dry."""
947 Returns less than L bytes if the iterator runs dry."""
944 left = l
948 left = l
945 buf = []
949 buf = []
946 queue = self._queue
950 queue = self._queue
947 while left > 0:
951 while left > 0:
948 # refill the queue
952 # refill the queue
949 if not queue:
953 if not queue:
950 target = 2**18
954 target = 2**18
951 for chunk in self.iter:
955 for chunk in self.iter:
952 queue.append(chunk)
956 queue.append(chunk)
953 target -= len(chunk)
957 target -= len(chunk)
954 if target <= 0:
958 if target <= 0:
955 break
959 break
956 if not queue:
960 if not queue:
957 break
961 break
958
962
959 chunk = queue.popleft()
963 chunk = queue.popleft()
960 left -= len(chunk)
964 left -= len(chunk)
961 if left < 0:
965 if left < 0:
962 queue.appendleft(chunk[left:])
966 queue.appendleft(chunk[left:])
963 buf.append(chunk[:left])
967 buf.append(chunk[:left])
964 else:
968 else:
965 buf.append(chunk)
969 buf.append(chunk)
966
970
967 return ''.join(buf)
971 return ''.join(buf)
968
972
969 def filechunkiter(f, size=65536, limit=None):
973 def filechunkiter(f, size=65536, limit=None):
970 """Create a generator that produces the data in the file size
974 """Create a generator that produces the data in the file size
971 (default 65536) bytes at a time, up to optional limit (default is
975 (default 65536) bytes at a time, up to optional limit (default is
972 to read all data). Chunks may be less than size bytes if the
976 to read all data). Chunks may be less than size bytes if the
973 chunk is the last chunk in the file, or the file is a socket or
977 chunk is the last chunk in the file, or the file is a socket or
974 some other type of file that sometimes reads less data than is
978 some other type of file that sometimes reads less data than is
975 requested."""
979 requested."""
976 assert size >= 0
980 assert size >= 0
977 assert limit is None or limit >= 0
981 assert limit is None or limit >= 0
978 while True:
982 while True:
979 if limit is None:
983 if limit is None:
980 nbytes = size
984 nbytes = size
981 else:
985 else:
982 nbytes = min(limit, size)
986 nbytes = min(limit, size)
983 s = nbytes and f.read(nbytes)
987 s = nbytes and f.read(nbytes)
984 if not s:
988 if not s:
985 break
989 break
986 if limit:
990 if limit:
987 limit -= len(s)
991 limit -= len(s)
988 yield s
992 yield s
989
993
990 def makedate():
994 def makedate():
991 ct = time.time()
995 ct = time.time()
992 if ct < 0:
996 if ct < 0:
993 hint = _("check your clock")
997 hint = _("check your clock")
994 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
998 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
995 delta = (datetime.datetime.utcfromtimestamp(ct) -
999 delta = (datetime.datetime.utcfromtimestamp(ct) -
996 datetime.datetime.fromtimestamp(ct))
1000 datetime.datetime.fromtimestamp(ct))
997 tz = delta.days * 86400 + delta.seconds
1001 tz = delta.days * 86400 + delta.seconds
998 return ct, tz
1002 return ct, tz
999
1003
1000 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1004 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1001 """represent a (unixtime, offset) tuple as a localized time.
1005 """represent a (unixtime, offset) tuple as a localized time.
1002 unixtime is seconds since the epoch, and offset is the time zone's
1006 unixtime is seconds since the epoch, and offset is the time zone's
1003 number of seconds away from UTC. if timezone is false, do not
1007 number of seconds away from UTC. if timezone is false, do not
1004 append time zone to string."""
1008 append time zone to string."""
1005 t, tz = date or makedate()
1009 t, tz = date or makedate()
1006 if t < 0:
1010 if t < 0:
1007 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1011 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1008 tz = 0
1012 tz = 0
1009 if "%1" in format or "%2" in format:
1013 if "%1" in format or "%2" in format:
1010 sign = (tz > 0) and "-" or "+"
1014 sign = (tz > 0) and "-" or "+"
1011 minutes = abs(tz) // 60
1015 minutes = abs(tz) // 60
1012 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1016 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1013 format = format.replace("%2", "%02d" % (minutes % 60))
1017 format = format.replace("%2", "%02d" % (minutes % 60))
1014 try:
1018 try:
1015 t = time.gmtime(float(t) - tz)
1019 t = time.gmtime(float(t) - tz)
1016 except ValueError:
1020 except ValueError:
1017 # time was out of range
1021 # time was out of range
1018 t = time.gmtime(sys.maxint)
1022 t = time.gmtime(sys.maxint)
1019 s = time.strftime(format, t)
1023 s = time.strftime(format, t)
1020 return s
1024 return s
1021
1025
1022 def shortdate(date=None):
1026 def shortdate(date=None):
1023 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1027 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1024 return datestr(date, format='%Y-%m-%d')
1028 return datestr(date, format='%Y-%m-%d')
1025
1029
1026 def strdate(string, format, defaults=[]):
1030 def strdate(string, format, defaults=[]):
1027 """parse a localized time string and return a (unixtime, offset) tuple.
1031 """parse a localized time string and return a (unixtime, offset) tuple.
1028 if the string cannot be parsed, ValueError is raised."""
1032 if the string cannot be parsed, ValueError is raised."""
1029 def timezone(string):
1033 def timezone(string):
1030 tz = string.split()[-1]
1034 tz = string.split()[-1]
1031 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1035 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1032 sign = (tz[0] == "+") and 1 or -1
1036 sign = (tz[0] == "+") and 1 or -1
1033 hours = int(tz[1:3])
1037 hours = int(tz[1:3])
1034 minutes = int(tz[3:5])
1038 minutes = int(tz[3:5])
1035 return -sign * (hours * 60 + minutes) * 60
1039 return -sign * (hours * 60 + minutes) * 60
1036 if tz == "GMT" or tz == "UTC":
1040 if tz == "GMT" or tz == "UTC":
1037 return 0
1041 return 0
1038 return None
1042 return None
1039
1043
1040 # NOTE: unixtime = localunixtime + offset
1044 # NOTE: unixtime = localunixtime + offset
1041 offset, date = timezone(string), string
1045 offset, date = timezone(string), string
1042 if offset is not None:
1046 if offset is not None:
1043 date = " ".join(string.split()[:-1])
1047 date = " ".join(string.split()[:-1])
1044
1048
1045 # add missing elements from defaults
1049 # add missing elements from defaults
1046 usenow = False # default to using biased defaults
1050 usenow = False # default to using biased defaults
1047 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1051 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1048 found = [True for p in part if ("%"+p) in format]
1052 found = [True for p in part if ("%"+p) in format]
1049 if not found:
1053 if not found:
1050 date += "@" + defaults[part][usenow]
1054 date += "@" + defaults[part][usenow]
1051 format += "@%" + part[0]
1055 format += "@%" + part[0]
1052 else:
1056 else:
1053 # We've found a specific time element, less specific time
1057 # We've found a specific time element, less specific time
1054 # elements are relative to today
1058 # elements are relative to today
1055 usenow = True
1059 usenow = True
1056
1060
1057 timetuple = time.strptime(date, format)
1061 timetuple = time.strptime(date, format)
1058 localunixtime = int(calendar.timegm(timetuple))
1062 localunixtime = int(calendar.timegm(timetuple))
1059 if offset is None:
1063 if offset is None:
1060 # local timezone
1064 # local timezone
1061 unixtime = int(time.mktime(timetuple))
1065 unixtime = int(time.mktime(timetuple))
1062 offset = unixtime - localunixtime
1066 offset = unixtime - localunixtime
1063 else:
1067 else:
1064 unixtime = localunixtime + offset
1068 unixtime = localunixtime + offset
1065 return unixtime, offset
1069 return unixtime, offset
1066
1070
1067 def parsedate(date, formats=None, bias={}):
1071 def parsedate(date, formats=None, bias={}):
1068 """parse a localized date/time and return a (unixtime, offset) tuple.
1072 """parse a localized date/time and return a (unixtime, offset) tuple.
1069
1073
1070 The date may be a "unixtime offset" string or in one of the specified
1074 The date may be a "unixtime offset" string or in one of the specified
1071 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1075 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1072
1076
1073 >>> parsedate(' today ') == parsedate(\
1077 >>> parsedate(' today ') == parsedate(\
1074 datetime.date.today().strftime('%b %d'))
1078 datetime.date.today().strftime('%b %d'))
1075 True
1079 True
1076 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1080 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1077 datetime.timedelta(days=1)\
1081 datetime.timedelta(days=1)\
1078 ).strftime('%b %d'))
1082 ).strftime('%b %d'))
1079 True
1083 True
1080 >>> now, tz = makedate()
1084 >>> now, tz = makedate()
1081 >>> strnow, strtz = parsedate('now')
1085 >>> strnow, strtz = parsedate('now')
1082 >>> (strnow - now) < 1
1086 >>> (strnow - now) < 1
1083 True
1087 True
1084 >>> tz == strtz
1088 >>> tz == strtz
1085 True
1089 True
1086 """
1090 """
1087 if not date:
1091 if not date:
1088 return 0, 0
1092 return 0, 0
1089 if isinstance(date, tuple) and len(date) == 2:
1093 if isinstance(date, tuple) and len(date) == 2:
1090 return date
1094 return date
1091 if not formats:
1095 if not formats:
1092 formats = defaultdateformats
1096 formats = defaultdateformats
1093 date = date.strip()
1097 date = date.strip()
1094
1098
1095 if date == _('now'):
1099 if date == _('now'):
1096 return makedate()
1100 return makedate()
1097 if date == _('today'):
1101 if date == _('today'):
1098 date = datetime.date.today().strftime('%b %d')
1102 date = datetime.date.today().strftime('%b %d')
1099 elif date == _('yesterday'):
1103 elif date == _('yesterday'):
1100 date = (datetime.date.today() -
1104 date = (datetime.date.today() -
1101 datetime.timedelta(days=1)).strftime('%b %d')
1105 datetime.timedelta(days=1)).strftime('%b %d')
1102
1106
1103 try:
1107 try:
1104 when, offset = map(int, date.split(' '))
1108 when, offset = map(int, date.split(' '))
1105 except ValueError:
1109 except ValueError:
1106 # fill out defaults
1110 # fill out defaults
1107 now = makedate()
1111 now = makedate()
1108 defaults = {}
1112 defaults = {}
1109 for part in ("d", "mb", "yY", "HI", "M", "S"):
1113 for part in ("d", "mb", "yY", "HI", "M", "S"):
1110 # this piece is for rounding the specific end of unknowns
1114 # this piece is for rounding the specific end of unknowns
1111 b = bias.get(part)
1115 b = bias.get(part)
1112 if b is None:
1116 if b is None:
1113 if part[0] in "HMS":
1117 if part[0] in "HMS":
1114 b = "00"
1118 b = "00"
1115 else:
1119 else:
1116 b = "0"
1120 b = "0"
1117
1121
1118 # this piece is for matching the generic end to today's date
1122 # this piece is for matching the generic end to today's date
1119 n = datestr(now, "%" + part[0])
1123 n = datestr(now, "%" + part[0])
1120
1124
1121 defaults[part] = (b, n)
1125 defaults[part] = (b, n)
1122
1126
1123 for format in formats:
1127 for format in formats:
1124 try:
1128 try:
1125 when, offset = strdate(date, format, defaults)
1129 when, offset = strdate(date, format, defaults)
1126 except (ValueError, OverflowError):
1130 except (ValueError, OverflowError):
1127 pass
1131 pass
1128 else:
1132 else:
1129 break
1133 break
1130 else:
1134 else:
1131 raise Abort(_('invalid date: %r') % date)
1135 raise Abort(_('invalid date: %r') % date)
1132 # validate explicit (probably user-specified) date and
1136 # validate explicit (probably user-specified) date and
1133 # time zone offset. values must fit in signed 32 bits for
1137 # time zone offset. values must fit in signed 32 bits for
1134 # current 32-bit linux runtimes. timezones go from UTC-12
1138 # current 32-bit linux runtimes. timezones go from UTC-12
1135 # to UTC+14
1139 # to UTC+14
1136 if abs(when) > 0x7fffffff:
1140 if abs(when) > 0x7fffffff:
1137 raise Abort(_('date exceeds 32 bits: %d') % when)
1141 raise Abort(_('date exceeds 32 bits: %d') % when)
1138 if when < 0:
1142 if when < 0:
1139 raise Abort(_('negative date value: %d') % when)
1143 raise Abort(_('negative date value: %d') % when)
1140 if offset < -50400 or offset > 43200:
1144 if offset < -50400 or offset > 43200:
1141 raise Abort(_('impossible time zone offset: %d') % offset)
1145 raise Abort(_('impossible time zone offset: %d') % offset)
1142 return when, offset
1146 return when, offset
1143
1147
1144 def matchdate(date):
1148 def matchdate(date):
1145 """Return a function that matches a given date match specifier
1149 """Return a function that matches a given date match specifier
1146
1150
1147 Formats include:
1151 Formats include:
1148
1152
1149 '{date}' match a given date to the accuracy provided
1153 '{date}' match a given date to the accuracy provided
1150
1154
1151 '<{date}' on or before a given date
1155 '<{date}' on or before a given date
1152
1156
1153 '>{date}' on or after a given date
1157 '>{date}' on or after a given date
1154
1158
1155 >>> p1 = parsedate("10:29:59")
1159 >>> p1 = parsedate("10:29:59")
1156 >>> p2 = parsedate("10:30:00")
1160 >>> p2 = parsedate("10:30:00")
1157 >>> p3 = parsedate("10:30:59")
1161 >>> p3 = parsedate("10:30:59")
1158 >>> p4 = parsedate("10:31:00")
1162 >>> p4 = parsedate("10:31:00")
1159 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1163 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1160 >>> f = matchdate("10:30")
1164 >>> f = matchdate("10:30")
1161 >>> f(p1[0])
1165 >>> f(p1[0])
1162 False
1166 False
1163 >>> f(p2[0])
1167 >>> f(p2[0])
1164 True
1168 True
1165 >>> f(p3[0])
1169 >>> f(p3[0])
1166 True
1170 True
1167 >>> f(p4[0])
1171 >>> f(p4[0])
1168 False
1172 False
1169 >>> f(p5[0])
1173 >>> f(p5[0])
1170 False
1174 False
1171 """
1175 """
1172
1176
1173 def lower(date):
1177 def lower(date):
1174 d = dict(mb="1", d="1")
1178 d = dict(mb="1", d="1")
1175 return parsedate(date, extendeddateformats, d)[0]
1179 return parsedate(date, extendeddateformats, d)[0]
1176
1180
1177 def upper(date):
1181 def upper(date):
1178 d = dict(mb="12", HI="23", M="59", S="59")
1182 d = dict(mb="12", HI="23", M="59", S="59")
1179 for days in ("31", "30", "29"):
1183 for days in ("31", "30", "29"):
1180 try:
1184 try:
1181 d["d"] = days
1185 d["d"] = days
1182 return parsedate(date, extendeddateformats, d)[0]
1186 return parsedate(date, extendeddateformats, d)[0]
1183 except Abort:
1187 except Abort:
1184 pass
1188 pass
1185 d["d"] = "28"
1189 d["d"] = "28"
1186 return parsedate(date, extendeddateformats, d)[0]
1190 return parsedate(date, extendeddateformats, d)[0]
1187
1191
1188 date = date.strip()
1192 date = date.strip()
1189
1193
1190 if not date:
1194 if not date:
1191 raise Abort(_("dates cannot consist entirely of whitespace"))
1195 raise Abort(_("dates cannot consist entirely of whitespace"))
1192 elif date[0] == "<":
1196 elif date[0] == "<":
1193 if not date[1:]:
1197 if not date[1:]:
1194 raise Abort(_("invalid day spec, use '<DATE'"))
1198 raise Abort(_("invalid day spec, use '<DATE'"))
1195 when = upper(date[1:])
1199 when = upper(date[1:])
1196 return lambda x: x <= when
1200 return lambda x: x <= when
1197 elif date[0] == ">":
1201 elif date[0] == ">":
1198 if not date[1:]:
1202 if not date[1:]:
1199 raise Abort(_("invalid day spec, use '>DATE'"))
1203 raise Abort(_("invalid day spec, use '>DATE'"))
1200 when = lower(date[1:])
1204 when = lower(date[1:])
1201 return lambda x: x >= when
1205 return lambda x: x >= when
1202 elif date[0] == "-":
1206 elif date[0] == "-":
1203 try:
1207 try:
1204 days = int(date[1:])
1208 days = int(date[1:])
1205 except ValueError:
1209 except ValueError:
1206 raise Abort(_("invalid day spec: %s") % date[1:])
1210 raise Abort(_("invalid day spec: %s") % date[1:])
1207 if days < 0:
1211 if days < 0:
1208 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1212 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1209 % date[1:])
1213 % date[1:])
1210 when = makedate()[0] - days * 3600 * 24
1214 when = makedate()[0] - days * 3600 * 24
1211 return lambda x: x >= when
1215 return lambda x: x >= when
1212 elif " to " in date:
1216 elif " to " in date:
1213 a, b = date.split(" to ")
1217 a, b = date.split(" to ")
1214 start, stop = lower(a), upper(b)
1218 start, stop = lower(a), upper(b)
1215 return lambda x: x >= start and x <= stop
1219 return lambda x: x >= start and x <= stop
1216 else:
1220 else:
1217 start, stop = lower(date), upper(date)
1221 start, stop = lower(date), upper(date)
1218 return lambda x: x >= start and x <= stop
1222 return lambda x: x >= start and x <= stop
1219
1223
1220 def shortuser(user):
1224 def shortuser(user):
1221 """Return a short representation of a user name or email address."""
1225 """Return a short representation of a user name or email address."""
1222 f = user.find('@')
1226 f = user.find('@')
1223 if f >= 0:
1227 if f >= 0:
1224 user = user[:f]
1228 user = user[:f]
1225 f = user.find('<')
1229 f = user.find('<')
1226 if f >= 0:
1230 if f >= 0:
1227 user = user[f + 1:]
1231 user = user[f + 1:]
1228 f = user.find(' ')
1232 f = user.find(' ')
1229 if f >= 0:
1233 if f >= 0:
1230 user = user[:f]
1234 user = user[:f]
1231 f = user.find('.')
1235 f = user.find('.')
1232 if f >= 0:
1236 if f >= 0:
1233 user = user[:f]
1237 user = user[:f]
1234 return user
1238 return user
1235
1239
1236 def emailuser(user):
1240 def emailuser(user):
1237 """Return the user portion of an email address."""
1241 """Return the user portion of an email address."""
1238 f = user.find('@')
1242 f = user.find('@')
1239 if f >= 0:
1243 if f >= 0:
1240 user = user[:f]
1244 user = user[:f]
1241 f = user.find('<')
1245 f = user.find('<')
1242 if f >= 0:
1246 if f >= 0:
1243 user = user[f + 1:]
1247 user = user[f + 1:]
1244 return user
1248 return user
1245
1249
1246 def email(author):
1250 def email(author):
1247 '''get email of author.'''
1251 '''get email of author.'''
1248 r = author.find('>')
1252 r = author.find('>')
1249 if r == -1:
1253 if r == -1:
1250 r = None
1254 r = None
1251 return author[author.find('<') + 1:r]
1255 return author[author.find('<') + 1:r]
1252
1256
1253 def _ellipsis(text, maxlength):
1257 def _ellipsis(text, maxlength):
1254 if len(text) <= maxlength:
1258 if len(text) <= maxlength:
1255 return text, False
1259 return text, False
1256 else:
1260 else:
1257 return "%s..." % (text[:maxlength - 3]), True
1261 return "%s..." % (text[:maxlength - 3]), True
1258
1262
1259 def ellipsis(text, maxlength=400):
1263 def ellipsis(text, maxlength=400):
1260 """Trim string to at most maxlength (default: 400) characters."""
1264 """Trim string to at most maxlength (default: 400) characters."""
1261 try:
1265 try:
1262 # use unicode not to split at intermediate multi-byte sequence
1266 # use unicode not to split at intermediate multi-byte sequence
1263 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1267 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1264 maxlength)
1268 maxlength)
1265 if not truncated:
1269 if not truncated:
1266 return text
1270 return text
1267 return utext.encode(encoding.encoding)
1271 return utext.encode(encoding.encoding)
1268 except (UnicodeDecodeError, UnicodeEncodeError):
1272 except (UnicodeDecodeError, UnicodeEncodeError):
1269 return _ellipsis(text, maxlength)[0]
1273 return _ellipsis(text, maxlength)[0]
1270
1274
1271 def unitcountfn(*unittable):
1275 def unitcountfn(*unittable):
1272 '''return a function that renders a readable count of some quantity'''
1276 '''return a function that renders a readable count of some quantity'''
1273
1277
1274 def go(count):
1278 def go(count):
1275 for multiplier, divisor, format in unittable:
1279 for multiplier, divisor, format in unittable:
1276 if count >= divisor * multiplier:
1280 if count >= divisor * multiplier:
1277 return format % (count / float(divisor))
1281 return format % (count / float(divisor))
1278 return unittable[-1][2] % count
1282 return unittable[-1][2] % count
1279
1283
1280 return go
1284 return go
1281
1285
1282 bytecount = unitcountfn(
1286 bytecount = unitcountfn(
1283 (100, 1 << 30, _('%.0f GB')),
1287 (100, 1 << 30, _('%.0f GB')),
1284 (10, 1 << 30, _('%.1f GB')),
1288 (10, 1 << 30, _('%.1f GB')),
1285 (1, 1 << 30, _('%.2f GB')),
1289 (1, 1 << 30, _('%.2f GB')),
1286 (100, 1 << 20, _('%.0f MB')),
1290 (100, 1 << 20, _('%.0f MB')),
1287 (10, 1 << 20, _('%.1f MB')),
1291 (10, 1 << 20, _('%.1f MB')),
1288 (1, 1 << 20, _('%.2f MB')),
1292 (1, 1 << 20, _('%.2f MB')),
1289 (100, 1 << 10, _('%.0f KB')),
1293 (100, 1 << 10, _('%.0f KB')),
1290 (10, 1 << 10, _('%.1f KB')),
1294 (10, 1 << 10, _('%.1f KB')),
1291 (1, 1 << 10, _('%.2f KB')),
1295 (1, 1 << 10, _('%.2f KB')),
1292 (1, 1, _('%.0f bytes')),
1296 (1, 1, _('%.0f bytes')),
1293 )
1297 )
1294
1298
1295 def uirepr(s):
1299 def uirepr(s):
1296 # Avoid double backslash in Windows path repr()
1300 # Avoid double backslash in Windows path repr()
1297 return repr(s).replace('\\\\', '\\')
1301 return repr(s).replace('\\\\', '\\')
1298
1302
1299 # delay import of textwrap
1303 # delay import of textwrap
1300 def MBTextWrapper(**kwargs):
1304 def MBTextWrapper(**kwargs):
1301 class tw(textwrap.TextWrapper):
1305 class tw(textwrap.TextWrapper):
1302 """
1306 """
1303 Extend TextWrapper for width-awareness.
1307 Extend TextWrapper for width-awareness.
1304
1308
1305 Neither number of 'bytes' in any encoding nor 'characters' is
1309 Neither number of 'bytes' in any encoding nor 'characters' is
1306 appropriate to calculate terminal columns for specified string.
1310 appropriate to calculate terminal columns for specified string.
1307
1311
1308 Original TextWrapper implementation uses built-in 'len()' directly,
1312 Original TextWrapper implementation uses built-in 'len()' directly,
1309 so overriding is needed to use width information of each characters.
1313 so overriding is needed to use width information of each characters.
1310
1314
1311 In addition, characters classified into 'ambiguous' width are
1315 In addition, characters classified into 'ambiguous' width are
1312 treated as wide in East Asian area, but as narrow in other.
1316 treated as wide in East Asian area, but as narrow in other.
1313
1317
1314 This requires use decision to determine width of such characters.
1318 This requires use decision to determine width of such characters.
1315 """
1319 """
1316 def __init__(self, **kwargs):
1320 def __init__(self, **kwargs):
1317 textwrap.TextWrapper.__init__(self, **kwargs)
1321 textwrap.TextWrapper.__init__(self, **kwargs)
1318
1322
1319 # for compatibility between 2.4 and 2.6
1323 # for compatibility between 2.4 and 2.6
1320 if getattr(self, 'drop_whitespace', None) is None:
1324 if getattr(self, 'drop_whitespace', None) is None:
1321 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1325 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1322
1326
1323 def _cutdown(self, ucstr, space_left):
1327 def _cutdown(self, ucstr, space_left):
1324 l = 0
1328 l = 0
1325 colwidth = encoding.ucolwidth
1329 colwidth = encoding.ucolwidth
1326 for i in xrange(len(ucstr)):
1330 for i in xrange(len(ucstr)):
1327 l += colwidth(ucstr[i])
1331 l += colwidth(ucstr[i])
1328 if space_left < l:
1332 if space_left < l:
1329 return (ucstr[:i], ucstr[i:])
1333 return (ucstr[:i], ucstr[i:])
1330 return ucstr, ''
1334 return ucstr, ''
1331
1335
1332 # overriding of base class
1336 # overriding of base class
1333 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1337 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1334 space_left = max(width - cur_len, 1)
1338 space_left = max(width - cur_len, 1)
1335
1339
1336 if self.break_long_words:
1340 if self.break_long_words:
1337 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1341 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1338 cur_line.append(cut)
1342 cur_line.append(cut)
1339 reversed_chunks[-1] = res
1343 reversed_chunks[-1] = res
1340 elif not cur_line:
1344 elif not cur_line:
1341 cur_line.append(reversed_chunks.pop())
1345 cur_line.append(reversed_chunks.pop())
1342
1346
1343 # this overriding code is imported from TextWrapper of python 2.6
1347 # this overriding code is imported from TextWrapper of python 2.6
1344 # to calculate columns of string by 'encoding.ucolwidth()'
1348 # to calculate columns of string by 'encoding.ucolwidth()'
1345 def _wrap_chunks(self, chunks):
1349 def _wrap_chunks(self, chunks):
1346 colwidth = encoding.ucolwidth
1350 colwidth = encoding.ucolwidth
1347
1351
1348 lines = []
1352 lines = []
1349 if self.width <= 0:
1353 if self.width <= 0:
1350 raise ValueError("invalid width %r (must be > 0)" % self.width)
1354 raise ValueError("invalid width %r (must be > 0)" % self.width)
1351
1355
1352 # Arrange in reverse order so items can be efficiently popped
1356 # Arrange in reverse order so items can be efficiently popped
1353 # from a stack of chucks.
1357 # from a stack of chucks.
1354 chunks.reverse()
1358 chunks.reverse()
1355
1359
1356 while chunks:
1360 while chunks:
1357
1361
1358 # Start the list of chunks that will make up the current line.
1362 # Start the list of chunks that will make up the current line.
1359 # cur_len is just the length of all the chunks in cur_line.
1363 # cur_len is just the length of all the chunks in cur_line.
1360 cur_line = []
1364 cur_line = []
1361 cur_len = 0
1365 cur_len = 0
1362
1366
1363 # Figure out which static string will prefix this line.
1367 # Figure out which static string will prefix this line.
1364 if lines:
1368 if lines:
1365 indent = self.subsequent_indent
1369 indent = self.subsequent_indent
1366 else:
1370 else:
1367 indent = self.initial_indent
1371 indent = self.initial_indent
1368
1372
1369 # Maximum width for this line.
1373 # Maximum width for this line.
1370 width = self.width - len(indent)
1374 width = self.width - len(indent)
1371
1375
1372 # First chunk on line is whitespace -- drop it, unless this
1376 # First chunk on line is whitespace -- drop it, unless this
1373 # is the very beginning of the text (i.e. no lines started yet).
1377 # is the very beginning of the text (i.e. no lines started yet).
1374 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1378 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1375 del chunks[-1]
1379 del chunks[-1]
1376
1380
1377 while chunks:
1381 while chunks:
1378 l = colwidth(chunks[-1])
1382 l = colwidth(chunks[-1])
1379
1383
1380 # Can at least squeeze this chunk onto the current line.
1384 # Can at least squeeze this chunk onto the current line.
1381 if cur_len + l <= width:
1385 if cur_len + l <= width:
1382 cur_line.append(chunks.pop())
1386 cur_line.append(chunks.pop())
1383 cur_len += l
1387 cur_len += l
1384
1388
1385 # Nope, this line is full.
1389 # Nope, this line is full.
1386 else:
1390 else:
1387 break
1391 break
1388
1392
1389 # The current line is full, and the next chunk is too big to
1393 # The current line is full, and the next chunk is too big to
1390 # fit on *any* line (not just this one).
1394 # fit on *any* line (not just this one).
1391 if chunks and colwidth(chunks[-1]) > width:
1395 if chunks and colwidth(chunks[-1]) > width:
1392 self._handle_long_word(chunks, cur_line, cur_len, width)
1396 self._handle_long_word(chunks, cur_line, cur_len, width)
1393
1397
1394 # If the last chunk on this line is all whitespace, drop it.
1398 # If the last chunk on this line is all whitespace, drop it.
1395 if (self.drop_whitespace and
1399 if (self.drop_whitespace and
1396 cur_line and cur_line[-1].strip() == ''):
1400 cur_line and cur_line[-1].strip() == ''):
1397 del cur_line[-1]
1401 del cur_line[-1]
1398
1402
1399 # Convert current line back to a string and store it in list
1403 # Convert current line back to a string and store it in list
1400 # of all lines (return value).
1404 # of all lines (return value).
1401 if cur_line:
1405 if cur_line:
1402 lines.append(indent + ''.join(cur_line))
1406 lines.append(indent + ''.join(cur_line))
1403
1407
1404 return lines
1408 return lines
1405
1409
1406 global MBTextWrapper
1410 global MBTextWrapper
1407 MBTextWrapper = tw
1411 MBTextWrapper = tw
1408 return tw(**kwargs)
1412 return tw(**kwargs)
1409
1413
1410 def wrap(line, width, initindent='', hangindent=''):
1414 def wrap(line, width, initindent='', hangindent=''):
1411 maxindent = max(len(hangindent), len(initindent))
1415 maxindent = max(len(hangindent), len(initindent))
1412 if width <= maxindent:
1416 if width <= maxindent:
1413 # adjust for weird terminal size
1417 # adjust for weird terminal size
1414 width = max(78, maxindent + 1)
1418 width = max(78, maxindent + 1)
1415 line = line.decode(encoding.encoding, encoding.encodingmode)
1419 line = line.decode(encoding.encoding, encoding.encodingmode)
1416 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1420 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1417 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1421 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1418 wrapper = MBTextWrapper(width=width,
1422 wrapper = MBTextWrapper(width=width,
1419 initial_indent=initindent,
1423 initial_indent=initindent,
1420 subsequent_indent=hangindent)
1424 subsequent_indent=hangindent)
1421 return wrapper.fill(line).encode(encoding.encoding)
1425 return wrapper.fill(line).encode(encoding.encoding)
1422
1426
1423 def iterlines(iterator):
1427 def iterlines(iterator):
1424 for chunk in iterator:
1428 for chunk in iterator:
1425 for line in chunk.splitlines():
1429 for line in chunk.splitlines():
1426 yield line
1430 yield line
1427
1431
1428 def expandpath(path):
1432 def expandpath(path):
1429 return os.path.expanduser(os.path.expandvars(path))
1433 return os.path.expanduser(os.path.expandvars(path))
1430
1434
1431 def hgcmd():
1435 def hgcmd():
1432 """Return the command used to execute current hg
1436 """Return the command used to execute current hg
1433
1437
1434 This is different from hgexecutable() because on Windows we want
1438 This is different from hgexecutable() because on Windows we want
1435 to avoid things opening new shell windows like batch files, so we
1439 to avoid things opening new shell windows like batch files, so we
1436 get either the python call or current executable.
1440 get either the python call or current executable.
1437 """
1441 """
1438 if mainfrozen():
1442 if mainfrozen():
1439 return [sys.executable]
1443 return [sys.executable]
1440 return gethgcmd()
1444 return gethgcmd()
1441
1445
1442 def rundetached(args, condfn):
1446 def rundetached(args, condfn):
1443 """Execute the argument list in a detached process.
1447 """Execute the argument list in a detached process.
1444
1448
1445 condfn is a callable which is called repeatedly and should return
1449 condfn is a callable which is called repeatedly and should return
1446 True once the child process is known to have started successfully.
1450 True once the child process is known to have started successfully.
1447 At this point, the child process PID is returned. If the child
1451 At this point, the child process PID is returned. If the child
1448 process fails to start or finishes before condfn() evaluates to
1452 process fails to start or finishes before condfn() evaluates to
1449 True, return -1.
1453 True, return -1.
1450 """
1454 """
1451 # Windows case is easier because the child process is either
1455 # Windows case is easier because the child process is either
1452 # successfully starting and validating the condition or exiting
1456 # successfully starting and validating the condition or exiting
1453 # on failure. We just poll on its PID. On Unix, if the child
1457 # on failure. We just poll on its PID. On Unix, if the child
1454 # process fails to start, it will be left in a zombie state until
1458 # process fails to start, it will be left in a zombie state until
1455 # the parent wait on it, which we cannot do since we expect a long
1459 # the parent wait on it, which we cannot do since we expect a long
1456 # running process on success. Instead we listen for SIGCHLD telling
1460 # running process on success. Instead we listen for SIGCHLD telling
1457 # us our child process terminated.
1461 # us our child process terminated.
1458 terminated = set()
1462 terminated = set()
1459 def handler(signum, frame):
1463 def handler(signum, frame):
1460 terminated.add(os.wait())
1464 terminated.add(os.wait())
1461 prevhandler = None
1465 prevhandler = None
1462 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1466 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1463 if SIGCHLD is not None:
1467 if SIGCHLD is not None:
1464 prevhandler = signal.signal(SIGCHLD, handler)
1468 prevhandler = signal.signal(SIGCHLD, handler)
1465 try:
1469 try:
1466 pid = spawndetached(args)
1470 pid = spawndetached(args)
1467 while not condfn():
1471 while not condfn():
1468 if ((pid in terminated or not testpid(pid))
1472 if ((pid in terminated or not testpid(pid))
1469 and not condfn()):
1473 and not condfn()):
1470 return -1
1474 return -1
1471 time.sleep(0.1)
1475 time.sleep(0.1)
1472 return pid
1476 return pid
1473 finally:
1477 finally:
1474 if prevhandler is not None:
1478 if prevhandler is not None:
1475 signal.signal(signal.SIGCHLD, prevhandler)
1479 signal.signal(signal.SIGCHLD, prevhandler)
1476
1480
1477 try:
1481 try:
1478 any, all = any, all
1482 any, all = any, all
1479 except NameError:
1483 except NameError:
1480 def any(iterable):
1484 def any(iterable):
1481 for i in iterable:
1485 for i in iterable:
1482 if i:
1486 if i:
1483 return True
1487 return True
1484 return False
1488 return False
1485
1489
1486 def all(iterable):
1490 def all(iterable):
1487 for i in iterable:
1491 for i in iterable:
1488 if not i:
1492 if not i:
1489 return False
1493 return False
1490 return True
1494 return True
1491
1495
1492 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1496 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1493 """Return the result of interpolating items in the mapping into string s.
1497 """Return the result of interpolating items in the mapping into string s.
1494
1498
1495 prefix is a single character string, or a two character string with
1499 prefix is a single character string, or a two character string with
1496 a backslash as the first character if the prefix needs to be escaped in
1500 a backslash as the first character if the prefix needs to be escaped in
1497 a regular expression.
1501 a regular expression.
1498
1502
1499 fn is an optional function that will be applied to the replacement text
1503 fn is an optional function that will be applied to the replacement text
1500 just before replacement.
1504 just before replacement.
1501
1505
1502 escape_prefix is an optional flag that allows using doubled prefix for
1506 escape_prefix is an optional flag that allows using doubled prefix for
1503 its escaping.
1507 its escaping.
1504 """
1508 """
1505 fn = fn or (lambda s: s)
1509 fn = fn or (lambda s: s)
1506 patterns = '|'.join(mapping.keys())
1510 patterns = '|'.join(mapping.keys())
1507 if escape_prefix:
1511 if escape_prefix:
1508 patterns += '|' + prefix
1512 patterns += '|' + prefix
1509 if len(prefix) > 1:
1513 if len(prefix) > 1:
1510 prefix_char = prefix[1:]
1514 prefix_char = prefix[1:]
1511 else:
1515 else:
1512 prefix_char = prefix
1516 prefix_char = prefix
1513 mapping[prefix_char] = prefix_char
1517 mapping[prefix_char] = prefix_char
1514 r = re.compile(r'%s(%s)' % (prefix, patterns))
1518 r = re.compile(r'%s(%s)' % (prefix, patterns))
1515 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1519 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1516
1520
1517 def getport(port):
1521 def getport(port):
1518 """Return the port for a given network service.
1522 """Return the port for a given network service.
1519
1523
1520 If port is an integer, it's returned as is. If it's a string, it's
1524 If port is an integer, it's returned as is. If it's a string, it's
1521 looked up using socket.getservbyname(). If there's no matching
1525 looked up using socket.getservbyname(). If there's no matching
1522 service, util.Abort is raised.
1526 service, util.Abort is raised.
1523 """
1527 """
1524 try:
1528 try:
1525 return int(port)
1529 return int(port)
1526 except ValueError:
1530 except ValueError:
1527 pass
1531 pass
1528
1532
1529 try:
1533 try:
1530 return socket.getservbyname(port)
1534 return socket.getservbyname(port)
1531 except socket.error:
1535 except socket.error:
1532 raise Abort(_("no port number associated with service '%s'") % port)
1536 raise Abort(_("no port number associated with service '%s'") % port)
1533
1537
1534 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1538 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1535 '0': False, 'no': False, 'false': False, 'off': False,
1539 '0': False, 'no': False, 'false': False, 'off': False,
1536 'never': False}
1540 'never': False}
1537
1541
1538 def parsebool(s):
1542 def parsebool(s):
1539 """Parse s into a boolean.
1543 """Parse s into a boolean.
1540
1544
1541 If s is not a valid boolean, returns None.
1545 If s is not a valid boolean, returns None.
1542 """
1546 """
1543 return _booleans.get(s.lower(), None)
1547 return _booleans.get(s.lower(), None)
1544
1548
1545 _hexdig = '0123456789ABCDEFabcdef'
1549 _hexdig = '0123456789ABCDEFabcdef'
1546 _hextochr = dict((a + b, chr(int(a + b, 16)))
1550 _hextochr = dict((a + b, chr(int(a + b, 16)))
1547 for a in _hexdig for b in _hexdig)
1551 for a in _hexdig for b in _hexdig)
1548
1552
1549 def _urlunquote(s):
1553 def _urlunquote(s):
1550 """Decode HTTP/HTML % encoding.
1554 """Decode HTTP/HTML % encoding.
1551
1555
1552 >>> _urlunquote('abc%20def')
1556 >>> _urlunquote('abc%20def')
1553 'abc def'
1557 'abc def'
1554 """
1558 """
1555 res = s.split('%')
1559 res = s.split('%')
1556 # fastpath
1560 # fastpath
1557 if len(res) == 1:
1561 if len(res) == 1:
1558 return s
1562 return s
1559 s = res[0]
1563 s = res[0]
1560 for item in res[1:]:
1564 for item in res[1:]:
1561 try:
1565 try:
1562 s += _hextochr[item[:2]] + item[2:]
1566 s += _hextochr[item[:2]] + item[2:]
1563 except KeyError:
1567 except KeyError:
1564 s += '%' + item
1568 s += '%' + item
1565 except UnicodeDecodeError:
1569 except UnicodeDecodeError:
1566 s += unichr(int(item[:2], 16)) + item[2:]
1570 s += unichr(int(item[:2], 16)) + item[2:]
1567 return s
1571 return s
1568
1572
1569 class url(object):
1573 class url(object):
1570 r"""Reliable URL parser.
1574 r"""Reliable URL parser.
1571
1575
1572 This parses URLs and provides attributes for the following
1576 This parses URLs and provides attributes for the following
1573 components:
1577 components:
1574
1578
1575 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1579 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1576
1580
1577 Missing components are set to None. The only exception is
1581 Missing components are set to None. The only exception is
1578 fragment, which is set to '' if present but empty.
1582 fragment, which is set to '' if present but empty.
1579
1583
1580 If parsefragment is False, fragment is included in query. If
1584 If parsefragment is False, fragment is included in query. If
1581 parsequery is False, query is included in path. If both are
1585 parsequery is False, query is included in path. If both are
1582 False, both fragment and query are included in path.
1586 False, both fragment and query are included in path.
1583
1587
1584 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1588 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1585
1589
1586 Note that for backward compatibility reasons, bundle URLs do not
1590 Note that for backward compatibility reasons, bundle URLs do not
1587 take host names. That means 'bundle://../' has a path of '../'.
1591 take host names. That means 'bundle://../' has a path of '../'.
1588
1592
1589 Examples:
1593 Examples:
1590
1594
1591 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1595 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1592 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1596 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1593 >>> url('ssh://[::1]:2200//home/joe/repo')
1597 >>> url('ssh://[::1]:2200//home/joe/repo')
1594 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1598 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1595 >>> url('file:///home/joe/repo')
1599 >>> url('file:///home/joe/repo')
1596 <url scheme: 'file', path: '/home/joe/repo'>
1600 <url scheme: 'file', path: '/home/joe/repo'>
1597 >>> url('file:///c:/temp/foo/')
1601 >>> url('file:///c:/temp/foo/')
1598 <url scheme: 'file', path: 'c:/temp/foo/'>
1602 <url scheme: 'file', path: 'c:/temp/foo/'>
1599 >>> url('bundle:foo')
1603 >>> url('bundle:foo')
1600 <url scheme: 'bundle', path: 'foo'>
1604 <url scheme: 'bundle', path: 'foo'>
1601 >>> url('bundle://../foo')
1605 >>> url('bundle://../foo')
1602 <url scheme: 'bundle', path: '../foo'>
1606 <url scheme: 'bundle', path: '../foo'>
1603 >>> url(r'c:\foo\bar')
1607 >>> url(r'c:\foo\bar')
1604 <url path: 'c:\\foo\\bar'>
1608 <url path: 'c:\\foo\\bar'>
1605 >>> url(r'\\blah\blah\blah')
1609 >>> url(r'\\blah\blah\blah')
1606 <url path: '\\\\blah\\blah\\blah'>
1610 <url path: '\\\\blah\\blah\\blah'>
1607 >>> url(r'\\blah\blah\blah#baz')
1611 >>> url(r'\\blah\blah\blah#baz')
1608 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1612 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1609
1613
1610 Authentication credentials:
1614 Authentication credentials:
1611
1615
1612 >>> url('ssh://joe:xyz@x/repo')
1616 >>> url('ssh://joe:xyz@x/repo')
1613 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1617 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1614 >>> url('ssh://joe@x/repo')
1618 >>> url('ssh://joe@x/repo')
1615 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1619 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1616
1620
1617 Query strings and fragments:
1621 Query strings and fragments:
1618
1622
1619 >>> url('http://host/a?b#c')
1623 >>> url('http://host/a?b#c')
1620 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1624 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1621 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1625 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1622 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1626 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1623 """
1627 """
1624
1628
1625 _safechars = "!~*'()+"
1629 _safechars = "!~*'()+"
1626 _safepchars = "/!~*'()+:"
1630 _safepchars = "/!~*'()+:"
1627 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1631 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1628
1632
1629 def __init__(self, path, parsequery=True, parsefragment=True):
1633 def __init__(self, path, parsequery=True, parsefragment=True):
1630 # We slowly chomp away at path until we have only the path left
1634 # We slowly chomp away at path until we have only the path left
1631 self.scheme = self.user = self.passwd = self.host = None
1635 self.scheme = self.user = self.passwd = self.host = None
1632 self.port = self.path = self.query = self.fragment = None
1636 self.port = self.path = self.query = self.fragment = None
1633 self._localpath = True
1637 self._localpath = True
1634 self._hostport = ''
1638 self._hostport = ''
1635 self._origpath = path
1639 self._origpath = path
1636
1640
1637 if parsefragment and '#' in path:
1641 if parsefragment and '#' in path:
1638 path, self.fragment = path.split('#', 1)
1642 path, self.fragment = path.split('#', 1)
1639 if not path:
1643 if not path:
1640 path = None
1644 path = None
1641
1645
1642 # special case for Windows drive letters and UNC paths
1646 # special case for Windows drive letters and UNC paths
1643 if hasdriveletter(path) or path.startswith(r'\\'):
1647 if hasdriveletter(path) or path.startswith(r'\\'):
1644 self.path = path
1648 self.path = path
1645 return
1649 return
1646
1650
1647 # For compatibility reasons, we can't handle bundle paths as
1651 # For compatibility reasons, we can't handle bundle paths as
1648 # normal URLS
1652 # normal URLS
1649 if path.startswith('bundle:'):
1653 if path.startswith('bundle:'):
1650 self.scheme = 'bundle'
1654 self.scheme = 'bundle'
1651 path = path[7:]
1655 path = path[7:]
1652 if path.startswith('//'):
1656 if path.startswith('//'):
1653 path = path[2:]
1657 path = path[2:]
1654 self.path = path
1658 self.path = path
1655 return
1659 return
1656
1660
1657 if self._matchscheme(path):
1661 if self._matchscheme(path):
1658 parts = path.split(':', 1)
1662 parts = path.split(':', 1)
1659 if parts[0]:
1663 if parts[0]:
1660 self.scheme, path = parts
1664 self.scheme, path = parts
1661 self._localpath = False
1665 self._localpath = False
1662
1666
1663 if not path:
1667 if not path:
1664 path = None
1668 path = None
1665 if self._localpath:
1669 if self._localpath:
1666 self.path = ''
1670 self.path = ''
1667 return
1671 return
1668 else:
1672 else:
1669 if self._localpath:
1673 if self._localpath:
1670 self.path = path
1674 self.path = path
1671 return
1675 return
1672
1676
1673 if parsequery and '?' in path:
1677 if parsequery and '?' in path:
1674 path, self.query = path.split('?', 1)
1678 path, self.query = path.split('?', 1)
1675 if not path:
1679 if not path:
1676 path = None
1680 path = None
1677 if not self.query:
1681 if not self.query:
1678 self.query = None
1682 self.query = None
1679
1683
1680 # // is required to specify a host/authority
1684 # // is required to specify a host/authority
1681 if path and path.startswith('//'):
1685 if path and path.startswith('//'):
1682 parts = path[2:].split('/', 1)
1686 parts = path[2:].split('/', 1)
1683 if len(parts) > 1:
1687 if len(parts) > 1:
1684 self.host, path = parts
1688 self.host, path = parts
1685 path = path
1689 path = path
1686 else:
1690 else:
1687 self.host = parts[0]
1691 self.host = parts[0]
1688 path = None
1692 path = None
1689 if not self.host:
1693 if not self.host:
1690 self.host = None
1694 self.host = None
1691 # path of file:///d is /d
1695 # path of file:///d is /d
1692 # path of file:///d:/ is d:/, not /d:/
1696 # path of file:///d:/ is d:/, not /d:/
1693 if path and not hasdriveletter(path):
1697 if path and not hasdriveletter(path):
1694 path = '/' + path
1698 path = '/' + path
1695
1699
1696 if self.host and '@' in self.host:
1700 if self.host and '@' in self.host:
1697 self.user, self.host = self.host.rsplit('@', 1)
1701 self.user, self.host = self.host.rsplit('@', 1)
1698 if ':' in self.user:
1702 if ':' in self.user:
1699 self.user, self.passwd = self.user.split(':', 1)
1703 self.user, self.passwd = self.user.split(':', 1)
1700 if not self.host:
1704 if not self.host:
1701 self.host = None
1705 self.host = None
1702
1706
1703 # Don't split on colons in IPv6 addresses without ports
1707 # Don't split on colons in IPv6 addresses without ports
1704 if (self.host and ':' in self.host and
1708 if (self.host and ':' in self.host and
1705 not (self.host.startswith('[') and self.host.endswith(']'))):
1709 not (self.host.startswith('[') and self.host.endswith(']'))):
1706 self._hostport = self.host
1710 self._hostport = self.host
1707 self.host, self.port = self.host.rsplit(':', 1)
1711 self.host, self.port = self.host.rsplit(':', 1)
1708 if not self.host:
1712 if not self.host:
1709 self.host = None
1713 self.host = None
1710
1714
1711 if (self.host and self.scheme == 'file' and
1715 if (self.host and self.scheme == 'file' and
1712 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1716 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1713 raise Abort(_('file:// URLs can only refer to localhost'))
1717 raise Abort(_('file:// URLs can only refer to localhost'))
1714
1718
1715 self.path = path
1719 self.path = path
1716
1720
1717 # leave the query string escaped
1721 # leave the query string escaped
1718 for a in ('user', 'passwd', 'host', 'port',
1722 for a in ('user', 'passwd', 'host', 'port',
1719 'path', 'fragment'):
1723 'path', 'fragment'):
1720 v = getattr(self, a)
1724 v = getattr(self, a)
1721 if v is not None:
1725 if v is not None:
1722 setattr(self, a, _urlunquote(v))
1726 setattr(self, a, _urlunquote(v))
1723
1727
1724 def __repr__(self):
1728 def __repr__(self):
1725 attrs = []
1729 attrs = []
1726 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1730 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1727 'query', 'fragment'):
1731 'query', 'fragment'):
1728 v = getattr(self, a)
1732 v = getattr(self, a)
1729 if v is not None:
1733 if v is not None:
1730 attrs.append('%s: %r' % (a, v))
1734 attrs.append('%s: %r' % (a, v))
1731 return '<url %s>' % ', '.join(attrs)
1735 return '<url %s>' % ', '.join(attrs)
1732
1736
1733 def __str__(self):
1737 def __str__(self):
1734 r"""Join the URL's components back into a URL string.
1738 r"""Join the URL's components back into a URL string.
1735
1739
1736 Examples:
1740 Examples:
1737
1741
1738 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1742 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1739 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1743 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1740 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1744 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1741 'http://user:pw@host:80/?foo=bar&baz=42'
1745 'http://user:pw@host:80/?foo=bar&baz=42'
1742 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1746 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1743 'http://user:pw@host:80/?foo=bar%3dbaz'
1747 'http://user:pw@host:80/?foo=bar%3dbaz'
1744 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1748 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1745 'ssh://user:pw@[::1]:2200//home/joe#'
1749 'ssh://user:pw@[::1]:2200//home/joe#'
1746 >>> str(url('http://localhost:80//'))
1750 >>> str(url('http://localhost:80//'))
1747 'http://localhost:80//'
1751 'http://localhost:80//'
1748 >>> str(url('http://localhost:80/'))
1752 >>> str(url('http://localhost:80/'))
1749 'http://localhost:80/'
1753 'http://localhost:80/'
1750 >>> str(url('http://localhost:80'))
1754 >>> str(url('http://localhost:80'))
1751 'http://localhost:80/'
1755 'http://localhost:80/'
1752 >>> str(url('bundle:foo'))
1756 >>> str(url('bundle:foo'))
1753 'bundle:foo'
1757 'bundle:foo'
1754 >>> str(url('bundle://../foo'))
1758 >>> str(url('bundle://../foo'))
1755 'bundle:../foo'
1759 'bundle:../foo'
1756 >>> str(url('path'))
1760 >>> str(url('path'))
1757 'path'
1761 'path'
1758 >>> str(url('file:///tmp/foo/bar'))
1762 >>> str(url('file:///tmp/foo/bar'))
1759 'file:///tmp/foo/bar'
1763 'file:///tmp/foo/bar'
1760 >>> str(url('file:///c:/tmp/foo/bar'))
1764 >>> str(url('file:///c:/tmp/foo/bar'))
1761 'file:///c:/tmp/foo/bar'
1765 'file:///c:/tmp/foo/bar'
1762 >>> print url(r'bundle:foo\bar')
1766 >>> print url(r'bundle:foo\bar')
1763 bundle:foo\bar
1767 bundle:foo\bar
1764 """
1768 """
1765 if self._localpath:
1769 if self._localpath:
1766 s = self.path
1770 s = self.path
1767 if self.scheme == 'bundle':
1771 if self.scheme == 'bundle':
1768 s = 'bundle:' + s
1772 s = 'bundle:' + s
1769 if self.fragment:
1773 if self.fragment:
1770 s += '#' + self.fragment
1774 s += '#' + self.fragment
1771 return s
1775 return s
1772
1776
1773 s = self.scheme + ':'
1777 s = self.scheme + ':'
1774 if self.user or self.passwd or self.host:
1778 if self.user or self.passwd or self.host:
1775 s += '//'
1779 s += '//'
1776 elif self.scheme and (not self.path or self.path.startswith('/')
1780 elif self.scheme and (not self.path or self.path.startswith('/')
1777 or hasdriveletter(self.path)):
1781 or hasdriveletter(self.path)):
1778 s += '//'
1782 s += '//'
1779 if hasdriveletter(self.path):
1783 if hasdriveletter(self.path):
1780 s += '/'
1784 s += '/'
1781 if self.user:
1785 if self.user:
1782 s += urllib.quote(self.user, safe=self._safechars)
1786 s += urllib.quote(self.user, safe=self._safechars)
1783 if self.passwd:
1787 if self.passwd:
1784 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1788 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1785 if self.user or self.passwd:
1789 if self.user or self.passwd:
1786 s += '@'
1790 s += '@'
1787 if self.host:
1791 if self.host:
1788 if not (self.host.startswith('[') and self.host.endswith(']')):
1792 if not (self.host.startswith('[') and self.host.endswith(']')):
1789 s += urllib.quote(self.host)
1793 s += urllib.quote(self.host)
1790 else:
1794 else:
1791 s += self.host
1795 s += self.host
1792 if self.port:
1796 if self.port:
1793 s += ':' + urllib.quote(self.port)
1797 s += ':' + urllib.quote(self.port)
1794 if self.host:
1798 if self.host:
1795 s += '/'
1799 s += '/'
1796 if self.path:
1800 if self.path:
1797 # TODO: similar to the query string, we should not unescape the
1801 # TODO: similar to the query string, we should not unescape the
1798 # path when we store it, the path might contain '%2f' = '/',
1802 # path when we store it, the path might contain '%2f' = '/',
1799 # which we should *not* escape.
1803 # which we should *not* escape.
1800 s += urllib.quote(self.path, safe=self._safepchars)
1804 s += urllib.quote(self.path, safe=self._safepchars)
1801 if self.query:
1805 if self.query:
1802 # we store the query in escaped form.
1806 # we store the query in escaped form.
1803 s += '?' + self.query
1807 s += '?' + self.query
1804 if self.fragment is not None:
1808 if self.fragment is not None:
1805 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1809 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1806 return s
1810 return s
1807
1811
1808 def authinfo(self):
1812 def authinfo(self):
1809 user, passwd = self.user, self.passwd
1813 user, passwd = self.user, self.passwd
1810 try:
1814 try:
1811 self.user, self.passwd = None, None
1815 self.user, self.passwd = None, None
1812 s = str(self)
1816 s = str(self)
1813 finally:
1817 finally:
1814 self.user, self.passwd = user, passwd
1818 self.user, self.passwd = user, passwd
1815 if not self.user:
1819 if not self.user:
1816 return (s, None)
1820 return (s, None)
1817 # authinfo[1] is passed to urllib2 password manager, and its
1821 # authinfo[1] is passed to urllib2 password manager, and its
1818 # URIs must not contain credentials. The host is passed in the
1822 # URIs must not contain credentials. The host is passed in the
1819 # URIs list because Python < 2.4.3 uses only that to search for
1823 # URIs list because Python < 2.4.3 uses only that to search for
1820 # a password.
1824 # a password.
1821 return (s, (None, (s, self.host),
1825 return (s, (None, (s, self.host),
1822 self.user, self.passwd or ''))
1826 self.user, self.passwd or ''))
1823
1827
1824 def isabs(self):
1828 def isabs(self):
1825 if self.scheme and self.scheme != 'file':
1829 if self.scheme and self.scheme != 'file':
1826 return True # remote URL
1830 return True # remote URL
1827 if hasdriveletter(self.path):
1831 if hasdriveletter(self.path):
1828 return True # absolute for our purposes - can't be joined()
1832 return True # absolute for our purposes - can't be joined()
1829 if self.path.startswith(r'\\'):
1833 if self.path.startswith(r'\\'):
1830 return True # Windows UNC path
1834 return True # Windows UNC path
1831 if self.path.startswith('/'):
1835 if self.path.startswith('/'):
1832 return True # POSIX-style
1836 return True # POSIX-style
1833 return False
1837 return False
1834
1838
1835 def localpath(self):
1839 def localpath(self):
1836 if self.scheme == 'file' or self.scheme == 'bundle':
1840 if self.scheme == 'file' or self.scheme == 'bundle':
1837 path = self.path or '/'
1841 path = self.path or '/'
1838 # For Windows, we need to promote hosts containing drive
1842 # For Windows, we need to promote hosts containing drive
1839 # letters to paths with drive letters.
1843 # letters to paths with drive letters.
1840 if hasdriveletter(self._hostport):
1844 if hasdriveletter(self._hostport):
1841 path = self._hostport + '/' + self.path
1845 path = self._hostport + '/' + self.path
1842 elif (self.host is not None and self.path
1846 elif (self.host is not None and self.path
1843 and not hasdriveletter(path)):
1847 and not hasdriveletter(path)):
1844 path = '/' + path
1848 path = '/' + path
1845 return path
1849 return path
1846 return self._origpath
1850 return self._origpath
1847
1851
1848 def hasscheme(path):
1852 def hasscheme(path):
1849 return bool(url(path).scheme)
1853 return bool(url(path).scheme)
1850
1854
1851 def hasdriveletter(path):
1855 def hasdriveletter(path):
1852 return path and path[1:2] == ':' and path[0:1].isalpha()
1856 return path and path[1:2] == ':' and path[0:1].isalpha()
1853
1857
1854 def urllocalpath(path):
1858 def urllocalpath(path):
1855 return url(path, parsequery=False, parsefragment=False).localpath()
1859 return url(path, parsequery=False, parsefragment=False).localpath()
1856
1860
1857 def hidepassword(u):
1861 def hidepassword(u):
1858 '''hide user credential in a url string'''
1862 '''hide user credential in a url string'''
1859 u = url(u)
1863 u = url(u)
1860 if u.passwd:
1864 if u.passwd:
1861 u.passwd = '***'
1865 u.passwd = '***'
1862 return str(u)
1866 return str(u)
1863
1867
1864 def removeauth(u):
1868 def removeauth(u):
1865 '''remove all authentication information from a url string'''
1869 '''remove all authentication information from a url string'''
1866 u = url(u)
1870 u = url(u)
1867 u.user = u.passwd = None
1871 u.user = u.passwd = None
1868 return str(u)
1872 return str(u)
1869
1873
1870 def isatty(fd):
1874 def isatty(fd):
1871 try:
1875 try:
1872 return fd.isatty()
1876 return fd.isatty()
1873 except AttributeError:
1877 except AttributeError:
1874 return False
1878 return False
1875
1879
1876 timecount = unitcountfn(
1880 timecount = unitcountfn(
1877 (1, 1e3, _('%.0f s')),
1881 (1, 1e3, _('%.0f s')),
1878 (100, 1, _('%.1f s')),
1882 (100, 1, _('%.1f s')),
1879 (10, 1, _('%.2f s')),
1883 (10, 1, _('%.2f s')),
1880 (1, 1, _('%.3f s')),
1884 (1, 1, _('%.3f s')),
1881 (100, 0.001, _('%.1f ms')),
1885 (100, 0.001, _('%.1f ms')),
1882 (10, 0.001, _('%.2f ms')),
1886 (10, 0.001, _('%.2f ms')),
1883 (1, 0.001, _('%.3f ms')),
1887 (1, 0.001, _('%.3f ms')),
1884 (100, 0.000001, _('%.1f us')),
1888 (100, 0.000001, _('%.1f us')),
1885 (10, 0.000001, _('%.2f us')),
1889 (10, 0.000001, _('%.2f us')),
1886 (1, 0.000001, _('%.3f us')),
1890 (1, 0.000001, _('%.3f us')),
1887 (100, 0.000000001, _('%.1f ns')),
1891 (100, 0.000000001, _('%.1f ns')),
1888 (10, 0.000000001, _('%.2f ns')),
1892 (10, 0.000000001, _('%.2f ns')),
1889 (1, 0.000000001, _('%.3f ns')),
1893 (1, 0.000000001, _('%.3f ns')),
1890 )
1894 )
1891
1895
1892 _timenesting = [0]
1896 _timenesting = [0]
1893
1897
1894 def timed(func):
1898 def timed(func):
1895 '''Report the execution time of a function call to stderr.
1899 '''Report the execution time of a function call to stderr.
1896
1900
1897 During development, use as a decorator when you need to measure
1901 During development, use as a decorator when you need to measure
1898 the cost of a function, e.g. as follows:
1902 the cost of a function, e.g. as follows:
1899
1903
1900 @util.timed
1904 @util.timed
1901 def foo(a, b, c):
1905 def foo(a, b, c):
1902 pass
1906 pass
1903 '''
1907 '''
1904
1908
1905 def wrapper(*args, **kwargs):
1909 def wrapper(*args, **kwargs):
1906 start = time.time()
1910 start = time.time()
1907 indent = 2
1911 indent = 2
1908 _timenesting[0] += indent
1912 _timenesting[0] += indent
1909 try:
1913 try:
1910 return func(*args, **kwargs)
1914 return func(*args, **kwargs)
1911 finally:
1915 finally:
1912 elapsed = time.time() - start
1916 elapsed = time.time() - start
1913 _timenesting[0] -= indent
1917 _timenesting[0] -= indent
1914 sys.stderr.write('%s%s: %s\n' %
1918 sys.stderr.write('%s%s: %s\n' %
1915 (' ' * _timenesting[0], func.__name__,
1919 (' ' * _timenesting[0], func.__name__,
1916 timecount(elapsed)))
1920 timecount(elapsed)))
1917 return wrapper
1921 return wrapper
General Comments 0
You need to be logged in to leave comments. Login now