##// END OF EJS Templates
procutil: rewrite popen() as a subprocess.Popen wrapper (issue4746) (API)...
Yuya Nishihara -
r37477:90c5ca71 default
parent child Browse files
Show More
@@ -1,694 +1,691 b''
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import fcntl
12 12 import getpass
13 13 import grp
14 14 import os
15 15 import pwd
16 16 import re
17 17 import select
18 18 import stat
19 19 import sys
20 20 import tempfile
21 21 import unicodedata
22 22
23 23 from .i18n import _
24 24 from . import (
25 25 encoding,
26 26 error,
27 27 policy,
28 28 pycompat,
29 29 )
30 30
31 31 osutil = policy.importmod(r'osutil')
32 32
33 33 posixfile = open
34 34 normpath = os.path.normpath
35 35 samestat = os.path.samestat
36 36 try:
37 37 oslink = os.link
38 38 except AttributeError:
39 39 # Some platforms build Python without os.link on systems that are
40 40 # vaguely unix-like but don't have hardlink support. For those
41 41 # poor souls, just say we tried and that it failed so we fall back
42 42 # to copies.
43 43 def oslink(src, dst):
44 44 raise OSError(errno.EINVAL,
45 45 'hardlinks not supported: %s to %s' % (src, dst))
46 46 unlink = os.unlink
47 47 rename = os.rename
48 48 removedirs = os.removedirs
49 49 expandglobs = False
50 50
51 51 umask = os.umask(0)
52 52 os.umask(umask)
53 53
54 54 def split(p):
55 55 '''Same as posixpath.split, but faster
56 56
57 57 >>> import posixpath
58 58 >>> for f in [b'/absolute/path/to/file',
59 59 ... b'relative/path/to/file',
60 60 ... b'file_alone',
61 61 ... b'path/to/directory/',
62 62 ... b'/multiple/path//separators',
63 63 ... b'/file_at_root',
64 64 ... b'///multiple_leading_separators_at_root',
65 65 ... b'']:
66 66 ... assert split(f) == posixpath.split(f), f
67 67 '''
68 68 ht = p.rsplit('/', 1)
69 69 if len(ht) == 1:
70 70 return '', p
71 71 nh = ht[0].rstrip('/')
72 72 if nh:
73 73 return nh, ht[1]
74 74 return ht[0] + '/', ht[1]
75 75
76 76 def openhardlinks():
77 77 '''return true if it is safe to hold open file handles to hardlinks'''
78 78 return True
79 79
80 80 def nlinks(name):
81 81 '''return number of hardlinks for the given file'''
82 82 return os.lstat(name).st_nlink
83 83
84 84 def parsepatchoutput(output_line):
85 85 """parses the output produced by patch and returns the filename"""
86 86 pf = output_line[14:]
87 87 if pycompat.sysplatform == 'OpenVMS':
88 88 if pf[0] == '`':
89 89 pf = pf[1:-1] # Remove the quotes
90 90 else:
91 91 if pf.startswith("'") and pf.endswith("'") and " " in pf:
92 92 pf = pf[1:-1] # Remove the quotes
93 93 return pf
94 94
95 95 def sshargs(sshcmd, host, user, port):
96 96 '''Build argument list for ssh'''
97 97 args = user and ("%s@%s" % (user, host)) or host
98 98 if '-' in args[:1]:
99 99 raise error.Abort(
100 100 _('illegal ssh hostname or username starting with -: %s') % args)
101 101 args = shellquote(args)
102 102 if port:
103 103 args = '-p %s %s' % (shellquote(port), args)
104 104 return args
105 105
106 106 def isexec(f):
107 107 """check whether a file is executable"""
108 108 return (os.lstat(f).st_mode & 0o100 != 0)
109 109
110 110 def setflags(f, l, x):
111 111 st = os.lstat(f)
112 112 s = st.st_mode
113 113 if l:
114 114 if not stat.S_ISLNK(s):
115 115 # switch file to link
116 116 fp = open(f, 'rb')
117 117 data = fp.read()
118 118 fp.close()
119 119 unlink(f)
120 120 try:
121 121 os.symlink(data, f)
122 122 except OSError:
123 123 # failed to make a link, rewrite file
124 124 fp = open(f, "wb")
125 125 fp.write(data)
126 126 fp.close()
127 127 # no chmod needed at this point
128 128 return
129 129 if stat.S_ISLNK(s):
130 130 # switch link to file
131 131 data = os.readlink(f)
132 132 unlink(f)
133 133 fp = open(f, "wb")
134 134 fp.write(data)
135 135 fp.close()
136 136 s = 0o666 & ~umask # avoid restatting for chmod
137 137
138 138 sx = s & 0o100
139 139 if st.st_nlink > 1 and bool(x) != bool(sx):
140 140 # the file is a hardlink, break it
141 141 with open(f, "rb") as fp:
142 142 data = fp.read()
143 143 unlink(f)
144 144 with open(f, "wb") as fp:
145 145 fp.write(data)
146 146
147 147 if x and not sx:
148 148 # Turn on +x for every +r bit when making a file executable
149 149 # and obey umask.
150 150 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
151 151 elif not x and sx:
152 152 # Turn off all +x bits
153 153 os.chmod(f, s & 0o666)
154 154
155 155 def copymode(src, dst, mode=None):
156 156 '''Copy the file mode from the file at path src to dst.
157 157 If src doesn't exist, we're using mode instead. If mode is None, we're
158 158 using umask.'''
159 159 try:
160 160 st_mode = os.lstat(src).st_mode & 0o777
161 161 except OSError as inst:
162 162 if inst.errno != errno.ENOENT:
163 163 raise
164 164 st_mode = mode
165 165 if st_mode is None:
166 166 st_mode = ~umask
167 167 st_mode &= 0o666
168 168 os.chmod(dst, st_mode)
169 169
170 170 def checkexec(path):
171 171 """
172 172 Check whether the given path is on a filesystem with UNIX-like exec flags
173 173
174 174 Requires a directory (like /foo/.hg)
175 175 """
176 176
177 177 # VFAT on some Linux versions can flip mode but it doesn't persist
178 178 # a FS remount. Frequently we can detect it if files are created
179 179 # with exec bit on.
180 180
181 181 try:
182 182 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
183 183 cachedir = os.path.join(path, '.hg', 'cache')
184 184 if os.path.isdir(cachedir):
185 185 checkisexec = os.path.join(cachedir, 'checkisexec')
186 186 checknoexec = os.path.join(cachedir, 'checknoexec')
187 187
188 188 try:
189 189 m = os.stat(checkisexec).st_mode
190 190 except OSError as e:
191 191 if e.errno != errno.ENOENT:
192 192 raise
193 193 # checkisexec does not exist - fall through ...
194 194 else:
195 195 # checkisexec exists, check if it actually is exec
196 196 if m & EXECFLAGS != 0:
197 197 # ensure checkisexec exists, check it isn't exec
198 198 try:
199 199 m = os.stat(checknoexec).st_mode
200 200 except OSError as e:
201 201 if e.errno != errno.ENOENT:
202 202 raise
203 203 open(checknoexec, 'w').close() # might fail
204 204 m = os.stat(checknoexec).st_mode
205 205 if m & EXECFLAGS == 0:
206 206 # check-exec is exec and check-no-exec is not exec
207 207 return True
208 208 # checknoexec exists but is exec - delete it
209 209 unlink(checknoexec)
210 210 # checkisexec exists but is not exec - delete it
211 211 unlink(checkisexec)
212 212
213 213 # check using one file, leave it as checkisexec
214 214 checkdir = cachedir
215 215 else:
216 216 # check directly in path and don't leave checkisexec behind
217 217 checkdir = path
218 218 checkisexec = None
219 219 fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
220 220 try:
221 221 os.close(fh)
222 222 m = os.stat(fn).st_mode
223 223 if m & EXECFLAGS == 0:
224 224 os.chmod(fn, m & 0o777 | EXECFLAGS)
225 225 if os.stat(fn).st_mode & EXECFLAGS != 0:
226 226 if checkisexec is not None:
227 227 os.rename(fn, checkisexec)
228 228 fn = None
229 229 return True
230 230 finally:
231 231 if fn is not None:
232 232 unlink(fn)
233 233 except (IOError, OSError):
234 234 # we don't care, the user probably won't be able to commit anyway
235 235 return False
236 236
237 237 def checklink(path):
238 238 """check whether the given path is on a symlink-capable filesystem"""
239 239 # mktemp is not racy because symlink creation will fail if the
240 240 # file already exists
241 241 while True:
242 242 cachedir = os.path.join(path, '.hg', 'cache')
243 243 checklink = os.path.join(cachedir, 'checklink')
244 244 # try fast path, read only
245 245 if os.path.islink(checklink):
246 246 return True
247 247 if os.path.isdir(cachedir):
248 248 checkdir = cachedir
249 249 else:
250 250 checkdir = path
251 251 cachedir = None
252 252 fscheckdir = pycompat.fsdecode(checkdir)
253 253 name = tempfile.mktemp(dir=fscheckdir,
254 254 prefix=r'checklink-')
255 255 name = pycompat.fsencode(name)
256 256 try:
257 257 fd = None
258 258 if cachedir is None:
259 259 fd = tempfile.NamedTemporaryFile(dir=fscheckdir,
260 260 prefix=r'hg-checklink-')
261 261 target = pycompat.fsencode(os.path.basename(fd.name))
262 262 else:
263 263 # create a fixed file to link to; doesn't matter if it
264 264 # already exists.
265 265 target = 'checklink-target'
266 266 try:
267 267 fullpath = os.path.join(cachedir, target)
268 268 open(fullpath, 'w').close()
269 269 except IOError as inst:
270 270 if inst[0] == errno.EACCES:
271 271 # If we can't write to cachedir, just pretend
272 272 # that the fs is readonly and by association
273 273 # that the fs won't support symlinks. This
274 274 # seems like the least dangerous way to avoid
275 275 # data loss.
276 276 return False
277 277 raise
278 278 try:
279 279 os.symlink(target, name)
280 280 if cachedir is None:
281 281 unlink(name)
282 282 else:
283 283 try:
284 284 os.rename(name, checklink)
285 285 except OSError:
286 286 unlink(name)
287 287 return True
288 288 except OSError as inst:
289 289 # link creation might race, try again
290 290 if inst[0] == errno.EEXIST:
291 291 continue
292 292 raise
293 293 finally:
294 294 if fd is not None:
295 295 fd.close()
296 296 except AttributeError:
297 297 return False
298 298 except OSError as inst:
299 299 # sshfs might report failure while successfully creating the link
300 300 if inst[0] == errno.EIO and os.path.exists(name):
301 301 unlink(name)
302 302 return False
303 303
304 304 def checkosfilename(path):
305 305 '''Check that the base-relative path is a valid filename on this platform.
306 306 Returns None if the path is ok, or a UI string describing the problem.'''
307 307 return None # on posix platforms, every path is ok
308 308
309 309 def getfsmountpoint(dirpath):
310 310 '''Get the filesystem mount point from a directory (best-effort)
311 311
312 312 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
313 313 '''
314 314 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
315 315
316 316 def getfstype(dirpath):
317 317 '''Get the filesystem type name from a directory (best-effort)
318 318
319 319 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
320 320 '''
321 321 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
322 322
323 323 def setbinary(fd):
324 324 pass
325 325
326 326 def pconvert(path):
327 327 return path
328 328
329 329 def localpath(path):
330 330 return path
331 331
332 332 def samefile(fpath1, fpath2):
333 333 """Returns whether path1 and path2 refer to the same file. This is only
334 334 guaranteed to work for files, not directories."""
335 335 return os.path.samefile(fpath1, fpath2)
336 336
337 337 def samedevice(fpath1, fpath2):
338 338 """Returns whether fpath1 and fpath2 are on the same device. This is only
339 339 guaranteed to work for files, not directories."""
340 340 st1 = os.lstat(fpath1)
341 341 st2 = os.lstat(fpath2)
342 342 return st1.st_dev == st2.st_dev
343 343
344 344 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
345 345 def normcase(path):
346 346 return path.lower()
347 347
348 348 # what normcase does to ASCII strings
349 349 normcasespec = encoding.normcasespecs.lower
350 350 # fallback normcase function for non-ASCII strings
351 351 normcasefallback = normcase
352 352
353 353 if pycompat.isdarwin:
354 354
355 355 def normcase(path):
356 356 '''
357 357 Normalize a filename for OS X-compatible comparison:
358 358 - escape-encode invalid characters
359 359 - decompose to NFD
360 360 - lowercase
361 361 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
362 362
363 363 >>> normcase(b'UPPER')
364 364 'upper'
365 365 >>> normcase(b'Caf\\xc3\\xa9')
366 366 'cafe\\xcc\\x81'
367 367 >>> normcase(b'\\xc3\\x89')
368 368 'e\\xcc\\x81'
369 369 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
370 370 '%b8%ca%c3\\xca\\xbe%c8.jpg'
371 371 '''
372 372
373 373 try:
374 374 return encoding.asciilower(path) # exception for non-ASCII
375 375 except UnicodeDecodeError:
376 376 return normcasefallback(path)
377 377
378 378 normcasespec = encoding.normcasespecs.lower
379 379
380 380 def normcasefallback(path):
381 381 try:
382 382 u = path.decode('utf-8')
383 383 except UnicodeDecodeError:
384 384 # OS X percent-encodes any bytes that aren't valid utf-8
385 385 s = ''
386 386 pos = 0
387 387 l = len(path)
388 388 while pos < l:
389 389 try:
390 390 c = encoding.getutf8char(path, pos)
391 391 pos += len(c)
392 392 except ValueError:
393 393 c = '%%%02X' % ord(path[pos:pos + 1])
394 394 pos += 1
395 395 s += c
396 396
397 397 u = s.decode('utf-8')
398 398
399 399 # Decompose then lowercase (HFS+ technote specifies lower)
400 400 enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
401 401 # drop HFS+ ignored characters
402 402 return encoding.hfsignoreclean(enc)
403 403
404 404 if pycompat.sysplatform == 'cygwin':
405 405 # workaround for cygwin, in which mount point part of path is
406 406 # treated as case sensitive, even though underlying NTFS is case
407 407 # insensitive.
408 408
409 409 # default mount points
410 410 cygwinmountpoints = sorted([
411 411 "/usr/bin",
412 412 "/usr/lib",
413 413 "/cygdrive",
414 414 ], reverse=True)
415 415
416 416 # use upper-ing as normcase as same as NTFS workaround
417 417 def normcase(path):
418 418 pathlen = len(path)
419 419 if (pathlen == 0) or (path[0] != pycompat.ossep):
420 420 # treat as relative
421 421 return encoding.upper(path)
422 422
423 423 # to preserve case of mountpoint part
424 424 for mp in cygwinmountpoints:
425 425 if not path.startswith(mp):
426 426 continue
427 427
428 428 mplen = len(mp)
429 429 if mplen == pathlen: # mount point itself
430 430 return mp
431 431 if path[mplen] == pycompat.ossep:
432 432 return mp + encoding.upper(path[mplen:])
433 433
434 434 return encoding.upper(path)
435 435
436 436 normcasespec = encoding.normcasespecs.other
437 437 normcasefallback = normcase
438 438
439 439 # Cygwin translates native ACLs to POSIX permissions,
440 440 # but these translations are not supported by native
441 441 # tools, so the exec bit tends to be set erroneously.
442 442 # Therefore, disable executable bit access on Cygwin.
443 443 def checkexec(path):
444 444 return False
445 445
446 446 # Similarly, Cygwin's symlink emulation is likely to create
447 447 # problems when Mercurial is used from both Cygwin and native
448 448 # Windows, with other native tools, or on shared volumes
449 449 def checklink(path):
450 450 return False
451 451
452 452 _needsshellquote = None
453 453 def shellquote(s):
454 454 if pycompat.sysplatform == 'OpenVMS':
455 455 return '"%s"' % s
456 456 global _needsshellquote
457 457 if _needsshellquote is None:
458 458 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
459 459 if s and not _needsshellquote(s):
460 460 # "s" shouldn't have to be quoted
461 461 return s
462 462 else:
463 463 return "'%s'" % s.replace("'", "'\\''")
464 464
465 465 def shellsplit(s):
466 466 """Parse a command string in POSIX shell way (best-effort)"""
467 467 return pycompat.shlexsplit(s, posix=True)
468 468
469 469 def quotecommand(cmd):
470 470 return cmd
471 471
472 def popen(command, mode='r'):
473 return os.popen(command, mode)
474
475 472 def testpid(pid):
476 473 '''return False if pid dead, True if running or not sure'''
477 474 if pycompat.sysplatform == 'OpenVMS':
478 475 return True
479 476 try:
480 477 os.kill(pid, 0)
481 478 return True
482 479 except OSError as inst:
483 480 return inst.errno != errno.ESRCH
484 481
485 482 def explainexit(code):
486 483 """return a 2-tuple (desc, code) describing a subprocess status
487 484 (codes from kill are negative - not os.system/wait encoding)"""
488 485 if code >= 0:
489 486 return _("exited with status %d") % code, code
490 487 return _("killed by signal %d") % -code, -code
491 488
492 489 def isowner(st):
493 490 """Return True if the stat object st is from the current user."""
494 491 return st.st_uid == os.getuid()
495 492
496 493 def findexe(command):
497 494 '''Find executable for command searching like which does.
498 495 If command is a basename then PATH is searched for command.
499 496 PATH isn't searched if command is an absolute or relative path.
500 497 If command isn't found None is returned.'''
501 498 if pycompat.sysplatform == 'OpenVMS':
502 499 return command
503 500
504 501 def findexisting(executable):
505 502 'Will return executable if existing file'
506 503 if os.path.isfile(executable) and os.access(executable, os.X_OK):
507 504 return executable
508 505 return None
509 506
510 507 if pycompat.ossep in command:
511 508 return findexisting(command)
512 509
513 510 if pycompat.sysplatform == 'plan9':
514 511 return findexisting(os.path.join('/bin', command))
515 512
516 513 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
517 514 executable = findexisting(os.path.join(path, command))
518 515 if executable is not None:
519 516 return executable
520 517 return None
521 518
522 519 def setsignalhandler():
523 520 pass
524 521
525 522 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
526 523
527 524 def statfiles(files):
528 525 '''Stat each file in files. Yield each stat, or None if a file does not
529 526 exist or has a type we don't care about.'''
530 527 lstat = os.lstat
531 528 getkind = stat.S_IFMT
532 529 for nf in files:
533 530 try:
534 531 st = lstat(nf)
535 532 if getkind(st.st_mode) not in _wantedkinds:
536 533 st = None
537 534 except OSError as err:
538 535 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
539 536 raise
540 537 st = None
541 538 yield st
542 539
543 540 def getuser():
544 541 '''return name of current user'''
545 542 return pycompat.fsencode(getpass.getuser())
546 543
547 544 def username(uid=None):
548 545 """Return the name of the user with the given uid.
549 546
550 547 If uid is None, return the name of the current user."""
551 548
552 549 if uid is None:
553 550 uid = os.getuid()
554 551 try:
555 552 return pwd.getpwuid(uid)[0]
556 553 except KeyError:
557 554 return str(uid)
558 555
559 556 def groupname(gid=None):
560 557 """Return the name of the group with the given gid.
561 558
562 559 If gid is None, return the name of the current group."""
563 560
564 561 if gid is None:
565 562 gid = os.getgid()
566 563 try:
567 564 return grp.getgrgid(gid)[0]
568 565 except KeyError:
569 566 return str(gid)
570 567
571 568 def groupmembers(name):
572 569 """Return the list of members of the group with the given
573 570 name, KeyError if the group does not exist.
574 571 """
575 572 return list(grp.getgrnam(name).gr_mem)
576 573
577 574 def spawndetached(args):
578 575 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
579 576 args[0], args)
580 577
581 578 def gethgcmd():
582 579 return sys.argv[:1]
583 580
584 581 def makedir(path, notindexed):
585 582 os.mkdir(path)
586 583
587 584 def lookupreg(key, name=None, scope=None):
588 585 return None
589 586
590 587 def hidewindow():
591 588 """Hide current shell window.
592 589
593 590 Used to hide the window opened when starting asynchronous
594 591 child process under Windows, unneeded on other systems.
595 592 """
596 593 pass
597 594
598 595 class cachestat(object):
599 596 def __init__(self, path):
600 597 self.stat = os.stat(path)
601 598
602 599 def cacheable(self):
603 600 return bool(self.stat.st_ino)
604 601
605 602 __hash__ = object.__hash__
606 603
607 604 def __eq__(self, other):
608 605 try:
609 606 # Only dev, ino, size, mtime and atime are likely to change. Out
610 607 # of these, we shouldn't compare atime but should compare the
611 608 # rest. However, one of the other fields changing indicates
612 609 # something fishy going on, so return False if anything but atime
613 610 # changes.
614 611 return (self.stat.st_mode == other.stat.st_mode and
615 612 self.stat.st_ino == other.stat.st_ino and
616 613 self.stat.st_dev == other.stat.st_dev and
617 614 self.stat.st_nlink == other.stat.st_nlink and
618 615 self.stat.st_uid == other.stat.st_uid and
619 616 self.stat.st_gid == other.stat.st_gid and
620 617 self.stat.st_size == other.stat.st_size and
621 618 self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME] and
622 619 self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME])
623 620 except AttributeError:
624 621 return False
625 622
626 623 def __ne__(self, other):
627 624 return not self == other
628 625
629 626 def statislink(st):
630 627 '''check whether a stat result is a symlink'''
631 628 return st and stat.S_ISLNK(st.st_mode)
632 629
633 630 def statisexec(st):
634 631 '''check whether a stat result is an executable file'''
635 632 return st and (st.st_mode & 0o100 != 0)
636 633
637 634 def poll(fds):
638 635 """block until something happens on any file descriptor
639 636
640 637 This is a generic helper that will check for any activity
641 638 (read, write. exception) and return the list of touched files.
642 639
643 640 In unsupported cases, it will raise a NotImplementedError"""
644 641 try:
645 642 while True:
646 643 try:
647 644 res = select.select(fds, fds, fds)
648 645 break
649 646 except select.error as inst:
650 647 if inst.args[0] == errno.EINTR:
651 648 continue
652 649 raise
653 650 except ValueError: # out of range file descriptor
654 651 raise NotImplementedError()
655 652 return sorted(list(set(sum(res, []))))
656 653
657 654 def readpipe(pipe):
658 655 """Read all available data from a pipe."""
659 656 # We can't fstat() a pipe because Linux will always report 0.
660 657 # So, we set the pipe to non-blocking mode and read everything
661 658 # that's available.
662 659 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
663 660 flags |= os.O_NONBLOCK
664 661 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
665 662
666 663 try:
667 664 chunks = []
668 665 while True:
669 666 try:
670 667 s = pipe.read()
671 668 if not s:
672 669 break
673 670 chunks.append(s)
674 671 except IOError:
675 672 break
676 673
677 674 return ''.join(chunks)
678 675 finally:
679 676 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
680 677
681 678 def bindunixsocket(sock, path):
682 679 """Bind the UNIX domain socket to the specified path"""
683 680 # use relative path instead of full path at bind() if possible, since
684 681 # AF_UNIX path has very small length limit (107 chars) on common
685 682 # platforms (see sys/un.h)
686 683 dirname, basename = os.path.split(path)
687 684 bakwdfd = None
688 685 if dirname:
689 686 bakwdfd = os.open('.', os.O_DIRECTORY)
690 687 os.chdir(dirname)
691 688 sock.bind(basename)
692 689 if bakwdfd:
693 690 os.fchdir(bakwdfd)
694 691 os.close(bakwdfd)
@@ -1,361 +1,403 b''
1 1 # procutil.py - utility for managing processes and executable environment
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 from __future__ import absolute_import
11 11
12 12 import contextlib
13 13 import imp
14 14 import io
15 15 import os
16 16 import signal
17 17 import subprocess
18 18 import sys
19 19 import tempfile
20 20 import time
21 21
22 22 from ..i18n import _
23 23
24 24 from .. import (
25 25 encoding,
26 26 error,
27 27 policy,
28 28 pycompat,
29 29 )
30 30
31 31 osutil = policy.importmod(r'osutil')
32 32
33 33 stderr = pycompat.stderr
34 34 stdin = pycompat.stdin
35 35 stdout = pycompat.stdout
36 36
37 37 def isatty(fp):
38 38 try:
39 39 return fp.isatty()
40 40 except AttributeError:
41 41 return False
42 42
43 43 # glibc determines buffering on first write to stdout - if we replace a TTY
44 44 # destined stdout with a pipe destined stdout (e.g. pager), we want line
45 45 # buffering
46 46 if isatty(stdout):
47 47 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
48 48
49 49 if pycompat.iswindows:
50 50 from .. import windows as platform
51 51 stdout = platform.winstdout(stdout)
52 52 else:
53 53 from .. import posix as platform
54 54
55 55 explainexit = platform.explainexit
56 56 findexe = platform.findexe
57 57 _gethgcmd = platform.gethgcmd
58 58 getuser = platform.getuser
59 59 getpid = os.getpid
60 60 hidewindow = platform.hidewindow
61 popen = platform.popen
62 61 quotecommand = platform.quotecommand
63 62 readpipe = platform.readpipe
64 63 setbinary = platform.setbinary
65 64 setsignalhandler = platform.setsignalhandler
66 65 shellquote = platform.shellquote
67 66 shellsplit = platform.shellsplit
68 67 spawndetached = platform.spawndetached
69 68 sshargs = platform.sshargs
70 69 testpid = platform.testpid
71 70
72 71 try:
73 72 setprocname = osutil.setprocname
74 73 except AttributeError:
75 74 pass
76 75 try:
77 76 unblocksignal = osutil.unblocksignal
78 77 except AttributeError:
79 78 pass
80 79
81 80 closefds = pycompat.isposix
82 81
82 class _pfile(object):
83 """File-like wrapper for a stream opened by subprocess.Popen()"""
84
85 def __init__(self, proc, fp):
86 self._proc = proc
87 self._fp = fp
88
89 def close(self):
90 # unlike os.popen(), this returns an integer in subprocess coding
91 self._fp.close()
92 return self._proc.wait()
93
94 def __iter__(self):
95 return iter(self._fp)
96
97 def __getattr__(self, attr):
98 return getattr(self._fp, attr)
99
100 def __enter__(self):
101 return self
102
103 def __exit__(self, exc_type, exc_value, exc_tb):
104 self.close()
105
106 def popen(cmd, mode='rb', bufsize=-1):
107 if mode == 'rb':
108 return _popenreader(cmd, bufsize)
109 elif mode == 'wb':
110 return _popenwriter(cmd, bufsize)
111 raise error.ProgrammingError('unsupported mode: %r' % mode)
112
113 def _popenreader(cmd, bufsize):
114 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
115 close_fds=closefds,
116 stdout=subprocess.PIPE)
117 return _pfile(p, p.stdout)
118
119 def _popenwriter(cmd, bufsize):
120 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
121 close_fds=closefds,
122 stdin=subprocess.PIPE)
123 return _pfile(p, p.stdin)
124
83 125 def popen2(cmd, env=None, newlines=False):
84 126 # Setting bufsize to -1 lets the system decide the buffer size.
85 127 # The default for bufsize is 0, meaning unbuffered. This leads to
86 128 # poor performance on Mac OS X: http://bugs.python.org/issue4194
87 129 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
88 130 close_fds=closefds,
89 131 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
90 132 universal_newlines=newlines,
91 133 env=env)
92 134 return p.stdin, p.stdout
93 135
94 136 def popen3(cmd, env=None, newlines=False):
95 137 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
96 138 return stdin, stdout, stderr
97 139
98 140 def popen4(cmd, env=None, newlines=False, bufsize=-1):
99 141 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
100 142 close_fds=closefds,
101 143 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
102 144 stderr=subprocess.PIPE,
103 145 universal_newlines=newlines,
104 146 env=env)
105 147 return p.stdin, p.stdout, p.stderr, p
106 148
107 149 def pipefilter(s, cmd):
108 150 '''filter string S through command CMD, returning its output'''
109 151 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
110 152 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
111 153 pout, perr = p.communicate(s)
112 154 return pout
113 155
114 156 def tempfilter(s, cmd):
115 157 '''filter string S through a pair of temporary files with CMD.
116 158 CMD is used as a template to create the real command to be run,
117 159 with the strings INFILE and OUTFILE replaced by the real names of
118 160 the temporary files generated.'''
119 161 inname, outname = None, None
120 162 try:
121 163 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
122 164 fp = os.fdopen(infd, r'wb')
123 165 fp.write(s)
124 166 fp.close()
125 167 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
126 168 os.close(outfd)
127 169 cmd = cmd.replace('INFILE', inname)
128 170 cmd = cmd.replace('OUTFILE', outname)
129 171 code = os.system(cmd)
130 172 if pycompat.sysplatform == 'OpenVMS' and code & 1:
131 173 code = 0
132 174 if code:
133 175 raise error.Abort(_("command '%s' failed: %s") %
134 176 (cmd, explainexit(code)))
135 177 with open(outname, 'rb') as fp:
136 178 return fp.read()
137 179 finally:
138 180 try:
139 181 if inname:
140 182 os.unlink(inname)
141 183 except OSError:
142 184 pass
143 185 try:
144 186 if outname:
145 187 os.unlink(outname)
146 188 except OSError:
147 189 pass
148 190
149 191 _filtertable = {
150 192 'tempfile:': tempfilter,
151 193 'pipe:': pipefilter,
152 194 }
153 195
154 196 def filter(s, cmd):
155 197 "filter a string through a command that transforms its input to its output"
156 198 for name, fn in _filtertable.iteritems():
157 199 if cmd.startswith(name):
158 200 return fn(s, cmd[len(name):].lstrip())
159 201 return pipefilter(s, cmd)
160 202
161 203 def mainfrozen():
162 204 """return True if we are a frozen executable.
163 205
164 206 The code supports py2exe (most common, Windows only) and tools/freeze
165 207 (portable, not much used).
166 208 """
167 209 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
168 210 pycompat.safehasattr(sys, "importers") or # old py2exe
169 211 imp.is_frozen(u"__main__")) # tools/freeze
170 212
171 213 _hgexecutable = None
172 214
173 215 def hgexecutable():
174 216 """return location of the 'hg' executable.
175 217
176 218 Defaults to $HG or 'hg' in the search path.
177 219 """
178 220 if _hgexecutable is None:
179 221 hg = encoding.environ.get('HG')
180 222 mainmod = sys.modules[r'__main__']
181 223 if hg:
182 224 _sethgexecutable(hg)
183 225 elif mainfrozen():
184 226 if getattr(sys, 'frozen', None) == 'macosx_app':
185 227 # Env variable set by py2app
186 228 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
187 229 else:
188 230 _sethgexecutable(pycompat.sysexecutable)
189 231 elif (os.path.basename(
190 232 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
191 233 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
192 234 else:
193 235 exe = findexe('hg') or os.path.basename(sys.argv[0])
194 236 _sethgexecutable(exe)
195 237 return _hgexecutable
196 238
197 239 def _sethgexecutable(path):
198 240 """set location of the 'hg' executable"""
199 241 global _hgexecutable
200 242 _hgexecutable = path
201 243
202 244 def _testfileno(f, stdf):
203 245 fileno = getattr(f, 'fileno', None)
204 246 try:
205 247 return fileno and fileno() == stdf.fileno()
206 248 except io.UnsupportedOperation:
207 249 return False # fileno() raised UnsupportedOperation
208 250
209 251 def isstdin(f):
210 252 return _testfileno(f, sys.__stdin__)
211 253
212 254 def isstdout(f):
213 255 return _testfileno(f, sys.__stdout__)
214 256
215 257 def protectstdio(uin, uout):
216 258 """Duplicate streams and redirect original if (uin, uout) are stdio
217 259
218 260 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
219 261 redirected to stderr so the output is still readable.
220 262
221 263 Returns (fin, fout) which point to the original (uin, uout) fds, but
222 264 may be copy of (uin, uout). The returned streams can be considered
223 265 "owned" in that print(), exec(), etc. never reach to them.
224 266 """
225 267 uout.flush()
226 268 fin, fout = uin, uout
227 269 if uin is stdin:
228 270 newfd = os.dup(uin.fileno())
229 271 nullfd = os.open(os.devnull, os.O_RDONLY)
230 272 os.dup2(nullfd, uin.fileno())
231 273 os.close(nullfd)
232 274 fin = os.fdopen(newfd, r'rb')
233 275 if uout is stdout:
234 276 newfd = os.dup(uout.fileno())
235 277 os.dup2(stderr.fileno(), uout.fileno())
236 278 fout = os.fdopen(newfd, r'wb')
237 279 return fin, fout
238 280
239 281 def restorestdio(uin, uout, fin, fout):
240 282 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
241 283 uout.flush()
242 284 for f, uif in [(fin, uin), (fout, uout)]:
243 285 if f is not uif:
244 286 os.dup2(f.fileno(), uif.fileno())
245 287 f.close()
246 288
247 289 @contextlib.contextmanager
248 290 def protectedstdio(uin, uout):
249 291 """Run code block with protected standard streams"""
250 292 fin, fout = protectstdio(uin, uout)
251 293 try:
252 294 yield fin, fout
253 295 finally:
254 296 restorestdio(uin, uout, fin, fout)
255 297
256 298 def shellenviron(environ=None):
257 299 """return environ with optional override, useful for shelling out"""
258 300 def py2shell(val):
259 301 'convert python object into string that is useful to shell'
260 302 if val is None or val is False:
261 303 return '0'
262 304 if val is True:
263 305 return '1'
264 306 return pycompat.bytestr(val)
265 307 env = dict(encoding.environ)
266 308 if environ:
267 309 env.update((k, py2shell(v)) for k, v in environ.iteritems())
268 310 env['HG'] = hgexecutable()
269 311 return env
270 312
271 313 def system(cmd, environ=None, cwd=None, out=None):
272 314 '''enhanced shell command execution.
273 315 run with environment maybe modified, maybe in different dir.
274 316
275 317 if out is specified, it is assumed to be a file-like object that has a
276 318 write() method. stdout and stderr will be redirected to out.'''
277 319 try:
278 320 stdout.flush()
279 321 except Exception:
280 322 pass
281 323 cmd = quotecommand(cmd)
282 324 env = shellenviron(environ)
283 325 if out is None or isstdout(out):
284 326 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
285 327 env=env, cwd=cwd)
286 328 else:
287 329 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
288 330 env=env, cwd=cwd, stdout=subprocess.PIPE,
289 331 stderr=subprocess.STDOUT)
290 332 for line in iter(proc.stdout.readline, ''):
291 333 out.write(line)
292 334 proc.wait()
293 335 rc = proc.returncode
294 336 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
295 337 rc = 0
296 338 return rc
297 339
298 340 def gui():
299 341 '''Are we running in a GUI?'''
300 342 if pycompat.isdarwin:
301 343 if 'SSH_CONNECTION' in encoding.environ:
302 344 # handle SSH access to a box where the user is logged in
303 345 return False
304 346 elif getattr(osutil, 'isgui', None):
305 347 # check if a CoreGraphics session is available
306 348 return osutil.isgui()
307 349 else:
308 350 # pure build; use a safe default
309 351 return True
310 352 else:
311 353 return pycompat.iswindows or encoding.environ.get("DISPLAY")
312 354
313 355 def hgcmd():
314 356 """Return the command used to execute current hg
315 357
316 358 This is different from hgexecutable() because on Windows we want
317 359 to avoid things opening new shell windows like batch files, so we
318 360 get either the python call or current executable.
319 361 """
320 362 if mainfrozen():
321 363 if getattr(sys, 'frozen', None) == 'macosx_app':
322 364 # Env variable set by py2app
323 365 return [encoding.environ['EXECUTABLEPATH']]
324 366 else:
325 367 return [pycompat.sysexecutable]
326 368 return _gethgcmd()
327 369
328 370 def rundetached(args, condfn):
329 371 """Execute the argument list in a detached process.
330 372
331 373 condfn is a callable which is called repeatedly and should return
332 374 True once the child process is known to have started successfully.
333 375 At this point, the child process PID is returned. If the child
334 376 process fails to start or finishes before condfn() evaluates to
335 377 True, return -1.
336 378 """
337 379 # Windows case is easier because the child process is either
338 380 # successfully starting and validating the condition or exiting
339 381 # on failure. We just poll on its PID. On Unix, if the child
340 382 # process fails to start, it will be left in a zombie state until
341 383 # the parent wait on it, which we cannot do since we expect a long
342 384 # running process on success. Instead we listen for SIGCHLD telling
343 385 # us our child process terminated.
344 386 terminated = set()
345 387 def handler(signum, frame):
346 388 terminated.add(os.wait())
347 389 prevhandler = None
348 390 SIGCHLD = getattr(signal, 'SIGCHLD', None)
349 391 if SIGCHLD is not None:
350 392 prevhandler = signal.signal(SIGCHLD, handler)
351 393 try:
352 394 pid = spawndetached(args)
353 395 while not condfn():
354 396 if ((pid in terminated or not testpid(pid))
355 397 and not condfn()):
356 398 return -1
357 399 time.sleep(0.1)
358 400 return pid
359 401 finally:
360 402 if prevhandler is not None:
361 403 signal.signal(signal.SIGCHLD, prevhandler)
@@ -1,495 +1,488 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import msvcrt
12 12 import os
13 13 import re
14 14 import stat
15 15 import sys
16 16
17 17 from .i18n import _
18 18 from . import (
19 19 encoding,
20 20 error,
21 21 policy,
22 22 pycompat,
23 23 win32,
24 24 )
25 25
26 26 try:
27 27 import _winreg as winreg
28 28 winreg.CloseKey
29 29 except ImportError:
30 30 import winreg
31 31
32 32 osutil = policy.importmod(r'osutil')
33 33
34 34 getfsmountpoint = win32.getvolumename
35 35 getfstype = win32.getfstype
36 36 getuser = win32.getuser
37 37 hidewindow = win32.hidewindow
38 38 makedir = win32.makedir
39 39 nlinks = win32.nlinks
40 40 oslink = win32.oslink
41 41 samedevice = win32.samedevice
42 42 samefile = win32.samefile
43 43 setsignalhandler = win32.setsignalhandler
44 44 spawndetached = win32.spawndetached
45 45 split = os.path.split
46 46 testpid = win32.testpid
47 47 unlink = win32.unlink
48 48
49 49 umask = 0o022
50 50
51 51 class mixedfilemodewrapper(object):
52 52 """Wraps a file handle when it is opened in read/write mode.
53 53
54 54 fopen() and fdopen() on Windows have a specific-to-Windows requirement
55 55 that files opened with mode r+, w+, or a+ make a call to a file positioning
56 56 function when switching between reads and writes. Without this extra call,
57 57 Python will raise a not very intuitive "IOError: [Errno 0] Error."
58 58
59 59 This class wraps posixfile instances when the file is opened in read/write
60 60 mode and automatically adds checks or inserts appropriate file positioning
61 61 calls when necessary.
62 62 """
63 63 OPNONE = 0
64 64 OPREAD = 1
65 65 OPWRITE = 2
66 66
67 67 def __init__(self, fp):
68 68 object.__setattr__(self, r'_fp', fp)
69 69 object.__setattr__(self, r'_lastop', 0)
70 70
71 71 def __enter__(self):
72 72 return self._fp.__enter__()
73 73
74 74 def __exit__(self, exc_type, exc_val, exc_tb):
75 75 self._fp.__exit__(exc_type, exc_val, exc_tb)
76 76
77 77 def __getattr__(self, name):
78 78 return getattr(self._fp, name)
79 79
80 80 def __setattr__(self, name, value):
81 81 return self._fp.__setattr__(name, value)
82 82
83 83 def _noopseek(self):
84 84 self._fp.seek(0, os.SEEK_CUR)
85 85
86 86 def seek(self, *args, **kwargs):
87 87 object.__setattr__(self, r'_lastop', self.OPNONE)
88 88 return self._fp.seek(*args, **kwargs)
89 89
90 90 def write(self, d):
91 91 if self._lastop == self.OPREAD:
92 92 self._noopseek()
93 93
94 94 object.__setattr__(self, r'_lastop', self.OPWRITE)
95 95 return self._fp.write(d)
96 96
97 97 def writelines(self, *args, **kwargs):
98 98 if self._lastop == self.OPREAD:
99 99 self._noopeseek()
100 100
101 101 object.__setattr__(self, r'_lastop', self.OPWRITE)
102 102 return self._fp.writelines(*args, **kwargs)
103 103
104 104 def read(self, *args, **kwargs):
105 105 if self._lastop == self.OPWRITE:
106 106 self._noopseek()
107 107
108 108 object.__setattr__(self, r'_lastop', self.OPREAD)
109 109 return self._fp.read(*args, **kwargs)
110 110
111 111 def readline(self, *args, **kwargs):
112 112 if self._lastop == self.OPWRITE:
113 113 self._noopseek()
114 114
115 115 object.__setattr__(self, r'_lastop', self.OPREAD)
116 116 return self._fp.readline(*args, **kwargs)
117 117
118 118 def readlines(self, *args, **kwargs):
119 119 if self._lastop == self.OPWRITE:
120 120 self._noopseek()
121 121
122 122 object.__setattr__(self, r'_lastop', self.OPREAD)
123 123 return self._fp.readlines(*args, **kwargs)
124 124
125 125 def posixfile(name, mode='r', buffering=-1):
126 126 '''Open a file with even more POSIX-like semantics'''
127 127 try:
128 128 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
129 129
130 130 # The position when opening in append mode is implementation defined, so
131 131 # make it consistent with other platforms, which position at EOF.
132 132 if 'a' in mode:
133 133 fp.seek(0, os.SEEK_END)
134 134
135 135 if '+' in mode:
136 136 return mixedfilemodewrapper(fp)
137 137
138 138 return fp
139 139 except WindowsError as err:
140 140 # convert to a friendlier exception
141 141 raise IOError(err.errno, '%s: %s' % (
142 142 name, encoding.strtolocal(err.strerror)))
143 143
144 144 # may be wrapped by win32mbcs extension
145 145 listdir = osutil.listdir
146 146
147 147 class winstdout(object):
148 148 '''stdout on windows misbehaves if sent through a pipe'''
149 149
150 150 def __init__(self, fp):
151 151 self.fp = fp
152 152
153 153 def __getattr__(self, key):
154 154 return getattr(self.fp, key)
155 155
156 156 def close(self):
157 157 try:
158 158 self.fp.close()
159 159 except IOError:
160 160 pass
161 161
162 162 def write(self, s):
163 163 try:
164 164 # This is workaround for "Not enough space" error on
165 165 # writing large size of data to console.
166 166 limit = 16000
167 167 l = len(s)
168 168 start = 0
169 169 self.softspace = 0
170 170 while start < l:
171 171 end = start + limit
172 172 self.fp.write(s[start:end])
173 173 start = end
174 174 except IOError as inst:
175 175 if inst.errno != 0:
176 176 raise
177 177 self.close()
178 178 raise IOError(errno.EPIPE, 'Broken pipe')
179 179
180 180 def flush(self):
181 181 try:
182 182 return self.fp.flush()
183 183 except IOError as inst:
184 184 if inst.errno != errno.EINVAL:
185 185 raise
186 186 raise IOError(errno.EPIPE, 'Broken pipe')
187 187
188 188 def _is_win_9x():
189 189 '''return true if run on windows 95, 98 or me.'''
190 190 try:
191 191 return sys.getwindowsversion()[3] == 1
192 192 except AttributeError:
193 193 return 'command' in encoding.environ.get('comspec', '')
194 194
195 195 def openhardlinks():
196 196 return not _is_win_9x()
197 197
198 198 def parsepatchoutput(output_line):
199 199 """parses the output produced by patch and returns the filename"""
200 200 pf = output_line[14:]
201 201 if pf[0] == '`':
202 202 pf = pf[1:-1] # Remove the quotes
203 203 return pf
204 204
205 205 def sshargs(sshcmd, host, user, port):
206 206 '''Build argument list for ssh or Plink'''
207 207 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
208 208 args = user and ("%s@%s" % (user, host)) or host
209 209 if args.startswith('-') or args.startswith('/'):
210 210 raise error.Abort(
211 211 _('illegal ssh hostname or username starting with - or /: %s') %
212 212 args)
213 213 args = shellquote(args)
214 214 if port:
215 215 args = '%s %s %s' % (pflag, shellquote(port), args)
216 216 return args
217 217
218 218 def setflags(f, l, x):
219 219 pass
220 220
221 221 def copymode(src, dst, mode=None):
222 222 pass
223 223
224 224 def checkexec(path):
225 225 return False
226 226
227 227 def checklink(path):
228 228 return False
229 229
230 230 def setbinary(fd):
231 231 # When run without console, pipes may expose invalid
232 232 # fileno(), usually set to -1.
233 233 fno = getattr(fd, 'fileno', None)
234 234 if fno is not None and fno() >= 0:
235 235 msvcrt.setmode(fno(), os.O_BINARY)
236 236
237 237 def pconvert(path):
238 238 return path.replace(pycompat.ossep, '/')
239 239
240 240 def localpath(path):
241 241 return path.replace('/', '\\')
242 242
243 243 def normpath(path):
244 244 return pconvert(os.path.normpath(path))
245 245
246 246 def normcase(path):
247 247 return encoding.upper(path) # NTFS compares via upper()
248 248
249 249 # see posix.py for definitions
250 250 normcasespec = encoding.normcasespecs.upper
251 251 normcasefallback = encoding.upperfallback
252 252
253 253 def samestat(s1, s2):
254 254 return False
255 255
256 256 # A sequence of backslashes is special iff it precedes a double quote:
257 257 # - if there's an even number of backslashes, the double quote is not
258 258 # quoted (i.e. it ends the quoted region)
259 259 # - if there's an odd number of backslashes, the double quote is quoted
260 260 # - in both cases, every pair of backslashes is unquoted into a single
261 261 # backslash
262 262 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
263 263 # So, to quote a string, we must surround it in double quotes, double
264 264 # the number of backslashes that precede double quotes and add another
265 265 # backslash before every double quote (being careful with the double
266 266 # quote we've appended to the end)
267 267 _quotere = None
268 268 _needsshellquote = None
269 269 def shellquote(s):
270 270 r"""
271 271 >>> shellquote(br'C:\Users\xyz')
272 272 '"C:\\Users\\xyz"'
273 273 >>> shellquote(br'C:\Users\xyz/mixed')
274 274 '"C:\\Users\\xyz/mixed"'
275 275 >>> # Would be safe not to quote too, since it is all double backslashes
276 276 >>> shellquote(br'C:\\Users\\xyz')
277 277 '"C:\\\\Users\\\\xyz"'
278 278 >>> # But this must be quoted
279 279 >>> shellquote(br'C:\\Users\\xyz/abc')
280 280 '"C:\\\\Users\\\\xyz/abc"'
281 281 """
282 282 global _quotere
283 283 if _quotere is None:
284 284 _quotere = re.compile(r'(\\*)("|\\$)')
285 285 global _needsshellquote
286 286 if _needsshellquote is None:
287 287 # ":" is also treated as "safe character", because it is used as a part
288 288 # of path name on Windows. "\" is also part of a path name, but isn't
289 289 # safe because shlex.split() (kind of) treats it as an escape char and
290 290 # drops it. It will leave the next character, even if it is another
291 291 # "\".
292 292 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
293 293 if s and not _needsshellquote(s) and not _quotere.search(s):
294 294 # "s" shouldn't have to be quoted
295 295 return s
296 296 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
297 297
298 298 def _unquote(s):
299 299 if s.startswith(b'"') and s.endswith(b'"'):
300 300 return s[1:-1]
301 301 return s
302 302
303 303 def shellsplit(s):
304 304 """Parse a command string in cmd.exe way (best-effort)"""
305 305 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
306 306
307 307 def quotecommand(cmd):
308 308 """Build a command string suitable for os.popen* calls."""
309 309 if sys.version_info < (2, 7, 1):
310 310 # Python versions since 2.7.1 do this extra quoting themselves
311 311 return '"' + cmd + '"'
312 312 return cmd
313 313
314 def popen(command, mode='r'):
315 # Work around "popen spawned process may not write to stdout
316 # under windows"
317 # http://bugs.python.org/issue1366
318 command += " 2> %s" % pycompat.bytestr(os.devnull)
319 return os.popen(quotecommand(command), mode)
320
321 314 def explainexit(code):
322 315 return _("exited with status %d") % code, code
323 316
324 317 # if you change this stub into a real check, please try to implement the
325 318 # username and groupname functions above, too.
326 319 def isowner(st):
327 320 return True
328 321
329 322 def findexe(command):
330 323 '''Find executable for command searching like cmd.exe does.
331 324 If command is a basename then PATH is searched for command.
332 325 PATH isn't searched if command is an absolute or relative path.
333 326 An extension from PATHEXT is found and added if not present.
334 327 If command isn't found None is returned.'''
335 328 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
336 329 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
337 330 if os.path.splitext(command)[1].lower() in pathexts:
338 331 pathexts = ['']
339 332
340 333 def findexisting(pathcommand):
341 334 'Will append extension (if needed) and return existing file'
342 335 for ext in pathexts:
343 336 executable = pathcommand + ext
344 337 if os.path.exists(executable):
345 338 return executable
346 339 return None
347 340
348 341 if pycompat.ossep in command:
349 342 return findexisting(command)
350 343
351 344 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
352 345 executable = findexisting(os.path.join(path, command))
353 346 if executable is not None:
354 347 return executable
355 348 return findexisting(os.path.expanduser(os.path.expandvars(command)))
356 349
357 350 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
358 351
359 352 def statfiles(files):
360 353 '''Stat each file in files. Yield each stat, or None if a file
361 354 does not exist or has a type we don't care about.
362 355
363 356 Cluster and cache stat per directory to minimize number of OS stat calls.'''
364 357 dircache = {} # dirname -> filename -> status | None if file does not exist
365 358 getkind = stat.S_IFMT
366 359 for nf in files:
367 360 nf = normcase(nf)
368 361 dir, base = os.path.split(nf)
369 362 if not dir:
370 363 dir = '.'
371 364 cache = dircache.get(dir, None)
372 365 if cache is None:
373 366 try:
374 367 dmap = dict([(normcase(n), s)
375 368 for n, k, s in listdir(dir, True)
376 369 if getkind(s.st_mode) in _wantedkinds])
377 370 except OSError as err:
378 371 # Python >= 2.5 returns ENOENT and adds winerror field
379 372 # EINVAL is raised if dir is not a directory.
380 373 if err.errno not in (errno.ENOENT, errno.EINVAL,
381 374 errno.ENOTDIR):
382 375 raise
383 376 dmap = {}
384 377 cache = dircache.setdefault(dir, dmap)
385 378 yield cache.get(base, None)
386 379
387 380 def username(uid=None):
388 381 """Return the name of the user with the given uid.
389 382
390 383 If uid is None, return the name of the current user."""
391 384 return None
392 385
393 386 def groupname(gid=None):
394 387 """Return the name of the group with the given gid.
395 388
396 389 If gid is None, return the name of the current group."""
397 390 return None
398 391
399 392 def removedirs(name):
400 393 """special version of os.removedirs that does not remove symlinked
401 394 directories or junction points if they actually contain files"""
402 395 if listdir(name):
403 396 return
404 397 os.rmdir(name)
405 398 head, tail = os.path.split(name)
406 399 if not tail:
407 400 head, tail = os.path.split(head)
408 401 while head and tail:
409 402 try:
410 403 if listdir(head):
411 404 return
412 405 os.rmdir(head)
413 406 except (ValueError, OSError):
414 407 break
415 408 head, tail = os.path.split(head)
416 409
417 410 def rename(src, dst):
418 411 '''atomically rename file src to dst, replacing dst if it exists'''
419 412 try:
420 413 os.rename(src, dst)
421 414 except OSError as e:
422 415 if e.errno != errno.EEXIST:
423 416 raise
424 417 unlink(dst)
425 418 os.rename(src, dst)
426 419
427 420 def gethgcmd():
428 421 return [sys.executable] + sys.argv[:1]
429 422
430 423 def groupmembers(name):
431 424 # Don't support groups on Windows for now
432 425 raise KeyError
433 426
434 427 def isexec(f):
435 428 return False
436 429
437 430 class cachestat(object):
438 431 def __init__(self, path):
439 432 pass
440 433
441 434 def cacheable(self):
442 435 return False
443 436
444 437 def lookupreg(key, valname=None, scope=None):
445 438 ''' Look up a key/value name in the Windows registry.
446 439
447 440 valname: value name. If unspecified, the default value for the key
448 441 is used.
449 442 scope: optionally specify scope for registry lookup, this can be
450 443 a sequence of scopes to look up in order. Default (CURRENT_USER,
451 444 LOCAL_MACHINE).
452 445 '''
453 446 if scope is None:
454 447 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
455 448 elif not isinstance(scope, (list, tuple)):
456 449 scope = (scope,)
457 450 for s in scope:
458 451 try:
459 452 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
460 453 # never let a Unicode string escape into the wild
461 454 return encoding.unitolocal(val)
462 455 except EnvironmentError:
463 456 pass
464 457
465 458 expandglobs = True
466 459
467 460 def statislink(st):
468 461 '''check whether a stat result is a symlink'''
469 462 return False
470 463
471 464 def statisexec(st):
472 465 '''check whether a stat result is an executable file'''
473 466 return False
474 467
475 468 def poll(fds):
476 469 # see posix.py for description
477 470 raise NotImplementedError()
478 471
479 472 def readpipe(pipe):
480 473 """Read all available data from a pipe."""
481 474 chunks = []
482 475 while True:
483 476 size = win32.peekpipe(pipe)
484 477 if not size:
485 478 break
486 479
487 480 s = pipe.read(size)
488 481 if not s:
489 482 break
490 483 chunks.append(s)
491 484
492 485 return ''.join(chunks)
493 486
494 487 def bindunixsocket(sock, path):
495 488 raise NotImplementedError('unsupported platform')
@@ -1,92 +1,100 b''
1 1 $ cat > patchtool.py <<EOF
2 2 > from __future__ import absolute_import, print_function
3 3 > import sys
4 4 > print('Using custom patch')
5 5 > if '--binary' in sys.argv:
6 6 > print('--binary found !')
7 7 > EOF
8 8
9 9 $ echo "[ui]" >> $HGRCPATH
10 10 $ echo "patch=$PYTHON ../patchtool.py" >> $HGRCPATH
11 11
12 12 $ hg init a
13 13 $ cd a
14 14 $ echo a > a
15 15 $ hg commit -Ama -d '1 0'
16 16 adding a
17 17 $ echo b >> a
18 18 $ hg commit -Amb -d '2 0'
19 19 $ cd ..
20 20
21 21 This test checks that:
22 22 - custom patch commands with arguments actually work
23 23 - patch code does not try to add weird arguments like
24 24 --binary when custom patch commands are used. For instance
25 25 --binary is added by default under win32.
26 26
27 27 check custom patch options are honored
28 28
29 29 $ hg --cwd a export -o ../a.diff tip
30 30 $ hg clone -r 0 a b
31 31 adding changesets
32 32 adding manifests
33 33 adding file changes
34 34 added 1 changesets with 1 changes to 1 files
35 35 new changesets 8580ff50825a
36 36 updating to branch default
37 37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38
39 39 $ hg --cwd b import -v ../a.diff
40 40 applying ../a.diff
41 41 Using custom patch
42 42 applied to working directory
43 43
44 44 Issue2417: hg import with # comments in description
45 45
46 46 Prepare source repo and patch:
47 47
48 48 $ rm $HGRCPATH
49 49 $ hg init c
50 50 $ cd c
51 51 $ printf "a\rc" > a
52 52 $ hg ci -A -m 0 a -d '0 0'
53 53 $ printf "a\rb\rc" > a
54 54 $ cat << eof > log
55 55 > first line which can't start with '# '
56 56 > # second line is a comment but that shouldn't be a problem.
57 57 > A patch marker like this was more problematic even after d7452292f9d3:
58 58 > # HG changeset patch
59 59 > # User lines looks like this - but it _is_ just a comment
60 60 > eof
61 61 $ hg ci -l log -d '0 0'
62 62 $ hg export -o p 1
63 63 $ cd ..
64 64
65 65 Clone and apply patch:
66 66
67 67 $ hg clone -r 0 c d
68 68 adding changesets
69 69 adding manifests
70 70 adding file changes
71 71 added 1 changesets with 1 changes to 1 files
72 72 new changesets 7fadb901d403
73 73 updating to branch default
74 74 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 75 $ cd d
76 76 $ hg import ../c/p
77 77 applying ../c/p
78 78 $ hg log -v -r 1
79 79 changeset: 1:cd0bde79c428
80 80 tag: tip
81 81 user: test
82 82 date: Thu Jan 01 00:00:00 1970 +0000
83 83 files: a
84 84 description:
85 85 first line which can't start with '# '
86 86 # second line is a comment but that shouldn't be a problem.
87 87 A patch marker like this was more problematic even after d7452292f9d3:
88 88 # HG changeset patch
89 89 # User lines looks like this - but it _is_ just a comment
90 90
91 91
92
93 Error exit (issue4746)
94
95 $ hg import ../c/p --config ui.patch='sh -c "exit 1"'
96 applying ../c/p
97 abort: patch command failed: exited with status 1
98 [255]
99
92 100 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now