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