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