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