##// END OF EJS Templates
util: move "default" hidewindow to posix.py...
Adrian Buehlmann -
r14911:5b395031 default
parent child Browse files
Show More
@@ -1,342 +1,350
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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 from i18n import _
8 from i18n import _
9 import os, sys, errno, stat, getpass, pwd, grp, tempfile
9 import os, sys, errno, stat, getpass, pwd, grp, tempfile
10
10
11 posixfile = open
11 posixfile = open
12 nulldev = '/dev/null'
12 nulldev = '/dev/null'
13 normpath = os.path.normpath
13 normpath = os.path.normpath
14 samestat = os.path.samestat
14 samestat = os.path.samestat
15 oslink = os.link
15 oslink = os.link
16 unlink = os.unlink
16 unlink = os.unlink
17 rename = os.rename
17 rename = os.rename
18 expandglobs = False
18 expandglobs = False
19
19
20 umask = os.umask(0)
20 umask = os.umask(0)
21 os.umask(umask)
21 os.umask(umask)
22
22
23 def openhardlinks():
23 def openhardlinks():
24 '''return true if it is safe to hold open file handles to hardlinks'''
24 '''return true if it is safe to hold open file handles to hardlinks'''
25 return True
25 return True
26
26
27 def nlinks(name):
27 def nlinks(name):
28 '''return number of hardlinks for the given file'''
28 '''return number of hardlinks for the given file'''
29 return os.lstat(name).st_nlink
29 return os.lstat(name).st_nlink
30
30
31 def parsepatchoutput(output_line):
31 def parsepatchoutput(output_line):
32 """parses the output produced by patch and returns the filename"""
32 """parses the output produced by patch and returns the filename"""
33 pf = output_line[14:]
33 pf = output_line[14:]
34 if os.sys.platform == 'OpenVMS':
34 if os.sys.platform == 'OpenVMS':
35 if pf[0] == '`':
35 if pf[0] == '`':
36 pf = pf[1:-1] # Remove the quotes
36 pf = pf[1:-1] # Remove the quotes
37 else:
37 else:
38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
39 pf = pf[1:-1] # Remove the quotes
39 pf = pf[1:-1] # Remove the quotes
40 return pf
40 return pf
41
41
42 def sshargs(sshcmd, host, user, port):
42 def sshargs(sshcmd, host, user, port):
43 '''Build argument list for ssh'''
43 '''Build argument list for ssh'''
44 args = user and ("%s@%s" % (user, host)) or host
44 args = user and ("%s@%s" % (user, host)) or host
45 return port and ("%s -p %s" % (args, port)) or args
45 return port and ("%s -p %s" % (args, port)) or args
46
46
47 def isexec(f):
47 def isexec(f):
48 """check whether a file is executable"""
48 """check whether a file is executable"""
49 return (os.lstat(f).st_mode & 0100 != 0)
49 return (os.lstat(f).st_mode & 0100 != 0)
50
50
51 def setflags(f, l, x):
51 def setflags(f, l, x):
52 s = os.lstat(f).st_mode
52 s = os.lstat(f).st_mode
53 if l:
53 if l:
54 if not stat.S_ISLNK(s):
54 if not stat.S_ISLNK(s):
55 # switch file to link
55 # switch file to link
56 fp = open(f)
56 fp = open(f)
57 data = fp.read()
57 data = fp.read()
58 fp.close()
58 fp.close()
59 os.unlink(f)
59 os.unlink(f)
60 try:
60 try:
61 os.symlink(data, f)
61 os.symlink(data, f)
62 except OSError:
62 except OSError:
63 # failed to make a link, rewrite file
63 # failed to make a link, rewrite file
64 fp = open(f, "w")
64 fp = open(f, "w")
65 fp.write(data)
65 fp.write(data)
66 fp.close()
66 fp.close()
67 # no chmod needed at this point
67 # no chmod needed at this point
68 return
68 return
69 if stat.S_ISLNK(s):
69 if stat.S_ISLNK(s):
70 # switch link to file
70 # switch link to file
71 data = os.readlink(f)
71 data = os.readlink(f)
72 os.unlink(f)
72 os.unlink(f)
73 fp = open(f, "w")
73 fp = open(f, "w")
74 fp.write(data)
74 fp.write(data)
75 fp.close()
75 fp.close()
76 s = 0666 & ~umask # avoid restatting for chmod
76 s = 0666 & ~umask # avoid restatting for chmod
77
77
78 sx = s & 0100
78 sx = s & 0100
79 if x and not sx:
79 if x and not sx:
80 # Turn on +x for every +r bit when making a file executable
80 # Turn on +x for every +r bit when making a file executable
81 # and obey umask.
81 # and obey umask.
82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
83 elif not x and sx:
83 elif not x and sx:
84 # Turn off all +x bits
84 # Turn off all +x bits
85 os.chmod(f, s & 0666)
85 os.chmod(f, s & 0666)
86
86
87 def checkexec(path):
87 def checkexec(path):
88 """
88 """
89 Check whether the given path is on a filesystem with UNIX-like exec flags
89 Check whether the given path is on a filesystem with UNIX-like exec flags
90
90
91 Requires a directory (like /foo/.hg)
91 Requires a directory (like /foo/.hg)
92 """
92 """
93
93
94 # VFAT on some Linux versions can flip mode but it doesn't persist
94 # VFAT on some Linux versions can flip mode but it doesn't persist
95 # a FS remount. Frequently we can detect it if files are created
95 # a FS remount. Frequently we can detect it if files are created
96 # with exec bit on.
96 # with exec bit on.
97
97
98 try:
98 try:
99 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
99 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
100 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
100 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
101 try:
101 try:
102 os.close(fh)
102 os.close(fh)
103 m = os.stat(fn).st_mode & 0777
103 m = os.stat(fn).st_mode & 0777
104 new_file_has_exec = m & EXECFLAGS
104 new_file_has_exec = m & EXECFLAGS
105 os.chmod(fn, m ^ EXECFLAGS)
105 os.chmod(fn, m ^ EXECFLAGS)
106 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
106 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
107 finally:
107 finally:
108 os.unlink(fn)
108 os.unlink(fn)
109 except (IOError, OSError):
109 except (IOError, OSError):
110 # we don't care, the user probably won't be able to commit anyway
110 # we don't care, the user probably won't be able to commit anyway
111 return False
111 return False
112 return not (new_file_has_exec or exec_flags_cannot_flip)
112 return not (new_file_has_exec or exec_flags_cannot_flip)
113
113
114 def checklink(path):
114 def checklink(path):
115 """check whether the given path is on a symlink-capable filesystem"""
115 """check whether the given path is on a symlink-capable filesystem"""
116 # mktemp is not racy because symlink creation will fail if the
116 # mktemp is not racy because symlink creation will fail if the
117 # file already exists
117 # file already exists
118 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
118 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
119 try:
119 try:
120 os.symlink(".", name)
120 os.symlink(".", name)
121 os.unlink(name)
121 os.unlink(name)
122 return True
122 return True
123 except (OSError, AttributeError):
123 except (OSError, AttributeError):
124 return False
124 return False
125
125
126 def checkosfilename(path):
126 def checkosfilename(path):
127 '''Check that the base-relative path is a valid filename on this platform.
127 '''Check that the base-relative path is a valid filename on this platform.
128 Returns None if the path is ok, or a UI string describing the problem.'''
128 Returns None if the path is ok, or a UI string describing the problem.'''
129 pass # on posix platforms, every path is ok
129 pass # on posix platforms, every path is ok
130
130
131 def setbinary(fd):
131 def setbinary(fd):
132 pass
132 pass
133
133
134 def pconvert(path):
134 def pconvert(path):
135 return path
135 return path
136
136
137 def localpath(path):
137 def localpath(path):
138 return path
138 return path
139
139
140 def samefile(fpath1, fpath2):
140 def samefile(fpath1, fpath2):
141 """Returns whether path1 and path2 refer to the same file. This is only
141 """Returns whether path1 and path2 refer to the same file. This is only
142 guaranteed to work for files, not directories."""
142 guaranteed to work for files, not directories."""
143 return os.path.samefile(fpath1, fpath2)
143 return os.path.samefile(fpath1, fpath2)
144
144
145 def samedevice(fpath1, fpath2):
145 def samedevice(fpath1, fpath2):
146 """Returns whether fpath1 and fpath2 are on the same device. This is only
146 """Returns whether fpath1 and fpath2 are on the same device. This is only
147 guaranteed to work for files, not directories."""
147 guaranteed to work for files, not directories."""
148 st1 = os.lstat(fpath1)
148 st1 = os.lstat(fpath1)
149 st2 = os.lstat(fpath2)
149 st2 = os.lstat(fpath2)
150 return st1.st_dev == st2.st_dev
150 return st1.st_dev == st2.st_dev
151
151
152 if sys.platform == 'darwin':
152 if sys.platform == 'darwin':
153 import fcntl # only needed on darwin, missing on jython
153 import fcntl # only needed on darwin, missing on jython
154 def realpath(path):
154 def realpath(path):
155 '''
155 '''
156 Returns the true, canonical file system path equivalent to the given
156 Returns the true, canonical file system path equivalent to the given
157 path.
157 path.
158
158
159 Equivalent means, in this case, resulting in the same, unique
159 Equivalent means, in this case, resulting in the same, unique
160 file system link to the path. Every file system entry, whether a file,
160 file system link to the path. Every file system entry, whether a file,
161 directory, hard link or symbolic link or special, will have a single
161 directory, hard link or symbolic link or special, will have a single
162 path preferred by the system, but may allow multiple, differing path
162 path preferred by the system, but may allow multiple, differing path
163 lookups to point to it.
163 lookups to point to it.
164
164
165 Most regular UNIX file systems only allow a file system entry to be
165 Most regular UNIX file systems only allow a file system entry to be
166 looked up by its distinct path. Obviously, this does not apply to case
166 looked up by its distinct path. Obviously, this does not apply to case
167 insensitive file systems, whether case preserving or not. The most
167 insensitive file systems, whether case preserving or not. The most
168 complex issue to deal with is file systems transparently reencoding the
168 complex issue to deal with is file systems transparently reencoding the
169 path, such as the non-standard Unicode normalisation required for HFS+
169 path, such as the non-standard Unicode normalisation required for HFS+
170 and HFSX.
170 and HFSX.
171 '''
171 '''
172 # Constants copied from /usr/include/sys/fcntl.h
172 # Constants copied from /usr/include/sys/fcntl.h
173 F_GETPATH = 50
173 F_GETPATH = 50
174 O_SYMLINK = 0x200000
174 O_SYMLINK = 0x200000
175
175
176 try:
176 try:
177 fd = os.open(path, O_SYMLINK)
177 fd = os.open(path, O_SYMLINK)
178 except OSError, err:
178 except OSError, err:
179 if err.errno == errno.ENOENT:
179 if err.errno == errno.ENOENT:
180 return path
180 return path
181 raise
181 raise
182
182
183 try:
183 try:
184 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
184 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
185 finally:
185 finally:
186 os.close(fd)
186 os.close(fd)
187 else:
187 else:
188 # Fallback to the likely inadequate Python builtin function.
188 # Fallback to the likely inadequate Python builtin function.
189 realpath = os.path.realpath
189 realpath = os.path.realpath
190
190
191 def shellquote(s):
191 def shellquote(s):
192 if os.sys.platform == 'OpenVMS':
192 if os.sys.platform == 'OpenVMS':
193 return '"%s"' % s
193 return '"%s"' % s
194 else:
194 else:
195 return "'%s'" % s.replace("'", "'\\''")
195 return "'%s'" % s.replace("'", "'\\''")
196
196
197 def quotecommand(cmd):
197 def quotecommand(cmd):
198 return cmd
198 return cmd
199
199
200 def popen(command, mode='r'):
200 def popen(command, mode='r'):
201 return os.popen(command, mode)
201 return os.popen(command, mode)
202
202
203 def testpid(pid):
203 def testpid(pid):
204 '''return False if pid dead, True if running or not sure'''
204 '''return False if pid dead, True if running or not sure'''
205 if os.sys.platform == 'OpenVMS':
205 if os.sys.platform == 'OpenVMS':
206 return True
206 return True
207 try:
207 try:
208 os.kill(pid, 0)
208 os.kill(pid, 0)
209 return True
209 return True
210 except OSError, inst:
210 except OSError, inst:
211 return inst.errno != errno.ESRCH
211 return inst.errno != errno.ESRCH
212
212
213 def explainexit(code):
213 def explainexit(code):
214 """return a 2-tuple (desc, code) describing a subprocess status
214 """return a 2-tuple (desc, code) describing a subprocess status
215 (codes from kill are negative - not os.system/wait encoding)"""
215 (codes from kill are negative - not os.system/wait encoding)"""
216 if code >= 0:
216 if code >= 0:
217 return _("exited with status %d") % code, code
217 return _("exited with status %d") % code, code
218 return _("killed by signal %d") % -code, -code
218 return _("killed by signal %d") % -code, -code
219
219
220 def isowner(st):
220 def isowner(st):
221 """Return True if the stat object st is from the current user."""
221 """Return True if the stat object st is from the current user."""
222 return st.st_uid == os.getuid()
222 return st.st_uid == os.getuid()
223
223
224 def findexe(command):
224 def findexe(command):
225 '''Find executable for command searching like which does.
225 '''Find executable for command searching like which does.
226 If command is a basename then PATH is searched for command.
226 If command is a basename then PATH is searched for command.
227 PATH isn't searched if command is an absolute or relative path.
227 PATH isn't searched if command is an absolute or relative path.
228 If command isn't found None is returned.'''
228 If command isn't found None is returned.'''
229 if sys.platform == 'OpenVMS':
229 if sys.platform == 'OpenVMS':
230 return command
230 return command
231
231
232 def findexisting(executable):
232 def findexisting(executable):
233 'Will return executable if existing file'
233 'Will return executable if existing file'
234 if os.path.exists(executable):
234 if os.path.exists(executable):
235 return executable
235 return executable
236 return None
236 return None
237
237
238 if os.sep in command:
238 if os.sep in command:
239 return findexisting(command)
239 return findexisting(command)
240
240
241 for path in os.environ.get('PATH', '').split(os.pathsep):
241 for path in os.environ.get('PATH', '').split(os.pathsep):
242 executable = findexisting(os.path.join(path, command))
242 executable = findexisting(os.path.join(path, command))
243 if executable is not None:
243 if executable is not None:
244 return executable
244 return executable
245 return None
245 return None
246
246
247 def setsignalhandler():
247 def setsignalhandler():
248 pass
248 pass
249
249
250 def statfiles(files):
250 def statfiles(files):
251 'Stat each file in files and yield stat or None if file does not exist.'
251 'Stat each file in files and yield stat or None if file does not exist.'
252 lstat = os.lstat
252 lstat = os.lstat
253 for nf in files:
253 for nf in files:
254 try:
254 try:
255 st = lstat(nf)
255 st = lstat(nf)
256 except OSError, err:
256 except OSError, err:
257 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
257 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
258 raise
258 raise
259 st = None
259 st = None
260 yield st
260 yield st
261
261
262 def getuser():
262 def getuser():
263 '''return name of current user'''
263 '''return name of current user'''
264 return getpass.getuser()
264 return getpass.getuser()
265
265
266 def username(uid=None):
266 def username(uid=None):
267 """Return the name of the user with the given uid.
267 """Return the name of the user with the given uid.
268
268
269 If uid is None, return the name of the current user."""
269 If uid is None, return the name of the current user."""
270
270
271 if uid is None:
271 if uid is None:
272 uid = os.getuid()
272 uid = os.getuid()
273 try:
273 try:
274 return pwd.getpwuid(uid)[0]
274 return pwd.getpwuid(uid)[0]
275 except KeyError:
275 except KeyError:
276 return str(uid)
276 return str(uid)
277
277
278 def groupname(gid=None):
278 def groupname(gid=None):
279 """Return the name of the group with the given gid.
279 """Return the name of the group with the given gid.
280
280
281 If gid is None, return the name of the current group."""
281 If gid is None, return the name of the current group."""
282
282
283 if gid is None:
283 if gid is None:
284 gid = os.getgid()
284 gid = os.getgid()
285 try:
285 try:
286 return grp.getgrgid(gid)[0]
286 return grp.getgrgid(gid)[0]
287 except KeyError:
287 except KeyError:
288 return str(gid)
288 return str(gid)
289
289
290 def groupmembers(name):
290 def groupmembers(name):
291 """Return the list of members of the group with the given
291 """Return the list of members of the group with the given
292 name, KeyError if the group does not exist.
292 name, KeyError if the group does not exist.
293 """
293 """
294 return list(grp.getgrnam(name).gr_mem)
294 return list(grp.getgrnam(name).gr_mem)
295
295
296 def spawndetached(args):
296 def spawndetached(args):
297 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
297 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
298 args[0], args)
298 args[0], args)
299
299
300 def gethgcmd():
300 def gethgcmd():
301 return sys.argv[:1]
301 return sys.argv[:1]
302
302
303 def termwidth():
303 def termwidth():
304 try:
304 try:
305 import termios, array, fcntl
305 import termios, array, fcntl
306 for dev in (sys.stderr, sys.stdout, sys.stdin):
306 for dev in (sys.stderr, sys.stdout, sys.stdin):
307 try:
307 try:
308 try:
308 try:
309 fd = dev.fileno()
309 fd = dev.fileno()
310 except AttributeError:
310 except AttributeError:
311 continue
311 continue
312 if not os.isatty(fd):
312 if not os.isatty(fd):
313 continue
313 continue
314 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
314 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
315 width = array.array('h', arri)[1]
315 width = array.array('h', arri)[1]
316 if width > 0:
316 if width > 0:
317 return width
317 return width
318 except ValueError:
318 except ValueError:
319 pass
319 pass
320 except IOError, e:
320 except IOError, e:
321 if e[0] == errno.EINVAL:
321 if e[0] == errno.EINVAL:
322 pass
322 pass
323 else:
323 else:
324 raise
324 raise
325 except ImportError:
325 except ImportError:
326 pass
326 pass
327 return 80
327 return 80
328
328
329 def makedir(path, notindexed):
329 def makedir(path, notindexed):
330 os.mkdir(path)
330 os.mkdir(path)
331
331
332 def unlinkpath(f):
332 def unlinkpath(f):
333 """unlink and remove the directory if it is empty"""
333 """unlink and remove the directory if it is empty"""
334 os.unlink(f)
334 os.unlink(f)
335 # try removing directories that might now be empty
335 # try removing directories that might now be empty
336 try:
336 try:
337 os.removedirs(os.path.dirname(f))
337 os.removedirs(os.path.dirname(f))
338 except OSError:
338 except OSError:
339 pass
339 pass
340
340
341 def lookupreg(key, name=None, scope=None):
341 def lookupreg(key, name=None, scope=None):
342 return None
342 return None
343
344 def hidewindow():
345 """Hide current shell window.
346
347 Used to hide the window opened when starting asynchronous
348 child process under Windows, unneeded on other systems.
349 """
350 pass
@@ -1,1597 +1,1589
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, unicodedata, signal
19 import os, time, calendar, textwrap, unicodedata, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
31 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 else:
33 else:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explainexit(code)))
200 (cmd, explainexit(code)))
201 fp = open(outname, 'rb')
201 fp = open(outname, 'rb')
202 r = fp.read()
202 r = fp.read()
203 fp.close()
203 fp.close()
204 return r
204 return r
205 finally:
205 finally:
206 try:
206 try:
207 if inname:
207 if inname:
208 os.unlink(inname)
208 os.unlink(inname)
209 except OSError:
209 except OSError:
210 pass
210 pass
211 try:
211 try:
212 if outname:
212 if outname:
213 os.unlink(outname)
213 os.unlink(outname)
214 except OSError:
214 except OSError:
215 pass
215 pass
216
216
217 filtertable = {
217 filtertable = {
218 'tempfile:': tempfilter,
218 'tempfile:': tempfilter,
219 'pipe:': pipefilter,
219 'pipe:': pipefilter,
220 }
220 }
221
221
222 def filter(s, cmd):
222 def filter(s, cmd):
223 "filter a string through a command that transforms its input to its output"
223 "filter a string through a command that transforms its input to its output"
224 for name, fn in filtertable.iteritems():
224 for name, fn in filtertable.iteritems():
225 if cmd.startswith(name):
225 if cmd.startswith(name):
226 return fn(s, cmd[len(name):].lstrip())
226 return fn(s, cmd[len(name):].lstrip())
227 return pipefilter(s, cmd)
227 return pipefilter(s, cmd)
228
228
229 def binary(s):
229 def binary(s):
230 """return true if a string is binary data"""
230 """return true if a string is binary data"""
231 return bool(s and '\0' in s)
231 return bool(s and '\0' in s)
232
232
233 def increasingchunks(source, min=1024, max=65536):
233 def increasingchunks(source, min=1024, max=65536):
234 '''return no less than min bytes per chunk while data remains,
234 '''return no less than min bytes per chunk while data remains,
235 doubling min after each chunk until it reaches max'''
235 doubling min after each chunk until it reaches max'''
236 def log2(x):
236 def log2(x):
237 if not x:
237 if not x:
238 return 0
238 return 0
239 i = 0
239 i = 0
240 while x:
240 while x:
241 x >>= 1
241 x >>= 1
242 i += 1
242 i += 1
243 return i - 1
243 return i - 1
244
244
245 buf = []
245 buf = []
246 blen = 0
246 blen = 0
247 for chunk in source:
247 for chunk in source:
248 buf.append(chunk)
248 buf.append(chunk)
249 blen += len(chunk)
249 blen += len(chunk)
250 if blen >= min:
250 if blen >= min:
251 if min < max:
251 if min < max:
252 min = min << 1
252 min = min << 1
253 nmin = 1 << log2(blen)
253 nmin = 1 << log2(blen)
254 if nmin > min:
254 if nmin > min:
255 min = nmin
255 min = nmin
256 if min > max:
256 if min > max:
257 min = max
257 min = max
258 yield ''.join(buf)
258 yield ''.join(buf)
259 blen = 0
259 blen = 0
260 buf = []
260 buf = []
261 if buf:
261 if buf:
262 yield ''.join(buf)
262 yield ''.join(buf)
263
263
264 Abort = error.Abort
264 Abort = error.Abort
265
265
266 def always(fn):
266 def always(fn):
267 return True
267 return True
268
268
269 def never(fn):
269 def never(fn):
270 return False
270 return False
271
271
272 def pathto(root, n1, n2):
272 def pathto(root, n1, n2):
273 '''return the relative path from one place to another.
273 '''return the relative path from one place to another.
274 root should use os.sep to separate directories
274 root should use os.sep to separate directories
275 n1 should use os.sep to separate directories
275 n1 should use os.sep to separate directories
276 n2 should use "/" to separate directories
276 n2 should use "/" to separate directories
277 returns an os.sep-separated path.
277 returns an os.sep-separated path.
278
278
279 If n1 is a relative path, it's assumed it's
279 If n1 is a relative path, it's assumed it's
280 relative to root.
280 relative to root.
281 n2 should always be relative to root.
281 n2 should always be relative to root.
282 '''
282 '''
283 if not n1:
283 if not n1:
284 return localpath(n2)
284 return localpath(n2)
285 if os.path.isabs(n1):
285 if os.path.isabs(n1):
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 return os.path.join(root, localpath(n2))
287 return os.path.join(root, localpath(n2))
288 n2 = '/'.join((pconvert(root), n2))
288 n2 = '/'.join((pconvert(root), n2))
289 a, b = splitpath(n1), n2.split('/')
289 a, b = splitpath(n1), n2.split('/')
290 a.reverse()
290 a.reverse()
291 b.reverse()
291 b.reverse()
292 while a and b and a[-1] == b[-1]:
292 while a and b and a[-1] == b[-1]:
293 a.pop()
293 a.pop()
294 b.pop()
294 b.pop()
295 b.reverse()
295 b.reverse()
296 return os.sep.join((['..'] * len(a)) + b) or '.'
296 return os.sep.join((['..'] * len(a)) + b) or '.'
297
297
298 _hgexecutable = None
298 _hgexecutable = None
299
299
300 def mainfrozen():
300 def mainfrozen():
301 """return True if we are a frozen executable.
301 """return True if we are a frozen executable.
302
302
303 The code supports py2exe (most common, Windows only) and tools/freeze
303 The code supports py2exe (most common, Windows only) and tools/freeze
304 (portable, not much used).
304 (portable, not much used).
305 """
305 """
306 return (hasattr(sys, "frozen") or # new py2exe
306 return (hasattr(sys, "frozen") or # new py2exe
307 hasattr(sys, "importers") or # old py2exe
307 hasattr(sys, "importers") or # old py2exe
308 imp.is_frozen("__main__")) # tools/freeze
308 imp.is_frozen("__main__")) # tools/freeze
309
309
310 def hgexecutable():
310 def hgexecutable():
311 """return location of the 'hg' executable.
311 """return location of the 'hg' executable.
312
312
313 Defaults to $HG or 'hg' in the search path.
313 Defaults to $HG or 'hg' in the search path.
314 """
314 """
315 if _hgexecutable is None:
315 if _hgexecutable is None:
316 hg = os.environ.get('HG')
316 hg = os.environ.get('HG')
317 if hg:
317 if hg:
318 _sethgexecutable(hg)
318 _sethgexecutable(hg)
319 elif mainfrozen():
319 elif mainfrozen():
320 _sethgexecutable(sys.executable)
320 _sethgexecutable(sys.executable)
321 else:
321 else:
322 exe = findexe('hg') or os.path.basename(sys.argv[0])
322 exe = findexe('hg') or os.path.basename(sys.argv[0])
323 _sethgexecutable(exe)
323 _sethgexecutable(exe)
324 return _hgexecutable
324 return _hgexecutable
325
325
326 def _sethgexecutable(path):
326 def _sethgexecutable(path):
327 """set location of the 'hg' executable"""
327 """set location of the 'hg' executable"""
328 global _hgexecutable
328 global _hgexecutable
329 _hgexecutable = path
329 _hgexecutable = path
330
330
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
332 '''enhanced shell command execution.
332 '''enhanced shell command execution.
333 run with environment maybe modified, maybe in different dir.
333 run with environment maybe modified, maybe in different dir.
334
334
335 if command fails and onerr is None, return status. if ui object,
335 if command fails and onerr is None, return status. if ui object,
336 print error message and return status, else raise onerr object as
336 print error message and return status, else raise onerr object as
337 exception.
337 exception.
338
338
339 if out is specified, it is assumed to be a file-like object that has a
339 if out is specified, it is assumed to be a file-like object that has a
340 write() method. stdout and stderr will be redirected to out.'''
340 write() method. stdout and stderr will be redirected to out.'''
341 try:
341 try:
342 sys.stdout.flush()
342 sys.stdout.flush()
343 except Exception:
343 except Exception:
344 pass
344 pass
345 def py2shell(val):
345 def py2shell(val):
346 'convert python object into string that is useful to shell'
346 'convert python object into string that is useful to shell'
347 if val is None or val is False:
347 if val is None or val is False:
348 return '0'
348 return '0'
349 if val is True:
349 if val is True:
350 return '1'
350 return '1'
351 return str(val)
351 return str(val)
352 origcmd = cmd
352 origcmd = cmd
353 cmd = quotecommand(cmd)
353 cmd = quotecommand(cmd)
354 env = dict(os.environ)
354 env = dict(os.environ)
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
356 env['HG'] = hgexecutable()
356 env['HG'] = hgexecutable()
357 if out is None or out == sys.__stdout__:
357 if out is None or out == sys.__stdout__:
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
359 env=env, cwd=cwd)
359 env=env, cwd=cwd)
360 else:
360 else:
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
363 stderr=subprocess.STDOUT)
363 stderr=subprocess.STDOUT)
364 for line in proc.stdout:
364 for line in proc.stdout:
365 out.write(line)
365 out.write(line)
366 proc.wait()
366 proc.wait()
367 rc = proc.returncode
367 rc = proc.returncode
368 if sys.platform == 'OpenVMS' and rc & 1:
368 if sys.platform == 'OpenVMS' and rc & 1:
369 rc = 0
369 rc = 0
370 if rc and onerr:
370 if rc and onerr:
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 explainexit(rc)[0])
372 explainexit(rc)[0])
373 if errprefix:
373 if errprefix:
374 errmsg = '%s: %s' % (errprefix, errmsg)
374 errmsg = '%s: %s' % (errprefix, errmsg)
375 try:
375 try:
376 onerr.warn(errmsg + '\n')
376 onerr.warn(errmsg + '\n')
377 except AttributeError:
377 except AttributeError:
378 raise onerr(errmsg)
378 raise onerr(errmsg)
379 return rc
379 return rc
380
380
381 def checksignature(func):
381 def checksignature(func):
382 '''wrap a function with code to check for calling errors'''
382 '''wrap a function with code to check for calling errors'''
383 def check(*args, **kwargs):
383 def check(*args, **kwargs):
384 try:
384 try:
385 return func(*args, **kwargs)
385 return func(*args, **kwargs)
386 except TypeError:
386 except TypeError:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 raise error.SignatureError
388 raise error.SignatureError
389 raise
389 raise
390
390
391 return check
391 return check
392
392
393 def copyfile(src, dest):
393 def copyfile(src, dest):
394 "copy a file, preserving mode and atime/mtime"
394 "copy a file, preserving mode and atime/mtime"
395 if os.path.islink(src):
395 if os.path.islink(src):
396 try:
396 try:
397 os.unlink(dest)
397 os.unlink(dest)
398 except OSError:
398 except OSError:
399 pass
399 pass
400 os.symlink(os.readlink(src), dest)
400 os.symlink(os.readlink(src), dest)
401 else:
401 else:
402 try:
402 try:
403 shutil.copyfile(src, dest)
403 shutil.copyfile(src, dest)
404 shutil.copymode(src, dest)
404 shutil.copymode(src, dest)
405 except shutil.Error, inst:
405 except shutil.Error, inst:
406 raise Abort(str(inst))
406 raise Abort(str(inst))
407
407
408 def copyfiles(src, dst, hardlink=None):
408 def copyfiles(src, dst, hardlink=None):
409 """Copy a directory tree using hardlinks if possible"""
409 """Copy a directory tree using hardlinks if possible"""
410
410
411 if hardlink is None:
411 if hardlink is None:
412 hardlink = (os.stat(src).st_dev ==
412 hardlink = (os.stat(src).st_dev ==
413 os.stat(os.path.dirname(dst)).st_dev)
413 os.stat(os.path.dirname(dst)).st_dev)
414
414
415 num = 0
415 num = 0
416 if os.path.isdir(src):
416 if os.path.isdir(src):
417 os.mkdir(dst)
417 os.mkdir(dst)
418 for name, kind in osutil.listdir(src):
418 for name, kind in osutil.listdir(src):
419 srcname = os.path.join(src, name)
419 srcname = os.path.join(src, name)
420 dstname = os.path.join(dst, name)
420 dstname = os.path.join(dst, name)
421 hardlink, n = copyfiles(srcname, dstname, hardlink)
421 hardlink, n = copyfiles(srcname, dstname, hardlink)
422 num += n
422 num += n
423 else:
423 else:
424 if hardlink:
424 if hardlink:
425 try:
425 try:
426 oslink(src, dst)
426 oslink(src, dst)
427 except (IOError, OSError):
427 except (IOError, OSError):
428 hardlink = False
428 hardlink = False
429 shutil.copy(src, dst)
429 shutil.copy(src, dst)
430 else:
430 else:
431 shutil.copy(src, dst)
431 shutil.copy(src, dst)
432 num += 1
432 num += 1
433
433
434 return hardlink, num
434 return hardlink, num
435
435
436 _winreservednames = '''con prn aux nul
436 _winreservednames = '''con prn aux nul
437 com1 com2 com3 com4 com5 com6 com7 com8 com9
437 com1 com2 com3 com4 com5 com6 com7 com8 com9
438 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
438 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
439 _winreservedchars = ':*?"<>|'
439 _winreservedchars = ':*?"<>|'
440 def checkwinfilename(path):
440 def checkwinfilename(path):
441 '''Check that the base-relative path is a valid filename on Windows.
441 '''Check that the base-relative path is a valid filename on Windows.
442 Returns None if the path is ok, or a UI string describing the problem.
442 Returns None if the path is ok, or a UI string describing the problem.
443
443
444 >>> checkwinfilename("just/a/normal/path")
444 >>> checkwinfilename("just/a/normal/path")
445 >>> checkwinfilename("foo/bar/con.xml")
445 >>> checkwinfilename("foo/bar/con.xml")
446 "filename contains 'con', which is reserved on Windows"
446 "filename contains 'con', which is reserved on Windows"
447 >>> checkwinfilename("foo/con.xml/bar")
447 >>> checkwinfilename("foo/con.xml/bar")
448 "filename contains 'con', which is reserved on Windows"
448 "filename contains 'con', which is reserved on Windows"
449 >>> checkwinfilename("foo/bar/xml.con")
449 >>> checkwinfilename("foo/bar/xml.con")
450 >>> checkwinfilename("foo/bar/AUX/bla.txt")
450 >>> checkwinfilename("foo/bar/AUX/bla.txt")
451 "filename contains 'AUX', which is reserved on Windows"
451 "filename contains 'AUX', which is reserved on Windows"
452 >>> checkwinfilename("foo/bar/bla:.txt")
452 >>> checkwinfilename("foo/bar/bla:.txt")
453 "filename contains ':', which is reserved on Windows"
453 "filename contains ':', which is reserved on Windows"
454 >>> checkwinfilename("foo/bar/b\07la.txt")
454 >>> checkwinfilename("foo/bar/b\07la.txt")
455 "filename contains '\\\\x07', which is invalid on Windows"
455 "filename contains '\\\\x07', which is invalid on Windows"
456 >>> checkwinfilename("foo/bar/bla ")
456 >>> checkwinfilename("foo/bar/bla ")
457 "filename ends with ' ', which is not allowed on Windows"
457 "filename ends with ' ', which is not allowed on Windows"
458 '''
458 '''
459 for n in path.replace('\\', '/').split('/'):
459 for n in path.replace('\\', '/').split('/'):
460 if not n:
460 if not n:
461 continue
461 continue
462 for c in n:
462 for c in n:
463 if c in _winreservedchars:
463 if c in _winreservedchars:
464 return _("filename contains '%s', which is reserved "
464 return _("filename contains '%s', which is reserved "
465 "on Windows") % c
465 "on Windows") % c
466 if ord(c) <= 31:
466 if ord(c) <= 31:
467 return _("filename contains %r, which is invalid "
467 return _("filename contains %r, which is invalid "
468 "on Windows") % c
468 "on Windows") % c
469 base = n.split('.')[0]
469 base = n.split('.')[0]
470 if base and base.lower() in _winreservednames:
470 if base and base.lower() in _winreservednames:
471 return _("filename contains '%s', which is reserved "
471 return _("filename contains '%s', which is reserved "
472 "on Windows") % base
472 "on Windows") % base
473 t = n[-1]
473 t = n[-1]
474 if t in '. ':
474 if t in '. ':
475 return _("filename ends with '%s', which is not allowed "
475 return _("filename ends with '%s', which is not allowed "
476 "on Windows") % t
476 "on Windows") % t
477
477
478 def hidewindow():
479 """Hide current shell window.
480
481 Used to hide the window opened when starting asynchronous
482 child process under Windows, unneeded on other systems.
483 """
484 pass
485
486 if os.name == 'nt':
478 if os.name == 'nt':
487 checkosfilename = checkwinfilename
479 checkosfilename = checkwinfilename
488 from windows import *
480 from windows import *
489 else:
481 else:
490 from posix import *
482 from posix import *
491
483
492 def makelock(info, pathname):
484 def makelock(info, pathname):
493 try:
485 try:
494 return os.symlink(info, pathname)
486 return os.symlink(info, pathname)
495 except OSError, why:
487 except OSError, why:
496 if why.errno == errno.EEXIST:
488 if why.errno == errno.EEXIST:
497 raise
489 raise
498 except AttributeError: # no symlink in os
490 except AttributeError: # no symlink in os
499 pass
491 pass
500
492
501 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
493 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
502 os.write(ld, info)
494 os.write(ld, info)
503 os.close(ld)
495 os.close(ld)
504
496
505 def readlock(pathname):
497 def readlock(pathname):
506 try:
498 try:
507 return os.readlink(pathname)
499 return os.readlink(pathname)
508 except OSError, why:
500 except OSError, why:
509 if why.errno not in (errno.EINVAL, errno.ENOSYS):
501 if why.errno not in (errno.EINVAL, errno.ENOSYS):
510 raise
502 raise
511 except AttributeError: # no symlink in os
503 except AttributeError: # no symlink in os
512 pass
504 pass
513 fp = posixfile(pathname)
505 fp = posixfile(pathname)
514 r = fp.read()
506 r = fp.read()
515 fp.close()
507 fp.close()
516 return r
508 return r
517
509
518 def fstat(fp):
510 def fstat(fp):
519 '''stat file object that may not have fileno method.'''
511 '''stat file object that may not have fileno method.'''
520 try:
512 try:
521 return os.fstat(fp.fileno())
513 return os.fstat(fp.fileno())
522 except AttributeError:
514 except AttributeError:
523 return os.stat(fp.name)
515 return os.stat(fp.name)
524
516
525 # File system features
517 # File system features
526
518
527 def checkcase(path):
519 def checkcase(path):
528 """
520 """
529 Check whether the given path is on a case-sensitive filesystem
521 Check whether the given path is on a case-sensitive filesystem
530
522
531 Requires a path (like /foo/.hg) ending with a foldable final
523 Requires a path (like /foo/.hg) ending with a foldable final
532 directory component.
524 directory component.
533 """
525 """
534 s1 = os.stat(path)
526 s1 = os.stat(path)
535 d, b = os.path.split(path)
527 d, b = os.path.split(path)
536 p2 = os.path.join(d, b.upper())
528 p2 = os.path.join(d, b.upper())
537 if path == p2:
529 if path == p2:
538 p2 = os.path.join(d, b.lower())
530 p2 = os.path.join(d, b.lower())
539 try:
531 try:
540 s2 = os.stat(p2)
532 s2 = os.stat(p2)
541 if s2 == s1:
533 if s2 == s1:
542 return False
534 return False
543 return True
535 return True
544 except OSError:
536 except OSError:
545 return True
537 return True
546
538
547 _fspathcache = {}
539 _fspathcache = {}
548 def fspath(name, root):
540 def fspath(name, root):
549 '''Get name in the case stored in the filesystem
541 '''Get name in the case stored in the filesystem
550
542
551 The name is either relative to root, or it is an absolute path starting
543 The name is either relative to root, or it is an absolute path starting
552 with root. Note that this function is unnecessary, and should not be
544 with root. Note that this function is unnecessary, and should not be
553 called, for case-sensitive filesystems (simply because it's expensive).
545 called, for case-sensitive filesystems (simply because it's expensive).
554 '''
546 '''
555 # If name is absolute, make it relative
547 # If name is absolute, make it relative
556 if name.lower().startswith(root.lower()):
548 if name.lower().startswith(root.lower()):
557 l = len(root)
549 l = len(root)
558 if name[l] == os.sep or name[l] == os.altsep:
550 if name[l] == os.sep or name[l] == os.altsep:
559 l = l + 1
551 l = l + 1
560 name = name[l:]
552 name = name[l:]
561
553
562 if not os.path.lexists(os.path.join(root, name)):
554 if not os.path.lexists(os.path.join(root, name)):
563 return None
555 return None
564
556
565 seps = os.sep
557 seps = os.sep
566 if os.altsep:
558 if os.altsep:
567 seps = seps + os.altsep
559 seps = seps + os.altsep
568 # Protect backslashes. This gets silly very quickly.
560 # Protect backslashes. This gets silly very quickly.
569 seps.replace('\\','\\\\')
561 seps.replace('\\','\\\\')
570 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
562 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
571 dir = os.path.normcase(os.path.normpath(root))
563 dir = os.path.normcase(os.path.normpath(root))
572 result = []
564 result = []
573 for part, sep in pattern.findall(name):
565 for part, sep in pattern.findall(name):
574 if sep:
566 if sep:
575 result.append(sep)
567 result.append(sep)
576 continue
568 continue
577
569
578 if dir not in _fspathcache:
570 if dir not in _fspathcache:
579 _fspathcache[dir] = os.listdir(dir)
571 _fspathcache[dir] = os.listdir(dir)
580 contents = _fspathcache[dir]
572 contents = _fspathcache[dir]
581
573
582 lpart = part.lower()
574 lpart = part.lower()
583 lenp = len(part)
575 lenp = len(part)
584 for n in contents:
576 for n in contents:
585 if lenp == len(n) and n.lower() == lpart:
577 if lenp == len(n) and n.lower() == lpart:
586 result.append(n)
578 result.append(n)
587 break
579 break
588 else:
580 else:
589 # Cannot happen, as the file exists!
581 # Cannot happen, as the file exists!
590 result.append(part)
582 result.append(part)
591 dir = os.path.join(dir, lpart)
583 dir = os.path.join(dir, lpart)
592
584
593 return ''.join(result)
585 return ''.join(result)
594
586
595 def checknlink(testfile):
587 def checknlink(testfile):
596 '''check whether hardlink count reporting works properly'''
588 '''check whether hardlink count reporting works properly'''
597
589
598 # testfile may be open, so we need a separate file for checking to
590 # testfile may be open, so we need a separate file for checking to
599 # work around issue2543 (or testfile may get lost on Samba shares)
591 # work around issue2543 (or testfile may get lost on Samba shares)
600 f1 = testfile + ".hgtmp1"
592 f1 = testfile + ".hgtmp1"
601 if os.path.lexists(f1):
593 if os.path.lexists(f1):
602 return False
594 return False
603 try:
595 try:
604 posixfile(f1, 'w').close()
596 posixfile(f1, 'w').close()
605 except IOError:
597 except IOError:
606 return False
598 return False
607
599
608 f2 = testfile + ".hgtmp2"
600 f2 = testfile + ".hgtmp2"
609 fd = None
601 fd = None
610 try:
602 try:
611 try:
603 try:
612 oslink(f1, f2)
604 oslink(f1, f2)
613 except OSError:
605 except OSError:
614 return False
606 return False
615
607
616 # nlinks() may behave differently for files on Windows shares if
608 # nlinks() may behave differently for files on Windows shares if
617 # the file is open.
609 # the file is open.
618 fd = posixfile(f2)
610 fd = posixfile(f2)
619 return nlinks(f2) > 1
611 return nlinks(f2) > 1
620 finally:
612 finally:
621 if fd is not None:
613 if fd is not None:
622 fd.close()
614 fd.close()
623 for f in (f1, f2):
615 for f in (f1, f2):
624 try:
616 try:
625 os.unlink(f)
617 os.unlink(f)
626 except OSError:
618 except OSError:
627 pass
619 pass
628
620
629 return False
621 return False
630
622
631 def endswithsep(path):
623 def endswithsep(path):
632 '''Check path ends with os.sep or os.altsep.'''
624 '''Check path ends with os.sep or os.altsep.'''
633 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
625 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
634
626
635 def splitpath(path):
627 def splitpath(path):
636 '''Split path by os.sep.
628 '''Split path by os.sep.
637 Note that this function does not use os.altsep because this is
629 Note that this function does not use os.altsep because this is
638 an alternative of simple "xxx.split(os.sep)".
630 an alternative of simple "xxx.split(os.sep)".
639 It is recommended to use os.path.normpath() before using this
631 It is recommended to use os.path.normpath() before using this
640 function if need.'''
632 function if need.'''
641 return path.split(os.sep)
633 return path.split(os.sep)
642
634
643 def gui():
635 def gui():
644 '''Are we running in a GUI?'''
636 '''Are we running in a GUI?'''
645 if sys.platform == 'darwin':
637 if sys.platform == 'darwin':
646 if 'SSH_CONNECTION' in os.environ:
638 if 'SSH_CONNECTION' in os.environ:
647 # handle SSH access to a box where the user is logged in
639 # handle SSH access to a box where the user is logged in
648 return False
640 return False
649 elif getattr(osutil, 'isgui', None):
641 elif getattr(osutil, 'isgui', None):
650 # check if a CoreGraphics session is available
642 # check if a CoreGraphics session is available
651 return osutil.isgui()
643 return osutil.isgui()
652 else:
644 else:
653 # pure build; use a safe default
645 # pure build; use a safe default
654 return True
646 return True
655 else:
647 else:
656 return os.name == "nt" or os.environ.get("DISPLAY")
648 return os.name == "nt" or os.environ.get("DISPLAY")
657
649
658 def mktempcopy(name, emptyok=False, createmode=None):
650 def mktempcopy(name, emptyok=False, createmode=None):
659 """Create a temporary file with the same contents from name
651 """Create a temporary file with the same contents from name
660
652
661 The permission bits are copied from the original file.
653 The permission bits are copied from the original file.
662
654
663 If the temporary file is going to be truncated immediately, you
655 If the temporary file is going to be truncated immediately, you
664 can use emptyok=True as an optimization.
656 can use emptyok=True as an optimization.
665
657
666 Returns the name of the temporary file.
658 Returns the name of the temporary file.
667 """
659 """
668 d, fn = os.path.split(name)
660 d, fn = os.path.split(name)
669 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
661 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
670 os.close(fd)
662 os.close(fd)
671 # Temporary files are created with mode 0600, which is usually not
663 # Temporary files are created with mode 0600, which is usually not
672 # what we want. If the original file already exists, just copy
664 # what we want. If the original file already exists, just copy
673 # its mode. Otherwise, manually obey umask.
665 # its mode. Otherwise, manually obey umask.
674 try:
666 try:
675 st_mode = os.lstat(name).st_mode & 0777
667 st_mode = os.lstat(name).st_mode & 0777
676 except OSError, inst:
668 except OSError, inst:
677 if inst.errno != errno.ENOENT:
669 if inst.errno != errno.ENOENT:
678 raise
670 raise
679 st_mode = createmode
671 st_mode = createmode
680 if st_mode is None:
672 if st_mode is None:
681 st_mode = ~umask
673 st_mode = ~umask
682 st_mode &= 0666
674 st_mode &= 0666
683 os.chmod(temp, st_mode)
675 os.chmod(temp, st_mode)
684 if emptyok:
676 if emptyok:
685 return temp
677 return temp
686 try:
678 try:
687 try:
679 try:
688 ifp = posixfile(name, "rb")
680 ifp = posixfile(name, "rb")
689 except IOError, inst:
681 except IOError, inst:
690 if inst.errno == errno.ENOENT:
682 if inst.errno == errno.ENOENT:
691 return temp
683 return temp
692 if not getattr(inst, 'filename', None):
684 if not getattr(inst, 'filename', None):
693 inst.filename = name
685 inst.filename = name
694 raise
686 raise
695 ofp = posixfile(temp, "wb")
687 ofp = posixfile(temp, "wb")
696 for chunk in filechunkiter(ifp):
688 for chunk in filechunkiter(ifp):
697 ofp.write(chunk)
689 ofp.write(chunk)
698 ifp.close()
690 ifp.close()
699 ofp.close()
691 ofp.close()
700 except:
692 except:
701 try: os.unlink(temp)
693 try: os.unlink(temp)
702 except: pass
694 except: pass
703 raise
695 raise
704 return temp
696 return temp
705
697
706 class atomictempfile(object):
698 class atomictempfile(object):
707 '''writeable file object that atomically updates a file
699 '''writeable file object that atomically updates a file
708
700
709 All writes will go to a temporary copy of the original file. Call
701 All writes will go to a temporary copy of the original file. Call
710 rename() when you are done writing, and atomictempfile will rename
702 rename() when you are done writing, and atomictempfile will rename
711 the temporary copy to the original name, making the changes visible.
703 the temporary copy to the original name, making the changes visible.
712
704
713 Unlike other file-like objects, close() discards your writes by
705 Unlike other file-like objects, close() discards your writes by
714 simply deleting the temporary file.
706 simply deleting the temporary file.
715 '''
707 '''
716 def __init__(self, name, mode='w+b', createmode=None):
708 def __init__(self, name, mode='w+b', createmode=None):
717 self.__name = name # permanent name
709 self.__name = name # permanent name
718 self._tempname = mktempcopy(name, emptyok=('w' in mode),
710 self._tempname = mktempcopy(name, emptyok=('w' in mode),
719 createmode=createmode)
711 createmode=createmode)
720 self._fp = posixfile(self._tempname, mode)
712 self._fp = posixfile(self._tempname, mode)
721
713
722 # delegated methods
714 # delegated methods
723 self.write = self._fp.write
715 self.write = self._fp.write
724 self.fileno = self._fp.fileno
716 self.fileno = self._fp.fileno
725
717
726 def rename(self):
718 def rename(self):
727 if not self._fp.closed:
719 if not self._fp.closed:
728 self._fp.close()
720 self._fp.close()
729 rename(self._tempname, localpath(self.__name))
721 rename(self._tempname, localpath(self.__name))
730
722
731 def close(self):
723 def close(self):
732 if not self._fp.closed:
724 if not self._fp.closed:
733 try:
725 try:
734 os.unlink(self._tempname)
726 os.unlink(self._tempname)
735 except OSError:
727 except OSError:
736 pass
728 pass
737 self._fp.close()
729 self._fp.close()
738
730
739 def __del__(self):
731 def __del__(self):
740 if hasattr(self, '_fp'): # constructor actually did something
732 if hasattr(self, '_fp'): # constructor actually did something
741 self.close()
733 self.close()
742
734
743 def makedirs(name, mode=None):
735 def makedirs(name, mode=None):
744 """recursive directory creation with parent mode inheritance"""
736 """recursive directory creation with parent mode inheritance"""
745 parent = os.path.abspath(os.path.dirname(name))
737 parent = os.path.abspath(os.path.dirname(name))
746 try:
738 try:
747 os.mkdir(name)
739 os.mkdir(name)
748 if mode is not None:
740 if mode is not None:
749 os.chmod(name, mode)
741 os.chmod(name, mode)
750 return
742 return
751 except OSError, err:
743 except OSError, err:
752 if err.errno == errno.EEXIST:
744 if err.errno == errno.EEXIST:
753 return
745 return
754 if not name or parent == name or err.errno != errno.ENOENT:
746 if not name or parent == name or err.errno != errno.ENOENT:
755 raise
747 raise
756 makedirs(parent, mode)
748 makedirs(parent, mode)
757 makedirs(name, mode)
749 makedirs(name, mode)
758
750
759 def readfile(path):
751 def readfile(path):
760 fp = open(path, 'rb')
752 fp = open(path, 'rb')
761 try:
753 try:
762 return fp.read()
754 return fp.read()
763 finally:
755 finally:
764 fp.close()
756 fp.close()
765
757
766 def writefile(path, text):
758 def writefile(path, text):
767 fp = open(path, 'wb')
759 fp = open(path, 'wb')
768 try:
760 try:
769 fp.write(text)
761 fp.write(text)
770 finally:
762 finally:
771 fp.close()
763 fp.close()
772
764
773 def appendfile(path, text):
765 def appendfile(path, text):
774 fp = open(path, 'ab')
766 fp = open(path, 'ab')
775 try:
767 try:
776 fp.write(text)
768 fp.write(text)
777 finally:
769 finally:
778 fp.close()
770 fp.close()
779
771
780 class chunkbuffer(object):
772 class chunkbuffer(object):
781 """Allow arbitrary sized chunks of data to be efficiently read from an
773 """Allow arbitrary sized chunks of data to be efficiently read from an
782 iterator over chunks of arbitrary size."""
774 iterator over chunks of arbitrary size."""
783
775
784 def __init__(self, in_iter):
776 def __init__(self, in_iter):
785 """in_iter is the iterator that's iterating over the input chunks.
777 """in_iter is the iterator that's iterating over the input chunks.
786 targetsize is how big a buffer to try to maintain."""
778 targetsize is how big a buffer to try to maintain."""
787 def splitbig(chunks):
779 def splitbig(chunks):
788 for chunk in chunks:
780 for chunk in chunks:
789 if len(chunk) > 2**20:
781 if len(chunk) > 2**20:
790 pos = 0
782 pos = 0
791 while pos < len(chunk):
783 while pos < len(chunk):
792 end = pos + 2 ** 18
784 end = pos + 2 ** 18
793 yield chunk[pos:end]
785 yield chunk[pos:end]
794 pos = end
786 pos = end
795 else:
787 else:
796 yield chunk
788 yield chunk
797 self.iter = splitbig(in_iter)
789 self.iter = splitbig(in_iter)
798 self._queue = []
790 self._queue = []
799
791
800 def read(self, l):
792 def read(self, l):
801 """Read L bytes of data from the iterator of chunks of data.
793 """Read L bytes of data from the iterator of chunks of data.
802 Returns less than L bytes if the iterator runs dry."""
794 Returns less than L bytes if the iterator runs dry."""
803 left = l
795 left = l
804 buf = ''
796 buf = ''
805 queue = self._queue
797 queue = self._queue
806 while left > 0:
798 while left > 0:
807 # refill the queue
799 # refill the queue
808 if not queue:
800 if not queue:
809 target = 2**18
801 target = 2**18
810 for chunk in self.iter:
802 for chunk in self.iter:
811 queue.append(chunk)
803 queue.append(chunk)
812 target -= len(chunk)
804 target -= len(chunk)
813 if target <= 0:
805 if target <= 0:
814 break
806 break
815 if not queue:
807 if not queue:
816 break
808 break
817
809
818 chunk = queue.pop(0)
810 chunk = queue.pop(0)
819 left -= len(chunk)
811 left -= len(chunk)
820 if left < 0:
812 if left < 0:
821 queue.insert(0, chunk[left:])
813 queue.insert(0, chunk[left:])
822 buf += chunk[:left]
814 buf += chunk[:left]
823 else:
815 else:
824 buf += chunk
816 buf += chunk
825
817
826 return buf
818 return buf
827
819
828 def filechunkiter(f, size=65536, limit=None):
820 def filechunkiter(f, size=65536, limit=None):
829 """Create a generator that produces the data in the file size
821 """Create a generator that produces the data in the file size
830 (default 65536) bytes at a time, up to optional limit (default is
822 (default 65536) bytes at a time, up to optional limit (default is
831 to read all data). Chunks may be less than size bytes if the
823 to read all data). Chunks may be less than size bytes if the
832 chunk is the last chunk in the file, or the file is a socket or
824 chunk is the last chunk in the file, or the file is a socket or
833 some other type of file that sometimes reads less data than is
825 some other type of file that sometimes reads less data than is
834 requested."""
826 requested."""
835 assert size >= 0
827 assert size >= 0
836 assert limit is None or limit >= 0
828 assert limit is None or limit >= 0
837 while True:
829 while True:
838 if limit is None:
830 if limit is None:
839 nbytes = size
831 nbytes = size
840 else:
832 else:
841 nbytes = min(limit, size)
833 nbytes = min(limit, size)
842 s = nbytes and f.read(nbytes)
834 s = nbytes and f.read(nbytes)
843 if not s:
835 if not s:
844 break
836 break
845 if limit:
837 if limit:
846 limit -= len(s)
838 limit -= len(s)
847 yield s
839 yield s
848
840
849 def makedate():
841 def makedate():
850 lt = time.localtime()
842 lt = time.localtime()
851 if lt[8] == 1 and time.daylight:
843 if lt[8] == 1 and time.daylight:
852 tz = time.altzone
844 tz = time.altzone
853 else:
845 else:
854 tz = time.timezone
846 tz = time.timezone
855 t = time.mktime(lt)
847 t = time.mktime(lt)
856 if t < 0:
848 if t < 0:
857 hint = _("check your clock")
849 hint = _("check your clock")
858 raise Abort(_("negative timestamp: %d") % t, hint=hint)
850 raise Abort(_("negative timestamp: %d") % t, hint=hint)
859 return t, tz
851 return t, tz
860
852
861 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
853 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
862 """represent a (unixtime, offset) tuple as a localized time.
854 """represent a (unixtime, offset) tuple as a localized time.
863 unixtime is seconds since the epoch, and offset is the time zone's
855 unixtime is seconds since the epoch, and offset is the time zone's
864 number of seconds away from UTC. if timezone is false, do not
856 number of seconds away from UTC. if timezone is false, do not
865 append time zone to string."""
857 append time zone to string."""
866 t, tz = date or makedate()
858 t, tz = date or makedate()
867 if t < 0:
859 if t < 0:
868 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
860 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
869 tz = 0
861 tz = 0
870 if "%1" in format or "%2" in format:
862 if "%1" in format or "%2" in format:
871 sign = (tz > 0) and "-" or "+"
863 sign = (tz > 0) and "-" or "+"
872 minutes = abs(tz) // 60
864 minutes = abs(tz) // 60
873 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
865 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
874 format = format.replace("%2", "%02d" % (minutes % 60))
866 format = format.replace("%2", "%02d" % (minutes % 60))
875 s = time.strftime(format, time.gmtime(float(t) - tz))
867 s = time.strftime(format, time.gmtime(float(t) - tz))
876 return s
868 return s
877
869
878 def shortdate(date=None):
870 def shortdate(date=None):
879 """turn (timestamp, tzoff) tuple into iso 8631 date."""
871 """turn (timestamp, tzoff) tuple into iso 8631 date."""
880 return datestr(date, format='%Y-%m-%d')
872 return datestr(date, format='%Y-%m-%d')
881
873
882 def strdate(string, format, defaults=[]):
874 def strdate(string, format, defaults=[]):
883 """parse a localized time string and return a (unixtime, offset) tuple.
875 """parse a localized time string and return a (unixtime, offset) tuple.
884 if the string cannot be parsed, ValueError is raised."""
876 if the string cannot be parsed, ValueError is raised."""
885 def timezone(string):
877 def timezone(string):
886 tz = string.split()[-1]
878 tz = string.split()[-1]
887 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
879 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
888 sign = (tz[0] == "+") and 1 or -1
880 sign = (tz[0] == "+") and 1 or -1
889 hours = int(tz[1:3])
881 hours = int(tz[1:3])
890 minutes = int(tz[3:5])
882 minutes = int(tz[3:5])
891 return -sign * (hours * 60 + minutes) * 60
883 return -sign * (hours * 60 + minutes) * 60
892 if tz == "GMT" or tz == "UTC":
884 if tz == "GMT" or tz == "UTC":
893 return 0
885 return 0
894 return None
886 return None
895
887
896 # NOTE: unixtime = localunixtime + offset
888 # NOTE: unixtime = localunixtime + offset
897 offset, date = timezone(string), string
889 offset, date = timezone(string), string
898 if offset is not None:
890 if offset is not None:
899 date = " ".join(string.split()[:-1])
891 date = " ".join(string.split()[:-1])
900
892
901 # add missing elements from defaults
893 # add missing elements from defaults
902 usenow = False # default to using biased defaults
894 usenow = False # default to using biased defaults
903 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
895 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
904 found = [True for p in part if ("%"+p) in format]
896 found = [True for p in part if ("%"+p) in format]
905 if not found:
897 if not found:
906 date += "@" + defaults[part][usenow]
898 date += "@" + defaults[part][usenow]
907 format += "@%" + part[0]
899 format += "@%" + part[0]
908 else:
900 else:
909 # We've found a specific time element, less specific time
901 # We've found a specific time element, less specific time
910 # elements are relative to today
902 # elements are relative to today
911 usenow = True
903 usenow = True
912
904
913 timetuple = time.strptime(date, format)
905 timetuple = time.strptime(date, format)
914 localunixtime = int(calendar.timegm(timetuple))
906 localunixtime = int(calendar.timegm(timetuple))
915 if offset is None:
907 if offset is None:
916 # local timezone
908 # local timezone
917 unixtime = int(time.mktime(timetuple))
909 unixtime = int(time.mktime(timetuple))
918 offset = unixtime - localunixtime
910 offset = unixtime - localunixtime
919 else:
911 else:
920 unixtime = localunixtime + offset
912 unixtime = localunixtime + offset
921 return unixtime, offset
913 return unixtime, offset
922
914
923 def parsedate(date, formats=None, bias={}):
915 def parsedate(date, formats=None, bias={}):
924 """parse a localized date/time and return a (unixtime, offset) tuple.
916 """parse a localized date/time and return a (unixtime, offset) tuple.
925
917
926 The date may be a "unixtime offset" string or in one of the specified
918 The date may be a "unixtime offset" string or in one of the specified
927 formats. If the date already is a (unixtime, offset) tuple, it is returned.
919 formats. If the date already is a (unixtime, offset) tuple, it is returned.
928 """
920 """
929 if not date:
921 if not date:
930 return 0, 0
922 return 0, 0
931 if isinstance(date, tuple) and len(date) == 2:
923 if isinstance(date, tuple) and len(date) == 2:
932 return date
924 return date
933 if not formats:
925 if not formats:
934 formats = defaultdateformats
926 formats = defaultdateformats
935 date = date.strip()
927 date = date.strip()
936 try:
928 try:
937 when, offset = map(int, date.split(' '))
929 when, offset = map(int, date.split(' '))
938 except ValueError:
930 except ValueError:
939 # fill out defaults
931 # fill out defaults
940 now = makedate()
932 now = makedate()
941 defaults = {}
933 defaults = {}
942 for part in ("d", "mb", "yY", "HI", "M", "S"):
934 for part in ("d", "mb", "yY", "HI", "M", "S"):
943 # this piece is for rounding the specific end of unknowns
935 # this piece is for rounding the specific end of unknowns
944 b = bias.get(part)
936 b = bias.get(part)
945 if b is None:
937 if b is None:
946 if part[0] in "HMS":
938 if part[0] in "HMS":
947 b = "00"
939 b = "00"
948 else:
940 else:
949 b = "0"
941 b = "0"
950
942
951 # this piece is for matching the generic end to today's date
943 # this piece is for matching the generic end to today's date
952 n = datestr(now, "%" + part[0])
944 n = datestr(now, "%" + part[0])
953
945
954 defaults[part] = (b, n)
946 defaults[part] = (b, n)
955
947
956 for format in formats:
948 for format in formats:
957 try:
949 try:
958 when, offset = strdate(date, format, defaults)
950 when, offset = strdate(date, format, defaults)
959 except (ValueError, OverflowError):
951 except (ValueError, OverflowError):
960 pass
952 pass
961 else:
953 else:
962 break
954 break
963 else:
955 else:
964 raise Abort(_('invalid date: %r') % date)
956 raise Abort(_('invalid date: %r') % date)
965 # validate explicit (probably user-specified) date and
957 # validate explicit (probably user-specified) date and
966 # time zone offset. values must fit in signed 32 bits for
958 # time zone offset. values must fit in signed 32 bits for
967 # current 32-bit linux runtimes. timezones go from UTC-12
959 # current 32-bit linux runtimes. timezones go from UTC-12
968 # to UTC+14
960 # to UTC+14
969 if abs(when) > 0x7fffffff:
961 if abs(when) > 0x7fffffff:
970 raise Abort(_('date exceeds 32 bits: %d') % when)
962 raise Abort(_('date exceeds 32 bits: %d') % when)
971 if when < 0:
963 if when < 0:
972 raise Abort(_('negative date value: %d') % when)
964 raise Abort(_('negative date value: %d') % when)
973 if offset < -50400 or offset > 43200:
965 if offset < -50400 or offset > 43200:
974 raise Abort(_('impossible time zone offset: %d') % offset)
966 raise Abort(_('impossible time zone offset: %d') % offset)
975 return when, offset
967 return when, offset
976
968
977 def matchdate(date):
969 def matchdate(date):
978 """Return a function that matches a given date match specifier
970 """Return a function that matches a given date match specifier
979
971
980 Formats include:
972 Formats include:
981
973
982 '{date}' match a given date to the accuracy provided
974 '{date}' match a given date to the accuracy provided
983
975
984 '<{date}' on or before a given date
976 '<{date}' on or before a given date
985
977
986 '>{date}' on or after a given date
978 '>{date}' on or after a given date
987
979
988 >>> p1 = parsedate("10:29:59")
980 >>> p1 = parsedate("10:29:59")
989 >>> p2 = parsedate("10:30:00")
981 >>> p2 = parsedate("10:30:00")
990 >>> p3 = parsedate("10:30:59")
982 >>> p3 = parsedate("10:30:59")
991 >>> p4 = parsedate("10:31:00")
983 >>> p4 = parsedate("10:31:00")
992 >>> p5 = parsedate("Sep 15 10:30:00 1999")
984 >>> p5 = parsedate("Sep 15 10:30:00 1999")
993 >>> f = matchdate("10:30")
985 >>> f = matchdate("10:30")
994 >>> f(p1[0])
986 >>> f(p1[0])
995 False
987 False
996 >>> f(p2[0])
988 >>> f(p2[0])
997 True
989 True
998 >>> f(p3[0])
990 >>> f(p3[0])
999 True
991 True
1000 >>> f(p4[0])
992 >>> f(p4[0])
1001 False
993 False
1002 >>> f(p5[0])
994 >>> f(p5[0])
1003 False
995 False
1004 """
996 """
1005
997
1006 def lower(date):
998 def lower(date):
1007 d = dict(mb="1", d="1")
999 d = dict(mb="1", d="1")
1008 return parsedate(date, extendeddateformats, d)[0]
1000 return parsedate(date, extendeddateformats, d)[0]
1009
1001
1010 def upper(date):
1002 def upper(date):
1011 d = dict(mb="12", HI="23", M="59", S="59")
1003 d = dict(mb="12", HI="23", M="59", S="59")
1012 for days in ("31", "30", "29"):
1004 for days in ("31", "30", "29"):
1013 try:
1005 try:
1014 d["d"] = days
1006 d["d"] = days
1015 return parsedate(date, extendeddateformats, d)[0]
1007 return parsedate(date, extendeddateformats, d)[0]
1016 except:
1008 except:
1017 pass
1009 pass
1018 d["d"] = "28"
1010 d["d"] = "28"
1019 return parsedate(date, extendeddateformats, d)[0]
1011 return parsedate(date, extendeddateformats, d)[0]
1020
1012
1021 date = date.strip()
1013 date = date.strip()
1022
1014
1023 if not date:
1015 if not date:
1024 raise Abort(_("dates cannot consist entirely of whitespace"))
1016 raise Abort(_("dates cannot consist entirely of whitespace"))
1025 elif date[0] == "<":
1017 elif date[0] == "<":
1026 if not date[1:]:
1018 if not date[1:]:
1027 raise Abort(_("invalid day spec, use '<DATE'"))
1019 raise Abort(_("invalid day spec, use '<DATE'"))
1028 when = upper(date[1:])
1020 when = upper(date[1:])
1029 return lambda x: x <= when
1021 return lambda x: x <= when
1030 elif date[0] == ">":
1022 elif date[0] == ">":
1031 if not date[1:]:
1023 if not date[1:]:
1032 raise Abort(_("invalid day spec, use '>DATE'"))
1024 raise Abort(_("invalid day spec, use '>DATE'"))
1033 when = lower(date[1:])
1025 when = lower(date[1:])
1034 return lambda x: x >= when
1026 return lambda x: x >= when
1035 elif date[0] == "-":
1027 elif date[0] == "-":
1036 try:
1028 try:
1037 days = int(date[1:])
1029 days = int(date[1:])
1038 except ValueError:
1030 except ValueError:
1039 raise Abort(_("invalid day spec: %s") % date[1:])
1031 raise Abort(_("invalid day spec: %s") % date[1:])
1040 if days < 0:
1032 if days < 0:
1041 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1033 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1042 % date[1:])
1034 % date[1:])
1043 when = makedate()[0] - days * 3600 * 24
1035 when = makedate()[0] - days * 3600 * 24
1044 return lambda x: x >= when
1036 return lambda x: x >= when
1045 elif " to " in date:
1037 elif " to " in date:
1046 a, b = date.split(" to ")
1038 a, b = date.split(" to ")
1047 start, stop = lower(a), upper(b)
1039 start, stop = lower(a), upper(b)
1048 return lambda x: x >= start and x <= stop
1040 return lambda x: x >= start and x <= stop
1049 else:
1041 else:
1050 start, stop = lower(date), upper(date)
1042 start, stop = lower(date), upper(date)
1051 return lambda x: x >= start and x <= stop
1043 return lambda x: x >= start and x <= stop
1052
1044
1053 def shortuser(user):
1045 def shortuser(user):
1054 """Return a short representation of a user name or email address."""
1046 """Return a short representation of a user name or email address."""
1055 f = user.find('@')
1047 f = user.find('@')
1056 if f >= 0:
1048 if f >= 0:
1057 user = user[:f]
1049 user = user[:f]
1058 f = user.find('<')
1050 f = user.find('<')
1059 if f >= 0:
1051 if f >= 0:
1060 user = user[f + 1:]
1052 user = user[f + 1:]
1061 f = user.find(' ')
1053 f = user.find(' ')
1062 if f >= 0:
1054 if f >= 0:
1063 user = user[:f]
1055 user = user[:f]
1064 f = user.find('.')
1056 f = user.find('.')
1065 if f >= 0:
1057 if f >= 0:
1066 user = user[:f]
1058 user = user[:f]
1067 return user
1059 return user
1068
1060
1069 def email(author):
1061 def email(author):
1070 '''get email of author.'''
1062 '''get email of author.'''
1071 r = author.find('>')
1063 r = author.find('>')
1072 if r == -1:
1064 if r == -1:
1073 r = None
1065 r = None
1074 return author[author.find('<') + 1:r]
1066 return author[author.find('<') + 1:r]
1075
1067
1076 def _ellipsis(text, maxlength):
1068 def _ellipsis(text, maxlength):
1077 if len(text) <= maxlength:
1069 if len(text) <= maxlength:
1078 return text, False
1070 return text, False
1079 else:
1071 else:
1080 return "%s..." % (text[:maxlength - 3]), True
1072 return "%s..." % (text[:maxlength - 3]), True
1081
1073
1082 def ellipsis(text, maxlength=400):
1074 def ellipsis(text, maxlength=400):
1083 """Trim string to at most maxlength (default: 400) characters."""
1075 """Trim string to at most maxlength (default: 400) characters."""
1084 try:
1076 try:
1085 # use unicode not to split at intermediate multi-byte sequence
1077 # use unicode not to split at intermediate multi-byte sequence
1086 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1078 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1087 maxlength)
1079 maxlength)
1088 if not truncated:
1080 if not truncated:
1089 return text
1081 return text
1090 return utext.encode(encoding.encoding)
1082 return utext.encode(encoding.encoding)
1091 except (UnicodeDecodeError, UnicodeEncodeError):
1083 except (UnicodeDecodeError, UnicodeEncodeError):
1092 return _ellipsis(text, maxlength)[0]
1084 return _ellipsis(text, maxlength)[0]
1093
1085
1094 def bytecount(nbytes):
1086 def bytecount(nbytes):
1095 '''return byte count formatted as readable string, with units'''
1087 '''return byte count formatted as readable string, with units'''
1096
1088
1097 units = (
1089 units = (
1098 (100, 1 << 30, _('%.0f GB')),
1090 (100, 1 << 30, _('%.0f GB')),
1099 (10, 1 << 30, _('%.1f GB')),
1091 (10, 1 << 30, _('%.1f GB')),
1100 (1, 1 << 30, _('%.2f GB')),
1092 (1, 1 << 30, _('%.2f GB')),
1101 (100, 1 << 20, _('%.0f MB')),
1093 (100, 1 << 20, _('%.0f MB')),
1102 (10, 1 << 20, _('%.1f MB')),
1094 (10, 1 << 20, _('%.1f MB')),
1103 (1, 1 << 20, _('%.2f MB')),
1095 (1, 1 << 20, _('%.2f MB')),
1104 (100, 1 << 10, _('%.0f KB')),
1096 (100, 1 << 10, _('%.0f KB')),
1105 (10, 1 << 10, _('%.1f KB')),
1097 (10, 1 << 10, _('%.1f KB')),
1106 (1, 1 << 10, _('%.2f KB')),
1098 (1, 1 << 10, _('%.2f KB')),
1107 (1, 1, _('%.0f bytes')),
1099 (1, 1, _('%.0f bytes')),
1108 )
1100 )
1109
1101
1110 for multiplier, divisor, format in units:
1102 for multiplier, divisor, format in units:
1111 if nbytes >= divisor * multiplier:
1103 if nbytes >= divisor * multiplier:
1112 return format % (nbytes / float(divisor))
1104 return format % (nbytes / float(divisor))
1113 return units[-1][2] % nbytes
1105 return units[-1][2] % nbytes
1114
1106
1115 def uirepr(s):
1107 def uirepr(s):
1116 # Avoid double backslash in Windows path repr()
1108 # Avoid double backslash in Windows path repr()
1117 return repr(s).replace('\\\\', '\\')
1109 return repr(s).replace('\\\\', '\\')
1118
1110
1119 # delay import of textwrap
1111 # delay import of textwrap
1120 def MBTextWrapper(**kwargs):
1112 def MBTextWrapper(**kwargs):
1121 class tw(textwrap.TextWrapper):
1113 class tw(textwrap.TextWrapper):
1122 """
1114 """
1123 Extend TextWrapper for double-width characters.
1115 Extend TextWrapper for double-width characters.
1124
1116
1125 Some Asian characters use two terminal columns instead of one.
1117 Some Asian characters use two terminal columns instead of one.
1126 A good example of this behavior can be seen with u'\u65e5\u672c',
1118 A good example of this behavior can be seen with u'\u65e5\u672c',
1127 the two Japanese characters for "Japan":
1119 the two Japanese characters for "Japan":
1128 len() returns 2, but when printed to a terminal, they eat 4 columns.
1120 len() returns 2, but when printed to a terminal, they eat 4 columns.
1129
1121
1130 (Note that this has nothing to do whatsoever with unicode
1122 (Note that this has nothing to do whatsoever with unicode
1131 representation, or encoding of the underlying string)
1123 representation, or encoding of the underlying string)
1132 """
1124 """
1133 def __init__(self, **kwargs):
1125 def __init__(self, **kwargs):
1134 textwrap.TextWrapper.__init__(self, **kwargs)
1126 textwrap.TextWrapper.__init__(self, **kwargs)
1135
1127
1136 def _cutdown(self, str, space_left):
1128 def _cutdown(self, str, space_left):
1137 l = 0
1129 l = 0
1138 ucstr = unicode(str, encoding.encoding)
1130 ucstr = unicode(str, encoding.encoding)
1139 colwidth = unicodedata.east_asian_width
1131 colwidth = unicodedata.east_asian_width
1140 for i in xrange(len(ucstr)):
1132 for i in xrange(len(ucstr)):
1141 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1133 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1142 if space_left < l:
1134 if space_left < l:
1143 return (ucstr[:i].encode(encoding.encoding),
1135 return (ucstr[:i].encode(encoding.encoding),
1144 ucstr[i:].encode(encoding.encoding))
1136 ucstr[i:].encode(encoding.encoding))
1145 return str, ''
1137 return str, ''
1146
1138
1147 # overriding of base class
1139 # overriding of base class
1148 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1140 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1149 space_left = max(width - cur_len, 1)
1141 space_left = max(width - cur_len, 1)
1150
1142
1151 if self.break_long_words:
1143 if self.break_long_words:
1152 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1144 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1153 cur_line.append(cut)
1145 cur_line.append(cut)
1154 reversed_chunks[-1] = res
1146 reversed_chunks[-1] = res
1155 elif not cur_line:
1147 elif not cur_line:
1156 cur_line.append(reversed_chunks.pop())
1148 cur_line.append(reversed_chunks.pop())
1157
1149
1158 global MBTextWrapper
1150 global MBTextWrapper
1159 MBTextWrapper = tw
1151 MBTextWrapper = tw
1160 return tw(**kwargs)
1152 return tw(**kwargs)
1161
1153
1162 def wrap(line, width, initindent='', hangindent=''):
1154 def wrap(line, width, initindent='', hangindent=''):
1163 maxindent = max(len(hangindent), len(initindent))
1155 maxindent = max(len(hangindent), len(initindent))
1164 if width <= maxindent:
1156 if width <= maxindent:
1165 # adjust for weird terminal size
1157 # adjust for weird terminal size
1166 width = max(78, maxindent + 1)
1158 width = max(78, maxindent + 1)
1167 wrapper = MBTextWrapper(width=width,
1159 wrapper = MBTextWrapper(width=width,
1168 initial_indent=initindent,
1160 initial_indent=initindent,
1169 subsequent_indent=hangindent)
1161 subsequent_indent=hangindent)
1170 return wrapper.fill(line)
1162 return wrapper.fill(line)
1171
1163
1172 def iterlines(iterator):
1164 def iterlines(iterator):
1173 for chunk in iterator:
1165 for chunk in iterator:
1174 for line in chunk.splitlines():
1166 for line in chunk.splitlines():
1175 yield line
1167 yield line
1176
1168
1177 def expandpath(path):
1169 def expandpath(path):
1178 return os.path.expanduser(os.path.expandvars(path))
1170 return os.path.expanduser(os.path.expandvars(path))
1179
1171
1180 def hgcmd():
1172 def hgcmd():
1181 """Return the command used to execute current hg
1173 """Return the command used to execute current hg
1182
1174
1183 This is different from hgexecutable() because on Windows we want
1175 This is different from hgexecutable() because on Windows we want
1184 to avoid things opening new shell windows like batch files, so we
1176 to avoid things opening new shell windows like batch files, so we
1185 get either the python call or current executable.
1177 get either the python call or current executable.
1186 """
1178 """
1187 if mainfrozen():
1179 if mainfrozen():
1188 return [sys.executable]
1180 return [sys.executable]
1189 return gethgcmd()
1181 return gethgcmd()
1190
1182
1191 def rundetached(args, condfn):
1183 def rundetached(args, condfn):
1192 """Execute the argument list in a detached process.
1184 """Execute the argument list in a detached process.
1193
1185
1194 condfn is a callable which is called repeatedly and should return
1186 condfn is a callable which is called repeatedly and should return
1195 True once the child process is known to have started successfully.
1187 True once the child process is known to have started successfully.
1196 At this point, the child process PID is returned. If the child
1188 At this point, the child process PID is returned. If the child
1197 process fails to start or finishes before condfn() evaluates to
1189 process fails to start or finishes before condfn() evaluates to
1198 True, return -1.
1190 True, return -1.
1199 """
1191 """
1200 # Windows case is easier because the child process is either
1192 # Windows case is easier because the child process is either
1201 # successfully starting and validating the condition or exiting
1193 # successfully starting and validating the condition or exiting
1202 # on failure. We just poll on its PID. On Unix, if the child
1194 # on failure. We just poll on its PID. On Unix, if the child
1203 # process fails to start, it will be left in a zombie state until
1195 # process fails to start, it will be left in a zombie state until
1204 # the parent wait on it, which we cannot do since we expect a long
1196 # the parent wait on it, which we cannot do since we expect a long
1205 # running process on success. Instead we listen for SIGCHLD telling
1197 # running process on success. Instead we listen for SIGCHLD telling
1206 # us our child process terminated.
1198 # us our child process terminated.
1207 terminated = set()
1199 terminated = set()
1208 def handler(signum, frame):
1200 def handler(signum, frame):
1209 terminated.add(os.wait())
1201 terminated.add(os.wait())
1210 prevhandler = None
1202 prevhandler = None
1211 if hasattr(signal, 'SIGCHLD'):
1203 if hasattr(signal, 'SIGCHLD'):
1212 prevhandler = signal.signal(signal.SIGCHLD, handler)
1204 prevhandler = signal.signal(signal.SIGCHLD, handler)
1213 try:
1205 try:
1214 pid = spawndetached(args)
1206 pid = spawndetached(args)
1215 while not condfn():
1207 while not condfn():
1216 if ((pid in terminated or not testpid(pid))
1208 if ((pid in terminated or not testpid(pid))
1217 and not condfn()):
1209 and not condfn()):
1218 return -1
1210 return -1
1219 time.sleep(0.1)
1211 time.sleep(0.1)
1220 return pid
1212 return pid
1221 finally:
1213 finally:
1222 if prevhandler is not None:
1214 if prevhandler is not None:
1223 signal.signal(signal.SIGCHLD, prevhandler)
1215 signal.signal(signal.SIGCHLD, prevhandler)
1224
1216
1225 try:
1217 try:
1226 any, all = any, all
1218 any, all = any, all
1227 except NameError:
1219 except NameError:
1228 def any(iterable):
1220 def any(iterable):
1229 for i in iterable:
1221 for i in iterable:
1230 if i:
1222 if i:
1231 return True
1223 return True
1232 return False
1224 return False
1233
1225
1234 def all(iterable):
1226 def all(iterable):
1235 for i in iterable:
1227 for i in iterable:
1236 if not i:
1228 if not i:
1237 return False
1229 return False
1238 return True
1230 return True
1239
1231
1240 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1232 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1241 """Return the result of interpolating items in the mapping into string s.
1233 """Return the result of interpolating items in the mapping into string s.
1242
1234
1243 prefix is a single character string, or a two character string with
1235 prefix is a single character string, or a two character string with
1244 a backslash as the first character if the prefix needs to be escaped in
1236 a backslash as the first character if the prefix needs to be escaped in
1245 a regular expression.
1237 a regular expression.
1246
1238
1247 fn is an optional function that will be applied to the replacement text
1239 fn is an optional function that will be applied to the replacement text
1248 just before replacement.
1240 just before replacement.
1249
1241
1250 escape_prefix is an optional flag that allows using doubled prefix for
1242 escape_prefix is an optional flag that allows using doubled prefix for
1251 its escaping.
1243 its escaping.
1252 """
1244 """
1253 fn = fn or (lambda s: s)
1245 fn = fn or (lambda s: s)
1254 patterns = '|'.join(mapping.keys())
1246 patterns = '|'.join(mapping.keys())
1255 if escape_prefix:
1247 if escape_prefix:
1256 patterns += '|' + prefix
1248 patterns += '|' + prefix
1257 if len(prefix) > 1:
1249 if len(prefix) > 1:
1258 prefix_char = prefix[1:]
1250 prefix_char = prefix[1:]
1259 else:
1251 else:
1260 prefix_char = prefix
1252 prefix_char = prefix
1261 mapping[prefix_char] = prefix_char
1253 mapping[prefix_char] = prefix_char
1262 r = re.compile(r'%s(%s)' % (prefix, patterns))
1254 r = re.compile(r'%s(%s)' % (prefix, patterns))
1263 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1255 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1264
1256
1265 def getport(port):
1257 def getport(port):
1266 """Return the port for a given network service.
1258 """Return the port for a given network service.
1267
1259
1268 If port is an integer, it's returned as is. If it's a string, it's
1260 If port is an integer, it's returned as is. If it's a string, it's
1269 looked up using socket.getservbyname(). If there's no matching
1261 looked up using socket.getservbyname(). If there's no matching
1270 service, util.Abort is raised.
1262 service, util.Abort is raised.
1271 """
1263 """
1272 try:
1264 try:
1273 return int(port)
1265 return int(port)
1274 except ValueError:
1266 except ValueError:
1275 pass
1267 pass
1276
1268
1277 try:
1269 try:
1278 return socket.getservbyname(port)
1270 return socket.getservbyname(port)
1279 except socket.error:
1271 except socket.error:
1280 raise Abort(_("no port number associated with service '%s'") % port)
1272 raise Abort(_("no port number associated with service '%s'") % port)
1281
1273
1282 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1274 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1283 '0': False, 'no': False, 'false': False, 'off': False,
1275 '0': False, 'no': False, 'false': False, 'off': False,
1284 'never': False}
1276 'never': False}
1285
1277
1286 def parsebool(s):
1278 def parsebool(s):
1287 """Parse s into a boolean.
1279 """Parse s into a boolean.
1288
1280
1289 If s is not a valid boolean, returns None.
1281 If s is not a valid boolean, returns None.
1290 """
1282 """
1291 return _booleans.get(s.lower(), None)
1283 return _booleans.get(s.lower(), None)
1292
1284
1293 _hexdig = '0123456789ABCDEFabcdef'
1285 _hexdig = '0123456789ABCDEFabcdef'
1294 _hextochr = dict((a + b, chr(int(a + b, 16)))
1286 _hextochr = dict((a + b, chr(int(a + b, 16)))
1295 for a in _hexdig for b in _hexdig)
1287 for a in _hexdig for b in _hexdig)
1296
1288
1297 def _urlunquote(s):
1289 def _urlunquote(s):
1298 """unquote('abc%20def') -> 'abc def'."""
1290 """unquote('abc%20def') -> 'abc def'."""
1299 res = s.split('%')
1291 res = s.split('%')
1300 # fastpath
1292 # fastpath
1301 if len(res) == 1:
1293 if len(res) == 1:
1302 return s
1294 return s
1303 s = res[0]
1295 s = res[0]
1304 for item in res[1:]:
1296 for item in res[1:]:
1305 try:
1297 try:
1306 s += _hextochr[item[:2]] + item[2:]
1298 s += _hextochr[item[:2]] + item[2:]
1307 except KeyError:
1299 except KeyError:
1308 s += '%' + item
1300 s += '%' + item
1309 except UnicodeDecodeError:
1301 except UnicodeDecodeError:
1310 s += unichr(int(item[:2], 16)) + item[2:]
1302 s += unichr(int(item[:2], 16)) + item[2:]
1311 return s
1303 return s
1312
1304
1313 class url(object):
1305 class url(object):
1314 r"""Reliable URL parser.
1306 r"""Reliable URL parser.
1315
1307
1316 This parses URLs and provides attributes for the following
1308 This parses URLs and provides attributes for the following
1317 components:
1309 components:
1318
1310
1319 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1311 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1320
1312
1321 Missing components are set to None. The only exception is
1313 Missing components are set to None. The only exception is
1322 fragment, which is set to '' if present but empty.
1314 fragment, which is set to '' if present but empty.
1323
1315
1324 If parsefragment is False, fragment is included in query. If
1316 If parsefragment is False, fragment is included in query. If
1325 parsequery is False, query is included in path. If both are
1317 parsequery is False, query is included in path. If both are
1326 False, both fragment and query are included in path.
1318 False, both fragment and query are included in path.
1327
1319
1328 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1320 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1329
1321
1330 Note that for backward compatibility reasons, bundle URLs do not
1322 Note that for backward compatibility reasons, bundle URLs do not
1331 take host names. That means 'bundle://../' has a path of '../'.
1323 take host names. That means 'bundle://../' has a path of '../'.
1332
1324
1333 Examples:
1325 Examples:
1334
1326
1335 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1327 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1336 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1328 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1337 >>> url('ssh://[::1]:2200//home/joe/repo')
1329 >>> url('ssh://[::1]:2200//home/joe/repo')
1338 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1330 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1339 >>> url('file:///home/joe/repo')
1331 >>> url('file:///home/joe/repo')
1340 <url scheme: 'file', path: '/home/joe/repo'>
1332 <url scheme: 'file', path: '/home/joe/repo'>
1341 >>> url('bundle:foo')
1333 >>> url('bundle:foo')
1342 <url scheme: 'bundle', path: 'foo'>
1334 <url scheme: 'bundle', path: 'foo'>
1343 >>> url('bundle://../foo')
1335 >>> url('bundle://../foo')
1344 <url scheme: 'bundle', path: '../foo'>
1336 <url scheme: 'bundle', path: '../foo'>
1345 >>> url(r'c:\foo\bar')
1337 >>> url(r'c:\foo\bar')
1346 <url path: 'c:\\foo\\bar'>
1338 <url path: 'c:\\foo\\bar'>
1347 >>> url(r'\\blah\blah\blah')
1339 >>> url(r'\\blah\blah\blah')
1348 <url path: '\\\\blah\\blah\\blah'>
1340 <url path: '\\\\blah\\blah\\blah'>
1349
1341
1350 Authentication credentials:
1342 Authentication credentials:
1351
1343
1352 >>> url('ssh://joe:xyz@x/repo')
1344 >>> url('ssh://joe:xyz@x/repo')
1353 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1345 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1354 >>> url('ssh://joe@x/repo')
1346 >>> url('ssh://joe@x/repo')
1355 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1347 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1356
1348
1357 Query strings and fragments:
1349 Query strings and fragments:
1358
1350
1359 >>> url('http://host/a?b#c')
1351 >>> url('http://host/a?b#c')
1360 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1352 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1361 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1353 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1362 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1354 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1363 """
1355 """
1364
1356
1365 _safechars = "!~*'()+"
1357 _safechars = "!~*'()+"
1366 _safepchars = "/!~*'()+"
1358 _safepchars = "/!~*'()+"
1367 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1359 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1368
1360
1369 def __init__(self, path, parsequery=True, parsefragment=True):
1361 def __init__(self, path, parsequery=True, parsefragment=True):
1370 # We slowly chomp away at path until we have only the path left
1362 # We slowly chomp away at path until we have only the path left
1371 self.scheme = self.user = self.passwd = self.host = None
1363 self.scheme = self.user = self.passwd = self.host = None
1372 self.port = self.path = self.query = self.fragment = None
1364 self.port = self.path = self.query = self.fragment = None
1373 self._localpath = True
1365 self._localpath = True
1374 self._hostport = ''
1366 self._hostport = ''
1375 self._origpath = path
1367 self._origpath = path
1376
1368
1377 # special case for Windows drive letters and UNC paths
1369 # special case for Windows drive letters and UNC paths
1378 if hasdriveletter(path) or path.startswith(r'\\'):
1370 if hasdriveletter(path) or path.startswith(r'\\'):
1379 self.path = path
1371 self.path = path
1380 return
1372 return
1381
1373
1382 # For compatibility reasons, we can't handle bundle paths as
1374 # For compatibility reasons, we can't handle bundle paths as
1383 # normal URLS
1375 # normal URLS
1384 if path.startswith('bundle:'):
1376 if path.startswith('bundle:'):
1385 self.scheme = 'bundle'
1377 self.scheme = 'bundle'
1386 path = path[7:]
1378 path = path[7:]
1387 if path.startswith('//'):
1379 if path.startswith('//'):
1388 path = path[2:]
1380 path = path[2:]
1389 self.path = path
1381 self.path = path
1390 return
1382 return
1391
1383
1392 if self._matchscheme(path):
1384 if self._matchscheme(path):
1393 parts = path.split(':', 1)
1385 parts = path.split(':', 1)
1394 if parts[0]:
1386 if parts[0]:
1395 self.scheme, path = parts
1387 self.scheme, path = parts
1396 self._localpath = False
1388 self._localpath = False
1397
1389
1398 if not path:
1390 if not path:
1399 path = None
1391 path = None
1400 if self._localpath:
1392 if self._localpath:
1401 self.path = ''
1393 self.path = ''
1402 return
1394 return
1403 else:
1395 else:
1404 if parsefragment and '#' in path:
1396 if parsefragment and '#' in path:
1405 path, self.fragment = path.split('#', 1)
1397 path, self.fragment = path.split('#', 1)
1406 if not path:
1398 if not path:
1407 path = None
1399 path = None
1408 if self._localpath:
1400 if self._localpath:
1409 self.path = path
1401 self.path = path
1410 return
1402 return
1411
1403
1412 if parsequery and '?' in path:
1404 if parsequery and '?' in path:
1413 path, self.query = path.split('?', 1)
1405 path, self.query = path.split('?', 1)
1414 if not path:
1406 if not path:
1415 path = None
1407 path = None
1416 if not self.query:
1408 if not self.query:
1417 self.query = None
1409 self.query = None
1418
1410
1419 # // is required to specify a host/authority
1411 # // is required to specify a host/authority
1420 if path and path.startswith('//'):
1412 if path and path.startswith('//'):
1421 parts = path[2:].split('/', 1)
1413 parts = path[2:].split('/', 1)
1422 if len(parts) > 1:
1414 if len(parts) > 1:
1423 self.host, path = parts
1415 self.host, path = parts
1424 path = path
1416 path = path
1425 else:
1417 else:
1426 self.host = parts[0]
1418 self.host = parts[0]
1427 path = None
1419 path = None
1428 if not self.host:
1420 if not self.host:
1429 self.host = None
1421 self.host = None
1430 if path:
1422 if path:
1431 path = '/' + path
1423 path = '/' + path
1432
1424
1433 if self.host and '@' in self.host:
1425 if self.host and '@' in self.host:
1434 self.user, self.host = self.host.rsplit('@', 1)
1426 self.user, self.host = self.host.rsplit('@', 1)
1435 if ':' in self.user:
1427 if ':' in self.user:
1436 self.user, self.passwd = self.user.split(':', 1)
1428 self.user, self.passwd = self.user.split(':', 1)
1437 if not self.host:
1429 if not self.host:
1438 self.host = None
1430 self.host = None
1439
1431
1440 # Don't split on colons in IPv6 addresses without ports
1432 # Don't split on colons in IPv6 addresses without ports
1441 if (self.host and ':' in self.host and
1433 if (self.host and ':' in self.host and
1442 not (self.host.startswith('[') and self.host.endswith(']'))):
1434 not (self.host.startswith('[') and self.host.endswith(']'))):
1443 self._hostport = self.host
1435 self._hostport = self.host
1444 self.host, self.port = self.host.rsplit(':', 1)
1436 self.host, self.port = self.host.rsplit(':', 1)
1445 if not self.host:
1437 if not self.host:
1446 self.host = None
1438 self.host = None
1447
1439
1448 if (self.host and self.scheme == 'file' and
1440 if (self.host and self.scheme == 'file' and
1449 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1441 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1450 raise Abort(_('file:// URLs can only refer to localhost'))
1442 raise Abort(_('file:// URLs can only refer to localhost'))
1451
1443
1452 self.path = path
1444 self.path = path
1453
1445
1454 for a in ('user', 'passwd', 'host', 'port',
1446 for a in ('user', 'passwd', 'host', 'port',
1455 'path', 'query', 'fragment'):
1447 'path', 'query', 'fragment'):
1456 v = getattr(self, a)
1448 v = getattr(self, a)
1457 if v is not None:
1449 if v is not None:
1458 setattr(self, a, _urlunquote(v))
1450 setattr(self, a, _urlunquote(v))
1459
1451
1460 def __repr__(self):
1452 def __repr__(self):
1461 attrs = []
1453 attrs = []
1462 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1454 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1463 'query', 'fragment'):
1455 'query', 'fragment'):
1464 v = getattr(self, a)
1456 v = getattr(self, a)
1465 if v is not None:
1457 if v is not None:
1466 attrs.append('%s: %r' % (a, v))
1458 attrs.append('%s: %r' % (a, v))
1467 return '<url %s>' % ', '.join(attrs)
1459 return '<url %s>' % ', '.join(attrs)
1468
1460
1469 def __str__(self):
1461 def __str__(self):
1470 r"""Join the URL's components back into a URL string.
1462 r"""Join the URL's components back into a URL string.
1471
1463
1472 Examples:
1464 Examples:
1473
1465
1474 >>> str(url('http://user:pw@host:80/?foo#bar'))
1466 >>> str(url('http://user:pw@host:80/?foo#bar'))
1475 'http://user:pw@host:80/?foo#bar'
1467 'http://user:pw@host:80/?foo#bar'
1476 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1468 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1477 'ssh://user:pw@[::1]:2200//home/joe#'
1469 'ssh://user:pw@[::1]:2200//home/joe#'
1478 >>> str(url('http://localhost:80//'))
1470 >>> str(url('http://localhost:80//'))
1479 'http://localhost:80//'
1471 'http://localhost:80//'
1480 >>> str(url('http://localhost:80/'))
1472 >>> str(url('http://localhost:80/'))
1481 'http://localhost:80/'
1473 'http://localhost:80/'
1482 >>> str(url('http://localhost:80'))
1474 >>> str(url('http://localhost:80'))
1483 'http://localhost:80/'
1475 'http://localhost:80/'
1484 >>> str(url('bundle:foo'))
1476 >>> str(url('bundle:foo'))
1485 'bundle:foo'
1477 'bundle:foo'
1486 >>> str(url('bundle://../foo'))
1478 >>> str(url('bundle://../foo'))
1487 'bundle:../foo'
1479 'bundle:../foo'
1488 >>> str(url('path'))
1480 >>> str(url('path'))
1489 'path'
1481 'path'
1490 >>> str(url('file:///tmp/foo/bar'))
1482 >>> str(url('file:///tmp/foo/bar'))
1491 'file:///tmp/foo/bar'
1483 'file:///tmp/foo/bar'
1492 >>> print url(r'bundle:foo\bar')
1484 >>> print url(r'bundle:foo\bar')
1493 bundle:foo\bar
1485 bundle:foo\bar
1494 """
1486 """
1495 if self._localpath:
1487 if self._localpath:
1496 s = self.path
1488 s = self.path
1497 if self.scheme == 'bundle':
1489 if self.scheme == 'bundle':
1498 s = 'bundle:' + s
1490 s = 'bundle:' + s
1499 if self.fragment:
1491 if self.fragment:
1500 s += '#' + self.fragment
1492 s += '#' + self.fragment
1501 return s
1493 return s
1502
1494
1503 s = self.scheme + ':'
1495 s = self.scheme + ':'
1504 if self.user or self.passwd or self.host:
1496 if self.user or self.passwd or self.host:
1505 s += '//'
1497 s += '//'
1506 elif self.scheme and (not self.path or self.path.startswith('/')):
1498 elif self.scheme and (not self.path or self.path.startswith('/')):
1507 s += '//'
1499 s += '//'
1508 if self.user:
1500 if self.user:
1509 s += urllib.quote(self.user, safe=self._safechars)
1501 s += urllib.quote(self.user, safe=self._safechars)
1510 if self.passwd:
1502 if self.passwd:
1511 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1503 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1512 if self.user or self.passwd:
1504 if self.user or self.passwd:
1513 s += '@'
1505 s += '@'
1514 if self.host:
1506 if self.host:
1515 if not (self.host.startswith('[') and self.host.endswith(']')):
1507 if not (self.host.startswith('[') and self.host.endswith(']')):
1516 s += urllib.quote(self.host)
1508 s += urllib.quote(self.host)
1517 else:
1509 else:
1518 s += self.host
1510 s += self.host
1519 if self.port:
1511 if self.port:
1520 s += ':' + urllib.quote(self.port)
1512 s += ':' + urllib.quote(self.port)
1521 if self.host:
1513 if self.host:
1522 s += '/'
1514 s += '/'
1523 if self.path:
1515 if self.path:
1524 s += urllib.quote(self.path, safe=self._safepchars)
1516 s += urllib.quote(self.path, safe=self._safepchars)
1525 if self.query:
1517 if self.query:
1526 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1518 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1527 if self.fragment is not None:
1519 if self.fragment is not None:
1528 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1520 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1529 return s
1521 return s
1530
1522
1531 def authinfo(self):
1523 def authinfo(self):
1532 user, passwd = self.user, self.passwd
1524 user, passwd = self.user, self.passwd
1533 try:
1525 try:
1534 self.user, self.passwd = None, None
1526 self.user, self.passwd = None, None
1535 s = str(self)
1527 s = str(self)
1536 finally:
1528 finally:
1537 self.user, self.passwd = user, passwd
1529 self.user, self.passwd = user, passwd
1538 if not self.user:
1530 if not self.user:
1539 return (s, None)
1531 return (s, None)
1540 return (s, (None, (str(self), self.host),
1532 return (s, (None, (str(self), self.host),
1541 self.user, self.passwd or ''))
1533 self.user, self.passwd or ''))
1542
1534
1543 def isabs(self):
1535 def isabs(self):
1544 if self.scheme and self.scheme != 'file':
1536 if self.scheme and self.scheme != 'file':
1545 return True # remote URL
1537 return True # remote URL
1546 if hasdriveletter(self.path):
1538 if hasdriveletter(self.path):
1547 return True # absolute for our purposes - can't be joined()
1539 return True # absolute for our purposes - can't be joined()
1548 if self.path.startswith(r'\\'):
1540 if self.path.startswith(r'\\'):
1549 return True # Windows UNC path
1541 return True # Windows UNC path
1550 if self.path.startswith('/'):
1542 if self.path.startswith('/'):
1551 return True # POSIX-style
1543 return True # POSIX-style
1552 return False
1544 return False
1553
1545
1554 def localpath(self):
1546 def localpath(self):
1555 if self.scheme == 'file' or self.scheme == 'bundle':
1547 if self.scheme == 'file' or self.scheme == 'bundle':
1556 path = self.path or '/'
1548 path = self.path or '/'
1557 # For Windows, we need to promote hosts containing drive
1549 # For Windows, we need to promote hosts containing drive
1558 # letters to paths with drive letters.
1550 # letters to paths with drive letters.
1559 if hasdriveletter(self._hostport):
1551 if hasdriveletter(self._hostport):
1560 path = self._hostport + '/' + self.path
1552 path = self._hostport + '/' + self.path
1561 elif self.host is not None and self.path:
1553 elif self.host is not None and self.path:
1562 path = '/' + path
1554 path = '/' + path
1563 # We also need to handle the case of file:///C:/, which
1555 # We also need to handle the case of file:///C:/, which
1564 # should return C:/, not /C:/.
1556 # should return C:/, not /C:/.
1565 elif hasdriveletter(path):
1557 elif hasdriveletter(path):
1566 # Strip leading slash from paths with drive names
1558 # Strip leading slash from paths with drive names
1567 return path[1:]
1559 return path[1:]
1568 return path
1560 return path
1569 return self._origpath
1561 return self._origpath
1570
1562
1571 def hasscheme(path):
1563 def hasscheme(path):
1572 return bool(url(path).scheme)
1564 return bool(url(path).scheme)
1573
1565
1574 def hasdriveletter(path):
1566 def hasdriveletter(path):
1575 return path[1:2] == ':' and path[0:1].isalpha()
1567 return path[1:2] == ':' and path[0:1].isalpha()
1576
1568
1577 def urllocalpath(path):
1569 def urllocalpath(path):
1578 return url(path, parsequery=False, parsefragment=False).localpath()
1570 return url(path, parsequery=False, parsefragment=False).localpath()
1579
1571
1580 def hidepassword(u):
1572 def hidepassword(u):
1581 '''hide user credential in a url string'''
1573 '''hide user credential in a url string'''
1582 u = url(u)
1574 u = url(u)
1583 if u.passwd:
1575 if u.passwd:
1584 u.passwd = '***'
1576 u.passwd = '***'
1585 return str(u)
1577 return str(u)
1586
1578
1587 def removeauth(u):
1579 def removeauth(u):
1588 '''remove all authentication information from a url string'''
1580 '''remove all authentication information from a url string'''
1589 u = url(u)
1581 u = url(u)
1590 u.user = u.passwd = None
1582 u.user = u.passwd = None
1591 return str(u)
1583 return str(u)
1592
1584
1593 def isatty(fd):
1585 def isatty(fd):
1594 try:
1586 try:
1595 return fd.isatty()
1587 return fd.isatty()
1596 except AttributeError:
1588 except AttributeError:
1597 return False
1589 return False
General Comments 0
You need to be logged in to leave comments. Login now