##// END OF EJS Templates
i18n: use encoding.lower/upper for encoding aware case folding...
FUJIWARA Katsunori -
r15672:2ebe3d0c stable
parent child Browse files
Show More
@@ -1,162 +1,160 b''
1 1 # win32mbcs.py -- MBCS filename support for Mercurial
2 2 #
3 3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
4 4 #
5 5 # Version: 0.3
6 6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
7 7 #
8 8 # This software may be used and distributed according to the terms of the
9 9 # GNU General Public License version 2 or any later version.
10 10 #
11 11
12 12 '''allow the use of MBCS paths with problematic encodings
13 13
14 14 Some MBCS encodings are not good for some path operations (i.e.
15 15 splitting path, case conversion, etc.) with its encoded bytes. We call
16 16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
17 17 This extension can be used to fix the issue with those encodings by
18 18 wrapping some functions to convert to Unicode string before path
19 19 operation.
20 20
21 21 This extension is useful for:
22 22
23 23 - Japanese Windows users using shift_jis encoding.
24 24 - Chinese Windows users using big5 encoding.
25 25 - All users who use a repository with one of problematic encodings on
26 26 case-insensitive file system.
27 27
28 28 This extension is not needed for:
29 29
30 30 - Any user who use only ASCII chars in path.
31 31 - Any user who do not use any of problematic encodings.
32 32
33 33 Note that there are some limitations on using this extension:
34 34
35 35 - You should use single encoding in one repository.
36 36 - If the repository path ends with 0x5c, .hg/hgrc cannot be read.
37 37 - win32mbcs is not compatible with fixutf8 extension.
38 38
39 39 By default, win32mbcs uses encoding.encoding decided by Mercurial.
40 40 You can specify the encoding by config option::
41 41
42 42 [win32mbcs]
43 43 encoding = sjis
44 44
45 45 It is useful for the users who want to commit with UTF-8 log message.
46 46 '''
47 47
48 48 import os, sys
49 49 from mercurial.i18n import _
50 50 from mercurial import util, encoding
51 51
52 52 _encoding = None # see extsetup
53 53
54 54 def decode(arg):
55 55 if isinstance(arg, str):
56 56 uarg = arg.decode(_encoding)
57 57 if arg == uarg.encode(_encoding):
58 58 return uarg
59 59 raise UnicodeError("Not local encoding")
60 60 elif isinstance(arg, tuple):
61 61 return tuple(map(decode, arg))
62 62 elif isinstance(arg, list):
63 63 return map(decode, arg)
64 64 elif isinstance(arg, dict):
65 65 for k, v in arg.items():
66 66 arg[k] = decode(v)
67 67 return arg
68 68
69 69 def encode(arg):
70 70 if isinstance(arg, unicode):
71 71 return arg.encode(_encoding)
72 72 elif isinstance(arg, tuple):
73 73 return tuple(map(encode, arg))
74 74 elif isinstance(arg, list):
75 75 return map(encode, arg)
76 76 elif isinstance(arg, dict):
77 77 for k, v in arg.items():
78 78 arg[k] = encode(v)
79 79 return arg
80 80
81 81 def appendsep(s):
82 82 # ensure the path ends with os.sep, appending it if necessary.
83 83 try:
84 84 us = decode(s)
85 85 except UnicodeError:
86 86 us = s
87 87 if us and us[-1] not in ':/\\':
88 88 s += os.sep
89 89 return s
90 90
91 91 def wrapper(func, args, kwds):
92 92 # check argument is unicode, then call original
93 93 for arg in args:
94 94 if isinstance(arg, unicode):
95 95 return func(*args, **kwds)
96 96
97 97 try:
98 98 # convert arguments to unicode, call func, then convert back
99 99 return encode(func(*decode(args), **decode(kwds)))
100 100 except UnicodeError:
101 101 raise util.Abort(_("[win32mbcs] filename conversion failed with"
102 102 " %s encoding\n") % (_encoding))
103 103
104 104 def wrapperforlistdir(func, args, kwds):
105 105 # Ensure 'path' argument ends with os.sep to avoids
106 106 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
107 107 if args:
108 108 args = list(args)
109 109 args[0] = appendsep(args[0])
110 110 if 'path' in kwds:
111 111 kwds['path'] = appendsep(kwds['path'])
112 112 return func(*args, **kwds)
113 113
114 114 def wrapname(name, wrapper):
115 115 module, name = name.rsplit('.', 1)
116 116 module = sys.modules[module]
117 117 func = getattr(module, name)
118 118 def f(*args, **kwds):
119 119 return wrapper(func, args, kwds)
120 120 try:
121 121 f.__name__ = func.__name__ # fail with python23
122 122 except Exception:
123 123 pass
124 124 setattr(module, name, f)
125 125
126 126 # List of functions to be wrapped.
127 127 # NOTE: os.path.dirname() and os.path.basename() are safe because
128 128 # they use result of os.path.split()
129 129 funcs = '''os.path.join os.path.split os.path.splitext
130 130 os.path.splitunc os.path.normpath os.makedirs
131 mercurial.windows.normcase
132 mercurial.util.normcase
133 131 mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
134 132 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
135 133 mercurial.util.checkwinfilename mercurial.util.checkosfilename'''
136 134
137 135 # codec and alias names of sjis and big5 to be faked.
138 136 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
139 137 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
140 138 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
141 139 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
142 140
143 141 def extsetup(ui):
144 142 # TODO: decide use of config section for this extension
145 143 if not os.path.supports_unicode_filenames:
146 144 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
147 145 return
148 146 # determine encoding for filename
149 147 global _encoding
150 148 _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
151 149 # fake is only for relevant environment.
152 150 if _encoding.lower() in problematic_encodings.split():
153 151 for f in funcs.split():
154 152 wrapname(f, wrapper)
155 153 wrapname("mercurial.osutil.listdir", wrapperforlistdir)
156 154 # Check sys.args manually instead of using ui.debug() because
157 155 # command line options is not yet applied when
158 156 # extensions.loadall() is called.
159 157 if '--debug' in sys.argv:
160 158 ui.write("[win32mbcs] activated with encoding: %s\n"
161 159 % _encoding)
162 160
@@ -1,173 +1,192 b''
1 1 # encoding.py - character transcoding support for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import error
9 9 import unicodedata, locale, os
10 10
11 11 def _getpreferredencoding():
12 12 '''
13 13 On darwin, getpreferredencoding ignores the locale environment and
14 14 always returns mac-roman. http://bugs.python.org/issue6202 fixes this
15 15 for Python 2.7 and up. This is the same corrected code for earlier
16 16 Python versions.
17 17
18 18 However, we can't use a version check for this method, as some distributions
19 19 patch Python to fix this. Instead, we use it as a 'fixer' for the mac-roman
20 20 encoding, as it is unlikely that this encoding is the actually expected.
21 21 '''
22 22 try:
23 23 locale.CODESET
24 24 except AttributeError:
25 25 # Fall back to parsing environment variables :-(
26 26 return locale.getdefaultlocale()[1]
27 27
28 28 oldloc = locale.setlocale(locale.LC_CTYPE)
29 29 locale.setlocale(locale.LC_CTYPE, "")
30 30 result = locale.nl_langinfo(locale.CODESET)
31 31 locale.setlocale(locale.LC_CTYPE, oldloc)
32 32
33 33 return result
34 34
35 35 _encodingfixers = {
36 36 '646': lambda: 'ascii',
37 37 'ANSI_X3.4-1968': lambda: 'ascii',
38 38 'mac-roman': _getpreferredencoding
39 39 }
40 40
41 41 try:
42 42 encoding = os.environ.get("HGENCODING")
43 43 if not encoding:
44 44 encoding = locale.getpreferredencoding() or 'ascii'
45 45 encoding = _encodingfixers.get(encoding, lambda: encoding)()
46 46 except locale.Error:
47 47 encoding = 'ascii'
48 48 encodingmode = os.environ.get("HGENCODINGMODE", "strict")
49 49 fallbackencoding = 'ISO-8859-1'
50 50
51 51 class localstr(str):
52 52 '''This class allows strings that are unmodified to be
53 53 round-tripped to the local encoding and back'''
54 54 def __new__(cls, u, l):
55 55 s = str.__new__(cls, l)
56 56 s._utf8 = u
57 57 return s
58 58 def __hash__(self):
59 59 return hash(self._utf8) # avoid collisions in local string space
60 60
61 61 def tolocal(s):
62 62 """
63 63 Convert a string from internal UTF-8 to local encoding
64 64
65 65 All internal strings should be UTF-8 but some repos before the
66 66 implementation of locale support may contain latin1 or possibly
67 67 other character sets. We attempt to decode everything strictly
68 68 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
69 69 replace unknown characters.
70 70
71 71 The localstr class is used to cache the known UTF-8 encoding of
72 72 strings next to their local representation to allow lossless
73 73 round-trip conversion back to UTF-8.
74 74
75 75 >>> u = 'foo: \\xc3\\xa4' # utf-8
76 76 >>> l = tolocal(u)
77 77 >>> l
78 78 'foo: ?'
79 79 >>> fromlocal(l)
80 80 'foo: \\xc3\\xa4'
81 81 >>> u2 = 'foo: \\xc3\\xa1'
82 82 >>> d = { l: 1, tolocal(u2): 2 }
83 83 >>> d # no collision
84 84 {'foo: ?': 1, 'foo: ?': 2}
85 85 >>> 'foo: ?' in d
86 86 False
87 87 >>> l1 = 'foo: \\xe4' # historical latin1 fallback
88 88 >>> l = tolocal(l1)
89 89 >>> l
90 90 'foo: ?'
91 91 >>> fromlocal(l) # magically in utf-8
92 92 'foo: \\xc3\\xa4'
93 93 """
94 94
95 95 for e in ('UTF-8', fallbackencoding):
96 96 try:
97 97 u = s.decode(e) # attempt strict decoding
98 98 r = u.encode(encoding, "replace")
99 99 if u == r.decode(encoding):
100 100 # r is a safe, non-lossy encoding of s
101 101 return r
102 102 elif e == 'UTF-8':
103 103 return localstr(s, r)
104 104 else:
105 105 return localstr(u.encode('UTF-8'), r)
106 106
107 107 except LookupError, k:
108 108 raise error.Abort("%s, please check your locale settings" % k)
109 109 except UnicodeDecodeError:
110 110 pass
111 111 u = s.decode("utf-8", "replace") # last ditch
112 112 return u.encode(encoding, "replace") # can't round-trip
113 113
114 114 def fromlocal(s):
115 115 """
116 116 Convert a string from the local character encoding to UTF-8
117 117
118 118 We attempt to decode strings using the encoding mode set by
119 119 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
120 120 characters will cause an error message. Other modes include
121 121 'replace', which replaces unknown characters with a special
122 122 Unicode character, and 'ignore', which drops the character.
123 123 """
124 124
125 125 # can we do a lossless round-trip?
126 126 if isinstance(s, localstr):
127 127 return s._utf8
128 128
129 129 try:
130 130 return s.decode(encoding, encodingmode).encode("utf-8")
131 131 except UnicodeDecodeError, inst:
132 132 sub = s[max(0, inst.start - 10):inst.start + 10]
133 133 raise error.Abort("decoding near '%s': %s!" % (sub, inst))
134 134 except LookupError, k:
135 135 raise error.Abort("%s, please check your locale settings" % k)
136 136
137 137 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
138 138 wide = (os.environ.get("HGENCODINGAMBIGUOUS", "narrow") == "wide"
139 139 and "WFA" or "WF")
140 140
141 141 def colwidth(s):
142 142 "Find the column width of a string for display in the local encoding"
143 143 return ucolwidth(s.decode(encoding, 'replace'))
144 144
145 145 def ucolwidth(d):
146 146 "Find the column width of a Unicode string for display"
147 147 eaw = getattr(unicodedata, 'east_asian_width', None)
148 148 if eaw is not None:
149 149 return sum([eaw(c) in wide and 2 or 1 for c in d])
150 150 return len(d)
151 151
152 152 def getcols(s, start, c):
153 153 '''Use colwidth to find a c-column substring of s starting at byte
154 154 index start'''
155 155 for x in xrange(start + c, len(s)):
156 156 t = s[start:x]
157 157 if colwidth(t) == c:
158 158 return t
159 159
160 160 def lower(s):
161 161 "best-effort encoding-aware case-folding of local string s"
162 162 try:
163 163 if isinstance(s, localstr):
164 164 u = s._utf8.decode("utf-8")
165 165 else:
166 166 u = s.decode(encoding, encodingmode)
167 167
168 168 lu = u.lower()
169 169 if u == lu:
170 170 return s # preserve localstring
171 171 return lu.encode(encoding)
172 172 except UnicodeError:
173 173 return s.lower() # we don't know how to fold this except in ASCII
174 except LookupError, k:
175 raise error.Abort(k, hint="please check your locale settings")
176
177 def upper(s):
178 "best-effort encoding-aware case-folding of local string s"
179 try:
180 if isinstance(s, localstr):
181 u = s._utf8.decode("utf-8")
182 else:
183 u = s.decode(encoding, encodingmode)
184
185 uu = u.upper()
186 if u == uu:
187 return s # preserve localstring
188 return uu.encode(encoding)
189 except UnicodeError:
190 return s.upper() # we don't know how to fold this except in ASCII
191 except LookupError, k:
192 raise error.Abort(k, hint="please check your locale settings")
@@ -1,416 +1,419 b''
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, errno, stat, getpass, pwd, grp, tempfile, unicodedata
10 10
11 11 posixfile = open
12 12 nulldev = '/dev/null'
13 13 normpath = os.path.normpath
14 14 samestat = os.path.samestat
15 15 oslink = os.link
16 16 unlink = os.unlink
17 17 rename = os.rename
18 18 expandglobs = False
19 19
20 20 umask = os.umask(0)
21 21 os.umask(umask)
22 22
23 23 def openhardlinks():
24 24 '''return true if it is safe to hold open file handles to hardlinks'''
25 25 return True
26 26
27 27 def nlinks(name):
28 28 '''return number of hardlinks for the given file'''
29 29 return os.lstat(name).st_nlink
30 30
31 31 def parsepatchoutput(output_line):
32 32 """parses the output produced by patch and returns the filename"""
33 33 pf = output_line[14:]
34 34 if os.sys.platform == 'OpenVMS':
35 35 if pf[0] == '`':
36 36 pf = pf[1:-1] # Remove the quotes
37 37 else:
38 38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
39 39 pf = pf[1:-1] # Remove the quotes
40 40 return pf
41 41
42 42 def sshargs(sshcmd, host, user, port):
43 43 '''Build argument list for ssh'''
44 44 args = user and ("%s@%s" % (user, host)) or host
45 45 return port and ("%s -p %s" % (args, port)) or args
46 46
47 47 def isexec(f):
48 48 """check whether a file is executable"""
49 49 return (os.lstat(f).st_mode & 0100 != 0)
50 50
51 51 def setflags(f, l, x):
52 52 s = os.lstat(f).st_mode
53 53 if l:
54 54 if not stat.S_ISLNK(s):
55 55 # switch file to link
56 56 fp = open(f)
57 57 data = fp.read()
58 58 fp.close()
59 59 os.unlink(f)
60 60 try:
61 61 os.symlink(data, f)
62 62 except OSError:
63 63 # failed to make a link, rewrite file
64 64 fp = open(f, "w")
65 65 fp.write(data)
66 66 fp.close()
67 67 # no chmod needed at this point
68 68 return
69 69 if stat.S_ISLNK(s):
70 70 # switch link to file
71 71 data = os.readlink(f)
72 72 os.unlink(f)
73 73 fp = open(f, "w")
74 74 fp.write(data)
75 75 fp.close()
76 76 s = 0666 & ~umask # avoid restatting for chmod
77 77
78 78 sx = s & 0100
79 79 if x and not sx:
80 80 # Turn on +x for every +r bit when making a file executable
81 81 # and obey umask.
82 82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
83 83 elif not x and sx:
84 84 # Turn off all +x bits
85 85 os.chmod(f, s & 0666)
86 86
87 87 def copymode(src, dst, mode=None):
88 88 '''Copy the file mode from the file at path src to dst.
89 89 If src doesn't exist, we're using mode instead. If mode is None, we're
90 90 using umask.'''
91 91 try:
92 92 st_mode = os.lstat(src).st_mode & 0777
93 93 except OSError, inst:
94 94 if inst.errno != errno.ENOENT:
95 95 raise
96 96 st_mode = mode
97 97 if st_mode is None:
98 98 st_mode = ~umask
99 99 st_mode &= 0666
100 100 os.chmod(dst, st_mode)
101 101
102 102 def checkexec(path):
103 103 """
104 104 Check whether the given path is on a filesystem with UNIX-like exec flags
105 105
106 106 Requires a directory (like /foo/.hg)
107 107 """
108 108
109 109 # VFAT on some Linux versions can flip mode but it doesn't persist
110 110 # a FS remount. Frequently we can detect it if files are created
111 111 # with exec bit on.
112 112
113 113 try:
114 114 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
115 115 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
116 116 try:
117 117 os.close(fh)
118 118 m = os.stat(fn).st_mode & 0777
119 119 new_file_has_exec = m & EXECFLAGS
120 120 os.chmod(fn, m ^ EXECFLAGS)
121 121 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
122 122 finally:
123 123 os.unlink(fn)
124 124 except (IOError, OSError):
125 125 # we don't care, the user probably won't be able to commit anyway
126 126 return False
127 127 return not (new_file_has_exec or exec_flags_cannot_flip)
128 128
129 129 def checklink(path):
130 130 """check whether the given path is on a symlink-capable filesystem"""
131 131 # mktemp is not racy because symlink creation will fail if the
132 132 # file already exists
133 133 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
134 134 try:
135 135 os.symlink(".", name)
136 136 os.unlink(name)
137 137 return True
138 138 except (OSError, AttributeError):
139 139 return False
140 140
141 141 def checkosfilename(path):
142 142 '''Check that the base-relative path is a valid filename on this platform.
143 143 Returns None if the path is ok, or a UI string describing the problem.'''
144 144 pass # on posix platforms, every path is ok
145 145
146 146 def setbinary(fd):
147 147 pass
148 148
149 149 def pconvert(path):
150 150 return path
151 151
152 152 def localpath(path):
153 153 return path
154 154
155 155 def samefile(fpath1, fpath2):
156 156 """Returns whether path1 and path2 refer to the same file. This is only
157 157 guaranteed to work for files, not directories."""
158 158 return os.path.samefile(fpath1, fpath2)
159 159
160 160 def samedevice(fpath1, fpath2):
161 161 """Returns whether fpath1 and fpath2 are on the same device. This is only
162 162 guaranteed to work for files, not directories."""
163 163 st1 = os.lstat(fpath1)
164 164 st2 = os.lstat(fpath2)
165 165 return st1.st_dev == st2.st_dev
166 166
167 encodinglower = None
168 encodingupper = None
169
167 170 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
168 171 def normcase(path):
169 172 return path.lower()
170 173
171 174 if sys.platform == 'darwin':
172 175 import fcntl # only needed on darwin, missing on jython
173 176
174 177 def normcase(path):
175 178 try:
176 179 u = path.decode('utf-8')
177 180 except UnicodeDecodeError:
178 181 # percent-encode any characters that don't round-trip
179 182 p2 = path.decode('utf-8', 'ignore').encode('utf-8')
180 183 s = ""
181 184 pos = 0
182 185 for c in path:
183 186 if p2[pos:pos + 1] == c:
184 187 s += c
185 188 pos += 1
186 189 else:
187 190 s += "%%%02X" % ord(c)
188 191 u = s.decode('utf-8')
189 192
190 193 # Decompose then lowercase (HFS+ technote specifies lower)
191 194 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
192 195
193 196 def realpath(path):
194 197 '''
195 198 Returns the true, canonical file system path equivalent to the given
196 199 path.
197 200
198 201 Equivalent means, in this case, resulting in the same, unique
199 202 file system link to the path. Every file system entry, whether a file,
200 203 directory, hard link or symbolic link or special, will have a single
201 204 path preferred by the system, but may allow multiple, differing path
202 205 lookups to point to it.
203 206
204 207 Most regular UNIX file systems only allow a file system entry to be
205 208 looked up by its distinct path. Obviously, this does not apply to case
206 209 insensitive file systems, whether case preserving or not. The most
207 210 complex issue to deal with is file systems transparently reencoding the
208 211 path, such as the non-standard Unicode normalisation required for HFS+
209 212 and HFSX.
210 213 '''
211 214 # Constants copied from /usr/include/sys/fcntl.h
212 215 F_GETPATH = 50
213 216 O_SYMLINK = 0x200000
214 217
215 218 try:
216 219 fd = os.open(path, O_SYMLINK)
217 220 except OSError, err:
218 221 if err.errno == errno.ENOENT:
219 222 return path
220 223 raise
221 224
222 225 try:
223 226 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
224 227 finally:
225 228 os.close(fd)
226 229 elif sys.version_info < (2, 4, 2, 'final'):
227 230 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
228 231 # didn't resolve symlinks that were the first component of the path.)
229 232 def realpath(path):
230 233 if os.path.isabs(path):
231 234 return os.path.realpath(path)
232 235 else:
233 236 return os.path.realpath('./' + path)
234 237 else:
235 238 # Fallback to the likely inadequate Python builtin function.
236 239 realpath = os.path.realpath
237 240
238 241 def shellquote(s):
239 242 if os.sys.platform == 'OpenVMS':
240 243 return '"%s"' % s
241 244 else:
242 245 return "'%s'" % s.replace("'", "'\\''")
243 246
244 247 def quotecommand(cmd):
245 248 return cmd
246 249
247 250 def popen(command, mode='r'):
248 251 return os.popen(command, mode)
249 252
250 253 def testpid(pid):
251 254 '''return False if pid dead, True if running or not sure'''
252 255 if os.sys.platform == 'OpenVMS':
253 256 return True
254 257 try:
255 258 os.kill(pid, 0)
256 259 return True
257 260 except OSError, inst:
258 261 return inst.errno != errno.ESRCH
259 262
260 263 def explainexit(code):
261 264 """return a 2-tuple (desc, code) describing a subprocess status
262 265 (codes from kill are negative - not os.system/wait encoding)"""
263 266 if code >= 0:
264 267 return _("exited with status %d") % code, code
265 268 return _("killed by signal %d") % -code, -code
266 269
267 270 def isowner(st):
268 271 """Return True if the stat object st is from the current user."""
269 272 return st.st_uid == os.getuid()
270 273
271 274 def findexe(command):
272 275 '''Find executable for command searching like which does.
273 276 If command is a basename then PATH is searched for command.
274 277 PATH isn't searched if command is an absolute or relative path.
275 278 If command isn't found None is returned.'''
276 279 if sys.platform == 'OpenVMS':
277 280 return command
278 281
279 282 def findexisting(executable):
280 283 'Will return executable if existing file'
281 284 if os.path.isfile(executable) and os.access(executable, os.X_OK):
282 285 return executable
283 286 return None
284 287
285 288 if os.sep in command:
286 289 return findexisting(command)
287 290
288 291 for path in os.environ.get('PATH', '').split(os.pathsep):
289 292 executable = findexisting(os.path.join(path, command))
290 293 if executable is not None:
291 294 return executable
292 295 return None
293 296
294 297 def setsignalhandler():
295 298 pass
296 299
297 300 def statfiles(files):
298 301 'Stat each file in files and yield stat or None if file does not exist.'
299 302 lstat = os.lstat
300 303 for nf in files:
301 304 try:
302 305 st = lstat(nf)
303 306 except OSError, err:
304 307 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
305 308 raise
306 309 st = None
307 310 yield st
308 311
309 312 def getuser():
310 313 '''return name of current user'''
311 314 return getpass.getuser()
312 315
313 316 def username(uid=None):
314 317 """Return the name of the user with the given uid.
315 318
316 319 If uid is None, return the name of the current user."""
317 320
318 321 if uid is None:
319 322 uid = os.getuid()
320 323 try:
321 324 return pwd.getpwuid(uid)[0]
322 325 except KeyError:
323 326 return str(uid)
324 327
325 328 def groupname(gid=None):
326 329 """Return the name of the group with the given gid.
327 330
328 331 If gid is None, return the name of the current group."""
329 332
330 333 if gid is None:
331 334 gid = os.getgid()
332 335 try:
333 336 return grp.getgrgid(gid)[0]
334 337 except KeyError:
335 338 return str(gid)
336 339
337 340 def groupmembers(name):
338 341 """Return the list of members of the group with the given
339 342 name, KeyError if the group does not exist.
340 343 """
341 344 return list(grp.getgrnam(name).gr_mem)
342 345
343 346 def spawndetached(args):
344 347 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
345 348 args[0], args)
346 349
347 350 def gethgcmd():
348 351 return sys.argv[:1]
349 352
350 353 def termwidth():
351 354 try:
352 355 import termios, array, fcntl
353 356 for dev in (sys.stderr, sys.stdout, sys.stdin):
354 357 try:
355 358 try:
356 359 fd = dev.fileno()
357 360 except AttributeError:
358 361 continue
359 362 if not os.isatty(fd):
360 363 continue
361 364 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
362 365 width = array.array('h', arri)[1]
363 366 if width > 0:
364 367 return width
365 368 except ValueError:
366 369 pass
367 370 except IOError, e:
368 371 if e[0] == errno.EINVAL:
369 372 pass
370 373 else:
371 374 raise
372 375 except ImportError:
373 376 pass
374 377 return 80
375 378
376 379 def makedir(path, notindexed):
377 380 os.mkdir(path)
378 381
379 382 def unlinkpath(f):
380 383 """unlink and remove the directory if it is empty"""
381 384 os.unlink(f)
382 385 # try removing directories that might now be empty
383 386 try:
384 387 os.removedirs(os.path.dirname(f))
385 388 except OSError:
386 389 pass
387 390
388 391 def lookupreg(key, name=None, scope=None):
389 392 return None
390 393
391 394 def hidewindow():
392 395 """Hide current shell window.
393 396
394 397 Used to hide the window opened when starting asynchronous
395 398 child process under Windows, unneeded on other systems.
396 399 """
397 400 pass
398 401
399 402 class cachestat(object):
400 403 def __init__(self, path):
401 404 self.stat = os.stat(path)
402 405
403 406 def cacheable(self):
404 407 return bool(self.stat.st_ino)
405 408
406 409 def __eq__(self, other):
407 410 try:
408 411 return self.stat == other.stat
409 412 except AttributeError:
410 413 return False
411 414
412 415 def __ne__(self, other):
413 416 return not self == other
414 417
415 418 def executablepath():
416 419 return None # available on Windows only
@@ -1,1750 +1,1753 b''
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, datetime, calendar, textwrap, signal
20 20 import imp, socket, urllib
21 21
22 22 if os.name == 'nt':
23 23 import windows as platform
24 24 else:
25 25 import posix as platform
26 26
27 platform.encodinglower = encoding.lower
28 platform.encodingupper = encoding.upper
29
27 30 cachestat = platform.cachestat
28 31 checkexec = platform.checkexec
29 32 checklink = platform.checklink
30 33 copymode = platform.copymode
31 34 executablepath = platform.executablepath
32 35 expandglobs = platform.expandglobs
33 36 explainexit = platform.explainexit
34 37 findexe = platform.findexe
35 38 gethgcmd = platform.gethgcmd
36 39 getuser = platform.getuser
37 40 groupmembers = platform.groupmembers
38 41 groupname = platform.groupname
39 42 hidewindow = platform.hidewindow
40 43 isexec = platform.isexec
41 44 isowner = platform.isowner
42 45 localpath = platform.localpath
43 46 lookupreg = platform.lookupreg
44 47 makedir = platform.makedir
45 48 nlinks = platform.nlinks
46 49 normpath = platform.normpath
47 50 normcase = platform.normcase
48 51 nulldev = platform.nulldev
49 52 openhardlinks = platform.openhardlinks
50 53 oslink = platform.oslink
51 54 parsepatchoutput = platform.parsepatchoutput
52 55 pconvert = platform.pconvert
53 56 popen = platform.popen
54 57 posixfile = platform.posixfile
55 58 quotecommand = platform.quotecommand
56 59 realpath = platform.realpath
57 60 rename = platform.rename
58 61 samedevice = platform.samedevice
59 62 samefile = platform.samefile
60 63 samestat = platform.samestat
61 64 setbinary = platform.setbinary
62 65 setflags = platform.setflags
63 66 setsignalhandler = platform.setsignalhandler
64 67 shellquote = platform.shellquote
65 68 spawndetached = platform.spawndetached
66 69 sshargs = platform.sshargs
67 70 statfiles = platform.statfiles
68 71 termwidth = platform.termwidth
69 72 testpid = platform.testpid
70 73 umask = platform.umask
71 74 unlink = platform.unlink
72 75 unlinkpath = platform.unlinkpath
73 76 username = platform.username
74 77
75 78 # Python compatibility
76 79
77 80 def sha1(s=''):
78 81 '''
79 82 Low-overhead wrapper around Python's SHA support
80 83
81 84 >>> f = _fastsha1
82 85 >>> a = sha1()
83 86 >>> a = f()
84 87 >>> a.hexdigest()
85 88 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
86 89 '''
87 90
88 91 return _fastsha1(s)
89 92
90 93 _notset = object()
91 94 def safehasattr(thing, attr):
92 95 return getattr(thing, attr, _notset) is not _notset
93 96
94 97 def _fastsha1(s=''):
95 98 # This function will import sha1 from hashlib or sha (whichever is
96 99 # available) and overwrite itself with it on the first call.
97 100 # Subsequent calls will go directly to the imported function.
98 101 if sys.version_info >= (2, 5):
99 102 from hashlib import sha1 as _sha1
100 103 else:
101 104 from sha import sha as _sha1
102 105 global _fastsha1, sha1
103 106 _fastsha1 = sha1 = _sha1
104 107 return _sha1(s)
105 108
106 109 import __builtin__
107 110
108 111 if sys.version_info[0] < 3:
109 112 def fakebuffer(sliceable, offset=0):
110 113 return sliceable[offset:]
111 114 else:
112 115 def fakebuffer(sliceable, offset=0):
113 116 return memoryview(sliceable)[offset:]
114 117 try:
115 118 buffer
116 119 except NameError:
117 120 __builtin__.buffer = fakebuffer
118 121
119 122 import subprocess
120 123 closefds = os.name == 'posix'
121 124
122 125 def popen2(cmd, env=None, newlines=False):
123 126 # Setting bufsize to -1 lets the system decide the buffer size.
124 127 # The default for bufsize is 0, meaning unbuffered. This leads to
125 128 # poor performance on Mac OS X: http://bugs.python.org/issue4194
126 129 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
127 130 close_fds=closefds,
128 131 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
129 132 universal_newlines=newlines,
130 133 env=env)
131 134 return p.stdin, p.stdout
132 135
133 136 def popen3(cmd, env=None, newlines=False):
134 137 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
135 138 close_fds=closefds,
136 139 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
137 140 stderr=subprocess.PIPE,
138 141 universal_newlines=newlines,
139 142 env=env)
140 143 return p.stdin, p.stdout, p.stderr
141 144
142 145 def version():
143 146 """Return version information if available."""
144 147 try:
145 148 import __version__
146 149 return __version__.version
147 150 except ImportError:
148 151 return 'unknown'
149 152
150 153 # used by parsedate
151 154 defaultdateformats = (
152 155 '%Y-%m-%d %H:%M:%S',
153 156 '%Y-%m-%d %I:%M:%S%p',
154 157 '%Y-%m-%d %H:%M',
155 158 '%Y-%m-%d %I:%M%p',
156 159 '%Y-%m-%d',
157 160 '%m-%d',
158 161 '%m/%d',
159 162 '%m/%d/%y',
160 163 '%m/%d/%Y',
161 164 '%a %b %d %H:%M:%S %Y',
162 165 '%a %b %d %I:%M:%S%p %Y',
163 166 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
164 167 '%b %d %H:%M:%S %Y',
165 168 '%b %d %I:%M:%S%p %Y',
166 169 '%b %d %H:%M:%S',
167 170 '%b %d %I:%M:%S%p',
168 171 '%b %d %H:%M',
169 172 '%b %d %I:%M%p',
170 173 '%b %d %Y',
171 174 '%b %d',
172 175 '%H:%M:%S',
173 176 '%I:%M:%S%p',
174 177 '%H:%M',
175 178 '%I:%M%p',
176 179 )
177 180
178 181 extendeddateformats = defaultdateformats + (
179 182 "%Y",
180 183 "%Y-%m",
181 184 "%b",
182 185 "%b %Y",
183 186 )
184 187
185 188 def cachefunc(func):
186 189 '''cache the result of function calls'''
187 190 # XXX doesn't handle keywords args
188 191 cache = {}
189 192 if func.func_code.co_argcount == 1:
190 193 # we gain a small amount of time because
191 194 # we don't need to pack/unpack the list
192 195 def f(arg):
193 196 if arg not in cache:
194 197 cache[arg] = func(arg)
195 198 return cache[arg]
196 199 else:
197 200 def f(*args):
198 201 if args not in cache:
199 202 cache[args] = func(*args)
200 203 return cache[args]
201 204
202 205 return f
203 206
204 207 def lrucachefunc(func):
205 208 '''cache most recent results of function calls'''
206 209 cache = {}
207 210 order = []
208 211 if func.func_code.co_argcount == 1:
209 212 def f(arg):
210 213 if arg not in cache:
211 214 if len(cache) > 20:
212 215 del cache[order.pop(0)]
213 216 cache[arg] = func(arg)
214 217 else:
215 218 order.remove(arg)
216 219 order.append(arg)
217 220 return cache[arg]
218 221 else:
219 222 def f(*args):
220 223 if args not in cache:
221 224 if len(cache) > 20:
222 225 del cache[order.pop(0)]
223 226 cache[args] = func(*args)
224 227 else:
225 228 order.remove(args)
226 229 order.append(args)
227 230 return cache[args]
228 231
229 232 return f
230 233
231 234 class propertycache(object):
232 235 def __init__(self, func):
233 236 self.func = func
234 237 self.name = func.__name__
235 238 def __get__(self, obj, type=None):
236 239 result = self.func(obj)
237 240 setattr(obj, self.name, result)
238 241 return result
239 242
240 243 def pipefilter(s, cmd):
241 244 '''filter string S through command CMD, returning its output'''
242 245 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
243 246 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
244 247 pout, perr = p.communicate(s)
245 248 return pout
246 249
247 250 def tempfilter(s, cmd):
248 251 '''filter string S through a pair of temporary files with CMD.
249 252 CMD is used as a template to create the real command to be run,
250 253 with the strings INFILE and OUTFILE replaced by the real names of
251 254 the temporary files generated.'''
252 255 inname, outname = None, None
253 256 try:
254 257 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
255 258 fp = os.fdopen(infd, 'wb')
256 259 fp.write(s)
257 260 fp.close()
258 261 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
259 262 os.close(outfd)
260 263 cmd = cmd.replace('INFILE', inname)
261 264 cmd = cmd.replace('OUTFILE', outname)
262 265 code = os.system(cmd)
263 266 if sys.platform == 'OpenVMS' and code & 1:
264 267 code = 0
265 268 if code:
266 269 raise Abort(_("command '%s' failed: %s") %
267 270 (cmd, explainexit(code)))
268 271 fp = open(outname, 'rb')
269 272 r = fp.read()
270 273 fp.close()
271 274 return r
272 275 finally:
273 276 try:
274 277 if inname:
275 278 os.unlink(inname)
276 279 except OSError:
277 280 pass
278 281 try:
279 282 if outname:
280 283 os.unlink(outname)
281 284 except OSError:
282 285 pass
283 286
284 287 filtertable = {
285 288 'tempfile:': tempfilter,
286 289 'pipe:': pipefilter,
287 290 }
288 291
289 292 def filter(s, cmd):
290 293 "filter a string through a command that transforms its input to its output"
291 294 for name, fn in filtertable.iteritems():
292 295 if cmd.startswith(name):
293 296 return fn(s, cmd[len(name):].lstrip())
294 297 return pipefilter(s, cmd)
295 298
296 299 def binary(s):
297 300 """return true if a string is binary data"""
298 301 return bool(s and '\0' in s)
299 302
300 303 def increasingchunks(source, min=1024, max=65536):
301 304 '''return no less than min bytes per chunk while data remains,
302 305 doubling min after each chunk until it reaches max'''
303 306 def log2(x):
304 307 if not x:
305 308 return 0
306 309 i = 0
307 310 while x:
308 311 x >>= 1
309 312 i += 1
310 313 return i - 1
311 314
312 315 buf = []
313 316 blen = 0
314 317 for chunk in source:
315 318 buf.append(chunk)
316 319 blen += len(chunk)
317 320 if blen >= min:
318 321 if min < max:
319 322 min = min << 1
320 323 nmin = 1 << log2(blen)
321 324 if nmin > min:
322 325 min = nmin
323 326 if min > max:
324 327 min = max
325 328 yield ''.join(buf)
326 329 blen = 0
327 330 buf = []
328 331 if buf:
329 332 yield ''.join(buf)
330 333
331 334 Abort = error.Abort
332 335
333 336 def always(fn):
334 337 return True
335 338
336 339 def never(fn):
337 340 return False
338 341
339 342 def pathto(root, n1, n2):
340 343 '''return the relative path from one place to another.
341 344 root should use os.sep to separate directories
342 345 n1 should use os.sep to separate directories
343 346 n2 should use "/" to separate directories
344 347 returns an os.sep-separated path.
345 348
346 349 If n1 is a relative path, it's assumed it's
347 350 relative to root.
348 351 n2 should always be relative to root.
349 352 '''
350 353 if not n1:
351 354 return localpath(n2)
352 355 if os.path.isabs(n1):
353 356 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
354 357 return os.path.join(root, localpath(n2))
355 358 n2 = '/'.join((pconvert(root), n2))
356 359 a, b = splitpath(n1), n2.split('/')
357 360 a.reverse()
358 361 b.reverse()
359 362 while a and b and a[-1] == b[-1]:
360 363 a.pop()
361 364 b.pop()
362 365 b.reverse()
363 366 return os.sep.join((['..'] * len(a)) + b) or '.'
364 367
365 368 _hgexecutable = None
366 369
367 370 def mainfrozen():
368 371 """return True if we are a frozen executable.
369 372
370 373 The code supports py2exe (most common, Windows only) and tools/freeze
371 374 (portable, not much used).
372 375 """
373 376 return (safehasattr(sys, "frozen") or # new py2exe
374 377 safehasattr(sys, "importers") or # old py2exe
375 378 imp.is_frozen("__main__")) # tools/freeze
376 379
377 380 def hgexecutable():
378 381 """return location of the 'hg' executable.
379 382
380 383 Defaults to $HG or 'hg' in the search path.
381 384 """
382 385 if _hgexecutable is None:
383 386 hg = os.environ.get('HG')
384 387 mainmod = sys.modules['__main__']
385 388 if hg:
386 389 _sethgexecutable(hg)
387 390 elif mainfrozen():
388 391 _sethgexecutable(sys.executable)
389 392 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
390 393 _sethgexecutable(mainmod.__file__)
391 394 else:
392 395 exe = findexe('hg') or os.path.basename(sys.argv[0])
393 396 _sethgexecutable(exe)
394 397 return _hgexecutable
395 398
396 399 def _sethgexecutable(path):
397 400 """set location of the 'hg' executable"""
398 401 global _hgexecutable
399 402 _hgexecutable = path
400 403
401 404 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
402 405 '''enhanced shell command execution.
403 406 run with environment maybe modified, maybe in different dir.
404 407
405 408 if command fails and onerr is None, return status. if ui object,
406 409 print error message and return status, else raise onerr object as
407 410 exception.
408 411
409 412 if out is specified, it is assumed to be a file-like object that has a
410 413 write() method. stdout and stderr will be redirected to out.'''
411 414 try:
412 415 sys.stdout.flush()
413 416 except Exception:
414 417 pass
415 418 def py2shell(val):
416 419 'convert python object into string that is useful to shell'
417 420 if val is None or val is False:
418 421 return '0'
419 422 if val is True:
420 423 return '1'
421 424 return str(val)
422 425 origcmd = cmd
423 426 cmd = quotecommand(cmd)
424 427 env = dict(os.environ)
425 428 env.update((k, py2shell(v)) for k, v in environ.iteritems())
426 429 env['HG'] = hgexecutable()
427 430 if out is None or out == sys.__stdout__:
428 431 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
429 432 env=env, cwd=cwd)
430 433 else:
431 434 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
432 435 env=env, cwd=cwd, stdout=subprocess.PIPE,
433 436 stderr=subprocess.STDOUT)
434 437 for line in proc.stdout:
435 438 out.write(line)
436 439 proc.wait()
437 440 rc = proc.returncode
438 441 if sys.platform == 'OpenVMS' and rc & 1:
439 442 rc = 0
440 443 if rc and onerr:
441 444 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
442 445 explainexit(rc)[0])
443 446 if errprefix:
444 447 errmsg = '%s: %s' % (errprefix, errmsg)
445 448 try:
446 449 onerr.warn(errmsg + '\n')
447 450 except AttributeError:
448 451 raise onerr(errmsg)
449 452 return rc
450 453
451 454 def checksignature(func):
452 455 '''wrap a function with code to check for calling errors'''
453 456 def check(*args, **kwargs):
454 457 try:
455 458 return func(*args, **kwargs)
456 459 except TypeError:
457 460 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
458 461 raise error.SignatureError
459 462 raise
460 463
461 464 return check
462 465
463 466 def copyfile(src, dest):
464 467 "copy a file, preserving mode and atime/mtime"
465 468 if os.path.islink(src):
466 469 try:
467 470 os.unlink(dest)
468 471 except OSError:
469 472 pass
470 473 os.symlink(os.readlink(src), dest)
471 474 else:
472 475 try:
473 476 shutil.copyfile(src, dest)
474 477 shutil.copymode(src, dest)
475 478 except shutil.Error, inst:
476 479 raise Abort(str(inst))
477 480
478 481 def copyfiles(src, dst, hardlink=None):
479 482 """Copy a directory tree using hardlinks if possible"""
480 483
481 484 if hardlink is None:
482 485 hardlink = (os.stat(src).st_dev ==
483 486 os.stat(os.path.dirname(dst)).st_dev)
484 487
485 488 num = 0
486 489 if os.path.isdir(src):
487 490 os.mkdir(dst)
488 491 for name, kind in osutil.listdir(src):
489 492 srcname = os.path.join(src, name)
490 493 dstname = os.path.join(dst, name)
491 494 hardlink, n = copyfiles(srcname, dstname, hardlink)
492 495 num += n
493 496 else:
494 497 if hardlink:
495 498 try:
496 499 oslink(src, dst)
497 500 except (IOError, OSError):
498 501 hardlink = False
499 502 shutil.copy(src, dst)
500 503 else:
501 504 shutil.copy(src, dst)
502 505 num += 1
503 506
504 507 return hardlink, num
505 508
506 509 _winreservednames = '''con prn aux nul
507 510 com1 com2 com3 com4 com5 com6 com7 com8 com9
508 511 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
509 512 _winreservedchars = ':*?"<>|'
510 513 def checkwinfilename(path):
511 514 '''Check that the base-relative path is a valid filename on Windows.
512 515 Returns None if the path is ok, or a UI string describing the problem.
513 516
514 517 >>> checkwinfilename("just/a/normal/path")
515 518 >>> checkwinfilename("foo/bar/con.xml")
516 519 "filename contains 'con', which is reserved on Windows"
517 520 >>> checkwinfilename("foo/con.xml/bar")
518 521 "filename contains 'con', which is reserved on Windows"
519 522 >>> checkwinfilename("foo/bar/xml.con")
520 523 >>> checkwinfilename("foo/bar/AUX/bla.txt")
521 524 "filename contains 'AUX', which is reserved on Windows"
522 525 >>> checkwinfilename("foo/bar/bla:.txt")
523 526 "filename contains ':', which is reserved on Windows"
524 527 >>> checkwinfilename("foo/bar/b\07la.txt")
525 528 "filename contains '\\\\x07', which is invalid on Windows"
526 529 >>> checkwinfilename("foo/bar/bla ")
527 530 "filename ends with ' ', which is not allowed on Windows"
528 531 >>> checkwinfilename("../bar")
529 532 '''
530 533 for n in path.replace('\\', '/').split('/'):
531 534 if not n:
532 535 continue
533 536 for c in n:
534 537 if c in _winreservedchars:
535 538 return _("filename contains '%s', which is reserved "
536 539 "on Windows") % c
537 540 if ord(c) <= 31:
538 541 return _("filename contains %r, which is invalid "
539 542 "on Windows") % c
540 543 base = n.split('.')[0]
541 544 if base and base.lower() in _winreservednames:
542 545 return _("filename contains '%s', which is reserved "
543 546 "on Windows") % base
544 547 t = n[-1]
545 548 if t in '. ' and n not in '..':
546 549 return _("filename ends with '%s', which is not allowed "
547 550 "on Windows") % t
548 551
549 552 if os.name == 'nt':
550 553 checkosfilename = checkwinfilename
551 554 else:
552 555 checkosfilename = platform.checkosfilename
553 556
554 557 def makelock(info, pathname):
555 558 try:
556 559 return os.symlink(info, pathname)
557 560 except OSError, why:
558 561 if why.errno == errno.EEXIST:
559 562 raise
560 563 except AttributeError: # no symlink in os
561 564 pass
562 565
563 566 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
564 567 os.write(ld, info)
565 568 os.close(ld)
566 569
567 570 def readlock(pathname):
568 571 try:
569 572 return os.readlink(pathname)
570 573 except OSError, why:
571 574 if why.errno not in (errno.EINVAL, errno.ENOSYS):
572 575 raise
573 576 except AttributeError: # no symlink in os
574 577 pass
575 578 fp = posixfile(pathname)
576 579 r = fp.read()
577 580 fp.close()
578 581 return r
579 582
580 583 def fstat(fp):
581 584 '''stat file object that may not have fileno method.'''
582 585 try:
583 586 return os.fstat(fp.fileno())
584 587 except AttributeError:
585 588 return os.stat(fp.name)
586 589
587 590 # File system features
588 591
589 592 def checkcase(path):
590 593 """
591 594 Check whether the given path is on a case-sensitive filesystem
592 595
593 596 Requires a path (like /foo/.hg) ending with a foldable final
594 597 directory component.
595 598 """
596 599 s1 = os.stat(path)
597 600 d, b = os.path.split(path)
598 601 b2 = b.upper()
599 602 if b == b2:
600 603 b2 = b.lower()
601 604 if b == b2:
602 605 return True # no evidence against case sensitivity
603 606 p2 = os.path.join(d, b2)
604 607 try:
605 608 s2 = os.stat(p2)
606 609 if s2 == s1:
607 610 return False
608 611 return True
609 612 except OSError:
610 613 return True
611 614
612 615 _fspathcache = {}
613 616 def fspath(name, root):
614 617 '''Get name in the case stored in the filesystem
615 618
616 619 The name is either relative to root, or it is an absolute path starting
617 620 with root. Note that this function is unnecessary, and should not be
618 621 called, for case-sensitive filesystems (simply because it's expensive).
619 622
620 623 Both name and root should be normcase-ed.
621 624 '''
622 625 # If name is absolute, make it relative
623 626 if name.startswith(root):
624 627 l = len(root)
625 628 if name[l] == os.sep or name[l] == os.altsep:
626 629 l = l + 1
627 630 name = name[l:]
628 631
629 632 if not os.path.lexists(os.path.join(root, name)):
630 633 return None
631 634
632 635 seps = os.sep
633 636 if os.altsep:
634 637 seps = seps + os.altsep
635 638 # Protect backslashes. This gets silly very quickly.
636 639 seps.replace('\\','\\\\')
637 640 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
638 641 dir = os.path.normpath(root)
639 642 result = []
640 643 for part, sep in pattern.findall(name):
641 644 if sep:
642 645 result.append(sep)
643 646 continue
644 647
645 648 if dir not in _fspathcache:
646 649 _fspathcache[dir] = os.listdir(dir)
647 650 contents = _fspathcache[dir]
648 651
649 652 lenp = len(part)
650 653 for n in contents:
651 654 if lenp == len(n) and normcase(n) == part:
652 655 result.append(n)
653 656 break
654 657 else:
655 658 # Cannot happen, as the file exists!
656 659 result.append(part)
657 660 dir = os.path.join(dir, part)
658 661
659 662 return ''.join(result)
660 663
661 664 def checknlink(testfile):
662 665 '''check whether hardlink count reporting works properly'''
663 666
664 667 # testfile may be open, so we need a separate file for checking to
665 668 # work around issue2543 (or testfile may get lost on Samba shares)
666 669 f1 = testfile + ".hgtmp1"
667 670 if os.path.lexists(f1):
668 671 return False
669 672 try:
670 673 posixfile(f1, 'w').close()
671 674 except IOError:
672 675 return False
673 676
674 677 f2 = testfile + ".hgtmp2"
675 678 fd = None
676 679 try:
677 680 try:
678 681 oslink(f1, f2)
679 682 except OSError:
680 683 return False
681 684
682 685 # nlinks() may behave differently for files on Windows shares if
683 686 # the file is open.
684 687 fd = posixfile(f2)
685 688 return nlinks(f2) > 1
686 689 finally:
687 690 if fd is not None:
688 691 fd.close()
689 692 for f in (f1, f2):
690 693 try:
691 694 os.unlink(f)
692 695 except OSError:
693 696 pass
694 697
695 698 return False
696 699
697 700 def endswithsep(path):
698 701 '''Check path ends with os.sep or os.altsep.'''
699 702 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
700 703
701 704 def splitpath(path):
702 705 '''Split path by os.sep.
703 706 Note that this function does not use os.altsep because this is
704 707 an alternative of simple "xxx.split(os.sep)".
705 708 It is recommended to use os.path.normpath() before using this
706 709 function if need.'''
707 710 return path.split(os.sep)
708 711
709 712 def gui():
710 713 '''Are we running in a GUI?'''
711 714 if sys.platform == 'darwin':
712 715 if 'SSH_CONNECTION' in os.environ:
713 716 # handle SSH access to a box where the user is logged in
714 717 return False
715 718 elif getattr(osutil, 'isgui', None):
716 719 # check if a CoreGraphics session is available
717 720 return osutil.isgui()
718 721 else:
719 722 # pure build; use a safe default
720 723 return True
721 724 else:
722 725 return os.name == "nt" or os.environ.get("DISPLAY")
723 726
724 727 def mktempcopy(name, emptyok=False, createmode=None):
725 728 """Create a temporary file with the same contents from name
726 729
727 730 The permission bits are copied from the original file.
728 731
729 732 If the temporary file is going to be truncated immediately, you
730 733 can use emptyok=True as an optimization.
731 734
732 735 Returns the name of the temporary file.
733 736 """
734 737 d, fn = os.path.split(name)
735 738 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
736 739 os.close(fd)
737 740 # Temporary files are created with mode 0600, which is usually not
738 741 # what we want. If the original file already exists, just copy
739 742 # its mode. Otherwise, manually obey umask.
740 743 copymode(name, temp, createmode)
741 744 if emptyok:
742 745 return temp
743 746 try:
744 747 try:
745 748 ifp = posixfile(name, "rb")
746 749 except IOError, inst:
747 750 if inst.errno == errno.ENOENT:
748 751 return temp
749 752 if not getattr(inst, 'filename', None):
750 753 inst.filename = name
751 754 raise
752 755 ofp = posixfile(temp, "wb")
753 756 for chunk in filechunkiter(ifp):
754 757 ofp.write(chunk)
755 758 ifp.close()
756 759 ofp.close()
757 760 except:
758 761 try: os.unlink(temp)
759 762 except: pass
760 763 raise
761 764 return temp
762 765
763 766 class atomictempfile(object):
764 767 '''writeable file object that atomically updates a file
765 768
766 769 All writes will go to a temporary copy of the original file. Call
767 770 close() when you are done writing, and atomictempfile will rename
768 771 the temporary copy to the original name, making the changes
769 772 visible. If the object is destroyed without being closed, all your
770 773 writes are discarded.
771 774 '''
772 775 def __init__(self, name, mode='w+b', createmode=None):
773 776 self.__name = name # permanent name
774 777 self._tempname = mktempcopy(name, emptyok=('w' in mode),
775 778 createmode=createmode)
776 779 self._fp = posixfile(self._tempname, mode)
777 780
778 781 # delegated methods
779 782 self.write = self._fp.write
780 783 self.fileno = self._fp.fileno
781 784
782 785 def close(self):
783 786 if not self._fp.closed:
784 787 self._fp.close()
785 788 rename(self._tempname, localpath(self.__name))
786 789
787 790 def discard(self):
788 791 if not self._fp.closed:
789 792 try:
790 793 os.unlink(self._tempname)
791 794 except OSError:
792 795 pass
793 796 self._fp.close()
794 797
795 798 def __del__(self):
796 799 if safehasattr(self, '_fp'): # constructor actually did something
797 800 self.discard()
798 801
799 802 def makedirs(name, mode=None):
800 803 """recursive directory creation with parent mode inheritance"""
801 804 try:
802 805 os.mkdir(name)
803 806 except OSError, err:
804 807 if err.errno == errno.EEXIST:
805 808 return
806 809 if err.errno != errno.ENOENT or not name:
807 810 raise
808 811 parent = os.path.dirname(os.path.abspath(name))
809 812 if parent == name:
810 813 raise
811 814 makedirs(parent, mode)
812 815 os.mkdir(name)
813 816 if mode is not None:
814 817 os.chmod(name, mode)
815 818
816 819 def readfile(path):
817 820 fp = open(path, 'rb')
818 821 try:
819 822 return fp.read()
820 823 finally:
821 824 fp.close()
822 825
823 826 def writefile(path, text):
824 827 fp = open(path, 'wb')
825 828 try:
826 829 fp.write(text)
827 830 finally:
828 831 fp.close()
829 832
830 833 def appendfile(path, text):
831 834 fp = open(path, 'ab')
832 835 try:
833 836 fp.write(text)
834 837 finally:
835 838 fp.close()
836 839
837 840 class chunkbuffer(object):
838 841 """Allow arbitrary sized chunks of data to be efficiently read from an
839 842 iterator over chunks of arbitrary size."""
840 843
841 844 def __init__(self, in_iter):
842 845 """in_iter is the iterator that's iterating over the input chunks.
843 846 targetsize is how big a buffer to try to maintain."""
844 847 def splitbig(chunks):
845 848 for chunk in chunks:
846 849 if len(chunk) > 2**20:
847 850 pos = 0
848 851 while pos < len(chunk):
849 852 end = pos + 2 ** 18
850 853 yield chunk[pos:end]
851 854 pos = end
852 855 else:
853 856 yield chunk
854 857 self.iter = splitbig(in_iter)
855 858 self._queue = []
856 859
857 860 def read(self, l):
858 861 """Read L bytes of data from the iterator of chunks of data.
859 862 Returns less than L bytes if the iterator runs dry."""
860 863 left = l
861 864 buf = ''
862 865 queue = self._queue
863 866 while left > 0:
864 867 # refill the queue
865 868 if not queue:
866 869 target = 2**18
867 870 for chunk in self.iter:
868 871 queue.append(chunk)
869 872 target -= len(chunk)
870 873 if target <= 0:
871 874 break
872 875 if not queue:
873 876 break
874 877
875 878 chunk = queue.pop(0)
876 879 left -= len(chunk)
877 880 if left < 0:
878 881 queue.insert(0, chunk[left:])
879 882 buf += chunk[:left]
880 883 else:
881 884 buf += chunk
882 885
883 886 return buf
884 887
885 888 def filechunkiter(f, size=65536, limit=None):
886 889 """Create a generator that produces the data in the file size
887 890 (default 65536) bytes at a time, up to optional limit (default is
888 891 to read all data). Chunks may be less than size bytes if the
889 892 chunk is the last chunk in the file, or the file is a socket or
890 893 some other type of file that sometimes reads less data than is
891 894 requested."""
892 895 assert size >= 0
893 896 assert limit is None or limit >= 0
894 897 while True:
895 898 if limit is None:
896 899 nbytes = size
897 900 else:
898 901 nbytes = min(limit, size)
899 902 s = nbytes and f.read(nbytes)
900 903 if not s:
901 904 break
902 905 if limit:
903 906 limit -= len(s)
904 907 yield s
905 908
906 909 def makedate():
907 910 ct = time.time()
908 911 if ct < 0:
909 912 hint = _("check your clock")
910 913 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
911 914 delta = (datetime.datetime.utcfromtimestamp(ct) -
912 915 datetime.datetime.fromtimestamp(ct))
913 916 tz = delta.days * 86400 + delta.seconds
914 917 return ct, tz
915 918
916 919 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
917 920 """represent a (unixtime, offset) tuple as a localized time.
918 921 unixtime is seconds since the epoch, and offset is the time zone's
919 922 number of seconds away from UTC. if timezone is false, do not
920 923 append time zone to string."""
921 924 t, tz = date or makedate()
922 925 if t < 0:
923 926 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
924 927 tz = 0
925 928 if "%1" in format or "%2" in format:
926 929 sign = (tz > 0) and "-" or "+"
927 930 minutes = abs(tz) // 60
928 931 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
929 932 format = format.replace("%2", "%02d" % (minutes % 60))
930 933 try:
931 934 t = time.gmtime(float(t) - tz)
932 935 except ValueError:
933 936 # time was out of range
934 937 t = time.gmtime(sys.maxint)
935 938 s = time.strftime(format, t)
936 939 return s
937 940
938 941 def shortdate(date=None):
939 942 """turn (timestamp, tzoff) tuple into iso 8631 date."""
940 943 return datestr(date, format='%Y-%m-%d')
941 944
942 945 def strdate(string, format, defaults=[]):
943 946 """parse a localized time string and return a (unixtime, offset) tuple.
944 947 if the string cannot be parsed, ValueError is raised."""
945 948 def timezone(string):
946 949 tz = string.split()[-1]
947 950 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
948 951 sign = (tz[0] == "+") and 1 or -1
949 952 hours = int(tz[1:3])
950 953 minutes = int(tz[3:5])
951 954 return -sign * (hours * 60 + minutes) * 60
952 955 if tz == "GMT" or tz == "UTC":
953 956 return 0
954 957 return None
955 958
956 959 # NOTE: unixtime = localunixtime + offset
957 960 offset, date = timezone(string), string
958 961 if offset is not None:
959 962 date = " ".join(string.split()[:-1])
960 963
961 964 # add missing elements from defaults
962 965 usenow = False # default to using biased defaults
963 966 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
964 967 found = [True for p in part if ("%"+p) in format]
965 968 if not found:
966 969 date += "@" + defaults[part][usenow]
967 970 format += "@%" + part[0]
968 971 else:
969 972 # We've found a specific time element, less specific time
970 973 # elements are relative to today
971 974 usenow = True
972 975
973 976 timetuple = time.strptime(date, format)
974 977 localunixtime = int(calendar.timegm(timetuple))
975 978 if offset is None:
976 979 # local timezone
977 980 unixtime = int(time.mktime(timetuple))
978 981 offset = unixtime - localunixtime
979 982 else:
980 983 unixtime = localunixtime + offset
981 984 return unixtime, offset
982 985
983 986 def parsedate(date, formats=None, bias={}):
984 987 """parse a localized date/time and return a (unixtime, offset) tuple.
985 988
986 989 The date may be a "unixtime offset" string or in one of the specified
987 990 formats. If the date already is a (unixtime, offset) tuple, it is returned.
988 991 """
989 992 if not date:
990 993 return 0, 0
991 994 if isinstance(date, tuple) and len(date) == 2:
992 995 return date
993 996 if not formats:
994 997 formats = defaultdateformats
995 998 date = date.strip()
996 999 try:
997 1000 when, offset = map(int, date.split(' '))
998 1001 except ValueError:
999 1002 # fill out defaults
1000 1003 now = makedate()
1001 1004 defaults = {}
1002 1005 for part in ("d", "mb", "yY", "HI", "M", "S"):
1003 1006 # this piece is for rounding the specific end of unknowns
1004 1007 b = bias.get(part)
1005 1008 if b is None:
1006 1009 if part[0] in "HMS":
1007 1010 b = "00"
1008 1011 else:
1009 1012 b = "0"
1010 1013
1011 1014 # this piece is for matching the generic end to today's date
1012 1015 n = datestr(now, "%" + part[0])
1013 1016
1014 1017 defaults[part] = (b, n)
1015 1018
1016 1019 for format in formats:
1017 1020 try:
1018 1021 when, offset = strdate(date, format, defaults)
1019 1022 except (ValueError, OverflowError):
1020 1023 pass
1021 1024 else:
1022 1025 break
1023 1026 else:
1024 1027 raise Abort(_('invalid date: %r') % date)
1025 1028 # validate explicit (probably user-specified) date and
1026 1029 # time zone offset. values must fit in signed 32 bits for
1027 1030 # current 32-bit linux runtimes. timezones go from UTC-12
1028 1031 # to UTC+14
1029 1032 if abs(when) > 0x7fffffff:
1030 1033 raise Abort(_('date exceeds 32 bits: %d') % when)
1031 1034 if when < 0:
1032 1035 raise Abort(_('negative date value: %d') % when)
1033 1036 if offset < -50400 or offset > 43200:
1034 1037 raise Abort(_('impossible time zone offset: %d') % offset)
1035 1038 return when, offset
1036 1039
1037 1040 def matchdate(date):
1038 1041 """Return a function that matches a given date match specifier
1039 1042
1040 1043 Formats include:
1041 1044
1042 1045 '{date}' match a given date to the accuracy provided
1043 1046
1044 1047 '<{date}' on or before a given date
1045 1048
1046 1049 '>{date}' on or after a given date
1047 1050
1048 1051 >>> p1 = parsedate("10:29:59")
1049 1052 >>> p2 = parsedate("10:30:00")
1050 1053 >>> p3 = parsedate("10:30:59")
1051 1054 >>> p4 = parsedate("10:31:00")
1052 1055 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1053 1056 >>> f = matchdate("10:30")
1054 1057 >>> f(p1[0])
1055 1058 False
1056 1059 >>> f(p2[0])
1057 1060 True
1058 1061 >>> f(p3[0])
1059 1062 True
1060 1063 >>> f(p4[0])
1061 1064 False
1062 1065 >>> f(p5[0])
1063 1066 False
1064 1067 """
1065 1068
1066 1069 def lower(date):
1067 1070 d = dict(mb="1", d="1")
1068 1071 return parsedate(date, extendeddateformats, d)[0]
1069 1072
1070 1073 def upper(date):
1071 1074 d = dict(mb="12", HI="23", M="59", S="59")
1072 1075 for days in ("31", "30", "29"):
1073 1076 try:
1074 1077 d["d"] = days
1075 1078 return parsedate(date, extendeddateformats, d)[0]
1076 1079 except:
1077 1080 pass
1078 1081 d["d"] = "28"
1079 1082 return parsedate(date, extendeddateformats, d)[0]
1080 1083
1081 1084 date = date.strip()
1082 1085
1083 1086 if not date:
1084 1087 raise Abort(_("dates cannot consist entirely of whitespace"))
1085 1088 elif date[0] == "<":
1086 1089 if not date[1:]:
1087 1090 raise Abort(_("invalid day spec, use '<DATE'"))
1088 1091 when = upper(date[1:])
1089 1092 return lambda x: x <= when
1090 1093 elif date[0] == ">":
1091 1094 if not date[1:]:
1092 1095 raise Abort(_("invalid day spec, use '>DATE'"))
1093 1096 when = lower(date[1:])
1094 1097 return lambda x: x >= when
1095 1098 elif date[0] == "-":
1096 1099 try:
1097 1100 days = int(date[1:])
1098 1101 except ValueError:
1099 1102 raise Abort(_("invalid day spec: %s") % date[1:])
1100 1103 if days < 0:
1101 1104 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1102 1105 % date[1:])
1103 1106 when = makedate()[0] - days * 3600 * 24
1104 1107 return lambda x: x >= when
1105 1108 elif " to " in date:
1106 1109 a, b = date.split(" to ")
1107 1110 start, stop = lower(a), upper(b)
1108 1111 return lambda x: x >= start and x <= stop
1109 1112 else:
1110 1113 start, stop = lower(date), upper(date)
1111 1114 return lambda x: x >= start and x <= stop
1112 1115
1113 1116 def shortuser(user):
1114 1117 """Return a short representation of a user name or email address."""
1115 1118 f = user.find('@')
1116 1119 if f >= 0:
1117 1120 user = user[:f]
1118 1121 f = user.find('<')
1119 1122 if f >= 0:
1120 1123 user = user[f + 1:]
1121 1124 f = user.find(' ')
1122 1125 if f >= 0:
1123 1126 user = user[:f]
1124 1127 f = user.find('.')
1125 1128 if f >= 0:
1126 1129 user = user[:f]
1127 1130 return user
1128 1131
1129 1132 def email(author):
1130 1133 '''get email of author.'''
1131 1134 r = author.find('>')
1132 1135 if r == -1:
1133 1136 r = None
1134 1137 return author[author.find('<') + 1:r]
1135 1138
1136 1139 def _ellipsis(text, maxlength):
1137 1140 if len(text) <= maxlength:
1138 1141 return text, False
1139 1142 else:
1140 1143 return "%s..." % (text[:maxlength - 3]), True
1141 1144
1142 1145 def ellipsis(text, maxlength=400):
1143 1146 """Trim string to at most maxlength (default: 400) characters."""
1144 1147 try:
1145 1148 # use unicode not to split at intermediate multi-byte sequence
1146 1149 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1147 1150 maxlength)
1148 1151 if not truncated:
1149 1152 return text
1150 1153 return utext.encode(encoding.encoding)
1151 1154 except (UnicodeDecodeError, UnicodeEncodeError):
1152 1155 return _ellipsis(text, maxlength)[0]
1153 1156
1154 1157 def bytecount(nbytes):
1155 1158 '''return byte count formatted as readable string, with units'''
1156 1159
1157 1160 units = (
1158 1161 (100, 1 << 30, _('%.0f GB')),
1159 1162 (10, 1 << 30, _('%.1f GB')),
1160 1163 (1, 1 << 30, _('%.2f GB')),
1161 1164 (100, 1 << 20, _('%.0f MB')),
1162 1165 (10, 1 << 20, _('%.1f MB')),
1163 1166 (1, 1 << 20, _('%.2f MB')),
1164 1167 (100, 1 << 10, _('%.0f KB')),
1165 1168 (10, 1 << 10, _('%.1f KB')),
1166 1169 (1, 1 << 10, _('%.2f KB')),
1167 1170 (1, 1, _('%.0f bytes')),
1168 1171 )
1169 1172
1170 1173 for multiplier, divisor, format in units:
1171 1174 if nbytes >= divisor * multiplier:
1172 1175 return format % (nbytes / float(divisor))
1173 1176 return units[-1][2] % nbytes
1174 1177
1175 1178 def uirepr(s):
1176 1179 # Avoid double backslash in Windows path repr()
1177 1180 return repr(s).replace('\\\\', '\\')
1178 1181
1179 1182 # delay import of textwrap
1180 1183 def MBTextWrapper(**kwargs):
1181 1184 class tw(textwrap.TextWrapper):
1182 1185 """
1183 1186 Extend TextWrapper for width-awareness.
1184 1187
1185 1188 Neither number of 'bytes' in any encoding nor 'characters' is
1186 1189 appropriate to calculate terminal columns for specified string.
1187 1190
1188 1191 Original TextWrapper implementation uses built-in 'len()' directly,
1189 1192 so overriding is needed to use width information of each characters.
1190 1193
1191 1194 In addition, characters classified into 'ambiguous' width are
1192 1195 treated as wide in east asian area, but as narrow in other.
1193 1196
1194 1197 This requires use decision to determine width of such characters.
1195 1198 """
1196 1199 def __init__(self, **kwargs):
1197 1200 textwrap.TextWrapper.__init__(self, **kwargs)
1198 1201
1199 1202 # for compatibility between 2.4 and 2.6
1200 1203 if getattr(self, 'drop_whitespace', None) is None:
1201 1204 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1202 1205
1203 1206 def _cutdown(self, ucstr, space_left):
1204 1207 l = 0
1205 1208 colwidth = encoding.ucolwidth
1206 1209 for i in xrange(len(ucstr)):
1207 1210 l += colwidth(ucstr[i])
1208 1211 if space_left < l:
1209 1212 return (ucstr[:i], ucstr[i:])
1210 1213 return ucstr, ''
1211 1214
1212 1215 # overriding of base class
1213 1216 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1214 1217 space_left = max(width - cur_len, 1)
1215 1218
1216 1219 if self.break_long_words:
1217 1220 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1218 1221 cur_line.append(cut)
1219 1222 reversed_chunks[-1] = res
1220 1223 elif not cur_line:
1221 1224 cur_line.append(reversed_chunks.pop())
1222 1225
1223 1226 # this overriding code is imported from TextWrapper of python 2.6
1224 1227 # to calculate columns of string by 'encoding.ucolwidth()'
1225 1228 def _wrap_chunks(self, chunks):
1226 1229 colwidth = encoding.ucolwidth
1227 1230
1228 1231 lines = []
1229 1232 if self.width <= 0:
1230 1233 raise ValueError("invalid width %r (must be > 0)" % self.width)
1231 1234
1232 1235 # Arrange in reverse order so items can be efficiently popped
1233 1236 # from a stack of chucks.
1234 1237 chunks.reverse()
1235 1238
1236 1239 while chunks:
1237 1240
1238 1241 # Start the list of chunks that will make up the current line.
1239 1242 # cur_len is just the length of all the chunks in cur_line.
1240 1243 cur_line = []
1241 1244 cur_len = 0
1242 1245
1243 1246 # Figure out which static string will prefix this line.
1244 1247 if lines:
1245 1248 indent = self.subsequent_indent
1246 1249 else:
1247 1250 indent = self.initial_indent
1248 1251
1249 1252 # Maximum width for this line.
1250 1253 width = self.width - len(indent)
1251 1254
1252 1255 # First chunk on line is whitespace -- drop it, unless this
1253 1256 # is the very beginning of the text (ie. no lines started yet).
1254 1257 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1255 1258 del chunks[-1]
1256 1259
1257 1260 while chunks:
1258 1261 l = colwidth(chunks[-1])
1259 1262
1260 1263 # Can at least squeeze this chunk onto the current line.
1261 1264 if cur_len + l <= width:
1262 1265 cur_line.append(chunks.pop())
1263 1266 cur_len += l
1264 1267
1265 1268 # Nope, this line is full.
1266 1269 else:
1267 1270 break
1268 1271
1269 1272 # The current line is full, and the next chunk is too big to
1270 1273 # fit on *any* line (not just this one).
1271 1274 if chunks and colwidth(chunks[-1]) > width:
1272 1275 self._handle_long_word(chunks, cur_line, cur_len, width)
1273 1276
1274 1277 # If the last chunk on this line is all whitespace, drop it.
1275 1278 if (self.drop_whitespace and
1276 1279 cur_line and cur_line[-1].strip() == ''):
1277 1280 del cur_line[-1]
1278 1281
1279 1282 # Convert current line back to a string and store it in list
1280 1283 # of all lines (return value).
1281 1284 if cur_line:
1282 1285 lines.append(indent + ''.join(cur_line))
1283 1286
1284 1287 return lines
1285 1288
1286 1289 global MBTextWrapper
1287 1290 MBTextWrapper = tw
1288 1291 return tw(**kwargs)
1289 1292
1290 1293 def wrap(line, width, initindent='', hangindent=''):
1291 1294 maxindent = max(len(hangindent), len(initindent))
1292 1295 if width <= maxindent:
1293 1296 # adjust for weird terminal size
1294 1297 width = max(78, maxindent + 1)
1295 1298 line = line.decode(encoding.encoding, encoding.encodingmode)
1296 1299 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1297 1300 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1298 1301 wrapper = MBTextWrapper(width=width,
1299 1302 initial_indent=initindent,
1300 1303 subsequent_indent=hangindent)
1301 1304 return wrapper.fill(line).encode(encoding.encoding)
1302 1305
1303 1306 def iterlines(iterator):
1304 1307 for chunk in iterator:
1305 1308 for line in chunk.splitlines():
1306 1309 yield line
1307 1310
1308 1311 def expandpath(path):
1309 1312 return os.path.expanduser(os.path.expandvars(path))
1310 1313
1311 1314 def hgcmd():
1312 1315 """Return the command used to execute current hg
1313 1316
1314 1317 This is different from hgexecutable() because on Windows we want
1315 1318 to avoid things opening new shell windows like batch files, so we
1316 1319 get either the python call or current executable.
1317 1320 """
1318 1321 if mainfrozen():
1319 1322 return [sys.executable]
1320 1323 return gethgcmd()
1321 1324
1322 1325 def rundetached(args, condfn):
1323 1326 """Execute the argument list in a detached process.
1324 1327
1325 1328 condfn is a callable which is called repeatedly and should return
1326 1329 True once the child process is known to have started successfully.
1327 1330 At this point, the child process PID is returned. If the child
1328 1331 process fails to start or finishes before condfn() evaluates to
1329 1332 True, return -1.
1330 1333 """
1331 1334 # Windows case is easier because the child process is either
1332 1335 # successfully starting and validating the condition or exiting
1333 1336 # on failure. We just poll on its PID. On Unix, if the child
1334 1337 # process fails to start, it will be left in a zombie state until
1335 1338 # the parent wait on it, which we cannot do since we expect a long
1336 1339 # running process on success. Instead we listen for SIGCHLD telling
1337 1340 # us our child process terminated.
1338 1341 terminated = set()
1339 1342 def handler(signum, frame):
1340 1343 terminated.add(os.wait())
1341 1344 prevhandler = None
1342 1345 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1343 1346 if SIGCHLD is not None:
1344 1347 prevhandler = signal.signal(SIGCHLD, handler)
1345 1348 try:
1346 1349 pid = spawndetached(args)
1347 1350 while not condfn():
1348 1351 if ((pid in terminated or not testpid(pid))
1349 1352 and not condfn()):
1350 1353 return -1
1351 1354 time.sleep(0.1)
1352 1355 return pid
1353 1356 finally:
1354 1357 if prevhandler is not None:
1355 1358 signal.signal(signal.SIGCHLD, prevhandler)
1356 1359
1357 1360 try:
1358 1361 any, all = any, all
1359 1362 except NameError:
1360 1363 def any(iterable):
1361 1364 for i in iterable:
1362 1365 if i:
1363 1366 return True
1364 1367 return False
1365 1368
1366 1369 def all(iterable):
1367 1370 for i in iterable:
1368 1371 if not i:
1369 1372 return False
1370 1373 return True
1371 1374
1372 1375 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1373 1376 """Return the result of interpolating items in the mapping into string s.
1374 1377
1375 1378 prefix is a single character string, or a two character string with
1376 1379 a backslash as the first character if the prefix needs to be escaped in
1377 1380 a regular expression.
1378 1381
1379 1382 fn is an optional function that will be applied to the replacement text
1380 1383 just before replacement.
1381 1384
1382 1385 escape_prefix is an optional flag that allows using doubled prefix for
1383 1386 its escaping.
1384 1387 """
1385 1388 fn = fn or (lambda s: s)
1386 1389 patterns = '|'.join(mapping.keys())
1387 1390 if escape_prefix:
1388 1391 patterns += '|' + prefix
1389 1392 if len(prefix) > 1:
1390 1393 prefix_char = prefix[1:]
1391 1394 else:
1392 1395 prefix_char = prefix
1393 1396 mapping[prefix_char] = prefix_char
1394 1397 r = re.compile(r'%s(%s)' % (prefix, patterns))
1395 1398 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1396 1399
1397 1400 def getport(port):
1398 1401 """Return the port for a given network service.
1399 1402
1400 1403 If port is an integer, it's returned as is. If it's a string, it's
1401 1404 looked up using socket.getservbyname(). If there's no matching
1402 1405 service, util.Abort is raised.
1403 1406 """
1404 1407 try:
1405 1408 return int(port)
1406 1409 except ValueError:
1407 1410 pass
1408 1411
1409 1412 try:
1410 1413 return socket.getservbyname(port)
1411 1414 except socket.error:
1412 1415 raise Abort(_("no port number associated with service '%s'") % port)
1413 1416
1414 1417 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1415 1418 '0': False, 'no': False, 'false': False, 'off': False,
1416 1419 'never': False}
1417 1420
1418 1421 def parsebool(s):
1419 1422 """Parse s into a boolean.
1420 1423
1421 1424 If s is not a valid boolean, returns None.
1422 1425 """
1423 1426 return _booleans.get(s.lower(), None)
1424 1427
1425 1428 _hexdig = '0123456789ABCDEFabcdef'
1426 1429 _hextochr = dict((a + b, chr(int(a + b, 16)))
1427 1430 for a in _hexdig for b in _hexdig)
1428 1431
1429 1432 def _urlunquote(s):
1430 1433 """unquote('abc%20def') -> 'abc def'."""
1431 1434 res = s.split('%')
1432 1435 # fastpath
1433 1436 if len(res) == 1:
1434 1437 return s
1435 1438 s = res[0]
1436 1439 for item in res[1:]:
1437 1440 try:
1438 1441 s += _hextochr[item[:2]] + item[2:]
1439 1442 except KeyError:
1440 1443 s += '%' + item
1441 1444 except UnicodeDecodeError:
1442 1445 s += unichr(int(item[:2], 16)) + item[2:]
1443 1446 return s
1444 1447
1445 1448 class url(object):
1446 1449 r"""Reliable URL parser.
1447 1450
1448 1451 This parses URLs and provides attributes for the following
1449 1452 components:
1450 1453
1451 1454 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1452 1455
1453 1456 Missing components are set to None. The only exception is
1454 1457 fragment, which is set to '' if present but empty.
1455 1458
1456 1459 If parsefragment is False, fragment is included in query. If
1457 1460 parsequery is False, query is included in path. If both are
1458 1461 False, both fragment and query are included in path.
1459 1462
1460 1463 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1461 1464
1462 1465 Note that for backward compatibility reasons, bundle URLs do not
1463 1466 take host names. That means 'bundle://../' has a path of '../'.
1464 1467
1465 1468 Examples:
1466 1469
1467 1470 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1468 1471 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1469 1472 >>> url('ssh://[::1]:2200//home/joe/repo')
1470 1473 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1471 1474 >>> url('file:///home/joe/repo')
1472 1475 <url scheme: 'file', path: '/home/joe/repo'>
1473 1476 >>> url('file:///c:/temp/foo/')
1474 1477 <url scheme: 'file', path: 'c:/temp/foo/'>
1475 1478 >>> url('bundle:foo')
1476 1479 <url scheme: 'bundle', path: 'foo'>
1477 1480 >>> url('bundle://../foo')
1478 1481 <url scheme: 'bundle', path: '../foo'>
1479 1482 >>> url(r'c:\foo\bar')
1480 1483 <url path: 'c:\\foo\\bar'>
1481 1484 >>> url(r'\\blah\blah\blah')
1482 1485 <url path: '\\\\blah\\blah\\blah'>
1483 1486 >>> url(r'\\blah\blah\blah#baz')
1484 1487 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1485 1488
1486 1489 Authentication credentials:
1487 1490
1488 1491 >>> url('ssh://joe:xyz@x/repo')
1489 1492 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1490 1493 >>> url('ssh://joe@x/repo')
1491 1494 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1492 1495
1493 1496 Query strings and fragments:
1494 1497
1495 1498 >>> url('http://host/a?b#c')
1496 1499 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1497 1500 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1498 1501 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1499 1502 """
1500 1503
1501 1504 _safechars = "!~*'()+"
1502 1505 _safepchars = "/!~*'()+"
1503 1506 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1504 1507
1505 1508 def __init__(self, path, parsequery=True, parsefragment=True):
1506 1509 # We slowly chomp away at path until we have only the path left
1507 1510 self.scheme = self.user = self.passwd = self.host = None
1508 1511 self.port = self.path = self.query = self.fragment = None
1509 1512 self._localpath = True
1510 1513 self._hostport = ''
1511 1514 self._origpath = path
1512 1515
1513 1516 if parsefragment and '#' in path:
1514 1517 path, self.fragment = path.split('#', 1)
1515 1518 if not path:
1516 1519 path = None
1517 1520
1518 1521 # special case for Windows drive letters and UNC paths
1519 1522 if hasdriveletter(path) or path.startswith(r'\\'):
1520 1523 self.path = path
1521 1524 return
1522 1525
1523 1526 # For compatibility reasons, we can't handle bundle paths as
1524 1527 # normal URLS
1525 1528 if path.startswith('bundle:'):
1526 1529 self.scheme = 'bundle'
1527 1530 path = path[7:]
1528 1531 if path.startswith('//'):
1529 1532 path = path[2:]
1530 1533 self.path = path
1531 1534 return
1532 1535
1533 1536 if self._matchscheme(path):
1534 1537 parts = path.split(':', 1)
1535 1538 if parts[0]:
1536 1539 self.scheme, path = parts
1537 1540 self._localpath = False
1538 1541
1539 1542 if not path:
1540 1543 path = None
1541 1544 if self._localpath:
1542 1545 self.path = ''
1543 1546 return
1544 1547 else:
1545 1548 if self._localpath:
1546 1549 self.path = path
1547 1550 return
1548 1551
1549 1552 if parsequery and '?' in path:
1550 1553 path, self.query = path.split('?', 1)
1551 1554 if not path:
1552 1555 path = None
1553 1556 if not self.query:
1554 1557 self.query = None
1555 1558
1556 1559 # // is required to specify a host/authority
1557 1560 if path and path.startswith('//'):
1558 1561 parts = path[2:].split('/', 1)
1559 1562 if len(parts) > 1:
1560 1563 self.host, path = parts
1561 1564 path = path
1562 1565 else:
1563 1566 self.host = parts[0]
1564 1567 path = None
1565 1568 if not self.host:
1566 1569 self.host = None
1567 1570 # path of file:///d is /d
1568 1571 # path of file:///d:/ is d:/, not /d:/
1569 1572 if path and not hasdriveletter(path):
1570 1573 path = '/' + path
1571 1574
1572 1575 if self.host and '@' in self.host:
1573 1576 self.user, self.host = self.host.rsplit('@', 1)
1574 1577 if ':' in self.user:
1575 1578 self.user, self.passwd = self.user.split(':', 1)
1576 1579 if not self.host:
1577 1580 self.host = None
1578 1581
1579 1582 # Don't split on colons in IPv6 addresses without ports
1580 1583 if (self.host and ':' in self.host and
1581 1584 not (self.host.startswith('[') and self.host.endswith(']'))):
1582 1585 self._hostport = self.host
1583 1586 self.host, self.port = self.host.rsplit(':', 1)
1584 1587 if not self.host:
1585 1588 self.host = None
1586 1589
1587 1590 if (self.host and self.scheme == 'file' and
1588 1591 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1589 1592 raise Abort(_('file:// URLs can only refer to localhost'))
1590 1593
1591 1594 self.path = path
1592 1595
1593 1596 # leave the query string escaped
1594 1597 for a in ('user', 'passwd', 'host', 'port',
1595 1598 'path', 'fragment'):
1596 1599 v = getattr(self, a)
1597 1600 if v is not None:
1598 1601 setattr(self, a, _urlunquote(v))
1599 1602
1600 1603 def __repr__(self):
1601 1604 attrs = []
1602 1605 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1603 1606 'query', 'fragment'):
1604 1607 v = getattr(self, a)
1605 1608 if v is not None:
1606 1609 attrs.append('%s: %r' % (a, v))
1607 1610 return '<url %s>' % ', '.join(attrs)
1608 1611
1609 1612 def __str__(self):
1610 1613 r"""Join the URL's components back into a URL string.
1611 1614
1612 1615 Examples:
1613 1616
1614 1617 >>> str(url('http://user:pw@host:80/?foo#bar'))
1615 1618 'http://user:pw@host:80/?foo#bar'
1616 1619 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1617 1620 'http://user:pw@host:80/?foo=bar&baz=42'
1618 1621 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1619 1622 'http://user:pw@host:80/?foo=bar%3dbaz'
1620 1623 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1621 1624 'ssh://user:pw@[::1]:2200//home/joe#'
1622 1625 >>> str(url('http://localhost:80//'))
1623 1626 'http://localhost:80//'
1624 1627 >>> str(url('http://localhost:80/'))
1625 1628 'http://localhost:80/'
1626 1629 >>> str(url('http://localhost:80'))
1627 1630 'http://localhost:80/'
1628 1631 >>> str(url('bundle:foo'))
1629 1632 'bundle:foo'
1630 1633 >>> str(url('bundle://../foo'))
1631 1634 'bundle:../foo'
1632 1635 >>> str(url('path'))
1633 1636 'path'
1634 1637 >>> str(url('file:///tmp/foo/bar'))
1635 1638 'file:///tmp/foo/bar'
1636 1639 >>> str(url('file:///c:/tmp/foo/bar'))
1637 1640 'file:///c%3A/tmp/foo/bar'
1638 1641 >>> print url(r'bundle:foo\bar')
1639 1642 bundle:foo\bar
1640 1643 """
1641 1644 if self._localpath:
1642 1645 s = self.path
1643 1646 if self.scheme == 'bundle':
1644 1647 s = 'bundle:' + s
1645 1648 if self.fragment:
1646 1649 s += '#' + self.fragment
1647 1650 return s
1648 1651
1649 1652 s = self.scheme + ':'
1650 1653 if self.user or self.passwd or self.host:
1651 1654 s += '//'
1652 1655 elif self.scheme and (not self.path or self.path.startswith('/')
1653 1656 or hasdriveletter(self.path)):
1654 1657 s += '//'
1655 1658 if hasdriveletter(self.path):
1656 1659 s += '/'
1657 1660 if self.user:
1658 1661 s += urllib.quote(self.user, safe=self._safechars)
1659 1662 if self.passwd:
1660 1663 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1661 1664 if self.user or self.passwd:
1662 1665 s += '@'
1663 1666 if self.host:
1664 1667 if not (self.host.startswith('[') and self.host.endswith(']')):
1665 1668 s += urllib.quote(self.host)
1666 1669 else:
1667 1670 s += self.host
1668 1671 if self.port:
1669 1672 s += ':' + urllib.quote(self.port)
1670 1673 if self.host:
1671 1674 s += '/'
1672 1675 if self.path:
1673 1676 # TODO: similar to the query string, we should not unescape the
1674 1677 # path when we store it, the path might contain '%2f' = '/',
1675 1678 # which we should *not* escape.
1676 1679 s += urllib.quote(self.path, safe=self._safepchars)
1677 1680 if self.query:
1678 1681 # we store the query in escaped form.
1679 1682 s += '?' + self.query
1680 1683 if self.fragment is not None:
1681 1684 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1682 1685 return s
1683 1686
1684 1687 def authinfo(self):
1685 1688 user, passwd = self.user, self.passwd
1686 1689 try:
1687 1690 self.user, self.passwd = None, None
1688 1691 s = str(self)
1689 1692 finally:
1690 1693 self.user, self.passwd = user, passwd
1691 1694 if not self.user:
1692 1695 return (s, None)
1693 1696 # authinfo[1] is passed to urllib2 password manager, and its
1694 1697 # URIs must not contain credentials. The host is passed in the
1695 1698 # URIs list because Python < 2.4.3 uses only that to search for
1696 1699 # a password.
1697 1700 return (s, (None, (s, self.host),
1698 1701 self.user, self.passwd or ''))
1699 1702
1700 1703 def isabs(self):
1701 1704 if self.scheme and self.scheme != 'file':
1702 1705 return True # remote URL
1703 1706 if hasdriveletter(self.path):
1704 1707 return True # absolute for our purposes - can't be joined()
1705 1708 if self.path.startswith(r'\\'):
1706 1709 return True # Windows UNC path
1707 1710 if self.path.startswith('/'):
1708 1711 return True # POSIX-style
1709 1712 return False
1710 1713
1711 1714 def localpath(self):
1712 1715 if self.scheme == 'file' or self.scheme == 'bundle':
1713 1716 path = self.path or '/'
1714 1717 # For Windows, we need to promote hosts containing drive
1715 1718 # letters to paths with drive letters.
1716 1719 if hasdriveletter(self._hostport):
1717 1720 path = self._hostport + '/' + self.path
1718 1721 elif (self.host is not None and self.path
1719 1722 and not hasdriveletter(path)):
1720 1723 path = '/' + path
1721 1724 return path
1722 1725 return self._origpath
1723 1726
1724 1727 def hasscheme(path):
1725 1728 return bool(url(path).scheme)
1726 1729
1727 1730 def hasdriveletter(path):
1728 1731 return path and path[1:2] == ':' and path[0:1].isalpha()
1729 1732
1730 1733 def urllocalpath(path):
1731 1734 return url(path, parsequery=False, parsefragment=False).localpath()
1732 1735
1733 1736 def hidepassword(u):
1734 1737 '''hide user credential in a url string'''
1735 1738 u = url(u)
1736 1739 if u.passwd:
1737 1740 u.passwd = '***'
1738 1741 return str(u)
1739 1742
1740 1743 def removeauth(u):
1741 1744 '''remove all authentication information from a url string'''
1742 1745 u = url(u)
1743 1746 u.user = u.passwd = None
1744 1747 return str(u)
1745 1748
1746 1749 def isatty(fd):
1747 1750 try:
1748 1751 return fd.isatty()
1749 1752 except AttributeError:
1750 1753 return False
@@ -1,316 +1,319 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import osutil
10 10 import errno, msvcrt, os, re, sys
11 11
12 12 import win32
13 13 executablepath = win32.executablepath
14 14 getuser = win32.getuser
15 15 hidewindow = win32.hidewindow
16 16 lookupreg = win32.lookupreg
17 17 makedir = win32.makedir
18 18 nlinks = win32.nlinks
19 19 oslink = win32.oslink
20 20 samedevice = win32.samedevice
21 21 samefile = win32.samefile
22 22 setsignalhandler = win32.setsignalhandler
23 23 spawndetached = win32.spawndetached
24 24 termwidth = win32.termwidth
25 25 testpid = win32.testpid
26 26 unlink = win32.unlink
27 27
28 28 nulldev = 'NUL:'
29 29 umask = 002
30 30
31 31 # wrap osutil.posixfile to provide friendlier exceptions
32 32 def posixfile(name, mode='r', buffering=-1):
33 33 try:
34 34 return osutil.posixfile(name, mode, buffering)
35 35 except WindowsError, err:
36 36 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
37 37 posixfile.__doc__ = osutil.posixfile.__doc__
38 38
39 39 class winstdout(object):
40 40 '''stdout on windows misbehaves if sent through a pipe'''
41 41
42 42 def __init__(self, fp):
43 43 self.fp = fp
44 44
45 45 def __getattr__(self, key):
46 46 return getattr(self.fp, key)
47 47
48 48 def close(self):
49 49 try:
50 50 self.fp.close()
51 51 except IOError:
52 52 pass
53 53
54 54 def write(self, s):
55 55 try:
56 56 # This is workaround for "Not enough space" error on
57 57 # writing large size of data to console.
58 58 limit = 16000
59 59 l = len(s)
60 60 start = 0
61 61 self.softspace = 0
62 62 while start < l:
63 63 end = start + limit
64 64 self.fp.write(s[start:end])
65 65 start = end
66 66 except IOError, inst:
67 67 if inst.errno != 0:
68 68 raise
69 69 self.close()
70 70 raise IOError(errno.EPIPE, 'Broken pipe')
71 71
72 72 def flush(self):
73 73 try:
74 74 return self.fp.flush()
75 75 except IOError, inst:
76 76 if inst.errno != errno.EINVAL:
77 77 raise
78 78 self.close()
79 79 raise IOError(errno.EPIPE, 'Broken pipe')
80 80
81 81 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
82 82
83 83 def _is_win_9x():
84 84 '''return true if run on windows 95, 98 or me.'''
85 85 try:
86 86 return sys.getwindowsversion()[3] == 1
87 87 except AttributeError:
88 88 return 'command' in os.environ.get('comspec', '')
89 89
90 90 def openhardlinks():
91 91 return not _is_win_9x()
92 92
93 93 def parsepatchoutput(output_line):
94 94 """parses the output produced by patch and returns the filename"""
95 95 pf = output_line[14:]
96 96 if pf[0] == '`':
97 97 pf = pf[1:-1] # Remove the quotes
98 98 return pf
99 99
100 100 def sshargs(sshcmd, host, user, port):
101 101 '''Build argument list for ssh or Plink'''
102 102 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
103 103 args = user and ("%s@%s" % (user, host)) or host
104 104 return port and ("%s %s %s" % (args, pflag, port)) or args
105 105
106 106 def setflags(f, l, x):
107 107 pass
108 108
109 109 def copymode(src, dst, mode=None):
110 110 pass
111 111
112 112 def checkexec(path):
113 113 return False
114 114
115 115 def checklink(path):
116 116 return False
117 117
118 118 def setbinary(fd):
119 119 # When run without console, pipes may expose invalid
120 120 # fileno(), usually set to -1.
121 121 fno = getattr(fd, 'fileno', None)
122 122 if fno is not None and fno() >= 0:
123 123 msvcrt.setmode(fno(), os.O_BINARY)
124 124
125 125 def pconvert(path):
126 126 return '/'.join(path.split(os.sep))
127 127
128 128 def localpath(path):
129 129 return path.replace('/', '\\')
130 130
131 131 def normpath(path):
132 132 return pconvert(os.path.normpath(path))
133 133
134 encodinglower = None
135 encodingupper = None
136
134 137 def normcase(path):
135 return path.upper()
138 return encodingupper(path)
136 139
137 140 def realpath(path):
138 141 '''
139 142 Returns the true, canonical file system path equivalent to the given
140 143 path.
141 144 '''
142 145 # TODO: There may be a more clever way to do this that also handles other,
143 146 # less common file systems.
144 147 return os.path.normpath(normcase(os.path.realpath(path)))
145 148
146 149 def samestat(s1, s2):
147 150 return False
148 151
149 152 # A sequence of backslashes is special iff it precedes a double quote:
150 153 # - if there's an even number of backslashes, the double quote is not
151 154 # quoted (i.e. it ends the quoted region)
152 155 # - if there's an odd number of backslashes, the double quote is quoted
153 156 # - in both cases, every pair of backslashes is unquoted into a single
154 157 # backslash
155 158 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
156 159 # So, to quote a string, we must surround it in double quotes, double
157 160 # the number of backslashes that preceed double quotes and add another
158 161 # backslash before every double quote (being careful with the double
159 162 # quote we've appended to the end)
160 163 _quotere = None
161 164 def shellquote(s):
162 165 global _quotere
163 166 if _quotere is None:
164 167 _quotere = re.compile(r'(\\*)("|\\$)')
165 168 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
166 169
167 170 def quotecommand(cmd):
168 171 """Build a command string suitable for os.popen* calls."""
169 172 if sys.version_info < (2, 7, 1):
170 173 # Python versions since 2.7.1 do this extra quoting themselves
171 174 return '"' + cmd + '"'
172 175 return cmd
173 176
174 177 def popen(command, mode='r'):
175 178 # Work around "popen spawned process may not write to stdout
176 179 # under windows"
177 180 # http://bugs.python.org/issue1366
178 181 command += " 2> %s" % nulldev
179 182 return os.popen(quotecommand(command), mode)
180 183
181 184 def explainexit(code):
182 185 return _("exited with status %d") % code, code
183 186
184 187 # if you change this stub into a real check, please try to implement the
185 188 # username and groupname functions above, too.
186 189 def isowner(st):
187 190 return True
188 191
189 192 def findexe(command):
190 193 '''Find executable for command searching like cmd.exe does.
191 194 If command is a basename then PATH is searched for command.
192 195 PATH isn't searched if command is an absolute or relative path.
193 196 An extension from PATHEXT is found and added if not present.
194 197 If command isn't found None is returned.'''
195 198 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
196 199 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
197 200 if os.path.splitext(command)[1].lower() in pathexts:
198 201 pathexts = ['']
199 202
200 203 def findexisting(pathcommand):
201 204 'Will append extension (if needed) and return existing file'
202 205 for ext in pathexts:
203 206 executable = pathcommand + ext
204 207 if os.path.exists(executable):
205 208 return executable
206 209 return None
207 210
208 211 if os.sep in command:
209 212 return findexisting(command)
210 213
211 214 for path in os.environ.get('PATH', '').split(os.pathsep):
212 215 executable = findexisting(os.path.join(path, command))
213 216 if executable is not None:
214 217 return executable
215 218 return findexisting(os.path.expanduser(os.path.expandvars(command)))
216 219
217 220 def statfiles(files):
218 221 '''Stat each file in files and yield stat or None if file does not exist.
219 222 Cluster and cache stat per directory to minimize number of OS stat calls.'''
220 223 dircache = {} # dirname -> filename -> status | None if file does not exist
221 224 for nf in files:
222 225 nf = normcase(nf)
223 226 dir, base = os.path.split(nf)
224 227 if not dir:
225 228 dir = '.'
226 229 cache = dircache.get(dir, None)
227 230 if cache is None:
228 231 try:
229 232 dmap = dict([(normcase(n), s)
230 233 for n, k, s in osutil.listdir(dir, True)])
231 234 except OSError, err:
232 235 # handle directory not found in Python version prior to 2.5
233 236 # Python <= 2.4 returns native Windows code 3 in errno
234 237 # Python >= 2.5 returns ENOENT and adds winerror field
235 238 # EINVAL is raised if dir is not a directory.
236 239 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
237 240 errno.ENOTDIR):
238 241 raise
239 242 dmap = {}
240 243 cache = dircache.setdefault(dir, dmap)
241 244 yield cache.get(base, None)
242 245
243 246 def username(uid=None):
244 247 """Return the name of the user with the given uid.
245 248
246 249 If uid is None, return the name of the current user."""
247 250 return None
248 251
249 252 def groupname(gid=None):
250 253 """Return the name of the group with the given gid.
251 254
252 255 If gid is None, return the name of the current group."""
253 256 return None
254 257
255 258 def _removedirs(name):
256 259 """special version of os.removedirs that does not remove symlinked
257 260 directories or junction points if they actually contain files"""
258 261 if osutil.listdir(name):
259 262 return
260 263 os.rmdir(name)
261 264 head, tail = os.path.split(name)
262 265 if not tail:
263 266 head, tail = os.path.split(head)
264 267 while head and tail:
265 268 try:
266 269 if osutil.listdir(head):
267 270 return
268 271 os.rmdir(head)
269 272 except (ValueError, OSError):
270 273 break
271 274 head, tail = os.path.split(head)
272 275
273 276 def unlinkpath(f):
274 277 """unlink and remove the directory if it is empty"""
275 278 unlink(f)
276 279 # try removing directories that might now be empty
277 280 try:
278 281 _removedirs(os.path.dirname(f))
279 282 except OSError:
280 283 pass
281 284
282 285 def rename(src, dst):
283 286 '''atomically rename file src to dst, replacing dst if it exists'''
284 287 try:
285 288 os.rename(src, dst)
286 289 except OSError, e:
287 290 if e.errno != errno.EEXIST:
288 291 raise
289 292 unlink(dst)
290 293 os.rename(src, dst)
291 294
292 295 def gethgcmd():
293 296 return [sys.executable] + sys.argv[:1]
294 297
295 298 def termwidth():
296 299 # cmd.exe does not handle CR like a unix console, the CR is
297 300 # counted in the line length. On 80 columns consoles, if 80
298 301 # characters are written, the following CR won't apply on the
299 302 # current line but on the new one. Keep room for it.
300 303 return 79
301 304
302 305 def groupmembers(name):
303 306 # Don't support groups on Windows for now
304 307 raise KeyError()
305 308
306 309 def isexec(f):
307 310 return False
308 311
309 312 class cachestat(object):
310 313 def __init__(self, path):
311 314 pass
312 315
313 316 def cacheable(self):
314 317 return False
315 318
316 319 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now