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