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