##// END OF EJS Templates
cleanup: fix some latent open(path).read() et al calls we previously missed...
Augie Fackler -
r36966:0585337e default
parent child Browse files
Show More
@@ -1,161 +1,163 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # hggettext - carefully extract docstrings for Mercurial
3 # hggettext - carefully extract docstrings for Mercurial
4 #
4 #
5 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
5 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
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 # The normalize function is taken from pygettext which is distributed
10 # The normalize function is taken from pygettext which is distributed
11 # with Python under the Python License, which is GPL compatible.
11 # with Python under the Python License, which is GPL compatible.
12
12
13 """Extract docstrings from Mercurial commands.
13 """Extract docstrings from Mercurial commands.
14
14
15 Compared to pygettext, this script knows about the cmdtable and table
15 Compared to pygettext, this script knows about the cmdtable and table
16 dictionaries used by Mercurial, and will only extract docstrings from
16 dictionaries used by Mercurial, and will only extract docstrings from
17 functions mentioned therein.
17 functions mentioned therein.
18
18
19 Use xgettext like normal to extract strings marked as translatable and
19 Use xgettext like normal to extract strings marked as translatable and
20 join the message cataloges to get the final catalog.
20 join the message cataloges to get the final catalog.
21 """
21 """
22
22
23 from __future__ import absolute_import, print_function
23 from __future__ import absolute_import, print_function
24
24
25 import inspect
25 import inspect
26 import os
26 import os
27 import re
27 import re
28 import sys
28 import sys
29
29
30
30
31 def escape(s):
31 def escape(s):
32 # The order is important, the backslash must be escaped first
32 # The order is important, the backslash must be escaped first
33 # since the other replacements introduce new backslashes
33 # since the other replacements introduce new backslashes
34 # themselves.
34 # themselves.
35 s = s.replace('\\', '\\\\')
35 s = s.replace('\\', '\\\\')
36 s = s.replace('\n', '\\n')
36 s = s.replace('\n', '\\n')
37 s = s.replace('\r', '\\r')
37 s = s.replace('\r', '\\r')
38 s = s.replace('\t', '\\t')
38 s = s.replace('\t', '\\t')
39 s = s.replace('"', '\\"')
39 s = s.replace('"', '\\"')
40 return s
40 return s
41
41
42
42
43 def normalize(s):
43 def normalize(s):
44 # This converts the various Python string types into a format that
44 # This converts the various Python string types into a format that
45 # is appropriate for .po files, namely much closer to C style.
45 # is appropriate for .po files, namely much closer to C style.
46 lines = s.split('\n')
46 lines = s.split('\n')
47 if len(lines) == 1:
47 if len(lines) == 1:
48 s = '"' + escape(s) + '"'
48 s = '"' + escape(s) + '"'
49 else:
49 else:
50 if not lines[-1]:
50 if not lines[-1]:
51 del lines[-1]
51 del lines[-1]
52 lines[-1] = lines[-1] + '\n'
52 lines[-1] = lines[-1] + '\n'
53 lines = map(escape, lines)
53 lines = map(escape, lines)
54 lineterm = '\\n"\n"'
54 lineterm = '\\n"\n"'
55 s = '""\n"' + lineterm.join(lines) + '"'
55 s = '""\n"' + lineterm.join(lines) + '"'
56 return s
56 return s
57
57
58
58
59 def poentry(path, lineno, s):
59 def poentry(path, lineno, s):
60 return ('#: %s:%d\n' % (path, lineno) +
60 return ('#: %s:%d\n' % (path, lineno) +
61 'msgid %s\n' % normalize(s) +
61 'msgid %s\n' % normalize(s) +
62 'msgstr ""\n')
62 'msgstr ""\n')
63
63
64 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
64 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
65
65
66 def offset(src, doc, name, default):
66 def offset(src, doc, name, default):
67 """Compute offset or issue a warning on stdout."""
67 """Compute offset or issue a warning on stdout."""
68 # remove doctest part, in order to avoid backslash mismatching
68 # remove doctest part, in order to avoid backslash mismatching
69 m = doctestre.search(doc)
69 m = doctestre.search(doc)
70 if m:
70 if m:
71 doc = doc[:m.start()]
71 doc = doc[:m.start()]
72
72
73 # Backslashes in doc appear doubled in src.
73 # Backslashes in doc appear doubled in src.
74 end = src.find(doc.replace('\\', '\\\\'))
74 end = src.find(doc.replace('\\', '\\\\'))
75 if end == -1:
75 if end == -1:
76 # This can happen if the docstring contains unnecessary escape
76 # This can happen if the docstring contains unnecessary escape
77 # sequences such as \" in a triple-quoted string. The problem
77 # sequences such as \" in a triple-quoted string. The problem
78 # is that \" is turned into " and so doc wont appear in src.
78 # is that \" is turned into " and so doc wont appear in src.
79 sys.stderr.write("warning: unknown offset in %s, assuming %d lines\n"
79 sys.stderr.write("warning: unknown offset in %s, assuming %d lines\n"
80 % (name, default))
80 % (name, default))
81 return default
81 return default
82 else:
82 else:
83 return src.count('\n', 0, end)
83 return src.count('\n', 0, end)
84
84
85
85
86 def importpath(path):
86 def importpath(path):
87 """Import a path like foo/bar/baz.py and return the baz module."""
87 """Import a path like foo/bar/baz.py and return the baz module."""
88 if path.endswith('.py'):
88 if path.endswith('.py'):
89 path = path[:-3]
89 path = path[:-3]
90 if path.endswith('/__init__'):
90 if path.endswith('/__init__'):
91 path = path[:-9]
91 path = path[:-9]
92 path = path.replace('/', '.')
92 path = path.replace('/', '.')
93 mod = __import__(path)
93 mod = __import__(path)
94 for comp in path.split('.')[1:]:
94 for comp in path.split('.')[1:]:
95 mod = getattr(mod, comp)
95 mod = getattr(mod, comp)
96 return mod
96 return mod
97
97
98
98
99 def docstrings(path):
99 def docstrings(path):
100 """Extract docstrings from path.
100 """Extract docstrings from path.
101
101
102 This respects the Mercurial cmdtable/table convention and will
102 This respects the Mercurial cmdtable/table convention and will
103 only extract docstrings from functions mentioned in these tables.
103 only extract docstrings from functions mentioned in these tables.
104 """
104 """
105 mod = importpath(path)
105 mod = importpath(path)
106 if not path.startswith('mercurial/') and mod.__doc__:
106 if not path.startswith('mercurial/') and mod.__doc__:
107 src = open(path).read()
107 with open(path) as fobj:
108 src = fobj.read()
108 lineno = 1 + offset(src, mod.__doc__, path, 7)
109 lineno = 1 + offset(src, mod.__doc__, path, 7)
109 print(poentry(path, lineno, mod.__doc__))
110 print(poentry(path, lineno, mod.__doc__))
110
111
111 functions = list(getattr(mod, 'i18nfunctions', []))
112 functions = list(getattr(mod, 'i18nfunctions', []))
112 functions = [(f, True) for f in functions]
113 functions = [(f, True) for f in functions]
113
114
114 cmdtable = getattr(mod, 'cmdtable', {})
115 cmdtable = getattr(mod, 'cmdtable', {})
115 if not cmdtable:
116 if not cmdtable:
116 # Maybe we are processing mercurial.commands?
117 # Maybe we are processing mercurial.commands?
117 cmdtable = getattr(mod, 'table', {})
118 cmdtable = getattr(mod, 'table', {})
118 functions.extend((c[0], False) for c in cmdtable.itervalues())
119 functions.extend((c[0], False) for c in cmdtable.itervalues())
119
120
120 for func, rstrip in functions:
121 for func, rstrip in functions:
121 if func.__doc__:
122 if func.__doc__:
122 docobj = func # this might be a proxy to provide formatted doc
123 docobj = func # this might be a proxy to provide formatted doc
123 func = getattr(func, '_origfunc', func)
124 func = getattr(func, '_origfunc', func)
124 funcmod = inspect.getmodule(func)
125 funcmod = inspect.getmodule(func)
125 extra = ''
126 extra = ''
126 if funcmod.__package__ == funcmod.__name__:
127 if funcmod.__package__ == funcmod.__name__:
127 extra = '/__init__'
128 extra = '/__init__'
128 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
129 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
129
130
130 src = inspect.getsource(func)
131 src = inspect.getsource(func)
131 name = "%s.%s" % (actualpath, func.__name__)
132 name = "%s.%s" % (actualpath, func.__name__)
132 lineno = inspect.getsourcelines(func)[1]
133 lineno = inspect.getsourcelines(func)[1]
133 doc = docobj.__doc__
134 doc = docobj.__doc__
134 origdoc = getattr(docobj, '_origdoc', '')
135 origdoc = getattr(docobj, '_origdoc', '')
135 if rstrip:
136 if rstrip:
136 doc = doc.rstrip()
137 doc = doc.rstrip()
137 origdoc = origdoc.rstrip()
138 origdoc = origdoc.rstrip()
138 if origdoc:
139 if origdoc:
139 lineno += offset(src, origdoc, name, 1)
140 lineno += offset(src, origdoc, name, 1)
140 else:
141 else:
141 lineno += offset(src, doc, name, 1)
142 lineno += offset(src, doc, name, 1)
142 print(poentry(actualpath, lineno, doc))
143 print(poentry(actualpath, lineno, doc))
143
144
144
145
145 def rawtext(path):
146 def rawtext(path):
146 src = open(path).read()
147 with open(path) as f:
148 src = f.read()
147 print(poentry(path, 1, src))
149 print(poentry(path, 1, src))
148
150
149
151
150 if __name__ == "__main__":
152 if __name__ == "__main__":
151 # It is very important that we import the Mercurial modules from
153 # It is very important that we import the Mercurial modules from
152 # the source tree where hggettext is executed. Otherwise we might
154 # the source tree where hggettext is executed. Otherwise we might
153 # accidentally import and extract strings from a Mercurial
155 # accidentally import and extract strings from a Mercurial
154 # installation mentioned in PYTHONPATH.
156 # installation mentioned in PYTHONPATH.
155 sys.path.insert(0, os.getcwd())
157 sys.path.insert(0, os.getcwd())
156 from mercurial import demandimport; demandimport.enable()
158 from mercurial import demandimport; demandimport.enable()
157 for path in sys.argv[1:]:
159 for path in sys.argv[1:]:
158 if path.endswith('.txt'):
160 if path.endswith('.txt'):
159 rawtext(path)
161 rawtext(path)
160 else:
162 else:
161 docstrings(path)
163 docstrings(path)
@@ -1,31 +1,32 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import cffi
3 import cffi
4 import os
4 import os
5
5
6 ffi = cffi.FFI()
6 ffi = cffi.FFI()
7 ffi.set_source("mercurial.cffi._bdiff",
7 with open(os.path.join(os.path.join(os.path.dirname(__file__), '..'),
8 open(os.path.join(os.path.join(os.path.dirname(__file__), '..'),
8 'bdiff.c')) as f:
9 'bdiff.c')).read(), include_dirs=['mercurial'])
9 ffi.set_source("mercurial.cffi._bdiff",
10 f.read(), include_dirs=['mercurial'])
10 ffi.cdef("""
11 ffi.cdef("""
11 struct bdiff_line {
12 struct bdiff_line {
12 int hash, n, e;
13 int hash, n, e;
13 ssize_t len;
14 ssize_t len;
14 const char *l;
15 const char *l;
15 };
16 };
16
17
17 struct bdiff_hunk;
18 struct bdiff_hunk;
18 struct bdiff_hunk {
19 struct bdiff_hunk {
19 int a1, a2, b1, b2;
20 int a1, a2, b1, b2;
20 struct bdiff_hunk *next;
21 struct bdiff_hunk *next;
21 };
22 };
22
23
23 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
24 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
24 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
25 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
25 struct bdiff_hunk *base);
26 struct bdiff_hunk *base);
26 void bdiff_freehunks(struct bdiff_hunk *l);
27 void bdiff_freehunks(struct bdiff_hunk *l);
27 void free(void*);
28 void free(void*);
28 """)
29 """)
29
30
30 if __name__ == '__main__':
31 if __name__ == '__main__':
31 ffi.compile()
32 ffi.compile()
@@ -1,35 +1,36 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import cffi
3 import cffi
4 import os
4 import os
5
5
6 ffi = cffi.FFI()
6 ffi = cffi.FFI()
7 mpatch_c = os.path.join(os.path.join(os.path.dirname(__file__), '..',
7 mpatch_c = os.path.join(os.path.join(os.path.dirname(__file__), '..',
8 'mpatch.c'))
8 'mpatch.c'))
9 ffi.set_source("mercurial.cffi._mpatch", open(mpatch_c).read(),
9 with open(mpatch_c) as f:
10 include_dirs=["mercurial"])
10 ffi.set_source("mercurial.cffi._mpatch", f.read(),
11 include_dirs=["mercurial"])
11 ffi.cdef("""
12 ffi.cdef("""
12
13
13 struct mpatch_frag {
14 struct mpatch_frag {
14 int start, end, len;
15 int start, end, len;
15 const char *data;
16 const char *data;
16 };
17 };
17
18
18 struct mpatch_flist {
19 struct mpatch_flist {
19 struct mpatch_frag *base, *head, *tail;
20 struct mpatch_frag *base, *head, *tail;
20 };
21 };
21
22
22 extern "Python" struct mpatch_flist* cffi_get_next_item(void*, ssize_t);
23 extern "Python" struct mpatch_flist* cffi_get_next_item(void*, ssize_t);
23
24
24 int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist** res);
25 int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist** res);
25 ssize_t mpatch_calcsize(size_t len, struct mpatch_flist *l);
26 ssize_t mpatch_calcsize(size_t len, struct mpatch_flist *l);
26 void mpatch_lfree(struct mpatch_flist *a);
27 void mpatch_lfree(struct mpatch_flist *a);
27 static int mpatch_apply(char *buf, const char *orig, size_t len,
28 static int mpatch_apply(char *buf, const char *orig, size_t len,
28 struct mpatch_flist *l);
29 struct mpatch_flist *l);
29 struct mpatch_flist *mpatch_fold(void *bins,
30 struct mpatch_flist *mpatch_fold(void *bins,
30 struct mpatch_flist* (*get_next_item)(void*, ssize_t),
31 struct mpatch_flist* (*get_next_item)(void*, ssize_t),
31 ssize_t start, ssize_t end);
32 ssize_t start, ssize_t end);
32 """)
33 """)
33
34
34 if __name__ == '__main__':
35 if __name__ == '__main__':
35 ffi.compile()
36 ffi.compile()
@@ -1,696 +1,697 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 open(os.path.join(cachedir, target), 'w').close()
267 fullpath = os.path.join(cachedir, target)
268 open(fullpath, 'w').close()
268 except IOError as inst:
269 except IOError as inst:
269 if inst[0] == errno.EACCES:
270 if inst[0] == errno.EACCES:
270 # If we can't write to cachedir, just pretend
271 # If we can't write to cachedir, just pretend
271 # that the fs is readonly and by association
272 # that the fs is readonly and by association
272 # that the fs won't support symlinks. This
273 # that the fs won't support symlinks. This
273 # seems like the least dangerous way to avoid
274 # seems like the least dangerous way to avoid
274 # data loss.
275 # data loss.
275 return False
276 return False
276 raise
277 raise
277 try:
278 try:
278 os.symlink(target, name)
279 os.symlink(target, name)
279 if cachedir is None:
280 if cachedir is None:
280 unlink(name)
281 unlink(name)
281 else:
282 else:
282 try:
283 try:
283 os.rename(name, checklink)
284 os.rename(name, checklink)
284 except OSError:
285 except OSError:
285 unlink(name)
286 unlink(name)
286 return True
287 return True
287 except OSError as inst:
288 except OSError as inst:
288 # link creation might race, try again
289 # link creation might race, try again
289 if inst[0] == errno.EEXIST:
290 if inst[0] == errno.EEXIST:
290 continue
291 continue
291 raise
292 raise
292 finally:
293 finally:
293 if fd is not None:
294 if fd is not None:
294 fd.close()
295 fd.close()
295 except AttributeError:
296 except AttributeError:
296 return False
297 return False
297 except OSError as inst:
298 except OSError as inst:
298 # sshfs might report failure while successfully creating the link
299 # sshfs might report failure while successfully creating the link
299 if inst[0] == errno.EIO and os.path.exists(name):
300 if inst[0] == errno.EIO and os.path.exists(name):
300 unlink(name)
301 unlink(name)
301 return False
302 return False
302
303
303 def checkosfilename(path):
304 def checkosfilename(path):
304 '''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.
305 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.'''
306 return None # on posix platforms, every path is ok
307 return None # on posix platforms, every path is ok
307
308
308 def getfsmountpoint(dirpath):
309 def getfsmountpoint(dirpath):
309 '''Get the filesystem mount point from a directory (best-effort)
310 '''Get the filesystem mount point from a directory (best-effort)
310
311
311 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.
312 '''
313 '''
313 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
314 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
314
315
315 def getfstype(dirpath):
316 def getfstype(dirpath):
316 '''Get the filesystem type name from a directory (best-effort)
317 '''Get the filesystem type name from a directory (best-effort)
317
318
318 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.
319 '''
320 '''
320 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
321 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
321
322
322 def setbinary(fd):
323 def setbinary(fd):
323 pass
324 pass
324
325
325 def pconvert(path):
326 def pconvert(path):
326 return path
327 return path
327
328
328 def localpath(path):
329 def localpath(path):
329 return path
330 return path
330
331
331 def samefile(fpath1, fpath2):
332 def samefile(fpath1, fpath2):
332 """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
333 guaranteed to work for files, not directories."""
334 guaranteed to work for files, not directories."""
334 return os.path.samefile(fpath1, fpath2)
335 return os.path.samefile(fpath1, fpath2)
335
336
336 def samedevice(fpath1, fpath2):
337 def samedevice(fpath1, fpath2):
337 """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
338 guaranteed to work for files, not directories."""
339 guaranteed to work for files, not directories."""
339 st1 = os.lstat(fpath1)
340 st1 = os.lstat(fpath1)
340 st2 = os.lstat(fpath2)
341 st2 = os.lstat(fpath2)
341 return st1.st_dev == st2.st_dev
342 return st1.st_dev == st2.st_dev
342
343
343 # 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
344 def normcase(path):
345 def normcase(path):
345 return path.lower()
346 return path.lower()
346
347
347 # what normcase does to ASCII strings
348 # what normcase does to ASCII strings
348 normcasespec = encoding.normcasespecs.lower
349 normcasespec = encoding.normcasespecs.lower
349 # fallback normcase function for non-ASCII strings
350 # fallback normcase function for non-ASCII strings
350 normcasefallback = normcase
351 normcasefallback = normcase
351
352
352 if pycompat.isdarwin:
353 if pycompat.isdarwin:
353
354
354 def normcase(path):
355 def normcase(path):
355 '''
356 '''
356 Normalize a filename for OS X-compatible comparison:
357 Normalize a filename for OS X-compatible comparison:
357 - escape-encode invalid characters
358 - escape-encode invalid characters
358 - decompose to NFD
359 - decompose to NFD
359 - lowercase
360 - lowercase
360 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
361 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
361
362
362 >>> normcase(b'UPPER')
363 >>> normcase(b'UPPER')
363 'upper'
364 'upper'
364 >>> normcase(b'Caf\\xc3\\xa9')
365 >>> normcase(b'Caf\\xc3\\xa9')
365 'cafe\\xcc\\x81'
366 'cafe\\xcc\\x81'
366 >>> normcase(b'\\xc3\\x89')
367 >>> normcase(b'\\xc3\\x89')
367 'e\\xcc\\x81'
368 'e\\xcc\\x81'
368 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
369 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
369 '%b8%ca%c3\\xca\\xbe%c8.jpg'
370 '%b8%ca%c3\\xca\\xbe%c8.jpg'
370 '''
371 '''
371
372
372 try:
373 try:
373 return encoding.asciilower(path) # exception for non-ASCII
374 return encoding.asciilower(path) # exception for non-ASCII
374 except UnicodeDecodeError:
375 except UnicodeDecodeError:
375 return normcasefallback(path)
376 return normcasefallback(path)
376
377
377 normcasespec = encoding.normcasespecs.lower
378 normcasespec = encoding.normcasespecs.lower
378
379
379 def normcasefallback(path):
380 def normcasefallback(path):
380 try:
381 try:
381 u = path.decode('utf-8')
382 u = path.decode('utf-8')
382 except UnicodeDecodeError:
383 except UnicodeDecodeError:
383 # 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
384 s = ''
385 s = ''
385 pos = 0
386 pos = 0
386 l = len(path)
387 l = len(path)
387 while pos < l:
388 while pos < l:
388 try:
389 try:
389 c = encoding.getutf8char(path, pos)
390 c = encoding.getutf8char(path, pos)
390 pos += len(c)
391 pos += len(c)
391 except ValueError:
392 except ValueError:
392 c = '%%%02X' % ord(path[pos:pos + 1])
393 c = '%%%02X' % ord(path[pos:pos + 1])
393 pos += 1
394 pos += 1
394 s += c
395 s += c
395
396
396 u = s.decode('utf-8')
397 u = s.decode('utf-8')
397
398
398 # Decompose then lowercase (HFS+ technote specifies lower)
399 # Decompose then lowercase (HFS+ technote specifies lower)
399 enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
400 enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
400 # drop HFS+ ignored characters
401 # drop HFS+ ignored characters
401 return encoding.hfsignoreclean(enc)
402 return encoding.hfsignoreclean(enc)
402
403
403 if pycompat.sysplatform == 'cygwin':
404 if pycompat.sysplatform == 'cygwin':
404 # workaround for cygwin, in which mount point part of path is
405 # workaround for cygwin, in which mount point part of path is
405 # treated as case sensitive, even though underlying NTFS is case
406 # treated as case sensitive, even though underlying NTFS is case
406 # insensitive.
407 # insensitive.
407
408
408 # default mount points
409 # default mount points
409 cygwinmountpoints = sorted([
410 cygwinmountpoints = sorted([
410 "/usr/bin",
411 "/usr/bin",
411 "/usr/lib",
412 "/usr/lib",
412 "/cygdrive",
413 "/cygdrive",
413 ], reverse=True)
414 ], reverse=True)
414
415
415 # use upper-ing as normcase as same as NTFS workaround
416 # use upper-ing as normcase as same as NTFS workaround
416 def normcase(path):
417 def normcase(path):
417 pathlen = len(path)
418 pathlen = len(path)
418 if (pathlen == 0) or (path[0] != pycompat.ossep):
419 if (pathlen == 0) or (path[0] != pycompat.ossep):
419 # treat as relative
420 # treat as relative
420 return encoding.upper(path)
421 return encoding.upper(path)
421
422
422 # to preserve case of mountpoint part
423 # to preserve case of mountpoint part
423 for mp in cygwinmountpoints:
424 for mp in cygwinmountpoints:
424 if not path.startswith(mp):
425 if not path.startswith(mp):
425 continue
426 continue
426
427
427 mplen = len(mp)
428 mplen = len(mp)
428 if mplen == pathlen: # mount point itself
429 if mplen == pathlen: # mount point itself
429 return mp
430 return mp
430 if path[mplen] == pycompat.ossep:
431 if path[mplen] == pycompat.ossep:
431 return mp + encoding.upper(path[mplen:])
432 return mp + encoding.upper(path[mplen:])
432
433
433 return encoding.upper(path)
434 return encoding.upper(path)
434
435
435 normcasespec = encoding.normcasespecs.other
436 normcasespec = encoding.normcasespecs.other
436 normcasefallback = normcase
437 normcasefallback = normcase
437
438
438 # Cygwin translates native ACLs to POSIX permissions,
439 # Cygwin translates native ACLs to POSIX permissions,
439 # but these translations are not supported by native
440 # but these translations are not supported by native
440 # tools, so the exec bit tends to be set erroneously.
441 # tools, so the exec bit tends to be set erroneously.
441 # Therefore, disable executable bit access on Cygwin.
442 # Therefore, disable executable bit access on Cygwin.
442 def checkexec(path):
443 def checkexec(path):
443 return False
444 return False
444
445
445 # Similarly, Cygwin's symlink emulation is likely to create
446 # Similarly, Cygwin's symlink emulation is likely to create
446 # problems when Mercurial is used from both Cygwin and native
447 # problems when Mercurial is used from both Cygwin and native
447 # Windows, with other native tools, or on shared volumes
448 # Windows, with other native tools, or on shared volumes
448 def checklink(path):
449 def checklink(path):
449 return False
450 return False
450
451
451 _needsshellquote = None
452 _needsshellquote = None
452 def shellquote(s):
453 def shellquote(s):
453 if pycompat.sysplatform == 'OpenVMS':
454 if pycompat.sysplatform == 'OpenVMS':
454 return '"%s"' % s
455 return '"%s"' % s
455 global _needsshellquote
456 global _needsshellquote
456 if _needsshellquote is None:
457 if _needsshellquote is None:
457 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
458 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
458 if s and not _needsshellquote(s):
459 if s and not _needsshellquote(s):
459 # "s" shouldn't have to be quoted
460 # "s" shouldn't have to be quoted
460 return s
461 return s
461 else:
462 else:
462 return "'%s'" % s.replace("'", "'\\''")
463 return "'%s'" % s.replace("'", "'\\''")
463
464
464 def shellsplit(s):
465 def shellsplit(s):
465 """Parse a command string in POSIX shell way (best-effort)"""
466 """Parse a command string in POSIX shell way (best-effort)"""
466 return pycompat.shlexsplit(s, posix=True)
467 return pycompat.shlexsplit(s, posix=True)
467
468
468 def quotecommand(cmd):
469 def quotecommand(cmd):
469 return cmd
470 return cmd
470
471
471 def popen(command, mode='r'):
472 def popen(command, mode='r'):
472 return os.popen(command, mode)
473 return os.popen(command, mode)
473
474
474 def testpid(pid):
475 def testpid(pid):
475 '''return False if pid dead, True if running or not sure'''
476 '''return False if pid dead, True if running or not sure'''
476 if pycompat.sysplatform == 'OpenVMS':
477 if pycompat.sysplatform == 'OpenVMS':
477 return True
478 return True
478 try:
479 try:
479 os.kill(pid, 0)
480 os.kill(pid, 0)
480 return True
481 return True
481 except OSError as inst:
482 except OSError as inst:
482 return inst.errno != errno.ESRCH
483 return inst.errno != errno.ESRCH
483
484
484 def explainexit(code):
485 def explainexit(code):
485 """return a 2-tuple (desc, code) describing a subprocess status
486 """return a 2-tuple (desc, code) describing a subprocess status
486 (codes from kill are negative - not os.system/wait encoding)"""
487 (codes from kill are negative - not os.system/wait encoding)"""
487 if code >= 0:
488 if code >= 0:
488 return _("exited with status %d") % code, code
489 return _("exited with status %d") % code, code
489 return _("killed by signal %d") % -code, -code
490 return _("killed by signal %d") % -code, -code
490
491
491 def isowner(st):
492 def isowner(st):
492 """Return True if the stat object st is from the current user."""
493 """Return True if the stat object st is from the current user."""
493 return st.st_uid == os.getuid()
494 return st.st_uid == os.getuid()
494
495
495 def findexe(command):
496 def findexe(command):
496 '''Find executable for command searching like which does.
497 '''Find executable for command searching like which does.
497 If command is a basename then PATH is searched for command.
498 If command is a basename then PATH is searched for command.
498 PATH isn't searched if command is an absolute or relative path.
499 PATH isn't searched if command is an absolute or relative path.
499 If command isn't found None is returned.'''
500 If command isn't found None is returned.'''
500 if pycompat.sysplatform == 'OpenVMS':
501 if pycompat.sysplatform == 'OpenVMS':
501 return command
502 return command
502
503
503 def findexisting(executable):
504 def findexisting(executable):
504 'Will return executable if existing file'
505 'Will return executable if existing file'
505 if os.path.isfile(executable) and os.access(executable, os.X_OK):
506 if os.path.isfile(executable) and os.access(executable, os.X_OK):
506 return executable
507 return executable
507 return None
508 return None
508
509
509 if pycompat.ossep in command:
510 if pycompat.ossep in command:
510 return findexisting(command)
511 return findexisting(command)
511
512
512 if pycompat.sysplatform == 'plan9':
513 if pycompat.sysplatform == 'plan9':
513 return findexisting(os.path.join('/bin', command))
514 return findexisting(os.path.join('/bin', command))
514
515
515 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
516 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
516 executable = findexisting(os.path.join(path, command))
517 executable = findexisting(os.path.join(path, command))
517 if executable is not None:
518 if executable is not None:
518 return executable
519 return executable
519 return None
520 return None
520
521
521 def setsignalhandler():
522 def setsignalhandler():
522 pass
523 pass
523
524
524 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
525 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
525
526
526 def statfiles(files):
527 def statfiles(files):
527 '''Stat each file in files. Yield each stat, or None if a file does not
528 '''Stat each file in files. Yield each stat, or None if a file does not
528 exist or has a type we don't care about.'''
529 exist or has a type we don't care about.'''
529 lstat = os.lstat
530 lstat = os.lstat
530 getkind = stat.S_IFMT
531 getkind = stat.S_IFMT
531 for nf in files:
532 for nf in files:
532 try:
533 try:
533 st = lstat(nf)
534 st = lstat(nf)
534 if getkind(st.st_mode) not in _wantedkinds:
535 if getkind(st.st_mode) not in _wantedkinds:
535 st = None
536 st = None
536 except OSError as err:
537 except OSError as err:
537 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
538 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
538 raise
539 raise
539 st = None
540 st = None
540 yield st
541 yield st
541
542
542 def getuser():
543 def getuser():
543 '''return name of current user'''
544 '''return name of current user'''
544 return pycompat.fsencode(getpass.getuser())
545 return pycompat.fsencode(getpass.getuser())
545
546
546 def username(uid=None):
547 def username(uid=None):
547 """Return the name of the user with the given uid.
548 """Return the name of the user with the given uid.
548
549
549 If uid is None, return the name of the current user."""
550 If uid is None, return the name of the current user."""
550
551
551 if uid is None:
552 if uid is None:
552 uid = os.getuid()
553 uid = os.getuid()
553 try:
554 try:
554 return pwd.getpwuid(uid)[0]
555 return pwd.getpwuid(uid)[0]
555 except KeyError:
556 except KeyError:
556 return str(uid)
557 return str(uid)
557
558
558 def groupname(gid=None):
559 def groupname(gid=None):
559 """Return the name of the group with the given gid.
560 """Return the name of the group with the given gid.
560
561
561 If gid is None, return the name of the current group."""
562 If gid is None, return the name of the current group."""
562
563
563 if gid is None:
564 if gid is None:
564 gid = os.getgid()
565 gid = os.getgid()
565 try:
566 try:
566 return grp.getgrgid(gid)[0]
567 return grp.getgrgid(gid)[0]
567 except KeyError:
568 except KeyError:
568 return str(gid)
569 return str(gid)
569
570
570 def groupmembers(name):
571 def groupmembers(name):
571 """Return the list of members of the group with the given
572 """Return the list of members of the group with the given
572 name, KeyError if the group does not exist.
573 name, KeyError if the group does not exist.
573 """
574 """
574 return list(grp.getgrnam(name).gr_mem)
575 return list(grp.getgrnam(name).gr_mem)
575
576
576 def spawndetached(args):
577 def spawndetached(args):
577 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
578 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
578 args[0], args)
579 args[0], args)
579
580
580 def gethgcmd():
581 def gethgcmd():
581 return sys.argv[:1]
582 return sys.argv[:1]
582
583
583 def makedir(path, notindexed):
584 def makedir(path, notindexed):
584 os.mkdir(path)
585 os.mkdir(path)
585
586
586 def lookupreg(key, name=None, scope=None):
587 def lookupreg(key, name=None, scope=None):
587 return None
588 return None
588
589
589 def hidewindow():
590 def hidewindow():
590 """Hide current shell window.
591 """Hide current shell window.
591
592
592 Used to hide the window opened when starting asynchronous
593 Used to hide the window opened when starting asynchronous
593 child process under Windows, unneeded on other systems.
594 child process under Windows, unneeded on other systems.
594 """
595 """
595 pass
596 pass
596
597
597 class cachestat(object):
598 class cachestat(object):
598 def __init__(self, path):
599 def __init__(self, path):
599 self.stat = os.stat(path)
600 self.stat = os.stat(path)
600
601
601 def cacheable(self):
602 def cacheable(self):
602 return bool(self.stat.st_ino)
603 return bool(self.stat.st_ino)
603
604
604 __hash__ = object.__hash__
605 __hash__ = object.__hash__
605
606
606 def __eq__(self, other):
607 def __eq__(self, other):
607 try:
608 try:
608 # Only dev, ino, size, mtime and atime are likely to change. Out
609 # Only dev, ino, size, mtime and atime are likely to change. Out
609 # of these, we shouldn't compare atime but should compare the
610 # of these, we shouldn't compare atime but should compare the
610 # rest. However, one of the other fields changing indicates
611 # rest. However, one of the other fields changing indicates
611 # something fishy going on, so return False if anything but atime
612 # something fishy going on, so return False if anything but atime
612 # changes.
613 # changes.
613 return (self.stat.st_mode == other.stat.st_mode and
614 return (self.stat.st_mode == other.stat.st_mode and
614 self.stat.st_ino == other.stat.st_ino and
615 self.stat.st_ino == other.stat.st_ino and
615 self.stat.st_dev == other.stat.st_dev and
616 self.stat.st_dev == other.stat.st_dev and
616 self.stat.st_nlink == other.stat.st_nlink and
617 self.stat.st_nlink == other.stat.st_nlink and
617 self.stat.st_uid == other.stat.st_uid and
618 self.stat.st_uid == other.stat.st_uid and
618 self.stat.st_gid == other.stat.st_gid and
619 self.stat.st_gid == other.stat.st_gid and
619 self.stat.st_size == other.stat.st_size and
620 self.stat.st_size == other.stat.st_size and
620 self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME] and
621 self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME] and
621 self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME])
622 self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME])
622 except AttributeError:
623 except AttributeError:
623 return False
624 return False
624
625
625 def __ne__(self, other):
626 def __ne__(self, other):
626 return not self == other
627 return not self == other
627
628
628 def executablepath():
629 def executablepath():
629 return None # available on Windows only
630 return None # available on Windows only
630
631
631 def statislink(st):
632 def statislink(st):
632 '''check whether a stat result is a symlink'''
633 '''check whether a stat result is a symlink'''
633 return st and stat.S_ISLNK(st.st_mode)
634 return st and stat.S_ISLNK(st.st_mode)
634
635
635 def statisexec(st):
636 def statisexec(st):
636 '''check whether a stat result is an executable file'''
637 '''check whether a stat result is an executable file'''
637 return st and (st.st_mode & 0o100 != 0)
638 return st and (st.st_mode & 0o100 != 0)
638
639
639 def poll(fds):
640 def poll(fds):
640 """block until something happens on any file descriptor
641 """block until something happens on any file descriptor
641
642
642 This is a generic helper that will check for any activity
643 This is a generic helper that will check for any activity
643 (read, write. exception) and return the list of touched files.
644 (read, write. exception) and return the list of touched files.
644
645
645 In unsupported cases, it will raise a NotImplementedError"""
646 In unsupported cases, it will raise a NotImplementedError"""
646 try:
647 try:
647 while True:
648 while True:
648 try:
649 try:
649 res = select.select(fds, fds, fds)
650 res = select.select(fds, fds, fds)
650 break
651 break
651 except select.error as inst:
652 except select.error as inst:
652 if inst.args[0] == errno.EINTR:
653 if inst.args[0] == errno.EINTR:
653 continue
654 continue
654 raise
655 raise
655 except ValueError: # out of range file descriptor
656 except ValueError: # out of range file descriptor
656 raise NotImplementedError()
657 raise NotImplementedError()
657 return sorted(list(set(sum(res, []))))
658 return sorted(list(set(sum(res, []))))
658
659
659 def readpipe(pipe):
660 def readpipe(pipe):
660 """Read all available data from a pipe."""
661 """Read all available data from a pipe."""
661 # We can't fstat() a pipe because Linux will always report 0.
662 # We can't fstat() a pipe because Linux will always report 0.
662 # So, we set the pipe to non-blocking mode and read everything
663 # So, we set the pipe to non-blocking mode and read everything
663 # that's available.
664 # that's available.
664 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
665 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
665 flags |= os.O_NONBLOCK
666 flags |= os.O_NONBLOCK
666 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
667 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
667
668
668 try:
669 try:
669 chunks = []
670 chunks = []
670 while True:
671 while True:
671 try:
672 try:
672 s = pipe.read()
673 s = pipe.read()
673 if not s:
674 if not s:
674 break
675 break
675 chunks.append(s)
676 chunks.append(s)
676 except IOError:
677 except IOError:
677 break
678 break
678
679
679 return ''.join(chunks)
680 return ''.join(chunks)
680 finally:
681 finally:
681 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
682 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
682
683
683 def bindunixsocket(sock, path):
684 def bindunixsocket(sock, path):
684 """Bind the UNIX domain socket to the specified path"""
685 """Bind the UNIX domain socket to the specified path"""
685 # use relative path instead of full path at bind() if possible, since
686 # use relative path instead of full path at bind() if possible, since
686 # AF_UNIX path has very small length limit (107 chars) on common
687 # AF_UNIX path has very small length limit (107 chars) on common
687 # platforms (see sys/un.h)
688 # platforms (see sys/un.h)
688 dirname, basename = os.path.split(path)
689 dirname, basename = os.path.split(path)
689 bakwdfd = None
690 bakwdfd = None
690 if dirname:
691 if dirname:
691 bakwdfd = os.open('.', os.O_DIRECTORY)
692 bakwdfd = os.open('.', os.O_DIRECTORY)
692 os.chdir(dirname)
693 os.chdir(dirname)
693 sock.bind(basename)
694 sock.bind(basename)
694 if bakwdfd:
695 if bakwdfd:
695 os.fchdir(bakwdfd)
696 os.fchdir(bakwdfd)
696 os.close(bakwdfd)
697 os.close(bakwdfd)
@@ -1,176 +1,177 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 """
3 """
4 Utility for inspecting files in various ways.
4 Utility for inspecting files in various ways.
5
5
6 This tool is like the collection of tools found in a unix environment but are
6 This tool is like the collection of tools found in a unix environment but are
7 cross platform and stable and suitable for our needs in the test suite.
7 cross platform and stable and suitable for our needs in the test suite.
8
8
9 This can be used instead of tools like:
9 This can be used instead of tools like:
10 [
10 [
11 dd
11 dd
12 find
12 find
13 head
13 head
14 hexdump
14 hexdump
15 ls
15 ls
16 md5sum
16 md5sum
17 readlink
17 readlink
18 sha1sum
18 sha1sum
19 stat
19 stat
20 tail
20 tail
21 test
21 test
22 readlink.py
22 readlink.py
23 md5sum.py
23 md5sum.py
24 """
24 """
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27
27
28 import binascii
28 import binascii
29 import glob
29 import glob
30 import hashlib
30 import hashlib
31 import optparse
31 import optparse
32 import os
32 import os
33 import re
33 import re
34 import sys
34 import sys
35
35
36 # Python 3 adapters
36 # Python 3 adapters
37 ispy3 = (sys.version_info[0] >= 3)
37 ispy3 = (sys.version_info[0] >= 3)
38 if ispy3:
38 if ispy3:
39 def iterbytes(s):
39 def iterbytes(s):
40 for i in range(len(s)):
40 for i in range(len(s)):
41 yield s[i:i + 1]
41 yield s[i:i + 1]
42 else:
42 else:
43 iterbytes = iter
43 iterbytes = iter
44
44
45 def visit(opts, filenames, outfile):
45 def visit(opts, filenames, outfile):
46 """Process filenames in the way specified in opts, writing output to
46 """Process filenames in the way specified in opts, writing output to
47 outfile."""
47 outfile."""
48 for f in sorted(filenames):
48 for f in sorted(filenames):
49 isstdin = f == '-'
49 isstdin = f == '-'
50 if not isstdin and not os.path.lexists(f):
50 if not isstdin and not os.path.lexists(f):
51 outfile.write(b'%s: file not found\n' % f.encode('utf-8'))
51 outfile.write(b'%s: file not found\n' % f.encode('utf-8'))
52 continue
52 continue
53 quiet = opts.quiet and not opts.recurse or isstdin
53 quiet = opts.quiet and not opts.recurse or isstdin
54 isdir = os.path.isdir(f)
54 isdir = os.path.isdir(f)
55 islink = os.path.islink(f)
55 islink = os.path.islink(f)
56 isfile = os.path.isfile(f) and not islink
56 isfile = os.path.isfile(f) and not islink
57 dirfiles = None
57 dirfiles = None
58 content = None
58 content = None
59 facts = []
59 facts = []
60 if isfile:
60 if isfile:
61 if opts.type:
61 if opts.type:
62 facts.append(b'file')
62 facts.append(b'file')
63 if any((opts.hexdump, opts.dump, opts.md5, opts.sha1, opts.sha256)):
63 if any((opts.hexdump, opts.dump, opts.md5, opts.sha1, opts.sha256)):
64 content = open(f, 'rb').read()
64 with open(f, 'rb') as fobj:
65 content = fobj.read()
65 elif islink:
66 elif islink:
66 if opts.type:
67 if opts.type:
67 facts.append(b'link')
68 facts.append(b'link')
68 content = os.readlink(f)
69 content = os.readlink(f)
69 elif isstdin:
70 elif isstdin:
70 content = getattr(sys.stdin, 'buffer', sys.stdin).read()
71 content = getattr(sys.stdin, 'buffer', sys.stdin).read()
71 if opts.size:
72 if opts.size:
72 facts.append(b'size=%d' % len(content))
73 facts.append(b'size=%d' % len(content))
73 elif isdir:
74 elif isdir:
74 if opts.recurse or opts.type:
75 if opts.recurse or opts.type:
75 dirfiles = glob.glob(f + '/*')
76 dirfiles = glob.glob(f + '/*')
76 facts.append(b'directory with %d files' % len(dirfiles))
77 facts.append(b'directory with %d files' % len(dirfiles))
77 elif opts.type:
78 elif opts.type:
78 facts.append(b'type unknown')
79 facts.append(b'type unknown')
79 if not isstdin:
80 if not isstdin:
80 stat = os.lstat(f)
81 stat = os.lstat(f)
81 if opts.size and not isdir:
82 if opts.size and not isdir:
82 facts.append(b'size=%d' % stat.st_size)
83 facts.append(b'size=%d' % stat.st_size)
83 if opts.mode and not islink:
84 if opts.mode and not islink:
84 facts.append(b'mode=%o' % (stat.st_mode & 0o777))
85 facts.append(b'mode=%o' % (stat.st_mode & 0o777))
85 if opts.links:
86 if opts.links:
86 facts.append(b'links=%s' % stat.st_nlink)
87 facts.append(b'links=%s' % stat.st_nlink)
87 if opts.newer:
88 if opts.newer:
88 # mtime might be in whole seconds so newer file might be same
89 # mtime might be in whole seconds so newer file might be same
89 if stat.st_mtime >= os.stat(opts.newer).st_mtime:
90 if stat.st_mtime >= os.stat(opts.newer).st_mtime:
90 facts.append(b'newer than %s' % opts.newer)
91 facts.append(b'newer than %s' % opts.newer)
91 else:
92 else:
92 facts.append(b'older than %s' % opts.newer)
93 facts.append(b'older than %s' % opts.newer)
93 if opts.md5 and content is not None:
94 if opts.md5 and content is not None:
94 h = hashlib.md5(content)
95 h = hashlib.md5(content)
95 facts.append(b'md5=%s' % binascii.hexlify(h.digest())[:opts.bytes])
96 facts.append(b'md5=%s' % binascii.hexlify(h.digest())[:opts.bytes])
96 if opts.sha1 and content is not None:
97 if opts.sha1 and content is not None:
97 h = hashlib.sha1(content)
98 h = hashlib.sha1(content)
98 facts.append(b'sha1=%s' % binascii.hexlify(h.digest())[:opts.bytes])
99 facts.append(b'sha1=%s' % binascii.hexlify(h.digest())[:opts.bytes])
99 if opts.sha256 and content is not None:
100 if opts.sha256 and content is not None:
100 h = hashlib.sha256(content)
101 h = hashlib.sha256(content)
101 facts.append(b'sha256=%s' %
102 facts.append(b'sha256=%s' %
102 binascii.hexlify(h.digest())[:opts.bytes])
103 binascii.hexlify(h.digest())[:opts.bytes])
103 if isstdin:
104 if isstdin:
104 outfile.write(b', '.join(facts) + b'\n')
105 outfile.write(b', '.join(facts) + b'\n')
105 elif facts:
106 elif facts:
106 outfile.write(b'%s: %s\n' % (f.encode('utf-8'), b', '.join(facts)))
107 outfile.write(b'%s: %s\n' % (f.encode('utf-8'), b', '.join(facts)))
107 elif not quiet:
108 elif not quiet:
108 outfile.write(b'%s:\n' % f.encode('utf-8'))
109 outfile.write(b'%s:\n' % f.encode('utf-8'))
109 if content is not None:
110 if content is not None:
110 chunk = content
111 chunk = content
111 if not islink:
112 if not islink:
112 if opts.lines:
113 if opts.lines:
113 if opts.lines >= 0:
114 if opts.lines >= 0:
114 chunk = b''.join(chunk.splitlines(True)[:opts.lines])
115 chunk = b''.join(chunk.splitlines(True)[:opts.lines])
115 else:
116 else:
116 chunk = b''.join(chunk.splitlines(True)[opts.lines:])
117 chunk = b''.join(chunk.splitlines(True)[opts.lines:])
117 if opts.bytes:
118 if opts.bytes:
118 if opts.bytes >= 0:
119 if opts.bytes >= 0:
119 chunk = chunk[:opts.bytes]
120 chunk = chunk[:opts.bytes]
120 else:
121 else:
121 chunk = chunk[opts.bytes:]
122 chunk = chunk[opts.bytes:]
122 if opts.hexdump:
123 if opts.hexdump:
123 for i in range(0, len(chunk), 16):
124 for i in range(0, len(chunk), 16):
124 s = chunk[i:i + 16]
125 s = chunk[i:i + 16]
125 outfile.write(b'%04x: %-47s |%s|\n' %
126 outfile.write(b'%04x: %-47s |%s|\n' %
126 (i, b' '.join(
127 (i, b' '.join(
127 b'%02x' % ord(c) for c in iterbytes(s)),
128 b'%02x' % ord(c) for c in iterbytes(s)),
128 re.sub(b'[^ -~]', b'.', s)))
129 re.sub(b'[^ -~]', b'.', s)))
129 if opts.dump:
130 if opts.dump:
130 if not quiet:
131 if not quiet:
131 outfile.write(b'>>>\n')
132 outfile.write(b'>>>\n')
132 outfile.write(chunk)
133 outfile.write(chunk)
133 if not quiet:
134 if not quiet:
134 if chunk.endswith(b'\n'):
135 if chunk.endswith(b'\n'):
135 outfile.write(b'<<<\n')
136 outfile.write(b'<<<\n')
136 else:
137 else:
137 outfile.write(b'\n<<< no trailing newline\n')
138 outfile.write(b'\n<<< no trailing newline\n')
138 if opts.recurse and dirfiles:
139 if opts.recurse and dirfiles:
139 assert not isstdin
140 assert not isstdin
140 visit(opts, dirfiles, outfile)
141 visit(opts, dirfiles, outfile)
141
142
142 if __name__ == "__main__":
143 if __name__ == "__main__":
143 parser = optparse.OptionParser("%prog [options] [filenames]")
144 parser = optparse.OptionParser("%prog [options] [filenames]")
144 parser.add_option("-t", "--type", action="store_true",
145 parser.add_option("-t", "--type", action="store_true",
145 help="show file type (file or directory)")
146 help="show file type (file or directory)")
146 parser.add_option("-m", "--mode", action="store_true",
147 parser.add_option("-m", "--mode", action="store_true",
147 help="show file mode")
148 help="show file mode")
148 parser.add_option("-l", "--links", action="store_true",
149 parser.add_option("-l", "--links", action="store_true",
149 help="show number of links")
150 help="show number of links")
150 parser.add_option("-s", "--size", action="store_true",
151 parser.add_option("-s", "--size", action="store_true",
151 help="show size of file")
152 help="show size of file")
152 parser.add_option("-n", "--newer", action="store",
153 parser.add_option("-n", "--newer", action="store",
153 help="check if file is newer (or same)")
154 help="check if file is newer (or same)")
154 parser.add_option("-r", "--recurse", action="store_true",
155 parser.add_option("-r", "--recurse", action="store_true",
155 help="recurse into directories")
156 help="recurse into directories")
156 parser.add_option("-S", "--sha1", action="store_true",
157 parser.add_option("-S", "--sha1", action="store_true",
157 help="show sha1 hash of the content")
158 help="show sha1 hash of the content")
158 parser.add_option("", "--sha256", action="store_true",
159 parser.add_option("", "--sha256", action="store_true",
159 help="show sha256 hash of the content")
160 help="show sha256 hash of the content")
160 parser.add_option("-M", "--md5", action="store_true",
161 parser.add_option("-M", "--md5", action="store_true",
161 help="show md5 hash of the content")
162 help="show md5 hash of the content")
162 parser.add_option("-D", "--dump", action="store_true",
163 parser.add_option("-D", "--dump", action="store_true",
163 help="dump file content")
164 help="dump file content")
164 parser.add_option("-H", "--hexdump", action="store_true",
165 parser.add_option("-H", "--hexdump", action="store_true",
165 help="hexdump file content")
166 help="hexdump file content")
166 parser.add_option("-B", "--bytes", type="int",
167 parser.add_option("-B", "--bytes", type="int",
167 help="number of characters to dump")
168 help="number of characters to dump")
168 parser.add_option("-L", "--lines", type="int",
169 parser.add_option("-L", "--lines", type="int",
169 help="number of lines to dump")
170 help="number of lines to dump")
170 parser.add_option("-q", "--quiet", action="store_true",
171 parser.add_option("-q", "--quiet", action="store_true",
171 help="no default output")
172 help="no default output")
172 (opts, filenames) = parser.parse_args(sys.argv[1:])
173 (opts, filenames) = parser.parse_args(sys.argv[1:])
173 if not filenames:
174 if not filenames:
174 filenames = ['-']
175 filenames = ['-']
175
176
176 visit(opts, filenames, getattr(sys.stdout, 'buffer', sys.stdout))
177 visit(opts, filenames, getattr(sys.stdout, 'buffer', sys.stdout))
General Comments 0
You need to be logged in to leave comments. Login now