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