39 # vaguely unix-like but don't have hardlink support. For those |
39 # vaguely unix-like but don't have hardlink support. For those |
40 # poor souls, just say we tried and that it failed so we fall back |
40 # poor souls, just say we tried and that it failed so we fall back |
41 # to copies. |
41 # to copies. |
42 def oslink(src, dst): |
42 def oslink(src, dst): |
43 raise OSError( |
43 raise OSError( |
44 errno.EINVAL, 'hardlinks not supported: %s to %s' % (src, dst) |
44 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst) |
45 ) |
45 ) |
46 |
46 |
47 |
47 |
48 readlink = os.readlink |
48 readlink = os.readlink |
49 unlink = os.unlink |
49 unlink = os.unlink |
83 ... b'/file_at_root', |
83 ... b'/file_at_root', |
84 ... b'///multiple_leading_separators_at_root', |
84 ... b'///multiple_leading_separators_at_root', |
85 ... b'']: |
85 ... b'']: |
86 ... assert split(f) == posixpath.split(f), f |
86 ... assert split(f) == posixpath.split(f), f |
87 ''' |
87 ''' |
88 ht = p.rsplit('/', 1) |
88 ht = p.rsplit(b'/', 1) |
89 if len(ht) == 1: |
89 if len(ht) == 1: |
90 return '', p |
90 return b'', p |
91 nh = ht[0].rstrip('/') |
91 nh = ht[0].rstrip(b'/') |
92 if nh: |
92 if nh: |
93 return nh, ht[1] |
93 return nh, ht[1] |
94 return ht[0] + '/', ht[1] |
94 return ht[0] + b'/', ht[1] |
95 |
95 |
96 |
96 |
97 def openhardlinks(): |
97 def openhardlinks(): |
98 '''return true if it is safe to hold open file handles to hardlinks''' |
98 '''return true if it is safe to hold open file handles to hardlinks''' |
99 return True |
99 return True |
105 |
105 |
106 |
106 |
107 def parsepatchoutput(output_line): |
107 def parsepatchoutput(output_line): |
108 """parses the output produced by patch and returns the filename""" |
108 """parses the output produced by patch and returns the filename""" |
109 pf = output_line[14:] |
109 pf = output_line[14:] |
110 if pycompat.sysplatform == 'OpenVMS': |
110 if pycompat.sysplatform == b'OpenVMS': |
111 if pf[0] == '`': |
111 if pf[0] == b'`': |
112 pf = pf[1:-1] # Remove the quotes |
112 pf = pf[1:-1] # Remove the quotes |
113 else: |
113 else: |
114 if pf.startswith("'") and pf.endswith("'") and " " in pf: |
114 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf: |
115 pf = pf[1:-1] # Remove the quotes |
115 pf = pf[1:-1] # Remove the quotes |
116 return pf |
116 return pf |
117 |
117 |
118 |
118 |
119 def sshargs(sshcmd, host, user, port): |
119 def sshargs(sshcmd, host, user, port): |
120 '''Build argument list for ssh''' |
120 '''Build argument list for ssh''' |
121 args = user and ("%s@%s" % (user, host)) or host |
121 args = user and (b"%s@%s" % (user, host)) or host |
122 if '-' in args[:1]: |
122 if b'-' in args[:1]: |
123 raise error.Abort( |
123 raise error.Abort( |
124 _('illegal ssh hostname or username starting with -: %s') % args |
124 _(b'illegal ssh hostname or username starting with -: %s') % args |
125 ) |
125 ) |
126 args = shellquote(args) |
126 args = shellquote(args) |
127 if port: |
127 if port: |
128 args = '-p %s %s' % (shellquote(port), args) |
128 args = b'-p %s %s' % (shellquote(port), args) |
129 return args |
129 return args |
130 |
130 |
131 |
131 |
132 def isexec(f): |
132 def isexec(f): |
133 """check whether a file is executable""" |
133 """check whether a file is executable""" |
138 st = os.lstat(f) |
138 st = os.lstat(f) |
139 s = st.st_mode |
139 s = st.st_mode |
140 if l: |
140 if l: |
141 if not stat.S_ISLNK(s): |
141 if not stat.S_ISLNK(s): |
142 # switch file to link |
142 # switch file to link |
143 fp = open(f, 'rb') |
143 fp = open(f, b'rb') |
144 data = fp.read() |
144 data = fp.read() |
145 fp.close() |
145 fp.close() |
146 unlink(f) |
146 unlink(f) |
147 try: |
147 try: |
148 os.symlink(data, f) |
148 os.symlink(data, f) |
149 except OSError: |
149 except OSError: |
150 # failed to make a link, rewrite file |
150 # failed to make a link, rewrite file |
151 fp = open(f, "wb") |
151 fp = open(f, b"wb") |
152 fp.write(data) |
152 fp.write(data) |
153 fp.close() |
153 fp.close() |
154 # no chmod needed at this point |
154 # no chmod needed at this point |
155 return |
155 return |
156 if stat.S_ISLNK(s): |
156 if stat.S_ISLNK(s): |
157 # switch link to file |
157 # switch link to file |
158 data = os.readlink(f) |
158 data = os.readlink(f) |
159 unlink(f) |
159 unlink(f) |
160 fp = open(f, "wb") |
160 fp = open(f, b"wb") |
161 fp.write(data) |
161 fp.write(data) |
162 fp.close() |
162 fp.close() |
163 s = 0o666 & ~umask # avoid restatting for chmod |
163 s = 0o666 & ~umask # avoid restatting for chmod |
164 |
164 |
165 sx = s & 0o100 |
165 sx = s & 0o100 |
166 if st.st_nlink > 1 and bool(x) != bool(sx): |
166 if st.st_nlink > 1 and bool(x) != bool(sx): |
167 # the file is a hardlink, break it |
167 # the file is a hardlink, break it |
168 with open(f, "rb") as fp: |
168 with open(f, b"rb") as fp: |
169 data = fp.read() |
169 data = fp.read() |
170 unlink(f) |
170 unlink(f) |
171 with open(f, "wb") as fp: |
171 with open(f, b"wb") as fp: |
172 fp.write(data) |
172 fp.write(data) |
173 |
173 |
174 if x and not sx: |
174 if x and not sx: |
175 # Turn on +x for every +r bit when making a file executable |
175 # Turn on +x for every +r bit when making a file executable |
176 # and obey umask. |
176 # and obey umask. |
213 # a FS remount. Frequently we can detect it if files are created |
213 # a FS remount. Frequently we can detect it if files are created |
214 # with exec bit on. |
214 # with exec bit on. |
215 |
215 |
216 try: |
216 try: |
217 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
217 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
218 basedir = os.path.join(path, '.hg') |
218 basedir = os.path.join(path, b'.hg') |
219 cachedir = os.path.join(basedir, 'wcache') |
219 cachedir = os.path.join(basedir, b'wcache') |
220 storedir = os.path.join(basedir, 'store') |
220 storedir = os.path.join(basedir, b'store') |
221 if not os.path.exists(cachedir): |
221 if not os.path.exists(cachedir): |
222 try: |
222 try: |
223 # we want to create the 'cache' directory, not the '.hg' one. |
223 # we want to create the 'cache' directory, not the '.hg' one. |
224 # Automatically creating '.hg' directory could silently spawn |
224 # Automatically creating '.hg' directory could silently spawn |
225 # invalid Mercurial repositories. That seems like a bad idea. |
225 # invalid Mercurial repositories. That seems like a bad idea. |
230 copymode(basedir, cachedir) |
230 copymode(basedir, cachedir) |
231 except (IOError, OSError): |
231 except (IOError, OSError): |
232 # we other fallback logic triggers |
232 # we other fallback logic triggers |
233 pass |
233 pass |
234 if os.path.isdir(cachedir): |
234 if os.path.isdir(cachedir): |
235 checkisexec = os.path.join(cachedir, 'checkisexec') |
235 checkisexec = os.path.join(cachedir, b'checkisexec') |
236 checknoexec = os.path.join(cachedir, 'checknoexec') |
236 checknoexec = os.path.join(cachedir, b'checknoexec') |
237 |
237 |
238 try: |
238 try: |
239 m = os.stat(checkisexec).st_mode |
239 m = os.stat(checkisexec).st_mode |
240 except OSError as e: |
240 except OSError as e: |
241 if e.errno != errno.ENOENT: |
241 if e.errno != errno.ENOENT: |
248 try: |
248 try: |
249 m = os.stat(checknoexec).st_mode |
249 m = os.stat(checknoexec).st_mode |
250 except OSError as e: |
250 except OSError as e: |
251 if e.errno != errno.ENOENT: |
251 if e.errno != errno.ENOENT: |
252 raise |
252 raise |
253 open(checknoexec, 'w').close() # might fail |
253 open(checknoexec, b'w').close() # might fail |
254 m = os.stat(checknoexec).st_mode |
254 m = os.stat(checknoexec).st_mode |
255 if m & EXECFLAGS == 0: |
255 if m & EXECFLAGS == 0: |
256 # check-exec is exec and check-no-exec is not exec |
256 # check-exec is exec and check-no-exec is not exec |
257 return True |
257 return True |
258 # checknoexec exists but is exec - delete it |
258 # checknoexec exists but is exec - delete it |
264 checkdir = cachedir |
264 checkdir = cachedir |
265 else: |
265 else: |
266 # check directly in path and don't leave checkisexec behind |
266 # check directly in path and don't leave checkisexec behind |
267 checkdir = path |
267 checkdir = path |
268 checkisexec = None |
268 checkisexec = None |
269 fh, fn = pycompat.mkstemp(dir=checkdir, prefix='hg-checkexec-') |
269 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-') |
270 try: |
270 try: |
271 os.close(fh) |
271 os.close(fh) |
272 m = os.stat(fn).st_mode |
272 m = os.stat(fn).st_mode |
273 if m & EXECFLAGS == 0: |
273 if m & EXECFLAGS == 0: |
274 os.chmod(fn, m & 0o777 | EXECFLAGS) |
274 os.chmod(fn, m & 0o777 | EXECFLAGS) |
288 def checklink(path): |
288 def checklink(path): |
289 """check whether the given path is on a symlink-capable filesystem""" |
289 """check whether the given path is on a symlink-capable filesystem""" |
290 # mktemp is not racy because symlink creation will fail if the |
290 # mktemp is not racy because symlink creation will fail if the |
291 # file already exists |
291 # file already exists |
292 while True: |
292 while True: |
293 cachedir = os.path.join(path, '.hg', 'wcache') |
293 cachedir = os.path.join(path, b'.hg', b'wcache') |
294 checklink = os.path.join(cachedir, 'checklink') |
294 checklink = os.path.join(cachedir, b'checklink') |
295 # try fast path, read only |
295 # try fast path, read only |
296 if os.path.islink(checklink): |
296 if os.path.islink(checklink): |
297 return True |
297 return True |
298 if os.path.isdir(cachedir): |
298 if os.path.isdir(cachedir): |
299 checkdir = cachedir |
299 checkdir = cachedir |
306 name = pycompat.fsencode(name) |
306 name = pycompat.fsencode(name) |
307 try: |
307 try: |
308 fd = None |
308 fd = None |
309 if cachedir is None: |
309 if cachedir is None: |
310 fd = pycompat.namedtempfile( |
310 fd = pycompat.namedtempfile( |
311 dir=checkdir, prefix='hg-checklink-' |
311 dir=checkdir, prefix=b'hg-checklink-' |
312 ) |
312 ) |
313 target = os.path.basename(fd.name) |
313 target = os.path.basename(fd.name) |
314 else: |
314 else: |
315 # create a fixed file to link to; doesn't matter if it |
315 # create a fixed file to link to; doesn't matter if it |
316 # already exists. |
316 # already exists. |
317 target = 'checklink-target' |
317 target = b'checklink-target' |
318 try: |
318 try: |
319 fullpath = os.path.join(cachedir, target) |
319 fullpath = os.path.join(cachedir, target) |
320 open(fullpath, 'w').close() |
320 open(fullpath, b'w').close() |
321 except IOError as inst: |
321 except IOError as inst: |
322 if inst[0] == errno.EACCES: |
322 if inst[0] == errno.EACCES: |
323 # If we can't write to cachedir, just pretend |
323 # If we can't write to cachedir, just pretend |
324 # that the fs is readonly and by association |
324 # that the fs is readonly and by association |
325 # that the fs won't support symlinks. This |
325 # that the fs won't support symlinks. This |
442 def normcasefallback(path): |
442 def normcasefallback(path): |
443 try: |
443 try: |
444 u = path.decode('utf-8') |
444 u = path.decode('utf-8') |
445 except UnicodeDecodeError: |
445 except UnicodeDecodeError: |
446 # OS X percent-encodes any bytes that aren't valid utf-8 |
446 # OS X percent-encodes any bytes that aren't valid utf-8 |
447 s = '' |
447 s = b'' |
448 pos = 0 |
448 pos = 0 |
449 l = len(path) |
449 l = len(path) |
450 while pos < l: |
450 while pos < l: |
451 try: |
451 try: |
452 c = encoding.getutf8char(path, pos) |
452 c = encoding.getutf8char(path, pos) |
453 pos += len(c) |
453 pos += len(c) |
454 except ValueError: |
454 except ValueError: |
455 c = '%%%02X' % ord(path[pos : pos + 1]) |
455 c = b'%%%02X' % ord(path[pos : pos + 1]) |
456 pos += 1 |
456 pos += 1 |
457 s += c |
457 s += c |
458 |
458 |
459 u = s.decode('utf-8') |
459 u = s.decode('utf-8') |
460 |
460 |
462 enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8') |
462 enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8') |
463 # drop HFS+ ignored characters |
463 # drop HFS+ ignored characters |
464 return encoding.hfsignoreclean(enc) |
464 return encoding.hfsignoreclean(enc) |
465 |
465 |
466 |
466 |
467 if pycompat.sysplatform == 'cygwin': |
467 if pycompat.sysplatform == b'cygwin': |
468 # workaround for cygwin, in which mount point part of path is |
468 # workaround for cygwin, in which mount point part of path is |
469 # treated as case sensitive, even though underlying NTFS is case |
469 # treated as case sensitive, even though underlying NTFS is case |
470 # insensitive. |
470 # insensitive. |
471 |
471 |
472 # default mount points |
472 # default mount points |
473 cygwinmountpoints = sorted( |
473 cygwinmountpoints = sorted( |
474 ["/usr/bin", "/usr/lib", "/cygdrive",], reverse=True |
474 [b"/usr/bin", b"/usr/lib", b"/cygdrive",], reverse=True |
475 ) |
475 ) |
476 |
476 |
477 # use upper-ing as normcase as same as NTFS workaround |
477 # use upper-ing as normcase as same as NTFS workaround |
478 def normcase(path): |
478 def normcase(path): |
479 pathlen = len(path) |
479 pathlen = len(path) |
513 |
513 |
514 _needsshellquote = None |
514 _needsshellquote = None |
515 |
515 |
516 |
516 |
517 def shellquote(s): |
517 def shellquote(s): |
518 if pycompat.sysplatform == 'OpenVMS': |
518 if pycompat.sysplatform == b'OpenVMS': |
519 return '"%s"' % s |
519 return b'"%s"' % s |
520 global _needsshellquote |
520 global _needsshellquote |
521 if _needsshellquote is None: |
521 if _needsshellquote is None: |
522 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search |
522 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search |
523 if s and not _needsshellquote(s): |
523 if s and not _needsshellquote(s): |
524 # "s" shouldn't have to be quoted |
524 # "s" shouldn't have to be quoted |
525 return s |
525 return s |
526 else: |
526 else: |
527 return "'%s'" % s.replace("'", "'\\''") |
527 return b"'%s'" % s.replace(b"'", b"'\\''") |
528 |
528 |
529 |
529 |
530 def shellsplit(s): |
530 def shellsplit(s): |
531 """Parse a command string in POSIX shell way (best-effort)""" |
531 """Parse a command string in POSIX shell way (best-effort)""" |
532 return pycompat.shlexsplit(s, posix=True) |
532 return pycompat.shlexsplit(s, posix=True) |
555 def findexe(command): |
555 def findexe(command): |
556 '''Find executable for command searching like which does. |
556 '''Find executable for command searching like which does. |
557 If command is a basename then PATH is searched for command. |
557 If command is a basename then PATH is searched for command. |
558 PATH isn't searched if command is an absolute or relative path. |
558 PATH isn't searched if command is an absolute or relative path. |
559 If command isn't found None is returned.''' |
559 If command isn't found None is returned.''' |
560 if pycompat.sysplatform == 'OpenVMS': |
560 if pycompat.sysplatform == b'OpenVMS': |
561 return command |
561 return command |
562 |
562 |
563 def findexisting(executable): |
563 def findexisting(executable): |
564 'Will return executable if existing file' |
564 b'Will return executable if existing file' |
565 if os.path.isfile(executable) and os.access(executable, os.X_OK): |
565 if os.path.isfile(executable) and os.access(executable, os.X_OK): |
566 return executable |
566 return executable |
567 return None |
567 return None |
568 |
568 |
569 if pycompat.ossep in command: |
569 if pycompat.ossep in command: |
570 return findexisting(command) |
570 return findexisting(command) |
571 |
571 |
572 if pycompat.sysplatform == 'plan9': |
572 if pycompat.sysplatform == b'plan9': |
573 return findexisting(os.path.join('/bin', command)) |
573 return findexisting(os.path.join(b'/bin', command)) |
574 |
574 |
575 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep): |
575 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep): |
576 executable = findexisting(os.path.join(path, command)) |
576 executable = findexisting(os.path.join(path, command)) |
577 if executable is not None: |
577 if executable is not None: |
578 return executable |
578 return executable |
579 return None |
579 return None |
580 |
580 |
763 # AF_UNIX path has very small length limit (107 chars) on common |
763 # AF_UNIX path has very small length limit (107 chars) on common |
764 # platforms (see sys/un.h) |
764 # platforms (see sys/un.h) |
765 dirname, basename = os.path.split(path) |
765 dirname, basename = os.path.split(path) |
766 bakwdfd = None |
766 bakwdfd = None |
767 if dirname: |
767 if dirname: |
768 bakwdfd = os.open('.', os.O_DIRECTORY) |
768 bakwdfd = os.open(b'.', os.O_DIRECTORY) |
769 os.chdir(dirname) |
769 os.chdir(dirname) |
770 sock.bind(basename) |
770 sock.bind(basename) |
771 if bakwdfd: |
771 if bakwdfd: |
772 os.fchdir(bakwdfd) |
772 os.fchdir(bakwdfd) |
773 os.close(bakwdfd) |
773 os.close(bakwdfd) |