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