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