##// END OF EJS Templates
osutil: proxy through util (and platform) modules (API)...
Yuya Nishihara -
r32203:d74b0cff default
parent child Browse files
Show More
@@ -1,194 +1,195
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 from __future__ import absolute_import
47 from __future__ import absolute_import
48
48
49 import os
49 import os
50 import sys
50 import sys
51
51
52 from mercurial.i18n import _
52 from mercurial.i18n import _
53 from mercurial import (
53 from mercurial import (
54 encoding,
54 encoding,
55 error,
55 error,
56 pycompat,
56 pycompat,
57 )
57 )
58
58
59 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
59 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
60 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
60 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
61 # be specifying the version(s) of Mercurial they are tested with, or
61 # be specifying the version(s) of Mercurial they are tested with, or
62 # leave the attribute unspecified.
62 # leave the attribute unspecified.
63 testedwith = 'ships-with-hg-core'
63 testedwith = 'ships-with-hg-core'
64
64
65 _encoding = None # see extsetup
65 _encoding = None # see extsetup
66
66
67 def decode(arg):
67 def decode(arg):
68 if isinstance(arg, str):
68 if isinstance(arg, str):
69 uarg = arg.decode(_encoding)
69 uarg = arg.decode(_encoding)
70 if arg == uarg.encode(_encoding):
70 if arg == uarg.encode(_encoding):
71 return uarg
71 return uarg
72 raise UnicodeError("Not local encoding")
72 raise UnicodeError("Not local encoding")
73 elif isinstance(arg, tuple):
73 elif isinstance(arg, tuple):
74 return tuple(map(decode, arg))
74 return tuple(map(decode, arg))
75 elif isinstance(arg, list):
75 elif isinstance(arg, list):
76 return map(decode, arg)
76 return map(decode, arg)
77 elif isinstance(arg, dict):
77 elif isinstance(arg, dict):
78 for k, v in arg.items():
78 for k, v in arg.items():
79 arg[k] = decode(v)
79 arg[k] = decode(v)
80 return arg
80 return arg
81
81
82 def encode(arg):
82 def encode(arg):
83 if isinstance(arg, unicode):
83 if isinstance(arg, unicode):
84 return arg.encode(_encoding)
84 return arg.encode(_encoding)
85 elif isinstance(arg, tuple):
85 elif isinstance(arg, tuple):
86 return tuple(map(encode, arg))
86 return tuple(map(encode, arg))
87 elif isinstance(arg, list):
87 elif isinstance(arg, list):
88 return map(encode, arg)
88 return map(encode, arg)
89 elif isinstance(arg, dict):
89 elif isinstance(arg, dict):
90 for k, v in arg.items():
90 for k, v in arg.items():
91 arg[k] = encode(v)
91 arg[k] = encode(v)
92 return arg
92 return arg
93
93
94 def appendsep(s):
94 def appendsep(s):
95 # ensure the path ends with os.sep, appending it if necessary.
95 # ensure the path ends with os.sep, appending it if necessary.
96 try:
96 try:
97 us = decode(s)
97 us = decode(s)
98 except UnicodeError:
98 except UnicodeError:
99 us = s
99 us = s
100 if us and us[-1] not in ':/\\':
100 if us and us[-1] not in ':/\\':
101 s += pycompat.ossep
101 s += pycompat.ossep
102 return s
102 return s
103
103
104
104
105 def basewrapper(func, argtype, enc, dec, args, kwds):
105 def basewrapper(func, argtype, enc, dec, args, kwds):
106 # check check already converted, then call original
106 # check check already converted, then call original
107 for arg in args:
107 for arg in args:
108 if isinstance(arg, argtype):
108 if isinstance(arg, argtype):
109 return func(*args, **kwds)
109 return func(*args, **kwds)
110
110
111 try:
111 try:
112 # convert string arguments, call func, then convert back the
112 # convert string arguments, call func, then convert back the
113 # return value.
113 # return value.
114 return enc(func(*dec(args), **dec(kwds)))
114 return enc(func(*dec(args), **dec(kwds)))
115 except UnicodeError:
115 except UnicodeError:
116 raise error.Abort(_("[win32mbcs] filename conversion failed with"
116 raise error.Abort(_("[win32mbcs] filename conversion failed with"
117 " %s encoding\n") % (_encoding))
117 " %s encoding\n") % (_encoding))
118
118
119 def wrapper(func, args, kwds):
119 def wrapper(func, args, kwds):
120 return basewrapper(func, unicode, encode, decode, args, kwds)
120 return basewrapper(func, unicode, encode, decode, args, kwds)
121
121
122
122
123 def reversewrapper(func, args, kwds):
123 def reversewrapper(func, args, kwds):
124 return basewrapper(func, str, decode, encode, args, kwds)
124 return basewrapper(func, str, decode, encode, args, kwds)
125
125
126 def wrapperforlistdir(func, args, kwds):
126 def wrapperforlistdir(func, args, kwds):
127 # Ensure 'path' argument ends with os.sep to avoids
127 # Ensure 'path' argument ends with os.sep to avoids
128 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
128 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
129 if args:
129 if args:
130 args = list(args)
130 args = list(args)
131 args[0] = appendsep(args[0])
131 args[0] = appendsep(args[0])
132 if 'path' in kwds:
132 if 'path' in kwds:
133 kwds['path'] = appendsep(kwds['path'])
133 kwds['path'] = appendsep(kwds['path'])
134 return func(*args, **kwds)
134 return func(*args, **kwds)
135
135
136 def wrapname(name, wrapper):
136 def wrapname(name, wrapper):
137 module, name = name.rsplit('.', 1)
137 module, name = name.rsplit('.', 1)
138 module = sys.modules[module]
138 module = sys.modules[module]
139 func = getattr(module, name)
139 func = getattr(module, name)
140 def f(*args, **kwds):
140 def f(*args, **kwds):
141 return wrapper(func, args, kwds)
141 return wrapper(func, args, kwds)
142 f.__name__ = func.__name__
142 f.__name__ = func.__name__
143 setattr(module, name, f)
143 setattr(module, name, f)
144
144
145 # List of functions to be wrapped.
145 # List of functions to be wrapped.
146 # NOTE: os.path.dirname() and os.path.basename() are safe because
146 # NOTE: os.path.dirname() and os.path.basename() are safe because
147 # they use result of os.path.split()
147 # they use result of os.path.split()
148 funcs = '''os.path.join os.path.split os.path.splitext
148 funcs = '''os.path.join os.path.split os.path.splitext
149 os.path.normpath os.makedirs mercurial.util.endswithsep
149 os.path.normpath os.makedirs mercurial.util.endswithsep
150 mercurial.util.splitpath mercurial.util.fscasesensitive
150 mercurial.util.splitpath mercurial.util.fscasesensitive
151 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
151 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
152 mercurial.util.checkwinfilename mercurial.util.checkosfilename
152 mercurial.util.checkwinfilename mercurial.util.checkosfilename
153 mercurial.util.split'''
153 mercurial.util.split'''
154
154
155 # These functions are required to be called with local encoded string
155 # These functions are required to be called with local encoded string
156 # because they expects argument is local encoded string and cause
156 # because they expects argument is local encoded string and cause
157 # problem with unicode string.
157 # problem with unicode string.
158 rfuncs = '''mercurial.encoding.upper mercurial.encoding.lower'''
158 rfuncs = '''mercurial.encoding.upper mercurial.encoding.lower'''
159
159
160 # List of Windows specific functions to be wrapped.
160 # List of Windows specific functions to be wrapped.
161 winfuncs = '''os.path.splitunc'''
161 winfuncs = '''os.path.splitunc'''
162
162
163 # codec and alias names of sjis and big5 to be faked.
163 # codec and alias names of sjis and big5 to be faked.
164 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
164 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
165 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
165 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
166 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
166 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
167 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
167 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
168
168
169 def extsetup(ui):
169 def extsetup(ui):
170 # TODO: decide use of config section for this extension
170 # TODO: decide use of config section for this extension
171 if ((not os.path.supports_unicode_filenames) and
171 if ((not os.path.supports_unicode_filenames) and
172 (pycompat.sysplatform != 'cygwin')):
172 (pycompat.sysplatform != 'cygwin')):
173 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
173 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
174 return
174 return
175 # determine encoding for filename
175 # determine encoding for filename
176 global _encoding
176 global _encoding
177 _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
177 _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
178 # fake is only for relevant environment.
178 # fake is only for relevant environment.
179 if _encoding.lower() in problematic_encodings.split():
179 if _encoding.lower() in problematic_encodings.split():
180 for f in funcs.split():
180 for f in funcs.split():
181 wrapname(f, wrapper)
181 wrapname(f, wrapper)
182 if pycompat.osname == 'nt':
182 if pycompat.osname == 'nt':
183 for f in winfuncs.split():
183 for f in winfuncs.split():
184 wrapname(f, wrapper)
184 wrapname(f, wrapper)
185 wrapname("mercurial.osutil.listdir", wrapperforlistdir)
185 wrapname("mercurial.util.listdir", wrapperforlistdir)
186 wrapname("mercurial.windows.listdir", wrapperforlistdir)
186 # wrap functions to be called with local byte string arguments
187 # wrap functions to be called with local byte string arguments
187 for f in rfuncs.split():
188 for f in rfuncs.split():
188 wrapname(f, reversewrapper)
189 wrapname(f, reversewrapper)
189 # Check sys.args manually instead of using ui.debug() because
190 # Check sys.args manually instead of using ui.debug() because
190 # command line options is not yet applied when
191 # command line options is not yet applied when
191 # extensions.loadall() is called.
192 # extensions.loadall() is called.
192 if '--debug' in sys.argv:
193 if '--debug' in sys.argv:
193 ui.write(("[win32mbcs] activated with encoding: %s\n")
194 ui.write(("[win32mbcs] activated with encoding: %s\n")
194 % _encoding)
195 % _encoding)
@@ -1,578 +1,577
1 # chgserver.py - command server extension for cHg
1 # chgserver.py - command server extension for cHg
2 #
2 #
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
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 """command server extension for cHg
8 """command server extension for cHg
9
9
10 'S' channel (read/write)
10 'S' channel (read/write)
11 propagate ui.system() request to client
11 propagate ui.system() request to client
12
12
13 'attachio' command
13 'attachio' command
14 attach client's stdio passed by sendmsg()
14 attach client's stdio passed by sendmsg()
15
15
16 'chdir' command
16 'chdir' command
17 change current directory
17 change current directory
18
18
19 'setenv' command
19 'setenv' command
20 replace os.environ completely
20 replace os.environ completely
21
21
22 'setumask' command
22 'setumask' command
23 set umask
23 set umask
24
24
25 'validate' command
25 'validate' command
26 reload the config and check if the server is up to date
26 reload the config and check if the server is up to date
27
27
28 Config
28 Config
29 ------
29 ------
30
30
31 ::
31 ::
32
32
33 [chgserver]
33 [chgserver]
34 # how long (in seconds) should an idle chg server exit
34 # how long (in seconds) should an idle chg server exit
35 idletimeout = 3600
35 idletimeout = 3600
36
36
37 # whether to skip config or env change checks
37 # whether to skip config or env change checks
38 skiphash = False
38 skiphash = False
39 """
39 """
40
40
41 from __future__ import absolute_import
41 from __future__ import absolute_import
42
42
43 import hashlib
43 import hashlib
44 import inspect
44 import inspect
45 import os
45 import os
46 import re
46 import re
47 import struct
47 import struct
48 import time
48 import time
49
49
50 from .i18n import _
50 from .i18n import _
51
51
52 from . import (
52 from . import (
53 commandserver,
53 commandserver,
54 encoding,
54 encoding,
55 error,
55 error,
56 extensions,
56 extensions,
57 osutil,
58 pycompat,
57 pycompat,
59 util,
58 util,
60 )
59 )
61
60
62 _log = commandserver.log
61 _log = commandserver.log
63
62
64 def _hashlist(items):
63 def _hashlist(items):
65 """return sha1 hexdigest for a list"""
64 """return sha1 hexdigest for a list"""
66 return hashlib.sha1(str(items)).hexdigest()
65 return hashlib.sha1(str(items)).hexdigest()
67
66
68 # sensitive config sections affecting confighash
67 # sensitive config sections affecting confighash
69 _configsections = [
68 _configsections = [
70 'alias', # affects global state commands.table
69 'alias', # affects global state commands.table
71 'extdiff', # uisetup will register new commands
70 'extdiff', # uisetup will register new commands
72 'extensions',
71 'extensions',
73 ]
72 ]
74
73
75 # sensitive environment variables affecting confighash
74 # sensitive environment variables affecting confighash
76 _envre = re.compile(r'''\A(?:
75 _envre = re.compile(r'''\A(?:
77 CHGHG
76 CHGHG
78 |HG(?:[A-Z].*)?
77 |HG(?:[A-Z].*)?
79 |LANG(?:UAGE)?
78 |LANG(?:UAGE)?
80 |LC_.*
79 |LC_.*
81 |LD_.*
80 |LD_.*
82 |PATH
81 |PATH
83 |PYTHON.*
82 |PYTHON.*
84 |TERM(?:INFO)?
83 |TERM(?:INFO)?
85 |TZ
84 |TZ
86 )\Z''', re.X)
85 )\Z''', re.X)
87
86
88 def _confighash(ui):
87 def _confighash(ui):
89 """return a quick hash for detecting config/env changes
88 """return a quick hash for detecting config/env changes
90
89
91 confighash is the hash of sensitive config items and environment variables.
90 confighash is the hash of sensitive config items and environment variables.
92
91
93 for chgserver, it is designed that once confighash changes, the server is
92 for chgserver, it is designed that once confighash changes, the server is
94 not qualified to serve its client and should redirect the client to a new
93 not qualified to serve its client and should redirect the client to a new
95 server. different from mtimehash, confighash change will not mark the
94 server. different from mtimehash, confighash change will not mark the
96 server outdated and exit since the user can have different configs at the
95 server outdated and exit since the user can have different configs at the
97 same time.
96 same time.
98 """
97 """
99 sectionitems = []
98 sectionitems = []
100 for section in _configsections:
99 for section in _configsections:
101 sectionitems.append(ui.configitems(section))
100 sectionitems.append(ui.configitems(section))
102 sectionhash = _hashlist(sectionitems)
101 sectionhash = _hashlist(sectionitems)
103 envitems = [(k, v) for k, v in encoding.environ.iteritems()
102 envitems = [(k, v) for k, v in encoding.environ.iteritems()
104 if _envre.match(k)]
103 if _envre.match(k)]
105 envhash = _hashlist(sorted(envitems))
104 envhash = _hashlist(sorted(envitems))
106 return sectionhash[:6] + envhash[:6]
105 return sectionhash[:6] + envhash[:6]
107
106
108 def _getmtimepaths(ui):
107 def _getmtimepaths(ui):
109 """get a list of paths that should be checked to detect change
108 """get a list of paths that should be checked to detect change
110
109
111 The list will include:
110 The list will include:
112 - extensions (will not cover all files for complex extensions)
111 - extensions (will not cover all files for complex extensions)
113 - mercurial/__version__.py
112 - mercurial/__version__.py
114 - python binary
113 - python binary
115 """
114 """
116 modules = [m for n, m in extensions.extensions(ui)]
115 modules = [m for n, m in extensions.extensions(ui)]
117 try:
116 try:
118 from . import __version__
117 from . import __version__
119 modules.append(__version__)
118 modules.append(__version__)
120 except ImportError:
119 except ImportError:
121 pass
120 pass
122 files = [pycompat.sysexecutable]
121 files = [pycompat.sysexecutable]
123 for m in modules:
122 for m in modules:
124 try:
123 try:
125 files.append(inspect.getabsfile(m))
124 files.append(inspect.getabsfile(m))
126 except TypeError:
125 except TypeError:
127 pass
126 pass
128 return sorted(set(files))
127 return sorted(set(files))
129
128
130 def _mtimehash(paths):
129 def _mtimehash(paths):
131 """return a quick hash for detecting file changes
130 """return a quick hash for detecting file changes
132
131
133 mtimehash calls stat on given paths and calculate a hash based on size and
132 mtimehash calls stat on given paths and calculate a hash based on size and
134 mtime of each file. mtimehash does not read file content because reading is
133 mtime of each file. mtimehash does not read file content because reading is
135 expensive. therefore it's not 100% reliable for detecting content changes.
134 expensive. therefore it's not 100% reliable for detecting content changes.
136 it's possible to return different hashes for same file contents.
135 it's possible to return different hashes for same file contents.
137 it's also possible to return a same hash for different file contents for
136 it's also possible to return a same hash for different file contents for
138 some carefully crafted situation.
137 some carefully crafted situation.
139
138
140 for chgserver, it is designed that once mtimehash changes, the server is
139 for chgserver, it is designed that once mtimehash changes, the server is
141 considered outdated immediately and should no longer provide service.
140 considered outdated immediately and should no longer provide service.
142
141
143 mtimehash is not included in confighash because we only know the paths of
142 mtimehash is not included in confighash because we only know the paths of
144 extensions after importing them (there is imp.find_module but that faces
143 extensions after importing them (there is imp.find_module but that faces
145 race conditions). We need to calculate confighash without importing.
144 race conditions). We need to calculate confighash without importing.
146 """
145 """
147 def trystat(path):
146 def trystat(path):
148 try:
147 try:
149 st = os.stat(path)
148 st = os.stat(path)
150 return (st.st_mtime, st.st_size)
149 return (st.st_mtime, st.st_size)
151 except OSError:
150 except OSError:
152 # could be ENOENT, EPERM etc. not fatal in any case
151 # could be ENOENT, EPERM etc. not fatal in any case
153 pass
152 pass
154 return _hashlist(map(trystat, paths))[:12]
153 return _hashlist(map(trystat, paths))[:12]
155
154
156 class hashstate(object):
155 class hashstate(object):
157 """a structure storing confighash, mtimehash, paths used for mtimehash"""
156 """a structure storing confighash, mtimehash, paths used for mtimehash"""
158 def __init__(self, confighash, mtimehash, mtimepaths):
157 def __init__(self, confighash, mtimehash, mtimepaths):
159 self.confighash = confighash
158 self.confighash = confighash
160 self.mtimehash = mtimehash
159 self.mtimehash = mtimehash
161 self.mtimepaths = mtimepaths
160 self.mtimepaths = mtimepaths
162
161
163 @staticmethod
162 @staticmethod
164 def fromui(ui, mtimepaths=None):
163 def fromui(ui, mtimepaths=None):
165 if mtimepaths is None:
164 if mtimepaths is None:
166 mtimepaths = _getmtimepaths(ui)
165 mtimepaths = _getmtimepaths(ui)
167 confighash = _confighash(ui)
166 confighash = _confighash(ui)
168 mtimehash = _mtimehash(mtimepaths)
167 mtimehash = _mtimehash(mtimepaths)
169 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
168 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
170 return hashstate(confighash, mtimehash, mtimepaths)
169 return hashstate(confighash, mtimehash, mtimepaths)
171
170
172 def _newchgui(srcui, csystem, attachio):
171 def _newchgui(srcui, csystem, attachio):
173 class chgui(srcui.__class__):
172 class chgui(srcui.__class__):
174 def __init__(self, src=None):
173 def __init__(self, src=None):
175 super(chgui, self).__init__(src)
174 super(chgui, self).__init__(src)
176 if src:
175 if src:
177 self._csystem = getattr(src, '_csystem', csystem)
176 self._csystem = getattr(src, '_csystem', csystem)
178 else:
177 else:
179 self._csystem = csystem
178 self._csystem = csystem
180
179
181 def _runsystem(self, cmd, environ, cwd, out):
180 def _runsystem(self, cmd, environ, cwd, out):
182 # fallback to the original system method if the output needs to be
181 # fallback to the original system method if the output needs to be
183 # captured (to self._buffers), or the output stream is not stdout
182 # captured (to self._buffers), or the output stream is not stdout
184 # (e.g. stderr, cStringIO), because the chg client is not aware of
183 # (e.g. stderr, cStringIO), because the chg client is not aware of
185 # these situations and will behave differently (write to stdout).
184 # these situations and will behave differently (write to stdout).
186 if (out is not self.fout
185 if (out is not self.fout
187 or not util.safehasattr(self.fout, 'fileno')
186 or not util.safehasattr(self.fout, 'fileno')
188 or self.fout.fileno() != util.stdout.fileno()):
187 or self.fout.fileno() != util.stdout.fileno()):
189 return util.system(cmd, environ=environ, cwd=cwd, out=out)
188 return util.system(cmd, environ=environ, cwd=cwd, out=out)
190 self.flush()
189 self.flush()
191 return self._csystem(cmd, util.shellenviron(environ), cwd)
190 return self._csystem(cmd, util.shellenviron(environ), cwd)
192
191
193 def _runpager(self, cmd, env=None):
192 def _runpager(self, cmd, env=None):
194 self._csystem(cmd, util.shellenviron(env), type='pager',
193 self._csystem(cmd, util.shellenviron(env), type='pager',
195 cmdtable={'attachio': attachio})
194 cmdtable={'attachio': attachio})
196 return True
195 return True
197
196
198 return chgui(srcui)
197 return chgui(srcui)
199
198
200 def _loadnewui(srcui, args):
199 def _loadnewui(srcui, args):
201 from . import dispatch # avoid cycle
200 from . import dispatch # avoid cycle
202
201
203 newui = srcui.__class__.load()
202 newui = srcui.__class__.load()
204 for a in ['fin', 'fout', 'ferr', 'environ']:
203 for a in ['fin', 'fout', 'ferr', 'environ']:
205 setattr(newui, a, getattr(srcui, a))
204 setattr(newui, a, getattr(srcui, a))
206 if util.safehasattr(srcui, '_csystem'):
205 if util.safehasattr(srcui, '_csystem'):
207 newui._csystem = srcui._csystem
206 newui._csystem = srcui._csystem
208
207
209 # command line args
208 # command line args
210 args = args[:]
209 args = args[:]
211 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
210 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
212
211
213 # stolen from tortoisehg.util.copydynamicconfig()
212 # stolen from tortoisehg.util.copydynamicconfig()
214 for section, name, value in srcui.walkconfig():
213 for section, name, value in srcui.walkconfig():
215 source = srcui.configsource(section, name)
214 source = srcui.configsource(section, name)
216 if ':' in source or source == '--config' or source.startswith('$'):
215 if ':' in source or source == '--config' or source.startswith('$'):
217 # path:line or command line, or environ
216 # path:line or command line, or environ
218 continue
217 continue
219 newui.setconfig(section, name, value, source)
218 newui.setconfig(section, name, value, source)
220
219
221 # load wd and repo config, copied from dispatch.py
220 # load wd and repo config, copied from dispatch.py
222 cwds = dispatch._earlygetopt(['--cwd'], args)
221 cwds = dispatch._earlygetopt(['--cwd'], args)
223 cwd = cwds and os.path.realpath(cwds[-1]) or None
222 cwd = cwds and os.path.realpath(cwds[-1]) or None
224 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
223 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
225 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
224 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
226
225
227 return (newui, newlui)
226 return (newui, newlui)
228
227
229 class channeledsystem(object):
228 class channeledsystem(object):
230 """Propagate ui.system() request in the following format:
229 """Propagate ui.system() request in the following format:
231
230
232 payload length (unsigned int),
231 payload length (unsigned int),
233 type, '\0',
232 type, '\0',
234 cmd, '\0',
233 cmd, '\0',
235 cwd, '\0',
234 cwd, '\0',
236 envkey, '=', val, '\0',
235 envkey, '=', val, '\0',
237 ...
236 ...
238 envkey, '=', val
237 envkey, '=', val
239
238
240 if type == 'system', waits for:
239 if type == 'system', waits for:
241
240
242 exitcode length (unsigned int),
241 exitcode length (unsigned int),
243 exitcode (int)
242 exitcode (int)
244
243
245 if type == 'pager', repetitively waits for a command name ending with '\n'
244 if type == 'pager', repetitively waits for a command name ending with '\n'
246 and executes it defined by cmdtable, or exits the loop if the command name
245 and executes it defined by cmdtable, or exits the loop if the command name
247 is empty.
246 is empty.
248 """
247 """
249 def __init__(self, in_, out, channel):
248 def __init__(self, in_, out, channel):
250 self.in_ = in_
249 self.in_ = in_
251 self.out = out
250 self.out = out
252 self.channel = channel
251 self.channel = channel
253
252
254 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
253 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
255 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
254 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
256 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
255 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
257 data = '\0'.join(args)
256 data = '\0'.join(args)
258 self.out.write(struct.pack('>cI', self.channel, len(data)))
257 self.out.write(struct.pack('>cI', self.channel, len(data)))
259 self.out.write(data)
258 self.out.write(data)
260 self.out.flush()
259 self.out.flush()
261
260
262 if type == 'system':
261 if type == 'system':
263 length = self.in_.read(4)
262 length = self.in_.read(4)
264 length, = struct.unpack('>I', length)
263 length, = struct.unpack('>I', length)
265 if length != 4:
264 if length != 4:
266 raise error.Abort(_('invalid response'))
265 raise error.Abort(_('invalid response'))
267 rc, = struct.unpack('>i', self.in_.read(4))
266 rc, = struct.unpack('>i', self.in_.read(4))
268 return rc
267 return rc
269 elif type == 'pager':
268 elif type == 'pager':
270 while True:
269 while True:
271 cmd = self.in_.readline()[:-1]
270 cmd = self.in_.readline()[:-1]
272 if not cmd:
271 if not cmd:
273 break
272 break
274 if cmdtable and cmd in cmdtable:
273 if cmdtable and cmd in cmdtable:
275 _log('pager subcommand: %s' % cmd)
274 _log('pager subcommand: %s' % cmd)
276 cmdtable[cmd]()
275 cmdtable[cmd]()
277 else:
276 else:
278 raise error.Abort(_('unexpected command: %s') % cmd)
277 raise error.Abort(_('unexpected command: %s') % cmd)
279 else:
278 else:
280 raise error.ProgrammingError('invalid S channel type: %s' % type)
279 raise error.ProgrammingError('invalid S channel type: %s' % type)
281
280
282 _iochannels = [
281 _iochannels = [
283 # server.ch, ui.fp, mode
282 # server.ch, ui.fp, mode
284 ('cin', 'fin', pycompat.sysstr('rb')),
283 ('cin', 'fin', pycompat.sysstr('rb')),
285 ('cout', 'fout', pycompat.sysstr('wb')),
284 ('cout', 'fout', pycompat.sysstr('wb')),
286 ('cerr', 'ferr', pycompat.sysstr('wb')),
285 ('cerr', 'ferr', pycompat.sysstr('wb')),
287 ]
286 ]
288
287
289 class chgcmdserver(commandserver.server):
288 class chgcmdserver(commandserver.server):
290 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
289 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
291 super(chgcmdserver, self).__init__(
290 super(chgcmdserver, self).__init__(
292 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
291 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
293 repo, fin, fout)
292 repo, fin, fout)
294 self.clientsock = sock
293 self.clientsock = sock
295 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
294 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
296 self.hashstate = hashstate
295 self.hashstate = hashstate
297 self.baseaddress = baseaddress
296 self.baseaddress = baseaddress
298 if hashstate is not None:
297 if hashstate is not None:
299 self.capabilities = self.capabilities.copy()
298 self.capabilities = self.capabilities.copy()
300 self.capabilities['validate'] = chgcmdserver.validate
299 self.capabilities['validate'] = chgcmdserver.validate
301
300
302 def cleanup(self):
301 def cleanup(self):
303 super(chgcmdserver, self).cleanup()
302 super(chgcmdserver, self).cleanup()
304 # dispatch._runcatch() does not flush outputs if exception is not
303 # dispatch._runcatch() does not flush outputs if exception is not
305 # handled by dispatch._dispatch()
304 # handled by dispatch._dispatch()
306 self.ui.flush()
305 self.ui.flush()
307 self._restoreio()
306 self._restoreio()
308
307
309 def attachio(self):
308 def attachio(self):
310 """Attach to client's stdio passed via unix domain socket; all
309 """Attach to client's stdio passed via unix domain socket; all
311 channels except cresult will no longer be used
310 channels except cresult will no longer be used
312 """
311 """
313 # tell client to sendmsg() with 1-byte payload, which makes it
312 # tell client to sendmsg() with 1-byte payload, which makes it
314 # distinctive from "attachio\n" command consumed by client.read()
313 # distinctive from "attachio\n" command consumed by client.read()
315 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
314 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
316 clientfds = osutil.recvfds(self.clientsock.fileno())
315 clientfds = util.recvfds(self.clientsock.fileno())
317 _log('received fds: %r\n' % clientfds)
316 _log('received fds: %r\n' % clientfds)
318
317
319 ui = self.ui
318 ui = self.ui
320 ui.flush()
319 ui.flush()
321 first = self._saveio()
320 first = self._saveio()
322 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
321 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
323 assert fd > 0
322 assert fd > 0
324 fp = getattr(ui, fn)
323 fp = getattr(ui, fn)
325 os.dup2(fd, fp.fileno())
324 os.dup2(fd, fp.fileno())
326 os.close(fd)
325 os.close(fd)
327 if not first:
326 if not first:
328 continue
327 continue
329 # reset buffering mode when client is first attached. as we want
328 # reset buffering mode when client is first attached. as we want
330 # to see output immediately on pager, the mode stays unchanged
329 # to see output immediately on pager, the mode stays unchanged
331 # when client re-attached. ferr is unchanged because it should
330 # when client re-attached. ferr is unchanged because it should
332 # be unbuffered no matter if it is a tty or not.
331 # be unbuffered no matter if it is a tty or not.
333 if fn == 'ferr':
332 if fn == 'ferr':
334 newfp = fp
333 newfp = fp
335 else:
334 else:
336 # make it line buffered explicitly because the default is
335 # make it line buffered explicitly because the default is
337 # decided on first write(), where fout could be a pager.
336 # decided on first write(), where fout could be a pager.
338 if fp.isatty():
337 if fp.isatty():
339 bufsize = 1 # line buffered
338 bufsize = 1 # line buffered
340 else:
339 else:
341 bufsize = -1 # system default
340 bufsize = -1 # system default
342 newfp = os.fdopen(fp.fileno(), mode, bufsize)
341 newfp = os.fdopen(fp.fileno(), mode, bufsize)
343 setattr(ui, fn, newfp)
342 setattr(ui, fn, newfp)
344 setattr(self, cn, newfp)
343 setattr(self, cn, newfp)
345
344
346 self.cresult.write(struct.pack('>i', len(clientfds)))
345 self.cresult.write(struct.pack('>i', len(clientfds)))
347
346
348 def _saveio(self):
347 def _saveio(self):
349 if self._oldios:
348 if self._oldios:
350 return False
349 return False
351 ui = self.ui
350 ui = self.ui
352 for cn, fn, _mode in _iochannels:
351 for cn, fn, _mode in _iochannels:
353 ch = getattr(self, cn)
352 ch = getattr(self, cn)
354 fp = getattr(ui, fn)
353 fp = getattr(ui, fn)
355 fd = os.dup(fp.fileno())
354 fd = os.dup(fp.fileno())
356 self._oldios.append((ch, fp, fd))
355 self._oldios.append((ch, fp, fd))
357 return True
356 return True
358
357
359 def _restoreio(self):
358 def _restoreio(self):
360 ui = self.ui
359 ui = self.ui
361 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
360 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
362 newfp = getattr(ui, fn)
361 newfp = getattr(ui, fn)
363 # close newfp while it's associated with client; otherwise it
362 # close newfp while it's associated with client; otherwise it
364 # would be closed when newfp is deleted
363 # would be closed when newfp is deleted
365 if newfp is not fp:
364 if newfp is not fp:
366 newfp.close()
365 newfp.close()
367 # restore original fd: fp is open again
366 # restore original fd: fp is open again
368 os.dup2(fd, fp.fileno())
367 os.dup2(fd, fp.fileno())
369 os.close(fd)
368 os.close(fd)
370 setattr(self, cn, ch)
369 setattr(self, cn, ch)
371 setattr(ui, fn, fp)
370 setattr(ui, fn, fp)
372 del self._oldios[:]
371 del self._oldios[:]
373
372
374 def validate(self):
373 def validate(self):
375 """Reload the config and check if the server is up to date
374 """Reload the config and check if the server is up to date
376
375
377 Read a list of '\0' separated arguments.
376 Read a list of '\0' separated arguments.
378 Write a non-empty list of '\0' separated instruction strings or '\0'
377 Write a non-empty list of '\0' separated instruction strings or '\0'
379 if the list is empty.
378 if the list is empty.
380 An instruction string could be either:
379 An instruction string could be either:
381 - "unlink $path", the client should unlink the path to stop the
380 - "unlink $path", the client should unlink the path to stop the
382 outdated server.
381 outdated server.
383 - "redirect $path", the client should attempt to connect to $path
382 - "redirect $path", the client should attempt to connect to $path
384 first. If it does not work, start a new server. It implies
383 first. If it does not work, start a new server. It implies
385 "reconnect".
384 "reconnect".
386 - "exit $n", the client should exit directly with code n.
385 - "exit $n", the client should exit directly with code n.
387 This may happen if we cannot parse the config.
386 This may happen if we cannot parse the config.
388 - "reconnect", the client should close the connection and
387 - "reconnect", the client should close the connection and
389 reconnect.
388 reconnect.
390 If neither "reconnect" nor "redirect" is included in the instruction
389 If neither "reconnect" nor "redirect" is included in the instruction
391 list, the client can continue with this server after completing all
390 list, the client can continue with this server after completing all
392 the instructions.
391 the instructions.
393 """
392 """
394 from . import dispatch # avoid cycle
393 from . import dispatch # avoid cycle
395
394
396 args = self._readlist()
395 args = self._readlist()
397 try:
396 try:
398 self.ui, lui = _loadnewui(self.ui, args)
397 self.ui, lui = _loadnewui(self.ui, args)
399 except error.ParseError as inst:
398 except error.ParseError as inst:
400 dispatch._formatparse(self.ui.warn, inst)
399 dispatch._formatparse(self.ui.warn, inst)
401 self.ui.flush()
400 self.ui.flush()
402 self.cresult.write('exit 255')
401 self.cresult.write('exit 255')
403 return
402 return
404 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
403 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
405 insts = []
404 insts = []
406 if newhash.mtimehash != self.hashstate.mtimehash:
405 if newhash.mtimehash != self.hashstate.mtimehash:
407 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
406 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
408 insts.append('unlink %s' % addr)
407 insts.append('unlink %s' % addr)
409 # mtimehash is empty if one or more extensions fail to load.
408 # mtimehash is empty if one or more extensions fail to load.
410 # to be compatible with hg, still serve the client this time.
409 # to be compatible with hg, still serve the client this time.
411 if self.hashstate.mtimehash:
410 if self.hashstate.mtimehash:
412 insts.append('reconnect')
411 insts.append('reconnect')
413 if newhash.confighash != self.hashstate.confighash:
412 if newhash.confighash != self.hashstate.confighash:
414 addr = _hashaddress(self.baseaddress, newhash.confighash)
413 addr = _hashaddress(self.baseaddress, newhash.confighash)
415 insts.append('redirect %s' % addr)
414 insts.append('redirect %s' % addr)
416 _log('validate: %s\n' % insts)
415 _log('validate: %s\n' % insts)
417 self.cresult.write('\0'.join(insts) or '\0')
416 self.cresult.write('\0'.join(insts) or '\0')
418
417
419 def chdir(self):
418 def chdir(self):
420 """Change current directory
419 """Change current directory
421
420
422 Note that the behavior of --cwd option is bit different from this.
421 Note that the behavior of --cwd option is bit different from this.
423 It does not affect --config parameter.
422 It does not affect --config parameter.
424 """
423 """
425 path = self._readstr()
424 path = self._readstr()
426 if not path:
425 if not path:
427 return
426 return
428 _log('chdir to %r\n' % path)
427 _log('chdir to %r\n' % path)
429 os.chdir(path)
428 os.chdir(path)
430
429
431 def setumask(self):
430 def setumask(self):
432 """Change umask"""
431 """Change umask"""
433 mask = struct.unpack('>I', self._read(4))[0]
432 mask = struct.unpack('>I', self._read(4))[0]
434 _log('setumask %r\n' % mask)
433 _log('setumask %r\n' % mask)
435 os.umask(mask)
434 os.umask(mask)
436
435
437 def runcommand(self):
436 def runcommand(self):
438 return super(chgcmdserver, self).runcommand()
437 return super(chgcmdserver, self).runcommand()
439
438
440 def setenv(self):
439 def setenv(self):
441 """Clear and update os.environ
440 """Clear and update os.environ
442
441
443 Note that not all variables can make an effect on the running process.
442 Note that not all variables can make an effect on the running process.
444 """
443 """
445 l = self._readlist()
444 l = self._readlist()
446 try:
445 try:
447 newenv = dict(s.split('=', 1) for s in l)
446 newenv = dict(s.split('=', 1) for s in l)
448 except ValueError:
447 except ValueError:
449 raise ValueError('unexpected value in setenv request')
448 raise ValueError('unexpected value in setenv request')
450 _log('setenv: %r\n' % sorted(newenv.keys()))
449 _log('setenv: %r\n' % sorted(newenv.keys()))
451 encoding.environ.clear()
450 encoding.environ.clear()
452 encoding.environ.update(newenv)
451 encoding.environ.update(newenv)
453
452
454 capabilities = commandserver.server.capabilities.copy()
453 capabilities = commandserver.server.capabilities.copy()
455 capabilities.update({'attachio': attachio,
454 capabilities.update({'attachio': attachio,
456 'chdir': chdir,
455 'chdir': chdir,
457 'runcommand': runcommand,
456 'runcommand': runcommand,
458 'setenv': setenv,
457 'setenv': setenv,
459 'setumask': setumask})
458 'setumask': setumask})
460
459
461 if util.safehasattr(osutil, 'setprocname'):
460 if util.safehasattr(util, 'setprocname'):
462 def setprocname(self):
461 def setprocname(self):
463 """Change process title"""
462 """Change process title"""
464 name = self._readstr()
463 name = self._readstr()
465 _log('setprocname: %r\n' % name)
464 _log('setprocname: %r\n' % name)
466 osutil.setprocname(name)
465 util.setprocname(name)
467 capabilities['setprocname'] = setprocname
466 capabilities['setprocname'] = setprocname
468
467
469 def _tempaddress(address):
468 def _tempaddress(address):
470 return '%s.%d.tmp' % (address, os.getpid())
469 return '%s.%d.tmp' % (address, os.getpid())
471
470
472 def _hashaddress(address, hashstr):
471 def _hashaddress(address, hashstr):
473 # if the basename of address contains '.', use only the left part. this
472 # if the basename of address contains '.', use only the left part. this
474 # makes it possible for the client to pass 'server.tmp$PID' and follow by
473 # makes it possible for the client to pass 'server.tmp$PID' and follow by
475 # an atomic rename to avoid locking when spawning new servers.
474 # an atomic rename to avoid locking when spawning new servers.
476 dirname, basename = os.path.split(address)
475 dirname, basename = os.path.split(address)
477 basename = basename.split('.', 1)[0]
476 basename = basename.split('.', 1)[0]
478 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
477 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
479
478
480 class chgunixservicehandler(object):
479 class chgunixservicehandler(object):
481 """Set of operations for chg services"""
480 """Set of operations for chg services"""
482
481
483 pollinterval = 1 # [sec]
482 pollinterval = 1 # [sec]
484
483
485 def __init__(self, ui):
484 def __init__(self, ui):
486 self.ui = ui
485 self.ui = ui
487 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
486 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
488 self._lastactive = time.time()
487 self._lastactive = time.time()
489
488
490 def bindsocket(self, sock, address):
489 def bindsocket(self, sock, address):
491 self._inithashstate(address)
490 self._inithashstate(address)
492 self._checkextensions()
491 self._checkextensions()
493 self._bind(sock)
492 self._bind(sock)
494 self._createsymlink()
493 self._createsymlink()
495
494
496 def _inithashstate(self, address):
495 def _inithashstate(self, address):
497 self._baseaddress = address
496 self._baseaddress = address
498 if self.ui.configbool('chgserver', 'skiphash', False):
497 if self.ui.configbool('chgserver', 'skiphash', False):
499 self._hashstate = None
498 self._hashstate = None
500 self._realaddress = address
499 self._realaddress = address
501 return
500 return
502 self._hashstate = hashstate.fromui(self.ui)
501 self._hashstate = hashstate.fromui(self.ui)
503 self._realaddress = _hashaddress(address, self._hashstate.confighash)
502 self._realaddress = _hashaddress(address, self._hashstate.confighash)
504
503
505 def _checkextensions(self):
504 def _checkextensions(self):
506 if not self._hashstate:
505 if not self._hashstate:
507 return
506 return
508 if extensions.notloaded():
507 if extensions.notloaded():
509 # one or more extensions failed to load. mtimehash becomes
508 # one or more extensions failed to load. mtimehash becomes
510 # meaningless because we do not know the paths of those extensions.
509 # meaningless because we do not know the paths of those extensions.
511 # set mtimehash to an illegal hash value to invalidate the server.
510 # set mtimehash to an illegal hash value to invalidate the server.
512 self._hashstate.mtimehash = ''
511 self._hashstate.mtimehash = ''
513
512
514 def _bind(self, sock):
513 def _bind(self, sock):
515 # use a unique temp address so we can stat the file and do ownership
514 # use a unique temp address so we can stat the file and do ownership
516 # check later
515 # check later
517 tempaddress = _tempaddress(self._realaddress)
516 tempaddress = _tempaddress(self._realaddress)
518 util.bindunixsocket(sock, tempaddress)
517 util.bindunixsocket(sock, tempaddress)
519 self._socketstat = os.stat(tempaddress)
518 self._socketstat = os.stat(tempaddress)
520 # rename will replace the old socket file if exists atomically. the
519 # rename will replace the old socket file if exists atomically. the
521 # old server will detect ownership change and exit.
520 # old server will detect ownership change and exit.
522 util.rename(tempaddress, self._realaddress)
521 util.rename(tempaddress, self._realaddress)
523
522
524 def _createsymlink(self):
523 def _createsymlink(self):
525 if self._baseaddress == self._realaddress:
524 if self._baseaddress == self._realaddress:
526 return
525 return
527 tempaddress = _tempaddress(self._baseaddress)
526 tempaddress = _tempaddress(self._baseaddress)
528 os.symlink(os.path.basename(self._realaddress), tempaddress)
527 os.symlink(os.path.basename(self._realaddress), tempaddress)
529 util.rename(tempaddress, self._baseaddress)
528 util.rename(tempaddress, self._baseaddress)
530
529
531 def _issocketowner(self):
530 def _issocketowner(self):
532 try:
531 try:
533 stat = os.stat(self._realaddress)
532 stat = os.stat(self._realaddress)
534 return (stat.st_ino == self._socketstat.st_ino and
533 return (stat.st_ino == self._socketstat.st_ino and
535 stat.st_mtime == self._socketstat.st_mtime)
534 stat.st_mtime == self._socketstat.st_mtime)
536 except OSError:
535 except OSError:
537 return False
536 return False
538
537
539 def unlinksocket(self, address):
538 def unlinksocket(self, address):
540 if not self._issocketowner():
539 if not self._issocketowner():
541 return
540 return
542 # it is possible to have a race condition here that we may
541 # it is possible to have a race condition here that we may
543 # remove another server's socket file. but that's okay
542 # remove another server's socket file. but that's okay
544 # since that server will detect and exit automatically and
543 # since that server will detect and exit automatically and
545 # the client will start a new server on demand.
544 # the client will start a new server on demand.
546 util.tryunlink(self._realaddress)
545 util.tryunlink(self._realaddress)
547
546
548 def printbanner(self, address):
547 def printbanner(self, address):
549 # no "listening at" message should be printed to simulate hg behavior
548 # no "listening at" message should be printed to simulate hg behavior
550 pass
549 pass
551
550
552 def shouldexit(self):
551 def shouldexit(self):
553 if not self._issocketowner():
552 if not self._issocketowner():
554 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
553 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
555 return True
554 return True
556 if time.time() - self._lastactive > self._idletimeout:
555 if time.time() - self._lastactive > self._idletimeout:
557 self.ui.debug('being idle too long. exiting.\n')
556 self.ui.debug('being idle too long. exiting.\n')
558 return True
557 return True
559 return False
558 return False
560
559
561 def newconnection(self):
560 def newconnection(self):
562 self._lastactive = time.time()
561 self._lastactive = time.time()
563
562
564 def createcmdserver(self, repo, conn, fin, fout):
563 def createcmdserver(self, repo, conn, fin, fout):
565 return chgcmdserver(self.ui, repo, fin, fout, conn,
564 return chgcmdserver(self.ui, repo, fin, fout, conn,
566 self._hashstate, self._baseaddress)
565 self._hashstate, self._baseaddress)
567
566
568 def chgunixservice(ui, repo, opts):
567 def chgunixservice(ui, repo, opts):
569 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
568 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
570 # start another chg. drop it to avoid possible side effects.
569 # start another chg. drop it to avoid possible side effects.
571 if 'CHGINTERNALMARK' in encoding.environ:
570 if 'CHGINTERNALMARK' in encoding.environ:
572 del encoding.environ['CHGINTERNALMARK']
571 del encoding.environ['CHGINTERNALMARK']
573
572
574 if repo:
573 if repo:
575 # one chgserver can serve multiple repos. drop repo information
574 # one chgserver can serve multiple repos. drop repo information
576 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
575 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
577 h = chgunixservicehandler(ui)
576 h = chgunixservicehandler(ui)
578 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
577 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,1288 +1,1287
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import errno
11 import errno
12 import os
12 import os
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import nullid
16 from .node import nullid
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 match as matchmod,
20 match as matchmod,
21 osutil,
22 parsers,
21 parsers,
23 pathutil,
22 pathutil,
24 pycompat,
23 pycompat,
25 scmutil,
24 scmutil,
26 txnutil,
25 txnutil,
27 util,
26 util,
28 )
27 )
29
28
30 propertycache = util.propertycache
29 propertycache = util.propertycache
31 filecache = scmutil.filecache
30 filecache = scmutil.filecache
32 _rangemask = 0x7fffffff
31 _rangemask = 0x7fffffff
33
32
34 dirstatetuple = parsers.dirstatetuple
33 dirstatetuple = parsers.dirstatetuple
35
34
36 class repocache(filecache):
35 class repocache(filecache):
37 """filecache for files in .hg/"""
36 """filecache for files in .hg/"""
38 def join(self, obj, fname):
37 def join(self, obj, fname):
39 return obj._opener.join(fname)
38 return obj._opener.join(fname)
40
39
41 class rootcache(filecache):
40 class rootcache(filecache):
42 """filecache for files in the repository root"""
41 """filecache for files in the repository root"""
43 def join(self, obj, fname):
42 def join(self, obj, fname):
44 return obj._join(fname)
43 return obj._join(fname)
45
44
46 def _getfsnow(vfs):
45 def _getfsnow(vfs):
47 '''Get "now" timestamp on filesystem'''
46 '''Get "now" timestamp on filesystem'''
48 tmpfd, tmpname = vfs.mkstemp()
47 tmpfd, tmpname = vfs.mkstemp()
49 try:
48 try:
50 return os.fstat(tmpfd).st_mtime
49 return os.fstat(tmpfd).st_mtime
51 finally:
50 finally:
52 os.close(tmpfd)
51 os.close(tmpfd)
53 vfs.unlink(tmpname)
52 vfs.unlink(tmpname)
54
53
55 def nonnormalentries(dmap):
54 def nonnormalentries(dmap):
56 '''Compute the nonnormal dirstate entries from the dmap'''
55 '''Compute the nonnormal dirstate entries from the dmap'''
57 try:
56 try:
58 return parsers.nonnormalotherparententries(dmap)
57 return parsers.nonnormalotherparententries(dmap)
59 except AttributeError:
58 except AttributeError:
60 nonnorm = set()
59 nonnorm = set()
61 otherparent = set()
60 otherparent = set()
62 for fname, e in dmap.iteritems():
61 for fname, e in dmap.iteritems():
63 if e[0] != 'n' or e[3] == -1:
62 if e[0] != 'n' or e[3] == -1:
64 nonnorm.add(fname)
63 nonnorm.add(fname)
65 if e[0] == 'n' and e[2] == -2:
64 if e[0] == 'n' and e[2] == -2:
66 otherparent.add(fname)
65 otherparent.add(fname)
67 return nonnorm, otherparent
66 return nonnorm, otherparent
68
67
69 class dirstate(object):
68 class dirstate(object):
70
69
71 def __init__(self, opener, ui, root, validate):
70 def __init__(self, opener, ui, root, validate):
72 '''Create a new dirstate object.
71 '''Create a new dirstate object.
73
72
74 opener is an open()-like callable that can be used to open the
73 opener is an open()-like callable that can be used to open the
75 dirstate file; root is the root of the directory tracked by
74 dirstate file; root is the root of the directory tracked by
76 the dirstate.
75 the dirstate.
77 '''
76 '''
78 self._opener = opener
77 self._opener = opener
79 self._validate = validate
78 self._validate = validate
80 self._root = root
79 self._root = root
81 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
80 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
82 # UNC path pointing to root share (issue4557)
81 # UNC path pointing to root share (issue4557)
83 self._rootdir = pathutil.normasprefix(root)
82 self._rootdir = pathutil.normasprefix(root)
84 # internal config: ui.forcecwd
83 # internal config: ui.forcecwd
85 forcecwd = ui.config('ui', 'forcecwd')
84 forcecwd = ui.config('ui', 'forcecwd')
86 if forcecwd:
85 if forcecwd:
87 self._cwd = forcecwd
86 self._cwd = forcecwd
88 self._dirty = False
87 self._dirty = False
89 self._dirtypl = False
88 self._dirtypl = False
90 self._lastnormaltime = 0
89 self._lastnormaltime = 0
91 self._ui = ui
90 self._ui = ui
92 self._filecache = {}
91 self._filecache = {}
93 self._parentwriters = 0
92 self._parentwriters = 0
94 self._filename = 'dirstate'
93 self._filename = 'dirstate'
95 self._pendingfilename = '%s.pending' % self._filename
94 self._pendingfilename = '%s.pending' % self._filename
96 self._plchangecallbacks = {}
95 self._plchangecallbacks = {}
97 self._origpl = None
96 self._origpl = None
98 self._updatedfiles = set()
97 self._updatedfiles = set()
99
98
100 # for consistent view between _pl() and _read() invocations
99 # for consistent view between _pl() and _read() invocations
101 self._pendingmode = None
100 self._pendingmode = None
102
101
103 def beginparentchange(self):
102 def beginparentchange(self):
104 '''Marks the beginning of a set of changes that involve changing
103 '''Marks the beginning of a set of changes that involve changing
105 the dirstate parents. If there is an exception during this time,
104 the dirstate parents. If there is an exception during this time,
106 the dirstate will not be written when the wlock is released. This
105 the dirstate will not be written when the wlock is released. This
107 prevents writing an incoherent dirstate where the parent doesn't
106 prevents writing an incoherent dirstate where the parent doesn't
108 match the contents.
107 match the contents.
109 '''
108 '''
110 self._parentwriters += 1
109 self._parentwriters += 1
111
110
112 def endparentchange(self):
111 def endparentchange(self):
113 '''Marks the end of a set of changes that involve changing the
112 '''Marks the end of a set of changes that involve changing the
114 dirstate parents. Once all parent changes have been marked done,
113 dirstate parents. Once all parent changes have been marked done,
115 the wlock will be free to write the dirstate on release.
114 the wlock will be free to write the dirstate on release.
116 '''
115 '''
117 if self._parentwriters > 0:
116 if self._parentwriters > 0:
118 self._parentwriters -= 1
117 self._parentwriters -= 1
119
118
120 def pendingparentchange(self):
119 def pendingparentchange(self):
121 '''Returns true if the dirstate is in the middle of a set of changes
120 '''Returns true if the dirstate is in the middle of a set of changes
122 that modify the dirstate parent.
121 that modify the dirstate parent.
123 '''
122 '''
124 return self._parentwriters > 0
123 return self._parentwriters > 0
125
124
126 @propertycache
125 @propertycache
127 def _map(self):
126 def _map(self):
128 '''Return the dirstate contents as a map from filename to
127 '''Return the dirstate contents as a map from filename to
129 (state, mode, size, time).'''
128 (state, mode, size, time).'''
130 self._read()
129 self._read()
131 return self._map
130 return self._map
132
131
133 @propertycache
132 @propertycache
134 def _copymap(self):
133 def _copymap(self):
135 self._read()
134 self._read()
136 return self._copymap
135 return self._copymap
137
136
138 @propertycache
137 @propertycache
139 def _nonnormalset(self):
138 def _nonnormalset(self):
140 nonnorm, otherparents = nonnormalentries(self._map)
139 nonnorm, otherparents = nonnormalentries(self._map)
141 self._otherparentset = otherparents
140 self._otherparentset = otherparents
142 return nonnorm
141 return nonnorm
143
142
144 @propertycache
143 @propertycache
145 def _otherparentset(self):
144 def _otherparentset(self):
146 nonnorm, otherparents = nonnormalentries(self._map)
145 nonnorm, otherparents = nonnormalentries(self._map)
147 self._nonnormalset = nonnorm
146 self._nonnormalset = nonnorm
148 return otherparents
147 return otherparents
149
148
150 @propertycache
149 @propertycache
151 def _filefoldmap(self):
150 def _filefoldmap(self):
152 try:
151 try:
153 makefilefoldmap = parsers.make_file_foldmap
152 makefilefoldmap = parsers.make_file_foldmap
154 except AttributeError:
153 except AttributeError:
155 pass
154 pass
156 else:
155 else:
157 return makefilefoldmap(self._map, util.normcasespec,
156 return makefilefoldmap(self._map, util.normcasespec,
158 util.normcasefallback)
157 util.normcasefallback)
159
158
160 f = {}
159 f = {}
161 normcase = util.normcase
160 normcase = util.normcase
162 for name, s in self._map.iteritems():
161 for name, s in self._map.iteritems():
163 if s[0] != 'r':
162 if s[0] != 'r':
164 f[normcase(name)] = name
163 f[normcase(name)] = name
165 f['.'] = '.' # prevents useless util.fspath() invocation
164 f['.'] = '.' # prevents useless util.fspath() invocation
166 return f
165 return f
167
166
168 @propertycache
167 @propertycache
169 def _dirfoldmap(self):
168 def _dirfoldmap(self):
170 f = {}
169 f = {}
171 normcase = util.normcase
170 normcase = util.normcase
172 for name in self._dirs:
171 for name in self._dirs:
173 f[normcase(name)] = name
172 f[normcase(name)] = name
174 return f
173 return f
175
174
176 @repocache('branch')
175 @repocache('branch')
177 def _branch(self):
176 def _branch(self):
178 try:
177 try:
179 return self._opener.read("branch").strip() or "default"
178 return self._opener.read("branch").strip() or "default"
180 except IOError as inst:
179 except IOError as inst:
181 if inst.errno != errno.ENOENT:
180 if inst.errno != errno.ENOENT:
182 raise
181 raise
183 return "default"
182 return "default"
184
183
185 @propertycache
184 @propertycache
186 def _pl(self):
185 def _pl(self):
187 try:
186 try:
188 fp = self._opendirstatefile()
187 fp = self._opendirstatefile()
189 st = fp.read(40)
188 st = fp.read(40)
190 fp.close()
189 fp.close()
191 l = len(st)
190 l = len(st)
192 if l == 40:
191 if l == 40:
193 return st[:20], st[20:40]
192 return st[:20], st[20:40]
194 elif l > 0 and l < 40:
193 elif l > 0 and l < 40:
195 raise error.Abort(_('working directory state appears damaged!'))
194 raise error.Abort(_('working directory state appears damaged!'))
196 except IOError as err:
195 except IOError as err:
197 if err.errno != errno.ENOENT:
196 if err.errno != errno.ENOENT:
198 raise
197 raise
199 return [nullid, nullid]
198 return [nullid, nullid]
200
199
201 @propertycache
200 @propertycache
202 def _dirs(self):
201 def _dirs(self):
203 return util.dirs(self._map, 'r')
202 return util.dirs(self._map, 'r')
204
203
205 def dirs(self):
204 def dirs(self):
206 return self._dirs
205 return self._dirs
207
206
208 @rootcache('.hgignore')
207 @rootcache('.hgignore')
209 def _ignore(self):
208 def _ignore(self):
210 files = self._ignorefiles()
209 files = self._ignorefiles()
211 if not files:
210 if not files:
212 return util.never
211 return util.never
213
212
214 pats = ['include:%s' % f for f in files]
213 pats = ['include:%s' % f for f in files]
215 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
214 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
216
215
217 @propertycache
216 @propertycache
218 def _slash(self):
217 def _slash(self):
219 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
218 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
220
219
221 @propertycache
220 @propertycache
222 def _checklink(self):
221 def _checklink(self):
223 return util.checklink(self._root)
222 return util.checklink(self._root)
224
223
225 @propertycache
224 @propertycache
226 def _checkexec(self):
225 def _checkexec(self):
227 return util.checkexec(self._root)
226 return util.checkexec(self._root)
228
227
229 @propertycache
228 @propertycache
230 def _checkcase(self):
229 def _checkcase(self):
231 return not util.fscasesensitive(self._join('.hg'))
230 return not util.fscasesensitive(self._join('.hg'))
232
231
233 def _join(self, f):
232 def _join(self, f):
234 # much faster than os.path.join()
233 # much faster than os.path.join()
235 # it's safe because f is always a relative path
234 # it's safe because f is always a relative path
236 return self._rootdir + f
235 return self._rootdir + f
237
236
238 def flagfunc(self, buildfallback):
237 def flagfunc(self, buildfallback):
239 if self._checklink and self._checkexec:
238 if self._checklink and self._checkexec:
240 def f(x):
239 def f(x):
241 try:
240 try:
242 st = os.lstat(self._join(x))
241 st = os.lstat(self._join(x))
243 if util.statislink(st):
242 if util.statislink(st):
244 return 'l'
243 return 'l'
245 if util.statisexec(st):
244 if util.statisexec(st):
246 return 'x'
245 return 'x'
247 except OSError:
246 except OSError:
248 pass
247 pass
249 return ''
248 return ''
250 return f
249 return f
251
250
252 fallback = buildfallback()
251 fallback = buildfallback()
253 if self._checklink:
252 if self._checklink:
254 def f(x):
253 def f(x):
255 if os.path.islink(self._join(x)):
254 if os.path.islink(self._join(x)):
256 return 'l'
255 return 'l'
257 if 'x' in fallback(x):
256 if 'x' in fallback(x):
258 return 'x'
257 return 'x'
259 return ''
258 return ''
260 return f
259 return f
261 if self._checkexec:
260 if self._checkexec:
262 def f(x):
261 def f(x):
263 if 'l' in fallback(x):
262 if 'l' in fallback(x):
264 return 'l'
263 return 'l'
265 if util.isexec(self._join(x)):
264 if util.isexec(self._join(x)):
266 return 'x'
265 return 'x'
267 return ''
266 return ''
268 return f
267 return f
269 else:
268 else:
270 return fallback
269 return fallback
271
270
272 @propertycache
271 @propertycache
273 def _cwd(self):
272 def _cwd(self):
274 return pycompat.getcwd()
273 return pycompat.getcwd()
275
274
276 def getcwd(self):
275 def getcwd(self):
277 '''Return the path from which a canonical path is calculated.
276 '''Return the path from which a canonical path is calculated.
278
277
279 This path should be used to resolve file patterns or to convert
278 This path should be used to resolve file patterns or to convert
280 canonical paths back to file paths for display. It shouldn't be
279 canonical paths back to file paths for display. It shouldn't be
281 used to get real file paths. Use vfs functions instead.
280 used to get real file paths. Use vfs functions instead.
282 '''
281 '''
283 cwd = self._cwd
282 cwd = self._cwd
284 if cwd == self._root:
283 if cwd == self._root:
285 return ''
284 return ''
286 # self._root ends with a path separator if self._root is '/' or 'C:\'
285 # self._root ends with a path separator if self._root is '/' or 'C:\'
287 rootsep = self._root
286 rootsep = self._root
288 if not util.endswithsep(rootsep):
287 if not util.endswithsep(rootsep):
289 rootsep += pycompat.ossep
288 rootsep += pycompat.ossep
290 if cwd.startswith(rootsep):
289 if cwd.startswith(rootsep):
291 return cwd[len(rootsep):]
290 return cwd[len(rootsep):]
292 else:
291 else:
293 # we're outside the repo. return an absolute path.
292 # we're outside the repo. return an absolute path.
294 return cwd
293 return cwd
295
294
296 def pathto(self, f, cwd=None):
295 def pathto(self, f, cwd=None):
297 if cwd is None:
296 if cwd is None:
298 cwd = self.getcwd()
297 cwd = self.getcwd()
299 path = util.pathto(self._root, cwd, f)
298 path = util.pathto(self._root, cwd, f)
300 if self._slash:
299 if self._slash:
301 return util.pconvert(path)
300 return util.pconvert(path)
302 return path
301 return path
303
302
304 def __getitem__(self, key):
303 def __getitem__(self, key):
305 '''Return the current state of key (a filename) in the dirstate.
304 '''Return the current state of key (a filename) in the dirstate.
306
305
307 States are:
306 States are:
308 n normal
307 n normal
309 m needs merging
308 m needs merging
310 r marked for removal
309 r marked for removal
311 a marked for addition
310 a marked for addition
312 ? not tracked
311 ? not tracked
313 '''
312 '''
314 return self._map.get(key, ("?",))[0]
313 return self._map.get(key, ("?",))[0]
315
314
316 def __contains__(self, key):
315 def __contains__(self, key):
317 return key in self._map
316 return key in self._map
318
317
319 def __iter__(self):
318 def __iter__(self):
320 for x in sorted(self._map):
319 for x in sorted(self._map):
321 yield x
320 yield x
322
321
323 def iteritems(self):
322 def iteritems(self):
324 return self._map.iteritems()
323 return self._map.iteritems()
325
324
326 def parents(self):
325 def parents(self):
327 return [self._validate(p) for p in self._pl]
326 return [self._validate(p) for p in self._pl]
328
327
329 def p1(self):
328 def p1(self):
330 return self._validate(self._pl[0])
329 return self._validate(self._pl[0])
331
330
332 def p2(self):
331 def p2(self):
333 return self._validate(self._pl[1])
332 return self._validate(self._pl[1])
334
333
335 def branch(self):
334 def branch(self):
336 return encoding.tolocal(self._branch)
335 return encoding.tolocal(self._branch)
337
336
338 def setparents(self, p1, p2=nullid):
337 def setparents(self, p1, p2=nullid):
339 """Set dirstate parents to p1 and p2.
338 """Set dirstate parents to p1 and p2.
340
339
341 When moving from two parents to one, 'm' merged entries a
340 When moving from two parents to one, 'm' merged entries a
342 adjusted to normal and previous copy records discarded and
341 adjusted to normal and previous copy records discarded and
343 returned by the call.
342 returned by the call.
344
343
345 See localrepo.setparents()
344 See localrepo.setparents()
346 """
345 """
347 if self._parentwriters == 0:
346 if self._parentwriters == 0:
348 raise ValueError("cannot set dirstate parent without "
347 raise ValueError("cannot set dirstate parent without "
349 "calling dirstate.beginparentchange")
348 "calling dirstate.beginparentchange")
350
349
351 self._dirty = self._dirtypl = True
350 self._dirty = self._dirtypl = True
352 oldp2 = self._pl[1]
351 oldp2 = self._pl[1]
353 if self._origpl is None:
352 if self._origpl is None:
354 self._origpl = self._pl
353 self._origpl = self._pl
355 self._pl = p1, p2
354 self._pl = p1, p2
356 copies = {}
355 copies = {}
357 if oldp2 != nullid and p2 == nullid:
356 if oldp2 != nullid and p2 == nullid:
358 candidatefiles = self._nonnormalset.union(self._otherparentset)
357 candidatefiles = self._nonnormalset.union(self._otherparentset)
359 for f in candidatefiles:
358 for f in candidatefiles:
360 s = self._map.get(f)
359 s = self._map.get(f)
361 if s is None:
360 if s is None:
362 continue
361 continue
363
362
364 # Discard 'm' markers when moving away from a merge state
363 # Discard 'm' markers when moving away from a merge state
365 if s[0] == 'm':
364 if s[0] == 'm':
366 if f in self._copymap:
365 if f in self._copymap:
367 copies[f] = self._copymap[f]
366 copies[f] = self._copymap[f]
368 self.normallookup(f)
367 self.normallookup(f)
369 # Also fix up otherparent markers
368 # Also fix up otherparent markers
370 elif s[0] == 'n' and s[2] == -2:
369 elif s[0] == 'n' and s[2] == -2:
371 if f in self._copymap:
370 if f in self._copymap:
372 copies[f] = self._copymap[f]
371 copies[f] = self._copymap[f]
373 self.add(f)
372 self.add(f)
374 return copies
373 return copies
375
374
376 def setbranch(self, branch):
375 def setbranch(self, branch):
377 self._branch = encoding.fromlocal(branch)
376 self._branch = encoding.fromlocal(branch)
378 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
377 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
379 try:
378 try:
380 f.write(self._branch + '\n')
379 f.write(self._branch + '\n')
381 f.close()
380 f.close()
382
381
383 # make sure filecache has the correct stat info for _branch after
382 # make sure filecache has the correct stat info for _branch after
384 # replacing the underlying file
383 # replacing the underlying file
385 ce = self._filecache['_branch']
384 ce = self._filecache['_branch']
386 if ce:
385 if ce:
387 ce.refresh()
386 ce.refresh()
388 except: # re-raises
387 except: # re-raises
389 f.discard()
388 f.discard()
390 raise
389 raise
391
390
392 def _opendirstatefile(self):
391 def _opendirstatefile(self):
393 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
392 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
394 if self._pendingmode is not None and self._pendingmode != mode:
393 if self._pendingmode is not None and self._pendingmode != mode:
395 fp.close()
394 fp.close()
396 raise error.Abort(_('working directory state may be '
395 raise error.Abort(_('working directory state may be '
397 'changed parallelly'))
396 'changed parallelly'))
398 self._pendingmode = mode
397 self._pendingmode = mode
399 return fp
398 return fp
400
399
401 def _read(self):
400 def _read(self):
402 self._map = {}
401 self._map = {}
403 self._copymap = {}
402 self._copymap = {}
404 try:
403 try:
405 fp = self._opendirstatefile()
404 fp = self._opendirstatefile()
406 try:
405 try:
407 st = fp.read()
406 st = fp.read()
408 finally:
407 finally:
409 fp.close()
408 fp.close()
410 except IOError as err:
409 except IOError as err:
411 if err.errno != errno.ENOENT:
410 if err.errno != errno.ENOENT:
412 raise
411 raise
413 return
412 return
414 if not st:
413 if not st:
415 return
414 return
416
415
417 if util.safehasattr(parsers, 'dict_new_presized'):
416 if util.safehasattr(parsers, 'dict_new_presized'):
418 # Make an estimate of the number of files in the dirstate based on
417 # Make an estimate of the number of files in the dirstate based on
419 # its size. From a linear regression on a set of real-world repos,
418 # its size. From a linear regression on a set of real-world repos,
420 # all over 10,000 files, the size of a dirstate entry is 85
419 # all over 10,000 files, the size of a dirstate entry is 85
421 # bytes. The cost of resizing is significantly higher than the cost
420 # bytes. The cost of resizing is significantly higher than the cost
422 # of filling in a larger presized dict, so subtract 20% from the
421 # of filling in a larger presized dict, so subtract 20% from the
423 # size.
422 # size.
424 #
423 #
425 # This heuristic is imperfect in many ways, so in a future dirstate
424 # This heuristic is imperfect in many ways, so in a future dirstate
426 # format update it makes sense to just record the number of entries
425 # format update it makes sense to just record the number of entries
427 # on write.
426 # on write.
428 self._map = parsers.dict_new_presized(len(st) / 71)
427 self._map = parsers.dict_new_presized(len(st) / 71)
429
428
430 # Python's garbage collector triggers a GC each time a certain number
429 # Python's garbage collector triggers a GC each time a certain number
431 # of container objects (the number being defined by
430 # of container objects (the number being defined by
432 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
431 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
433 # for each file in the dirstate. The C version then immediately marks
432 # for each file in the dirstate. The C version then immediately marks
434 # them as not to be tracked by the collector. However, this has no
433 # them as not to be tracked by the collector. However, this has no
435 # effect on when GCs are triggered, only on what objects the GC looks
434 # effect on when GCs are triggered, only on what objects the GC looks
436 # into. This means that O(number of files) GCs are unavoidable.
435 # into. This means that O(number of files) GCs are unavoidable.
437 # Depending on when in the process's lifetime the dirstate is parsed,
436 # Depending on when in the process's lifetime the dirstate is parsed,
438 # this can get very expensive. As a workaround, disable GC while
437 # this can get very expensive. As a workaround, disable GC while
439 # parsing the dirstate.
438 # parsing the dirstate.
440 #
439 #
441 # (we cannot decorate the function directly since it is in a C module)
440 # (we cannot decorate the function directly since it is in a C module)
442 parse_dirstate = util.nogc(parsers.parse_dirstate)
441 parse_dirstate = util.nogc(parsers.parse_dirstate)
443 p = parse_dirstate(self._map, self._copymap, st)
442 p = parse_dirstate(self._map, self._copymap, st)
444 if not self._dirtypl:
443 if not self._dirtypl:
445 self._pl = p
444 self._pl = p
446
445
447 def invalidate(self):
446 def invalidate(self):
448 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
447 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
449 "_pl", "_dirs", "_ignore", "_nonnormalset",
448 "_pl", "_dirs", "_ignore", "_nonnormalset",
450 "_otherparentset"):
449 "_otherparentset"):
451 if a in self.__dict__:
450 if a in self.__dict__:
452 delattr(self, a)
451 delattr(self, a)
453 self._lastnormaltime = 0
452 self._lastnormaltime = 0
454 self._dirty = False
453 self._dirty = False
455 self._updatedfiles.clear()
454 self._updatedfiles.clear()
456 self._parentwriters = 0
455 self._parentwriters = 0
457 self._origpl = None
456 self._origpl = None
458
457
459 def copy(self, source, dest):
458 def copy(self, source, dest):
460 """Mark dest as a copy of source. Unmark dest if source is None."""
459 """Mark dest as a copy of source. Unmark dest if source is None."""
461 if source == dest:
460 if source == dest:
462 return
461 return
463 self._dirty = True
462 self._dirty = True
464 if source is not None:
463 if source is not None:
465 self._copymap[dest] = source
464 self._copymap[dest] = source
466 self._updatedfiles.add(source)
465 self._updatedfiles.add(source)
467 self._updatedfiles.add(dest)
466 self._updatedfiles.add(dest)
468 elif dest in self._copymap:
467 elif dest in self._copymap:
469 del self._copymap[dest]
468 del self._copymap[dest]
470 self._updatedfiles.add(dest)
469 self._updatedfiles.add(dest)
471
470
472 def copied(self, file):
471 def copied(self, file):
473 return self._copymap.get(file, None)
472 return self._copymap.get(file, None)
474
473
475 def copies(self):
474 def copies(self):
476 return self._copymap
475 return self._copymap
477
476
478 def _droppath(self, f):
477 def _droppath(self, f):
479 if self[f] not in "?r" and "_dirs" in self.__dict__:
478 if self[f] not in "?r" and "_dirs" in self.__dict__:
480 self._dirs.delpath(f)
479 self._dirs.delpath(f)
481
480
482 if "_filefoldmap" in self.__dict__:
481 if "_filefoldmap" in self.__dict__:
483 normed = util.normcase(f)
482 normed = util.normcase(f)
484 if normed in self._filefoldmap:
483 if normed in self._filefoldmap:
485 del self._filefoldmap[normed]
484 del self._filefoldmap[normed]
486
485
487 self._updatedfiles.add(f)
486 self._updatedfiles.add(f)
488
487
489 def _addpath(self, f, state, mode, size, mtime):
488 def _addpath(self, f, state, mode, size, mtime):
490 oldstate = self[f]
489 oldstate = self[f]
491 if state == 'a' or oldstate == 'r':
490 if state == 'a' or oldstate == 'r':
492 scmutil.checkfilename(f)
491 scmutil.checkfilename(f)
493 if f in self._dirs:
492 if f in self._dirs:
494 raise error.Abort(_('directory %r already in dirstate') % f)
493 raise error.Abort(_('directory %r already in dirstate') % f)
495 # shadows
494 # shadows
496 for d in util.finddirs(f):
495 for d in util.finddirs(f):
497 if d in self._dirs:
496 if d in self._dirs:
498 break
497 break
499 if d in self._map and self[d] != 'r':
498 if d in self._map and self[d] != 'r':
500 raise error.Abort(
499 raise error.Abort(
501 _('file %r in dirstate clashes with %r') % (d, f))
500 _('file %r in dirstate clashes with %r') % (d, f))
502 if oldstate in "?r" and "_dirs" in self.__dict__:
501 if oldstate in "?r" and "_dirs" in self.__dict__:
503 self._dirs.addpath(f)
502 self._dirs.addpath(f)
504 self._dirty = True
503 self._dirty = True
505 self._updatedfiles.add(f)
504 self._updatedfiles.add(f)
506 self._map[f] = dirstatetuple(state, mode, size, mtime)
505 self._map[f] = dirstatetuple(state, mode, size, mtime)
507 if state != 'n' or mtime == -1:
506 if state != 'n' or mtime == -1:
508 self._nonnormalset.add(f)
507 self._nonnormalset.add(f)
509 if size == -2:
508 if size == -2:
510 self._otherparentset.add(f)
509 self._otherparentset.add(f)
511
510
512 def normal(self, f):
511 def normal(self, f):
513 '''Mark a file normal and clean.'''
512 '''Mark a file normal and clean.'''
514 s = os.lstat(self._join(f))
513 s = os.lstat(self._join(f))
515 mtime = s.st_mtime
514 mtime = s.st_mtime
516 self._addpath(f, 'n', s.st_mode,
515 self._addpath(f, 'n', s.st_mode,
517 s.st_size & _rangemask, mtime & _rangemask)
516 s.st_size & _rangemask, mtime & _rangemask)
518 if f in self._copymap:
517 if f in self._copymap:
519 del self._copymap[f]
518 del self._copymap[f]
520 if f in self._nonnormalset:
519 if f in self._nonnormalset:
521 self._nonnormalset.remove(f)
520 self._nonnormalset.remove(f)
522 if mtime > self._lastnormaltime:
521 if mtime > self._lastnormaltime:
523 # Remember the most recent modification timeslot for status(),
522 # Remember the most recent modification timeslot for status(),
524 # to make sure we won't miss future size-preserving file content
523 # to make sure we won't miss future size-preserving file content
525 # modifications that happen within the same timeslot.
524 # modifications that happen within the same timeslot.
526 self._lastnormaltime = mtime
525 self._lastnormaltime = mtime
527
526
528 def normallookup(self, f):
527 def normallookup(self, f):
529 '''Mark a file normal, but possibly dirty.'''
528 '''Mark a file normal, but possibly dirty.'''
530 if self._pl[1] != nullid and f in self._map:
529 if self._pl[1] != nullid and f in self._map:
531 # if there is a merge going on and the file was either
530 # if there is a merge going on and the file was either
532 # in state 'm' (-1) or coming from other parent (-2) before
531 # in state 'm' (-1) or coming from other parent (-2) before
533 # being removed, restore that state.
532 # being removed, restore that state.
534 entry = self._map[f]
533 entry = self._map[f]
535 if entry[0] == 'r' and entry[2] in (-1, -2):
534 if entry[0] == 'r' and entry[2] in (-1, -2):
536 source = self._copymap.get(f)
535 source = self._copymap.get(f)
537 if entry[2] == -1:
536 if entry[2] == -1:
538 self.merge(f)
537 self.merge(f)
539 elif entry[2] == -2:
538 elif entry[2] == -2:
540 self.otherparent(f)
539 self.otherparent(f)
541 if source:
540 if source:
542 self.copy(source, f)
541 self.copy(source, f)
543 return
542 return
544 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
543 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
545 return
544 return
546 self._addpath(f, 'n', 0, -1, -1)
545 self._addpath(f, 'n', 0, -1, -1)
547 if f in self._copymap:
546 if f in self._copymap:
548 del self._copymap[f]
547 del self._copymap[f]
549 if f in self._nonnormalset:
548 if f in self._nonnormalset:
550 self._nonnormalset.remove(f)
549 self._nonnormalset.remove(f)
551
550
552 def otherparent(self, f):
551 def otherparent(self, f):
553 '''Mark as coming from the other parent, always dirty.'''
552 '''Mark as coming from the other parent, always dirty.'''
554 if self._pl[1] == nullid:
553 if self._pl[1] == nullid:
555 raise error.Abort(_("setting %r to other parent "
554 raise error.Abort(_("setting %r to other parent "
556 "only allowed in merges") % f)
555 "only allowed in merges") % f)
557 if f in self and self[f] == 'n':
556 if f in self and self[f] == 'n':
558 # merge-like
557 # merge-like
559 self._addpath(f, 'm', 0, -2, -1)
558 self._addpath(f, 'm', 0, -2, -1)
560 else:
559 else:
561 # add-like
560 # add-like
562 self._addpath(f, 'n', 0, -2, -1)
561 self._addpath(f, 'n', 0, -2, -1)
563
562
564 if f in self._copymap:
563 if f in self._copymap:
565 del self._copymap[f]
564 del self._copymap[f]
566
565
567 def add(self, f):
566 def add(self, f):
568 '''Mark a file added.'''
567 '''Mark a file added.'''
569 self._addpath(f, 'a', 0, -1, -1)
568 self._addpath(f, 'a', 0, -1, -1)
570 if f in self._copymap:
569 if f in self._copymap:
571 del self._copymap[f]
570 del self._copymap[f]
572
571
573 def remove(self, f):
572 def remove(self, f):
574 '''Mark a file removed.'''
573 '''Mark a file removed.'''
575 self._dirty = True
574 self._dirty = True
576 self._droppath(f)
575 self._droppath(f)
577 size = 0
576 size = 0
578 if self._pl[1] != nullid and f in self._map:
577 if self._pl[1] != nullid and f in self._map:
579 # backup the previous state
578 # backup the previous state
580 entry = self._map[f]
579 entry = self._map[f]
581 if entry[0] == 'm': # merge
580 if entry[0] == 'm': # merge
582 size = -1
581 size = -1
583 elif entry[0] == 'n' and entry[2] == -2: # other parent
582 elif entry[0] == 'n' and entry[2] == -2: # other parent
584 size = -2
583 size = -2
585 self._otherparentset.add(f)
584 self._otherparentset.add(f)
586 self._map[f] = dirstatetuple('r', 0, size, 0)
585 self._map[f] = dirstatetuple('r', 0, size, 0)
587 self._nonnormalset.add(f)
586 self._nonnormalset.add(f)
588 if size == 0 and f in self._copymap:
587 if size == 0 and f in self._copymap:
589 del self._copymap[f]
588 del self._copymap[f]
590
589
591 def merge(self, f):
590 def merge(self, f):
592 '''Mark a file merged.'''
591 '''Mark a file merged.'''
593 if self._pl[1] == nullid:
592 if self._pl[1] == nullid:
594 return self.normallookup(f)
593 return self.normallookup(f)
595 return self.otherparent(f)
594 return self.otherparent(f)
596
595
597 def drop(self, f):
596 def drop(self, f):
598 '''Drop a file from the dirstate'''
597 '''Drop a file from the dirstate'''
599 if f in self._map:
598 if f in self._map:
600 self._dirty = True
599 self._dirty = True
601 self._droppath(f)
600 self._droppath(f)
602 del self._map[f]
601 del self._map[f]
603 if f in self._nonnormalset:
602 if f in self._nonnormalset:
604 self._nonnormalset.remove(f)
603 self._nonnormalset.remove(f)
605 if f in self._copymap:
604 if f in self._copymap:
606 del self._copymap[f]
605 del self._copymap[f]
607
606
608 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
607 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
609 if exists is None:
608 if exists is None:
610 exists = os.path.lexists(os.path.join(self._root, path))
609 exists = os.path.lexists(os.path.join(self._root, path))
611 if not exists:
610 if not exists:
612 # Maybe a path component exists
611 # Maybe a path component exists
613 if not ignoremissing and '/' in path:
612 if not ignoremissing and '/' in path:
614 d, f = path.rsplit('/', 1)
613 d, f = path.rsplit('/', 1)
615 d = self._normalize(d, False, ignoremissing, None)
614 d = self._normalize(d, False, ignoremissing, None)
616 folded = d + "/" + f
615 folded = d + "/" + f
617 else:
616 else:
618 # No path components, preserve original case
617 # No path components, preserve original case
619 folded = path
618 folded = path
620 else:
619 else:
621 # recursively normalize leading directory components
620 # recursively normalize leading directory components
622 # against dirstate
621 # against dirstate
623 if '/' in normed:
622 if '/' in normed:
624 d, f = normed.rsplit('/', 1)
623 d, f = normed.rsplit('/', 1)
625 d = self._normalize(d, False, ignoremissing, True)
624 d = self._normalize(d, False, ignoremissing, True)
626 r = self._root + "/" + d
625 r = self._root + "/" + d
627 folded = d + "/" + util.fspath(f, r)
626 folded = d + "/" + util.fspath(f, r)
628 else:
627 else:
629 folded = util.fspath(normed, self._root)
628 folded = util.fspath(normed, self._root)
630 storemap[normed] = folded
629 storemap[normed] = folded
631
630
632 return folded
631 return folded
633
632
634 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
633 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
635 normed = util.normcase(path)
634 normed = util.normcase(path)
636 folded = self._filefoldmap.get(normed, None)
635 folded = self._filefoldmap.get(normed, None)
637 if folded is None:
636 if folded is None:
638 if isknown:
637 if isknown:
639 folded = path
638 folded = path
640 else:
639 else:
641 folded = self._discoverpath(path, normed, ignoremissing, exists,
640 folded = self._discoverpath(path, normed, ignoremissing, exists,
642 self._filefoldmap)
641 self._filefoldmap)
643 return folded
642 return folded
644
643
645 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
644 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
646 normed = util.normcase(path)
645 normed = util.normcase(path)
647 folded = self._filefoldmap.get(normed, None)
646 folded = self._filefoldmap.get(normed, None)
648 if folded is None:
647 if folded is None:
649 folded = self._dirfoldmap.get(normed, None)
648 folded = self._dirfoldmap.get(normed, None)
650 if folded is None:
649 if folded is None:
651 if isknown:
650 if isknown:
652 folded = path
651 folded = path
653 else:
652 else:
654 # store discovered result in dirfoldmap so that future
653 # store discovered result in dirfoldmap so that future
655 # normalizefile calls don't start matching directories
654 # normalizefile calls don't start matching directories
656 folded = self._discoverpath(path, normed, ignoremissing, exists,
655 folded = self._discoverpath(path, normed, ignoremissing, exists,
657 self._dirfoldmap)
656 self._dirfoldmap)
658 return folded
657 return folded
659
658
660 def normalize(self, path, isknown=False, ignoremissing=False):
659 def normalize(self, path, isknown=False, ignoremissing=False):
661 '''
660 '''
662 normalize the case of a pathname when on a casefolding filesystem
661 normalize the case of a pathname when on a casefolding filesystem
663
662
664 isknown specifies whether the filename came from walking the
663 isknown specifies whether the filename came from walking the
665 disk, to avoid extra filesystem access.
664 disk, to avoid extra filesystem access.
666
665
667 If ignoremissing is True, missing path are returned
666 If ignoremissing is True, missing path are returned
668 unchanged. Otherwise, we try harder to normalize possibly
667 unchanged. Otherwise, we try harder to normalize possibly
669 existing path components.
668 existing path components.
670
669
671 The normalized case is determined based on the following precedence:
670 The normalized case is determined based on the following precedence:
672
671
673 - version of name already stored in the dirstate
672 - version of name already stored in the dirstate
674 - version of name stored on disk
673 - version of name stored on disk
675 - version provided via command arguments
674 - version provided via command arguments
676 '''
675 '''
677
676
678 if self._checkcase:
677 if self._checkcase:
679 return self._normalize(path, isknown, ignoremissing)
678 return self._normalize(path, isknown, ignoremissing)
680 return path
679 return path
681
680
682 def clear(self):
681 def clear(self):
683 self._map = {}
682 self._map = {}
684 self._nonnormalset = set()
683 self._nonnormalset = set()
685 self._otherparentset = set()
684 self._otherparentset = set()
686 if "_dirs" in self.__dict__:
685 if "_dirs" in self.__dict__:
687 delattr(self, "_dirs")
686 delattr(self, "_dirs")
688 self._copymap = {}
687 self._copymap = {}
689 self._pl = [nullid, nullid]
688 self._pl = [nullid, nullid]
690 self._lastnormaltime = 0
689 self._lastnormaltime = 0
691 self._updatedfiles.clear()
690 self._updatedfiles.clear()
692 self._dirty = True
691 self._dirty = True
693
692
694 def rebuild(self, parent, allfiles, changedfiles=None):
693 def rebuild(self, parent, allfiles, changedfiles=None):
695 if changedfiles is None:
694 if changedfiles is None:
696 # Rebuild entire dirstate
695 # Rebuild entire dirstate
697 changedfiles = allfiles
696 changedfiles = allfiles
698 lastnormaltime = self._lastnormaltime
697 lastnormaltime = self._lastnormaltime
699 self.clear()
698 self.clear()
700 self._lastnormaltime = lastnormaltime
699 self._lastnormaltime = lastnormaltime
701
700
702 if self._origpl is None:
701 if self._origpl is None:
703 self._origpl = self._pl
702 self._origpl = self._pl
704 self._pl = (parent, nullid)
703 self._pl = (parent, nullid)
705 for f in changedfiles:
704 for f in changedfiles:
706 if f in allfiles:
705 if f in allfiles:
707 self.normallookup(f)
706 self.normallookup(f)
708 else:
707 else:
709 self.drop(f)
708 self.drop(f)
710
709
711 self._dirty = True
710 self._dirty = True
712
711
713 def write(self, tr):
712 def write(self, tr):
714 if not self._dirty:
713 if not self._dirty:
715 return
714 return
716
715
717 filename = self._filename
716 filename = self._filename
718 if tr:
717 if tr:
719 # 'dirstate.write()' is not only for writing in-memory
718 # 'dirstate.write()' is not only for writing in-memory
720 # changes out, but also for dropping ambiguous timestamp.
719 # changes out, but also for dropping ambiguous timestamp.
721 # delayed writing re-raise "ambiguous timestamp issue".
720 # delayed writing re-raise "ambiguous timestamp issue".
722 # See also the wiki page below for detail:
721 # See also the wiki page below for detail:
723 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
722 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
724
723
725 # emulate dropping timestamp in 'parsers.pack_dirstate'
724 # emulate dropping timestamp in 'parsers.pack_dirstate'
726 now = _getfsnow(self._opener)
725 now = _getfsnow(self._opener)
727 dmap = self._map
726 dmap = self._map
728 for f in self._updatedfiles:
727 for f in self._updatedfiles:
729 e = dmap.get(f)
728 e = dmap.get(f)
730 if e is not None and e[0] == 'n' and e[3] == now:
729 if e is not None and e[0] == 'n' and e[3] == now:
731 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
730 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
732 self._nonnormalset.add(f)
731 self._nonnormalset.add(f)
733
732
734 # emulate that all 'dirstate.normal' results are written out
733 # emulate that all 'dirstate.normal' results are written out
735 self._lastnormaltime = 0
734 self._lastnormaltime = 0
736 self._updatedfiles.clear()
735 self._updatedfiles.clear()
737
736
738 # delay writing in-memory changes out
737 # delay writing in-memory changes out
739 tr.addfilegenerator('dirstate', (self._filename,),
738 tr.addfilegenerator('dirstate', (self._filename,),
740 self._writedirstate, location='plain')
739 self._writedirstate, location='plain')
741 return
740 return
742
741
743 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
742 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
744 self._writedirstate(st)
743 self._writedirstate(st)
745
744
746 def addparentchangecallback(self, category, callback):
745 def addparentchangecallback(self, category, callback):
747 """add a callback to be called when the wd parents are changed
746 """add a callback to be called when the wd parents are changed
748
747
749 Callback will be called with the following arguments:
748 Callback will be called with the following arguments:
750 dirstate, (oldp1, oldp2), (newp1, newp2)
749 dirstate, (oldp1, oldp2), (newp1, newp2)
751
750
752 Category is a unique identifier to allow overwriting an old callback
751 Category is a unique identifier to allow overwriting an old callback
753 with a newer callback.
752 with a newer callback.
754 """
753 """
755 self._plchangecallbacks[category] = callback
754 self._plchangecallbacks[category] = callback
756
755
757 def _writedirstate(self, st):
756 def _writedirstate(self, st):
758 # notify callbacks about parents change
757 # notify callbacks about parents change
759 if self._origpl is not None and self._origpl != self._pl:
758 if self._origpl is not None and self._origpl != self._pl:
760 for c, callback in sorted(self._plchangecallbacks.iteritems()):
759 for c, callback in sorted(self._plchangecallbacks.iteritems()):
761 callback(self, self._origpl, self._pl)
760 callback(self, self._origpl, self._pl)
762 self._origpl = None
761 self._origpl = None
763 # use the modification time of the newly created temporary file as the
762 # use the modification time of the newly created temporary file as the
764 # filesystem's notion of 'now'
763 # filesystem's notion of 'now'
765 now = util.fstat(st).st_mtime & _rangemask
764 now = util.fstat(st).st_mtime & _rangemask
766
765
767 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
766 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
768 # timestamp of each entries in dirstate, because of 'now > mtime'
767 # timestamp of each entries in dirstate, because of 'now > mtime'
769 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
768 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
770 if delaywrite > 0:
769 if delaywrite > 0:
771 # do we have any files to delay for?
770 # do we have any files to delay for?
772 for f, e in self._map.iteritems():
771 for f, e in self._map.iteritems():
773 if e[0] == 'n' and e[3] == now:
772 if e[0] == 'n' and e[3] == now:
774 import time # to avoid useless import
773 import time # to avoid useless import
775 # rather than sleep n seconds, sleep until the next
774 # rather than sleep n seconds, sleep until the next
776 # multiple of n seconds
775 # multiple of n seconds
777 clock = time.time()
776 clock = time.time()
778 start = int(clock) - (int(clock) % delaywrite)
777 start = int(clock) - (int(clock) % delaywrite)
779 end = start + delaywrite
778 end = start + delaywrite
780 time.sleep(end - clock)
779 time.sleep(end - clock)
781 now = end # trust our estimate that the end is near now
780 now = end # trust our estimate that the end is near now
782 break
781 break
783
782
784 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
783 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
785 self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
784 self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
786 st.close()
785 st.close()
787 self._lastnormaltime = 0
786 self._lastnormaltime = 0
788 self._dirty = self._dirtypl = False
787 self._dirty = self._dirtypl = False
789
788
790 def _dirignore(self, f):
789 def _dirignore(self, f):
791 if f == '.':
790 if f == '.':
792 return False
791 return False
793 if self._ignore(f):
792 if self._ignore(f):
794 return True
793 return True
795 for p in util.finddirs(f):
794 for p in util.finddirs(f):
796 if self._ignore(p):
795 if self._ignore(p):
797 return True
796 return True
798 return False
797 return False
799
798
800 def _ignorefiles(self):
799 def _ignorefiles(self):
801 files = []
800 files = []
802 if os.path.exists(self._join('.hgignore')):
801 if os.path.exists(self._join('.hgignore')):
803 files.append(self._join('.hgignore'))
802 files.append(self._join('.hgignore'))
804 for name, path in self._ui.configitems("ui"):
803 for name, path in self._ui.configitems("ui"):
805 if name == 'ignore' or name.startswith('ignore.'):
804 if name == 'ignore' or name.startswith('ignore.'):
806 # we need to use os.path.join here rather than self._join
805 # we need to use os.path.join here rather than self._join
807 # because path is arbitrary and user-specified
806 # because path is arbitrary and user-specified
808 files.append(os.path.join(self._rootdir, util.expandpath(path)))
807 files.append(os.path.join(self._rootdir, util.expandpath(path)))
809 return files
808 return files
810
809
811 def _ignorefileandline(self, f):
810 def _ignorefileandline(self, f):
812 files = collections.deque(self._ignorefiles())
811 files = collections.deque(self._ignorefiles())
813 visited = set()
812 visited = set()
814 while files:
813 while files:
815 i = files.popleft()
814 i = files.popleft()
816 patterns = matchmod.readpatternfile(i, self._ui.warn,
815 patterns = matchmod.readpatternfile(i, self._ui.warn,
817 sourceinfo=True)
816 sourceinfo=True)
818 for pattern, lineno, line in patterns:
817 for pattern, lineno, line in patterns:
819 kind, p = matchmod._patsplit(pattern, 'glob')
818 kind, p = matchmod._patsplit(pattern, 'glob')
820 if kind == "subinclude":
819 if kind == "subinclude":
821 if p not in visited:
820 if p not in visited:
822 files.append(p)
821 files.append(p)
823 continue
822 continue
824 m = matchmod.match(self._root, '', [], [pattern],
823 m = matchmod.match(self._root, '', [], [pattern],
825 warn=self._ui.warn)
824 warn=self._ui.warn)
826 if m(f):
825 if m(f):
827 return (i, lineno, line)
826 return (i, lineno, line)
828 visited.add(i)
827 visited.add(i)
829 return (None, -1, "")
828 return (None, -1, "")
830
829
831 def _walkexplicit(self, match, subrepos):
830 def _walkexplicit(self, match, subrepos):
832 '''Get stat data about the files explicitly specified by match.
831 '''Get stat data about the files explicitly specified by match.
833
832
834 Return a triple (results, dirsfound, dirsnotfound).
833 Return a triple (results, dirsfound, dirsnotfound).
835 - results is a mapping from filename to stat result. It also contains
834 - results is a mapping from filename to stat result. It also contains
836 listings mapping subrepos and .hg to None.
835 listings mapping subrepos and .hg to None.
837 - dirsfound is a list of files found to be directories.
836 - dirsfound is a list of files found to be directories.
838 - dirsnotfound is a list of files that the dirstate thinks are
837 - dirsnotfound is a list of files that the dirstate thinks are
839 directories and that were not found.'''
838 directories and that were not found.'''
840
839
841 def badtype(mode):
840 def badtype(mode):
842 kind = _('unknown')
841 kind = _('unknown')
843 if stat.S_ISCHR(mode):
842 if stat.S_ISCHR(mode):
844 kind = _('character device')
843 kind = _('character device')
845 elif stat.S_ISBLK(mode):
844 elif stat.S_ISBLK(mode):
846 kind = _('block device')
845 kind = _('block device')
847 elif stat.S_ISFIFO(mode):
846 elif stat.S_ISFIFO(mode):
848 kind = _('fifo')
847 kind = _('fifo')
849 elif stat.S_ISSOCK(mode):
848 elif stat.S_ISSOCK(mode):
850 kind = _('socket')
849 kind = _('socket')
851 elif stat.S_ISDIR(mode):
850 elif stat.S_ISDIR(mode):
852 kind = _('directory')
851 kind = _('directory')
853 return _('unsupported file type (type is %s)') % kind
852 return _('unsupported file type (type is %s)') % kind
854
853
855 matchedir = match.explicitdir
854 matchedir = match.explicitdir
856 badfn = match.bad
855 badfn = match.bad
857 dmap = self._map
856 dmap = self._map
858 lstat = os.lstat
857 lstat = os.lstat
859 getkind = stat.S_IFMT
858 getkind = stat.S_IFMT
860 dirkind = stat.S_IFDIR
859 dirkind = stat.S_IFDIR
861 regkind = stat.S_IFREG
860 regkind = stat.S_IFREG
862 lnkkind = stat.S_IFLNK
861 lnkkind = stat.S_IFLNK
863 join = self._join
862 join = self._join
864 dirsfound = []
863 dirsfound = []
865 foundadd = dirsfound.append
864 foundadd = dirsfound.append
866 dirsnotfound = []
865 dirsnotfound = []
867 notfoundadd = dirsnotfound.append
866 notfoundadd = dirsnotfound.append
868
867
869 if not match.isexact() and self._checkcase:
868 if not match.isexact() and self._checkcase:
870 normalize = self._normalize
869 normalize = self._normalize
871 else:
870 else:
872 normalize = None
871 normalize = None
873
872
874 files = sorted(match.files())
873 files = sorted(match.files())
875 subrepos.sort()
874 subrepos.sort()
876 i, j = 0, 0
875 i, j = 0, 0
877 while i < len(files) and j < len(subrepos):
876 while i < len(files) and j < len(subrepos):
878 subpath = subrepos[j] + "/"
877 subpath = subrepos[j] + "/"
879 if files[i] < subpath:
878 if files[i] < subpath:
880 i += 1
879 i += 1
881 continue
880 continue
882 while i < len(files) and files[i].startswith(subpath):
881 while i < len(files) and files[i].startswith(subpath):
883 del files[i]
882 del files[i]
884 j += 1
883 j += 1
885
884
886 if not files or '.' in files:
885 if not files or '.' in files:
887 files = ['.']
886 files = ['.']
888 results = dict.fromkeys(subrepos)
887 results = dict.fromkeys(subrepos)
889 results['.hg'] = None
888 results['.hg'] = None
890
889
891 alldirs = None
890 alldirs = None
892 for ff in files:
891 for ff in files:
893 # constructing the foldmap is expensive, so don't do it for the
892 # constructing the foldmap is expensive, so don't do it for the
894 # common case where files is ['.']
893 # common case where files is ['.']
895 if normalize and ff != '.':
894 if normalize and ff != '.':
896 nf = normalize(ff, False, True)
895 nf = normalize(ff, False, True)
897 else:
896 else:
898 nf = ff
897 nf = ff
899 if nf in results:
898 if nf in results:
900 continue
899 continue
901
900
902 try:
901 try:
903 st = lstat(join(nf))
902 st = lstat(join(nf))
904 kind = getkind(st.st_mode)
903 kind = getkind(st.st_mode)
905 if kind == dirkind:
904 if kind == dirkind:
906 if nf in dmap:
905 if nf in dmap:
907 # file replaced by dir on disk but still in dirstate
906 # file replaced by dir on disk but still in dirstate
908 results[nf] = None
907 results[nf] = None
909 if matchedir:
908 if matchedir:
910 matchedir(nf)
909 matchedir(nf)
911 foundadd((nf, ff))
910 foundadd((nf, ff))
912 elif kind == regkind or kind == lnkkind:
911 elif kind == regkind or kind == lnkkind:
913 results[nf] = st
912 results[nf] = st
914 else:
913 else:
915 badfn(ff, badtype(kind))
914 badfn(ff, badtype(kind))
916 if nf in dmap:
915 if nf in dmap:
917 results[nf] = None
916 results[nf] = None
918 except OSError as inst: # nf not found on disk - it is dirstate only
917 except OSError as inst: # nf not found on disk - it is dirstate only
919 if nf in dmap: # does it exactly match a missing file?
918 if nf in dmap: # does it exactly match a missing file?
920 results[nf] = None
919 results[nf] = None
921 else: # does it match a missing directory?
920 else: # does it match a missing directory?
922 if alldirs is None:
921 if alldirs is None:
923 alldirs = util.dirs(dmap)
922 alldirs = util.dirs(dmap)
924 if nf in alldirs:
923 if nf in alldirs:
925 if matchedir:
924 if matchedir:
926 matchedir(nf)
925 matchedir(nf)
927 notfoundadd(nf)
926 notfoundadd(nf)
928 else:
927 else:
929 badfn(ff, inst.strerror)
928 badfn(ff, inst.strerror)
930
929
931 # Case insensitive filesystems cannot rely on lstat() failing to detect
930 # Case insensitive filesystems cannot rely on lstat() failing to detect
932 # a case-only rename. Prune the stat object for any file that does not
931 # a case-only rename. Prune the stat object for any file that does not
933 # match the case in the filesystem, if there are multiple files that
932 # match the case in the filesystem, if there are multiple files that
934 # normalize to the same path.
933 # normalize to the same path.
935 if match.isexact() and self._checkcase:
934 if match.isexact() and self._checkcase:
936 normed = {}
935 normed = {}
937
936
938 for f, st in results.iteritems():
937 for f, st in results.iteritems():
939 if st is None:
938 if st is None:
940 continue
939 continue
941
940
942 nc = util.normcase(f)
941 nc = util.normcase(f)
943 paths = normed.get(nc)
942 paths = normed.get(nc)
944
943
945 if paths is None:
944 if paths is None:
946 paths = set()
945 paths = set()
947 normed[nc] = paths
946 normed[nc] = paths
948
947
949 paths.add(f)
948 paths.add(f)
950
949
951 for norm, paths in normed.iteritems():
950 for norm, paths in normed.iteritems():
952 if len(paths) > 1:
951 if len(paths) > 1:
953 for path in paths:
952 for path in paths:
954 folded = self._discoverpath(path, norm, True, None,
953 folded = self._discoverpath(path, norm, True, None,
955 self._dirfoldmap)
954 self._dirfoldmap)
956 if path != folded:
955 if path != folded:
957 results[path] = None
956 results[path] = None
958
957
959 return results, dirsfound, dirsnotfound
958 return results, dirsfound, dirsnotfound
960
959
961 def walk(self, match, subrepos, unknown, ignored, full=True):
960 def walk(self, match, subrepos, unknown, ignored, full=True):
962 '''
961 '''
963 Walk recursively through the directory tree, finding all files
962 Walk recursively through the directory tree, finding all files
964 matched by match.
963 matched by match.
965
964
966 If full is False, maybe skip some known-clean files.
965 If full is False, maybe skip some known-clean files.
967
966
968 Return a dict mapping filename to stat-like object (either
967 Return a dict mapping filename to stat-like object (either
969 mercurial.osutil.stat instance or return value of os.stat()).
968 mercurial.osutil.stat instance or return value of os.stat()).
970
969
971 '''
970 '''
972 # full is a flag that extensions that hook into walk can use -- this
971 # full is a flag that extensions that hook into walk can use -- this
973 # implementation doesn't use it at all. This satisfies the contract
972 # implementation doesn't use it at all. This satisfies the contract
974 # because we only guarantee a "maybe".
973 # because we only guarantee a "maybe".
975
974
976 if ignored:
975 if ignored:
977 ignore = util.never
976 ignore = util.never
978 dirignore = util.never
977 dirignore = util.never
979 elif unknown:
978 elif unknown:
980 ignore = self._ignore
979 ignore = self._ignore
981 dirignore = self._dirignore
980 dirignore = self._dirignore
982 else:
981 else:
983 # if not unknown and not ignored, drop dir recursion and step 2
982 # if not unknown and not ignored, drop dir recursion and step 2
984 ignore = util.always
983 ignore = util.always
985 dirignore = util.always
984 dirignore = util.always
986
985
987 matchfn = match.matchfn
986 matchfn = match.matchfn
988 matchalways = match.always()
987 matchalways = match.always()
989 matchtdir = match.traversedir
988 matchtdir = match.traversedir
990 dmap = self._map
989 dmap = self._map
991 listdir = osutil.listdir
990 listdir = util.listdir
992 lstat = os.lstat
991 lstat = os.lstat
993 dirkind = stat.S_IFDIR
992 dirkind = stat.S_IFDIR
994 regkind = stat.S_IFREG
993 regkind = stat.S_IFREG
995 lnkkind = stat.S_IFLNK
994 lnkkind = stat.S_IFLNK
996 join = self._join
995 join = self._join
997
996
998 exact = skipstep3 = False
997 exact = skipstep3 = False
999 if match.isexact(): # match.exact
998 if match.isexact(): # match.exact
1000 exact = True
999 exact = True
1001 dirignore = util.always # skip step 2
1000 dirignore = util.always # skip step 2
1002 elif match.prefix(): # match.match, no patterns
1001 elif match.prefix(): # match.match, no patterns
1003 skipstep3 = True
1002 skipstep3 = True
1004
1003
1005 if not exact and self._checkcase:
1004 if not exact and self._checkcase:
1006 normalize = self._normalize
1005 normalize = self._normalize
1007 normalizefile = self._normalizefile
1006 normalizefile = self._normalizefile
1008 skipstep3 = False
1007 skipstep3 = False
1009 else:
1008 else:
1010 normalize = self._normalize
1009 normalize = self._normalize
1011 normalizefile = None
1010 normalizefile = None
1012
1011
1013 # step 1: find all explicit files
1012 # step 1: find all explicit files
1014 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1013 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1015
1014
1016 skipstep3 = skipstep3 and not (work or dirsnotfound)
1015 skipstep3 = skipstep3 and not (work or dirsnotfound)
1017 work = [d for d in work if not dirignore(d[0])]
1016 work = [d for d in work if not dirignore(d[0])]
1018
1017
1019 # step 2: visit subdirectories
1018 # step 2: visit subdirectories
1020 def traverse(work, alreadynormed):
1019 def traverse(work, alreadynormed):
1021 wadd = work.append
1020 wadd = work.append
1022 while work:
1021 while work:
1023 nd = work.pop()
1022 nd = work.pop()
1024 if not match.visitdir(nd):
1023 if not match.visitdir(nd):
1025 continue
1024 continue
1026 skip = None
1025 skip = None
1027 if nd == '.':
1026 if nd == '.':
1028 nd = ''
1027 nd = ''
1029 else:
1028 else:
1030 skip = '.hg'
1029 skip = '.hg'
1031 try:
1030 try:
1032 entries = listdir(join(nd), stat=True, skip=skip)
1031 entries = listdir(join(nd), stat=True, skip=skip)
1033 except OSError as inst:
1032 except OSError as inst:
1034 if inst.errno in (errno.EACCES, errno.ENOENT):
1033 if inst.errno in (errno.EACCES, errno.ENOENT):
1035 match.bad(self.pathto(nd), inst.strerror)
1034 match.bad(self.pathto(nd), inst.strerror)
1036 continue
1035 continue
1037 raise
1036 raise
1038 for f, kind, st in entries:
1037 for f, kind, st in entries:
1039 if normalizefile:
1038 if normalizefile:
1040 # even though f might be a directory, we're only
1039 # even though f might be a directory, we're only
1041 # interested in comparing it to files currently in the
1040 # interested in comparing it to files currently in the
1042 # dmap -- therefore normalizefile is enough
1041 # dmap -- therefore normalizefile is enough
1043 nf = normalizefile(nd and (nd + "/" + f) or f, True,
1042 nf = normalizefile(nd and (nd + "/" + f) or f, True,
1044 True)
1043 True)
1045 else:
1044 else:
1046 nf = nd and (nd + "/" + f) or f
1045 nf = nd and (nd + "/" + f) or f
1047 if nf not in results:
1046 if nf not in results:
1048 if kind == dirkind:
1047 if kind == dirkind:
1049 if not ignore(nf):
1048 if not ignore(nf):
1050 if matchtdir:
1049 if matchtdir:
1051 matchtdir(nf)
1050 matchtdir(nf)
1052 wadd(nf)
1051 wadd(nf)
1053 if nf in dmap and (matchalways or matchfn(nf)):
1052 if nf in dmap and (matchalways or matchfn(nf)):
1054 results[nf] = None
1053 results[nf] = None
1055 elif kind == regkind or kind == lnkkind:
1054 elif kind == regkind or kind == lnkkind:
1056 if nf in dmap:
1055 if nf in dmap:
1057 if matchalways or matchfn(nf):
1056 if matchalways or matchfn(nf):
1058 results[nf] = st
1057 results[nf] = st
1059 elif ((matchalways or matchfn(nf))
1058 elif ((matchalways or matchfn(nf))
1060 and not ignore(nf)):
1059 and not ignore(nf)):
1061 # unknown file -- normalize if necessary
1060 # unknown file -- normalize if necessary
1062 if not alreadynormed:
1061 if not alreadynormed:
1063 nf = normalize(nf, False, True)
1062 nf = normalize(nf, False, True)
1064 results[nf] = st
1063 results[nf] = st
1065 elif nf in dmap and (matchalways or matchfn(nf)):
1064 elif nf in dmap and (matchalways or matchfn(nf)):
1066 results[nf] = None
1065 results[nf] = None
1067
1066
1068 for nd, d in work:
1067 for nd, d in work:
1069 # alreadynormed means that processwork doesn't have to do any
1068 # alreadynormed means that processwork doesn't have to do any
1070 # expensive directory normalization
1069 # expensive directory normalization
1071 alreadynormed = not normalize or nd == d
1070 alreadynormed = not normalize or nd == d
1072 traverse([d], alreadynormed)
1071 traverse([d], alreadynormed)
1073
1072
1074 for s in subrepos:
1073 for s in subrepos:
1075 del results[s]
1074 del results[s]
1076 del results['.hg']
1075 del results['.hg']
1077
1076
1078 # step 3: visit remaining files from dmap
1077 # step 3: visit remaining files from dmap
1079 if not skipstep3 and not exact:
1078 if not skipstep3 and not exact:
1080 # If a dmap file is not in results yet, it was either
1079 # If a dmap file is not in results yet, it was either
1081 # a) not matching matchfn b) ignored, c) missing, or d) under a
1080 # a) not matching matchfn b) ignored, c) missing, or d) under a
1082 # symlink directory.
1081 # symlink directory.
1083 if not results and matchalways:
1082 if not results and matchalways:
1084 visit = [f for f in dmap]
1083 visit = [f for f in dmap]
1085 else:
1084 else:
1086 visit = [f for f in dmap if f not in results and matchfn(f)]
1085 visit = [f for f in dmap if f not in results and matchfn(f)]
1087 visit.sort()
1086 visit.sort()
1088
1087
1089 if unknown:
1088 if unknown:
1090 # unknown == True means we walked all dirs under the roots
1089 # unknown == True means we walked all dirs under the roots
1091 # that wasn't ignored, and everything that matched was stat'ed
1090 # that wasn't ignored, and everything that matched was stat'ed
1092 # and is already in results.
1091 # and is already in results.
1093 # The rest must thus be ignored or under a symlink.
1092 # The rest must thus be ignored or under a symlink.
1094 audit_path = pathutil.pathauditor(self._root)
1093 audit_path = pathutil.pathauditor(self._root)
1095
1094
1096 for nf in iter(visit):
1095 for nf in iter(visit):
1097 # If a stat for the same file was already added with a
1096 # If a stat for the same file was already added with a
1098 # different case, don't add one for this, since that would
1097 # different case, don't add one for this, since that would
1099 # make it appear as if the file exists under both names
1098 # make it appear as if the file exists under both names
1100 # on disk.
1099 # on disk.
1101 if (normalizefile and
1100 if (normalizefile and
1102 normalizefile(nf, True, True) in results):
1101 normalizefile(nf, True, True) in results):
1103 results[nf] = None
1102 results[nf] = None
1104 # Report ignored items in the dmap as long as they are not
1103 # Report ignored items in the dmap as long as they are not
1105 # under a symlink directory.
1104 # under a symlink directory.
1106 elif audit_path.check(nf):
1105 elif audit_path.check(nf):
1107 try:
1106 try:
1108 results[nf] = lstat(join(nf))
1107 results[nf] = lstat(join(nf))
1109 # file was just ignored, no links, and exists
1108 # file was just ignored, no links, and exists
1110 except OSError:
1109 except OSError:
1111 # file doesn't exist
1110 # file doesn't exist
1112 results[nf] = None
1111 results[nf] = None
1113 else:
1112 else:
1114 # It's either missing or under a symlink directory
1113 # It's either missing or under a symlink directory
1115 # which we in this case report as missing
1114 # which we in this case report as missing
1116 results[nf] = None
1115 results[nf] = None
1117 else:
1116 else:
1118 # We may not have walked the full directory tree above,
1117 # We may not have walked the full directory tree above,
1119 # so stat and check everything we missed.
1118 # so stat and check everything we missed.
1120 iv = iter(visit)
1119 iv = iter(visit)
1121 for st in util.statfiles([join(i) for i in visit]):
1120 for st in util.statfiles([join(i) for i in visit]):
1122 results[next(iv)] = st
1121 results[next(iv)] = st
1123 return results
1122 return results
1124
1123
1125 def status(self, match, subrepos, ignored, clean, unknown):
1124 def status(self, match, subrepos, ignored, clean, unknown):
1126 '''Determine the status of the working copy relative to the
1125 '''Determine the status of the working copy relative to the
1127 dirstate and return a pair of (unsure, status), where status is of type
1126 dirstate and return a pair of (unsure, status), where status is of type
1128 scmutil.status and:
1127 scmutil.status and:
1129
1128
1130 unsure:
1129 unsure:
1131 files that might have been modified since the dirstate was
1130 files that might have been modified since the dirstate was
1132 written, but need to be read to be sure (size is the same
1131 written, but need to be read to be sure (size is the same
1133 but mtime differs)
1132 but mtime differs)
1134 status.modified:
1133 status.modified:
1135 files that have definitely been modified since the dirstate
1134 files that have definitely been modified since the dirstate
1136 was written (different size or mode)
1135 was written (different size or mode)
1137 status.clean:
1136 status.clean:
1138 files that have definitely not been modified since the
1137 files that have definitely not been modified since the
1139 dirstate was written
1138 dirstate was written
1140 '''
1139 '''
1141 listignored, listclean, listunknown = ignored, clean, unknown
1140 listignored, listclean, listunknown = ignored, clean, unknown
1142 lookup, modified, added, unknown, ignored = [], [], [], [], []
1141 lookup, modified, added, unknown, ignored = [], [], [], [], []
1143 removed, deleted, clean = [], [], []
1142 removed, deleted, clean = [], [], []
1144
1143
1145 dmap = self._map
1144 dmap = self._map
1146 ladd = lookup.append # aka "unsure"
1145 ladd = lookup.append # aka "unsure"
1147 madd = modified.append
1146 madd = modified.append
1148 aadd = added.append
1147 aadd = added.append
1149 uadd = unknown.append
1148 uadd = unknown.append
1150 iadd = ignored.append
1149 iadd = ignored.append
1151 radd = removed.append
1150 radd = removed.append
1152 dadd = deleted.append
1151 dadd = deleted.append
1153 cadd = clean.append
1152 cadd = clean.append
1154 mexact = match.exact
1153 mexact = match.exact
1155 dirignore = self._dirignore
1154 dirignore = self._dirignore
1156 checkexec = self._checkexec
1155 checkexec = self._checkexec
1157 copymap = self._copymap
1156 copymap = self._copymap
1158 lastnormaltime = self._lastnormaltime
1157 lastnormaltime = self._lastnormaltime
1159
1158
1160 # We need to do full walks when either
1159 # We need to do full walks when either
1161 # - we're listing all clean files, or
1160 # - we're listing all clean files, or
1162 # - match.traversedir does something, because match.traversedir should
1161 # - match.traversedir does something, because match.traversedir should
1163 # be called for every dir in the working dir
1162 # be called for every dir in the working dir
1164 full = listclean or match.traversedir is not None
1163 full = listclean or match.traversedir is not None
1165 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1164 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1166 full=full).iteritems():
1165 full=full).iteritems():
1167 if fn not in dmap:
1166 if fn not in dmap:
1168 if (listignored or mexact(fn)) and dirignore(fn):
1167 if (listignored or mexact(fn)) and dirignore(fn):
1169 if listignored:
1168 if listignored:
1170 iadd(fn)
1169 iadd(fn)
1171 else:
1170 else:
1172 uadd(fn)
1171 uadd(fn)
1173 continue
1172 continue
1174
1173
1175 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1174 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1176 # written like that for performance reasons. dmap[fn] is not a
1175 # written like that for performance reasons. dmap[fn] is not a
1177 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1176 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1178 # opcode has fast paths when the value to be unpacked is a tuple or
1177 # opcode has fast paths when the value to be unpacked is a tuple or
1179 # a list, but falls back to creating a full-fledged iterator in
1178 # a list, but falls back to creating a full-fledged iterator in
1180 # general. That is much slower than simply accessing and storing the
1179 # general. That is much slower than simply accessing and storing the
1181 # tuple members one by one.
1180 # tuple members one by one.
1182 t = dmap[fn]
1181 t = dmap[fn]
1183 state = t[0]
1182 state = t[0]
1184 mode = t[1]
1183 mode = t[1]
1185 size = t[2]
1184 size = t[2]
1186 time = t[3]
1185 time = t[3]
1187
1186
1188 if not st and state in "nma":
1187 if not st and state in "nma":
1189 dadd(fn)
1188 dadd(fn)
1190 elif state == 'n':
1189 elif state == 'n':
1191 if (size >= 0 and
1190 if (size >= 0 and
1192 ((size != st.st_size and size != st.st_size & _rangemask)
1191 ((size != st.st_size and size != st.st_size & _rangemask)
1193 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1192 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1194 or size == -2 # other parent
1193 or size == -2 # other parent
1195 or fn in copymap):
1194 or fn in copymap):
1196 madd(fn)
1195 madd(fn)
1197 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1196 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1198 ladd(fn)
1197 ladd(fn)
1199 elif st.st_mtime == lastnormaltime:
1198 elif st.st_mtime == lastnormaltime:
1200 # fn may have just been marked as normal and it may have
1199 # fn may have just been marked as normal and it may have
1201 # changed in the same second without changing its size.
1200 # changed in the same second without changing its size.
1202 # This can happen if we quickly do multiple commits.
1201 # This can happen if we quickly do multiple commits.
1203 # Force lookup, so we don't miss such a racy file change.
1202 # Force lookup, so we don't miss such a racy file change.
1204 ladd(fn)
1203 ladd(fn)
1205 elif listclean:
1204 elif listclean:
1206 cadd(fn)
1205 cadd(fn)
1207 elif state == 'm':
1206 elif state == 'm':
1208 madd(fn)
1207 madd(fn)
1209 elif state == 'a':
1208 elif state == 'a':
1210 aadd(fn)
1209 aadd(fn)
1211 elif state == 'r':
1210 elif state == 'r':
1212 radd(fn)
1211 radd(fn)
1213
1212
1214 return (lookup, scmutil.status(modified, added, removed, deleted,
1213 return (lookup, scmutil.status(modified, added, removed, deleted,
1215 unknown, ignored, clean))
1214 unknown, ignored, clean))
1216
1215
1217 def matches(self, match):
1216 def matches(self, match):
1218 '''
1217 '''
1219 return files in the dirstate (in whatever state) filtered by match
1218 return files in the dirstate (in whatever state) filtered by match
1220 '''
1219 '''
1221 dmap = self._map
1220 dmap = self._map
1222 if match.always():
1221 if match.always():
1223 return dmap.keys()
1222 return dmap.keys()
1224 files = match.files()
1223 files = match.files()
1225 if match.isexact():
1224 if match.isexact():
1226 # fast path -- filter the other way around, since typically files is
1225 # fast path -- filter the other way around, since typically files is
1227 # much smaller than dmap
1226 # much smaller than dmap
1228 return [f for f in files if f in dmap]
1227 return [f for f in files if f in dmap]
1229 if match.prefix() and all(fn in dmap for fn in files):
1228 if match.prefix() and all(fn in dmap for fn in files):
1230 # fast path -- all the values are known to be files, so just return
1229 # fast path -- all the values are known to be files, so just return
1231 # that
1230 # that
1232 return list(files)
1231 return list(files)
1233 return [f for f in dmap if match(f)]
1232 return [f for f in dmap if match(f)]
1234
1233
1235 def _actualfilename(self, tr):
1234 def _actualfilename(self, tr):
1236 if tr:
1235 if tr:
1237 return self._pendingfilename
1236 return self._pendingfilename
1238 else:
1237 else:
1239 return self._filename
1238 return self._filename
1240
1239
1241 def savebackup(self, tr, suffix='', prefix=''):
1240 def savebackup(self, tr, suffix='', prefix=''):
1242 '''Save current dirstate into backup file with suffix'''
1241 '''Save current dirstate into backup file with suffix'''
1243 assert len(suffix) > 0 or len(prefix) > 0
1242 assert len(suffix) > 0 or len(prefix) > 0
1244 filename = self._actualfilename(tr)
1243 filename = self._actualfilename(tr)
1245
1244
1246 # use '_writedirstate' instead of 'write' to write changes certainly,
1245 # use '_writedirstate' instead of 'write' to write changes certainly,
1247 # because the latter omits writing out if transaction is running.
1246 # because the latter omits writing out if transaction is running.
1248 # output file will be used to create backup of dirstate at this point.
1247 # output file will be used to create backup of dirstate at this point.
1249 if self._dirty or not self._opener.exists(filename):
1248 if self._dirty or not self._opener.exists(filename):
1250 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1249 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1251 checkambig=True))
1250 checkambig=True))
1252
1251
1253 if tr:
1252 if tr:
1254 # ensure that subsequent tr.writepending returns True for
1253 # ensure that subsequent tr.writepending returns True for
1255 # changes written out above, even if dirstate is never
1254 # changes written out above, even if dirstate is never
1256 # changed after this
1255 # changed after this
1257 tr.addfilegenerator('dirstate', (self._filename,),
1256 tr.addfilegenerator('dirstate', (self._filename,),
1258 self._writedirstate, location='plain')
1257 self._writedirstate, location='plain')
1259
1258
1260 # ensure that pending file written above is unlinked at
1259 # ensure that pending file written above is unlinked at
1261 # failure, even if tr.writepending isn't invoked until the
1260 # failure, even if tr.writepending isn't invoked until the
1262 # end of this transaction
1261 # end of this transaction
1263 tr.registertmp(filename, location='plain')
1262 tr.registertmp(filename, location='plain')
1264
1263
1265 backupname = prefix + self._filename + suffix
1264 backupname = prefix + self._filename + suffix
1266 assert backupname != filename
1265 assert backupname != filename
1267 self._opener.tryunlink(backupname)
1266 self._opener.tryunlink(backupname)
1268 # hardlink backup is okay because _writedirstate is always called
1267 # hardlink backup is okay because _writedirstate is always called
1269 # with an "atomictemp=True" file.
1268 # with an "atomictemp=True" file.
1270 util.copyfile(self._opener.join(filename),
1269 util.copyfile(self._opener.join(filename),
1271 self._opener.join(backupname), hardlink=True)
1270 self._opener.join(backupname), hardlink=True)
1272
1271
1273 def restorebackup(self, tr, suffix='', prefix=''):
1272 def restorebackup(self, tr, suffix='', prefix=''):
1274 '''Restore dirstate by backup file with suffix'''
1273 '''Restore dirstate by backup file with suffix'''
1275 assert len(suffix) > 0 or len(prefix) > 0
1274 assert len(suffix) > 0 or len(prefix) > 0
1276 # this "invalidate()" prevents "wlock.release()" from writing
1275 # this "invalidate()" prevents "wlock.release()" from writing
1277 # changes of dirstate out after restoring from backup file
1276 # changes of dirstate out after restoring from backup file
1278 self.invalidate()
1277 self.invalidate()
1279 filename = self._actualfilename(tr)
1278 filename = self._actualfilename(tr)
1280 # using self._filename to avoid having "pending" in the backup filename
1279 # using self._filename to avoid having "pending" in the backup filename
1281 self._opener.rename(prefix + self._filename + suffix, filename,
1280 self._opener.rename(prefix + self._filename + suffix, filename,
1282 checkambig=True)
1281 checkambig=True)
1283
1282
1284 def clearbackup(self, tr, suffix='', prefix=''):
1283 def clearbackup(self, tr, suffix='', prefix=''):
1285 '''Clear backup file with suffix'''
1284 '''Clear backup file with suffix'''
1286 assert len(suffix) > 0 or len(prefix) > 0
1285 assert len(suffix) > 0 or len(prefix) > 0
1287 # using self._filename to avoid having "pending" in the backup filename
1286 # using self._filename to avoid having "pending" in the backup filename
1288 self._opener.unlink(prefix + self._filename + suffix)
1287 self._opener.unlink(prefix + self._filename + suffix)
@@ -1,99 +1,98
1 # rcutil.py - utilities about config paths, special config sections etc.
1 # rcutil.py - utilities about config paths, special config sections etc.
2 #
2 #
3 # Copyright Mercurial Contributors
3 # Copyright Mercurial Contributors
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from . import (
12 from . import (
13 encoding,
13 encoding,
14 osutil,
15 pycompat,
14 pycompat,
16 util,
15 util,
17 )
16 )
18
17
19 if pycompat.osname == 'nt':
18 if pycompat.osname == 'nt':
20 from . import scmwindows as scmplatform
19 from . import scmwindows as scmplatform
21 else:
20 else:
22 from . import scmposix as scmplatform
21 from . import scmposix as scmplatform
23
22
24 fallbackpager = scmplatform.fallbackpager
23 fallbackpager = scmplatform.fallbackpager
25 systemrcpath = scmplatform.systemrcpath
24 systemrcpath = scmplatform.systemrcpath
26 userrcpath = scmplatform.userrcpath
25 userrcpath = scmplatform.userrcpath
27
26
28 def _expandrcpath(path):
27 def _expandrcpath(path):
29 '''path could be a file or a directory. return a list of file paths'''
28 '''path could be a file or a directory. return a list of file paths'''
30 p = util.expandpath(path)
29 p = util.expandpath(path)
31 if os.path.isdir(p):
30 if os.path.isdir(p):
32 join = os.path.join
31 join = os.path.join
33 return [join(p, f) for f, k in osutil.listdir(p) if f.endswith('.rc')]
32 return [join(p, f) for f, k in util.listdir(p) if f.endswith('.rc')]
34 return [p]
33 return [p]
35
34
36 def envrcitems(env=None):
35 def envrcitems(env=None):
37 '''Return [(section, name, value, source)] config items.
36 '''Return [(section, name, value, source)] config items.
38
37
39 The config items are extracted from environment variables specified by env,
38 The config items are extracted from environment variables specified by env,
40 used to override systemrc, but not userrc.
39 used to override systemrc, but not userrc.
41
40
42 If env is not provided, encoding.environ will be used.
41 If env is not provided, encoding.environ will be used.
43 '''
42 '''
44 if env is None:
43 if env is None:
45 env = encoding.environ
44 env = encoding.environ
46 checklist = [
45 checklist = [
47 ('EDITOR', 'ui', 'editor'),
46 ('EDITOR', 'ui', 'editor'),
48 ('VISUAL', 'ui', 'editor'),
47 ('VISUAL', 'ui', 'editor'),
49 ('PAGER', 'pager', 'pager'),
48 ('PAGER', 'pager', 'pager'),
50 ]
49 ]
51 result = []
50 result = []
52 for envname, section, configname in checklist:
51 for envname, section, configname in checklist:
53 if envname not in env:
52 if envname not in env:
54 continue
53 continue
55 result.append((section, configname, env[envname], '$%s' % envname))
54 result.append((section, configname, env[envname], '$%s' % envname))
56 return result
55 return result
57
56
58 def defaultrcpath():
57 def defaultrcpath():
59 '''return rc paths in default.d'''
58 '''return rc paths in default.d'''
60 path = []
59 path = []
61 defaultpath = os.path.join(util.datapath, 'default.d')
60 defaultpath = os.path.join(util.datapath, 'default.d')
62 if os.path.isdir(defaultpath):
61 if os.path.isdir(defaultpath):
63 path = _expandrcpath(defaultpath)
62 path = _expandrcpath(defaultpath)
64 return path
63 return path
65
64
66 def rccomponents():
65 def rccomponents():
67 '''return an ordered [(type, obj)] about where to load configs.
66 '''return an ordered [(type, obj)] about where to load configs.
68
67
69 respect $HGRCPATH. if $HGRCPATH is empty, only .hg/hgrc of current repo is
68 respect $HGRCPATH. if $HGRCPATH is empty, only .hg/hgrc of current repo is
70 used. if $HGRCPATH is not set, the platform default will be used.
69 used. if $HGRCPATH is not set, the platform default will be used.
71
70
72 if a directory is provided, *.rc files under it will be used.
71 if a directory is provided, *.rc files under it will be used.
73
72
74 type could be either 'path' or 'items', if type is 'path', obj is a string,
73 type could be either 'path' or 'items', if type is 'path', obj is a string,
75 and is the config file path. if type is 'items', obj is a list of (section,
74 and is the config file path. if type is 'items', obj is a list of (section,
76 name, value, source) that should fill the config directly.
75 name, value, source) that should fill the config directly.
77 '''
76 '''
78 envrc = ('items', envrcitems())
77 envrc = ('items', envrcitems())
79
78
80 if 'HGRCPATH' in encoding.environ:
79 if 'HGRCPATH' in encoding.environ:
81 # assume HGRCPATH is all about user configs so environments can be
80 # assume HGRCPATH is all about user configs so environments can be
82 # overridden.
81 # overridden.
83 _rccomponents = [envrc]
82 _rccomponents = [envrc]
84 for p in encoding.environ['HGRCPATH'].split(pycompat.ospathsep):
83 for p in encoding.environ['HGRCPATH'].split(pycompat.ospathsep):
85 if not p:
84 if not p:
86 continue
85 continue
87 _rccomponents.extend(('path', p) for p in _expandrcpath(p))
86 _rccomponents.extend(('path', p) for p in _expandrcpath(p))
88 else:
87 else:
89 normpaths = lambda paths: [('path', os.path.normpath(p)) for p in paths]
88 normpaths = lambda paths: [('path', os.path.normpath(p)) for p in paths]
90 _rccomponents = normpaths(defaultrcpath() + systemrcpath())
89 _rccomponents = normpaths(defaultrcpath() + systemrcpath())
91 _rccomponents.append(envrc)
90 _rccomponents.append(envrc)
92 _rccomponents.extend(normpaths(userrcpath()))
91 _rccomponents.extend(normpaths(userrcpath()))
93 return _rccomponents
92 return _rccomponents
94
93
95 def defaultpagerenv():
94 def defaultpagerenv():
96 '''return a dict of default environment variables and their values,
95 '''return a dict of default environment variables and their values,
97 intended to be set before starting a pager.
96 intended to be set before starting a pager.
98 '''
97 '''
99 return {'LESS': 'FRX', 'LV': '-c'}
98 return {'LESS': 'FRX', 'LV': '-c'}
@@ -1,85 +1,85
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import array
3 import array
4 import errno
4 import errno
5 import fcntl
5 import fcntl
6 import os
6 import os
7 import sys
7 import sys
8
8
9 from . import (
9 from . import (
10 encoding,
10 encoding,
11 osutil,
12 pycompat,
11 pycompat,
12 util,
13 )
13 )
14
14
15 # BSD 'more' escapes ANSI color sequences by default. This can be disabled by
15 # BSD 'more' escapes ANSI color sequences by default. This can be disabled by
16 # $MORE variable, but there's no compatible option with Linux 'more'. Given
16 # $MORE variable, but there's no compatible option with Linux 'more'. Given
17 # OS X is widely used and most modern Unix systems would have 'less', setting
17 # OS X is widely used and most modern Unix systems would have 'less', setting
18 # 'less' as the default seems reasonable.
18 # 'less' as the default seems reasonable.
19 fallbackpager = 'less'
19 fallbackpager = 'less'
20
20
21 def _rcfiles(path):
21 def _rcfiles(path):
22 rcs = [os.path.join(path, 'hgrc')]
22 rcs = [os.path.join(path, 'hgrc')]
23 rcdir = os.path.join(path, 'hgrc.d')
23 rcdir = os.path.join(path, 'hgrc.d')
24 try:
24 try:
25 rcs.extend([os.path.join(rcdir, f)
25 rcs.extend([os.path.join(rcdir, f)
26 for f, kind in osutil.listdir(rcdir)
26 for f, kind in util.listdir(rcdir)
27 if f.endswith(".rc")])
27 if f.endswith(".rc")])
28 except OSError:
28 except OSError:
29 pass
29 pass
30 return rcs
30 return rcs
31
31
32 def systemrcpath():
32 def systemrcpath():
33 path = []
33 path = []
34 if pycompat.sysplatform == 'plan9':
34 if pycompat.sysplatform == 'plan9':
35 root = 'lib/mercurial'
35 root = 'lib/mercurial'
36 else:
36 else:
37 root = 'etc/mercurial'
37 root = 'etc/mercurial'
38 # old mod_python does not set sys.argv
38 # old mod_python does not set sys.argv
39 if len(getattr(sys, 'argv', [])) > 0:
39 if len(getattr(sys, 'argv', [])) > 0:
40 p = os.path.dirname(os.path.dirname(pycompat.sysargv[0]))
40 p = os.path.dirname(os.path.dirname(pycompat.sysargv[0]))
41 if p != '/':
41 if p != '/':
42 path.extend(_rcfiles(os.path.join(p, root)))
42 path.extend(_rcfiles(os.path.join(p, root)))
43 path.extend(_rcfiles('/' + root))
43 path.extend(_rcfiles('/' + root))
44 return path
44 return path
45
45
46 def userrcpath():
46 def userrcpath():
47 if pycompat.sysplatform == 'plan9':
47 if pycompat.sysplatform == 'plan9':
48 return [encoding.environ['home'] + '/lib/hgrc']
48 return [encoding.environ['home'] + '/lib/hgrc']
49 elif pycompat.sysplatform == 'darwin':
49 elif pycompat.sysplatform == 'darwin':
50 return [os.path.expanduser('~/.hgrc')]
50 return [os.path.expanduser('~/.hgrc')]
51 else:
51 else:
52 confighome = encoding.environ.get('XDG_CONFIG_HOME')
52 confighome = encoding.environ.get('XDG_CONFIG_HOME')
53 if confighome is None or not os.path.isabs(confighome):
53 if confighome is None or not os.path.isabs(confighome):
54 confighome = os.path.expanduser('~/.config')
54 confighome = os.path.expanduser('~/.config')
55
55
56 return [os.path.expanduser('~/.hgrc'),
56 return [os.path.expanduser('~/.hgrc'),
57 os.path.join(confighome, 'hg', 'hgrc')]
57 os.path.join(confighome, 'hg', 'hgrc')]
58
58
59 def termsize(ui):
59 def termsize(ui):
60 try:
60 try:
61 import termios
61 import termios
62 TIOCGWINSZ = termios.TIOCGWINSZ # unavailable on IRIX (issue3449)
62 TIOCGWINSZ = termios.TIOCGWINSZ # unavailable on IRIX (issue3449)
63 except (AttributeError, ImportError):
63 except (AttributeError, ImportError):
64 return 80, 24
64 return 80, 24
65
65
66 for dev in (ui.ferr, ui.fout, ui.fin):
66 for dev in (ui.ferr, ui.fout, ui.fin):
67 try:
67 try:
68 try:
68 try:
69 fd = dev.fileno()
69 fd = dev.fileno()
70 except AttributeError:
70 except AttributeError:
71 continue
71 continue
72 if not os.isatty(fd):
72 if not os.isatty(fd):
73 continue
73 continue
74 arri = fcntl.ioctl(fd, TIOCGWINSZ, '\0' * 8)
74 arri = fcntl.ioctl(fd, TIOCGWINSZ, '\0' * 8)
75 height, width = array.array(r'h', arri)[:2]
75 height, width = array.array(r'h', arri)[:2]
76 if width > 0 and height > 0:
76 if width > 0 and height > 0:
77 return width, height
77 return width, height
78 except ValueError:
78 except ValueError:
79 pass
79 pass
80 except IOError as e:
80 except IOError as e:
81 if e[0] == errno.EINVAL:
81 if e[0] == errno.EINVAL:
82 pass
82 pass
83 else:
83 else:
84 raise
84 raise
85 return 80, 24
85 return 80, 24
@@ -1,62 +1,61
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import os
3 import os
4
4
5 from . import (
5 from . import (
6 encoding,
6 encoding,
7 osutil,
8 pycompat,
7 pycompat,
9 util,
8 util,
10 win32,
9 win32,
11 )
10 )
12
11
13 try:
12 try:
14 import _winreg as winreg
13 import _winreg as winreg
15 winreg.CloseKey
14 winreg.CloseKey
16 except ImportError:
15 except ImportError:
17 import winreg
16 import winreg
18
17
19 # MS-DOS 'more' is the only pager available by default on Windows.
18 # MS-DOS 'more' is the only pager available by default on Windows.
20 fallbackpager = 'more'
19 fallbackpager = 'more'
21
20
22 def systemrcpath():
21 def systemrcpath():
23 '''return default os-specific hgrc search path'''
22 '''return default os-specific hgrc search path'''
24 rcpath = []
23 rcpath = []
25 filename = util.executablepath()
24 filename = util.executablepath()
26 # Use mercurial.ini found in directory with hg.exe
25 # Use mercurial.ini found in directory with hg.exe
27 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
26 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
28 rcpath.append(progrc)
27 rcpath.append(progrc)
29 # Use hgrc.d found in directory with hg.exe
28 # Use hgrc.d found in directory with hg.exe
30 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
29 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
31 if os.path.isdir(progrcd):
30 if os.path.isdir(progrcd):
32 for f, kind in osutil.listdir(progrcd):
31 for f, kind in util.listdir(progrcd):
33 if f.endswith('.rc'):
32 if f.endswith('.rc'):
34 rcpath.append(os.path.join(progrcd, f))
33 rcpath.append(os.path.join(progrcd, f))
35 # else look for a system rcpath in the registry
34 # else look for a system rcpath in the registry
36 value = util.lookupreg('SOFTWARE\\Mercurial', None,
35 value = util.lookupreg('SOFTWARE\\Mercurial', None,
37 winreg.HKEY_LOCAL_MACHINE)
36 winreg.HKEY_LOCAL_MACHINE)
38 if not isinstance(value, str) or not value:
37 if not isinstance(value, str) or not value:
39 return rcpath
38 return rcpath
40 value = util.localpath(value)
39 value = util.localpath(value)
41 for p in value.split(pycompat.ospathsep):
40 for p in value.split(pycompat.ospathsep):
42 if p.lower().endswith('mercurial.ini'):
41 if p.lower().endswith('mercurial.ini'):
43 rcpath.append(p)
42 rcpath.append(p)
44 elif os.path.isdir(p):
43 elif os.path.isdir(p):
45 for f, kind in osutil.listdir(p):
44 for f, kind in util.listdir(p):
46 if f.endswith('.rc'):
45 if f.endswith('.rc'):
47 rcpath.append(os.path.join(p, f))
46 rcpath.append(os.path.join(p, f))
48 return rcpath
47 return rcpath
49
48
50 def userrcpath():
49 def userrcpath():
51 '''return os-specific hgrc search path to the user dir'''
50 '''return os-specific hgrc search path to the user dir'''
52 home = os.path.expanduser('~')
51 home = os.path.expanduser('~')
53 path = [os.path.join(home, 'mercurial.ini'),
52 path = [os.path.join(home, 'mercurial.ini'),
54 os.path.join(home, '.hgrc')]
53 os.path.join(home, '.hgrc')]
55 userprofile = encoding.environ.get('USERPROFILE')
54 userprofile = encoding.environ.get('USERPROFILE')
56 if userprofile and userprofile != home:
55 if userprofile and userprofile != home:
57 path.append(os.path.join(userprofile, 'mercurial.ini'))
56 path.append(os.path.join(userprofile, 'mercurial.ini'))
58 path.append(os.path.join(userprofile, '.hgrc'))
57 path.append(os.path.join(userprofile, '.hgrc'))
59 return path
58 return path
60
59
61 def termsize(ui):
60 def termsize(ui):
62 return win32.termsize()
61 return win32.termsize()
@@ -1,3750 +1,3760
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific 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 specific implementations.
10 """Mercurial utility functions and platform specific 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 __future__ import absolute_import
16 from __future__ import absolute_import
17
17
18 import bz2
18 import bz2
19 import calendar
19 import calendar
20 import codecs
20 import codecs
21 import collections
21 import collections
22 import datetime
22 import datetime
23 import errno
23 import errno
24 import gc
24 import gc
25 import hashlib
25 import hashlib
26 import imp
26 import imp
27 import os
27 import os
28 import platform as pyplatform
28 import platform as pyplatform
29 import re as remod
29 import re as remod
30 import shutil
30 import shutil
31 import signal
31 import signal
32 import socket
32 import socket
33 import stat
33 import stat
34 import string
34 import string
35 import subprocess
35 import subprocess
36 import sys
36 import sys
37 import tempfile
37 import tempfile
38 import textwrap
38 import textwrap
39 import time
39 import time
40 import traceback
40 import traceback
41 import warnings
41 import warnings
42 import zlib
42 import zlib
43
43
44 from . import (
44 from . import (
45 base85,
45 base85,
46 encoding,
46 encoding,
47 error,
47 error,
48 i18n,
48 i18n,
49 osutil,
49 osutil,
50 parsers,
50 parsers,
51 pycompat,
51 pycompat,
52 )
52 )
53
53
54 b85decode = base85.b85decode
54 b85decode = base85.b85decode
55 b85encode = base85.b85encode
55 b85encode = base85.b85encode
56
56
57 cookielib = pycompat.cookielib
57 cookielib = pycompat.cookielib
58 empty = pycompat.empty
58 empty = pycompat.empty
59 httplib = pycompat.httplib
59 httplib = pycompat.httplib
60 httpserver = pycompat.httpserver
60 httpserver = pycompat.httpserver
61 pickle = pycompat.pickle
61 pickle = pycompat.pickle
62 queue = pycompat.queue
62 queue = pycompat.queue
63 socketserver = pycompat.socketserver
63 socketserver = pycompat.socketserver
64 stderr = pycompat.stderr
64 stderr = pycompat.stderr
65 stdin = pycompat.stdin
65 stdin = pycompat.stdin
66 stdout = pycompat.stdout
66 stdout = pycompat.stdout
67 stringio = pycompat.stringio
67 stringio = pycompat.stringio
68 urlerr = pycompat.urlerr
68 urlerr = pycompat.urlerr
69 urlreq = pycompat.urlreq
69 urlreq = pycompat.urlreq
70 xmlrpclib = pycompat.xmlrpclib
70 xmlrpclib = pycompat.xmlrpclib
71
71
72 def isatty(fp):
72 def isatty(fp):
73 try:
73 try:
74 return fp.isatty()
74 return fp.isatty()
75 except AttributeError:
75 except AttributeError:
76 return False
76 return False
77
77
78 # glibc determines buffering on first write to stdout - if we replace a TTY
78 # glibc determines buffering on first write to stdout - if we replace a TTY
79 # destined stdout with a pipe destined stdout (e.g. pager), we want line
79 # destined stdout with a pipe destined stdout (e.g. pager), we want line
80 # buffering
80 # buffering
81 if isatty(stdout):
81 if isatty(stdout):
82 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
82 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
83
83
84 if pycompat.osname == 'nt':
84 if pycompat.osname == 'nt':
85 from . import windows as platform
85 from . import windows as platform
86 stdout = platform.winstdout(stdout)
86 stdout = platform.winstdout(stdout)
87 else:
87 else:
88 from . import posix as platform
88 from . import posix as platform
89
89
90 _ = i18n._
90 _ = i18n._
91
91
92 bindunixsocket = platform.bindunixsocket
92 bindunixsocket = platform.bindunixsocket
93 cachestat = platform.cachestat
93 cachestat = platform.cachestat
94 checkexec = platform.checkexec
94 checkexec = platform.checkexec
95 checklink = platform.checklink
95 checklink = platform.checklink
96 copymode = platform.copymode
96 copymode = platform.copymode
97 executablepath = platform.executablepath
97 executablepath = platform.executablepath
98 expandglobs = platform.expandglobs
98 expandglobs = platform.expandglobs
99 explainexit = platform.explainexit
99 explainexit = platform.explainexit
100 findexe = platform.findexe
100 findexe = platform.findexe
101 gethgcmd = platform.gethgcmd
101 gethgcmd = platform.gethgcmd
102 getuser = platform.getuser
102 getuser = platform.getuser
103 getpid = os.getpid
103 getpid = os.getpid
104 groupmembers = platform.groupmembers
104 groupmembers = platform.groupmembers
105 groupname = platform.groupname
105 groupname = platform.groupname
106 hidewindow = platform.hidewindow
106 hidewindow = platform.hidewindow
107 isexec = platform.isexec
107 isexec = platform.isexec
108 isowner = platform.isowner
108 isowner = platform.isowner
109 listdir = osutil.listdir
109 localpath = platform.localpath
110 localpath = platform.localpath
110 lookupreg = platform.lookupreg
111 lookupreg = platform.lookupreg
111 makedir = platform.makedir
112 makedir = platform.makedir
112 nlinks = platform.nlinks
113 nlinks = platform.nlinks
113 normpath = platform.normpath
114 normpath = platform.normpath
114 normcase = platform.normcase
115 normcase = platform.normcase
115 normcasespec = platform.normcasespec
116 normcasespec = platform.normcasespec
116 normcasefallback = platform.normcasefallback
117 normcasefallback = platform.normcasefallback
117 openhardlinks = platform.openhardlinks
118 openhardlinks = platform.openhardlinks
118 oslink = platform.oslink
119 oslink = platform.oslink
119 parsepatchoutput = platform.parsepatchoutput
120 parsepatchoutput = platform.parsepatchoutput
120 pconvert = platform.pconvert
121 pconvert = platform.pconvert
121 poll = platform.poll
122 poll = platform.poll
122 popen = platform.popen
123 popen = platform.popen
123 posixfile = platform.posixfile
124 posixfile = platform.posixfile
124 quotecommand = platform.quotecommand
125 quotecommand = platform.quotecommand
125 readpipe = platform.readpipe
126 readpipe = platform.readpipe
126 rename = platform.rename
127 rename = platform.rename
127 removedirs = platform.removedirs
128 removedirs = platform.removedirs
128 samedevice = platform.samedevice
129 samedevice = platform.samedevice
129 samefile = platform.samefile
130 samefile = platform.samefile
130 samestat = platform.samestat
131 samestat = platform.samestat
131 setbinary = platform.setbinary
132 setbinary = platform.setbinary
132 setflags = platform.setflags
133 setflags = platform.setflags
133 setsignalhandler = platform.setsignalhandler
134 setsignalhandler = platform.setsignalhandler
134 shellquote = platform.shellquote
135 shellquote = platform.shellquote
135 spawndetached = platform.spawndetached
136 spawndetached = platform.spawndetached
136 split = platform.split
137 split = platform.split
137 sshargs = platform.sshargs
138 sshargs = platform.sshargs
138 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
139 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
139 statisexec = platform.statisexec
140 statisexec = platform.statisexec
140 statislink = platform.statislink
141 statislink = platform.statislink
141 testpid = platform.testpid
142 testpid = platform.testpid
142 umask = platform.umask
143 umask = platform.umask
143 unlink = platform.unlink
144 unlink = platform.unlink
144 username = platform.username
145 username = platform.username
145
146
147 try:
148 recvfds = osutil.recvfds
149 except AttributeError:
150 pass
151 try:
152 setprocname = osutil.setprocname
153 except AttributeError:
154 pass
155
146 # Python compatibility
156 # Python compatibility
147
157
148 _notset = object()
158 _notset = object()
149
159
150 # disable Python's problematic floating point timestamps (issue4836)
160 # disable Python's problematic floating point timestamps (issue4836)
151 # (Python hypocritically says you shouldn't change this behavior in
161 # (Python hypocritically says you shouldn't change this behavior in
152 # libraries, and sure enough Mercurial is not a library.)
162 # libraries, and sure enough Mercurial is not a library.)
153 os.stat_float_times(False)
163 os.stat_float_times(False)
154
164
155 def safehasattr(thing, attr):
165 def safehasattr(thing, attr):
156 return getattr(thing, attr, _notset) is not _notset
166 return getattr(thing, attr, _notset) is not _notset
157
167
158 def bitsfrom(container):
168 def bitsfrom(container):
159 bits = 0
169 bits = 0
160 for bit in container:
170 for bit in container:
161 bits |= bit
171 bits |= bit
162 return bits
172 return bits
163
173
164 # python 2.6 still have deprecation warning enabled by default. We do not want
174 # python 2.6 still have deprecation warning enabled by default. We do not want
165 # to display anything to standard user so detect if we are running test and
175 # to display anything to standard user so detect if we are running test and
166 # only use python deprecation warning in this case.
176 # only use python deprecation warning in this case.
167 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
177 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
168 if _dowarn:
178 if _dowarn:
169 # explicitly unfilter our warning for python 2.7
179 # explicitly unfilter our warning for python 2.7
170 #
180 #
171 # The option of setting PYTHONWARNINGS in the test runner was investigated.
181 # The option of setting PYTHONWARNINGS in the test runner was investigated.
172 # However, module name set through PYTHONWARNINGS was exactly matched, so
182 # However, module name set through PYTHONWARNINGS was exactly matched, so
173 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
183 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
174 # makes the whole PYTHONWARNINGS thing useless for our usecase.
184 # makes the whole PYTHONWARNINGS thing useless for our usecase.
175 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
185 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
176 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
186 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
177 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
187 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
178
188
179 def nouideprecwarn(msg, version, stacklevel=1):
189 def nouideprecwarn(msg, version, stacklevel=1):
180 """Issue an python native deprecation warning
190 """Issue an python native deprecation warning
181
191
182 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
192 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
183 """
193 """
184 if _dowarn:
194 if _dowarn:
185 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
195 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
186 " update your code.)") % version
196 " update your code.)") % version
187 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
197 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
188
198
189 DIGESTS = {
199 DIGESTS = {
190 'md5': hashlib.md5,
200 'md5': hashlib.md5,
191 'sha1': hashlib.sha1,
201 'sha1': hashlib.sha1,
192 'sha512': hashlib.sha512,
202 'sha512': hashlib.sha512,
193 }
203 }
194 # List of digest types from strongest to weakest
204 # List of digest types from strongest to weakest
195 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
205 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
196
206
197 for k in DIGESTS_BY_STRENGTH:
207 for k in DIGESTS_BY_STRENGTH:
198 assert k in DIGESTS
208 assert k in DIGESTS
199
209
200 class digester(object):
210 class digester(object):
201 """helper to compute digests.
211 """helper to compute digests.
202
212
203 This helper can be used to compute one or more digests given their name.
213 This helper can be used to compute one or more digests given their name.
204
214
205 >>> d = digester(['md5', 'sha1'])
215 >>> d = digester(['md5', 'sha1'])
206 >>> d.update('foo')
216 >>> d.update('foo')
207 >>> [k for k in sorted(d)]
217 >>> [k for k in sorted(d)]
208 ['md5', 'sha1']
218 ['md5', 'sha1']
209 >>> d['md5']
219 >>> d['md5']
210 'acbd18db4cc2f85cedef654fccc4a4d8'
220 'acbd18db4cc2f85cedef654fccc4a4d8'
211 >>> d['sha1']
221 >>> d['sha1']
212 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
222 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
213 >>> digester.preferred(['md5', 'sha1'])
223 >>> digester.preferred(['md5', 'sha1'])
214 'sha1'
224 'sha1'
215 """
225 """
216
226
217 def __init__(self, digests, s=''):
227 def __init__(self, digests, s=''):
218 self._hashes = {}
228 self._hashes = {}
219 for k in digests:
229 for k in digests:
220 if k not in DIGESTS:
230 if k not in DIGESTS:
221 raise Abort(_('unknown digest type: %s') % k)
231 raise Abort(_('unknown digest type: %s') % k)
222 self._hashes[k] = DIGESTS[k]()
232 self._hashes[k] = DIGESTS[k]()
223 if s:
233 if s:
224 self.update(s)
234 self.update(s)
225
235
226 def update(self, data):
236 def update(self, data):
227 for h in self._hashes.values():
237 for h in self._hashes.values():
228 h.update(data)
238 h.update(data)
229
239
230 def __getitem__(self, key):
240 def __getitem__(self, key):
231 if key not in DIGESTS:
241 if key not in DIGESTS:
232 raise Abort(_('unknown digest type: %s') % k)
242 raise Abort(_('unknown digest type: %s') % k)
233 return self._hashes[key].hexdigest()
243 return self._hashes[key].hexdigest()
234
244
235 def __iter__(self):
245 def __iter__(self):
236 return iter(self._hashes)
246 return iter(self._hashes)
237
247
238 @staticmethod
248 @staticmethod
239 def preferred(supported):
249 def preferred(supported):
240 """returns the strongest digest type in both supported and DIGESTS."""
250 """returns the strongest digest type in both supported and DIGESTS."""
241
251
242 for k in DIGESTS_BY_STRENGTH:
252 for k in DIGESTS_BY_STRENGTH:
243 if k in supported:
253 if k in supported:
244 return k
254 return k
245 return None
255 return None
246
256
247 class digestchecker(object):
257 class digestchecker(object):
248 """file handle wrapper that additionally checks content against a given
258 """file handle wrapper that additionally checks content against a given
249 size and digests.
259 size and digests.
250
260
251 d = digestchecker(fh, size, {'md5': '...'})
261 d = digestchecker(fh, size, {'md5': '...'})
252
262
253 When multiple digests are given, all of them are validated.
263 When multiple digests are given, all of them are validated.
254 """
264 """
255
265
256 def __init__(self, fh, size, digests):
266 def __init__(self, fh, size, digests):
257 self._fh = fh
267 self._fh = fh
258 self._size = size
268 self._size = size
259 self._got = 0
269 self._got = 0
260 self._digests = dict(digests)
270 self._digests = dict(digests)
261 self._digester = digester(self._digests.keys())
271 self._digester = digester(self._digests.keys())
262
272
263 def read(self, length=-1):
273 def read(self, length=-1):
264 content = self._fh.read(length)
274 content = self._fh.read(length)
265 self._digester.update(content)
275 self._digester.update(content)
266 self._got += len(content)
276 self._got += len(content)
267 return content
277 return content
268
278
269 def validate(self):
279 def validate(self):
270 if self._size != self._got:
280 if self._size != self._got:
271 raise Abort(_('size mismatch: expected %d, got %d') %
281 raise Abort(_('size mismatch: expected %d, got %d') %
272 (self._size, self._got))
282 (self._size, self._got))
273 for k, v in self._digests.items():
283 for k, v in self._digests.items():
274 if v != self._digester[k]:
284 if v != self._digester[k]:
275 # i18n: first parameter is a digest name
285 # i18n: first parameter is a digest name
276 raise Abort(_('%s mismatch: expected %s, got %s') %
286 raise Abort(_('%s mismatch: expected %s, got %s') %
277 (k, v, self._digester[k]))
287 (k, v, self._digester[k]))
278
288
279 try:
289 try:
280 buffer = buffer
290 buffer = buffer
281 except NameError:
291 except NameError:
282 if not pycompat.ispy3:
292 if not pycompat.ispy3:
283 def buffer(sliceable, offset=0, length=None):
293 def buffer(sliceable, offset=0, length=None):
284 if length is not None:
294 if length is not None:
285 return sliceable[offset:offset + length]
295 return sliceable[offset:offset + length]
286 return sliceable[offset:]
296 return sliceable[offset:]
287 else:
297 else:
288 def buffer(sliceable, offset=0, length=None):
298 def buffer(sliceable, offset=0, length=None):
289 if length is not None:
299 if length is not None:
290 return memoryview(sliceable)[offset:offset + length]
300 return memoryview(sliceable)[offset:offset + length]
291 return memoryview(sliceable)[offset:]
301 return memoryview(sliceable)[offset:]
292
302
293 closefds = pycompat.osname == 'posix'
303 closefds = pycompat.osname == 'posix'
294
304
295 _chunksize = 4096
305 _chunksize = 4096
296
306
297 class bufferedinputpipe(object):
307 class bufferedinputpipe(object):
298 """a manually buffered input pipe
308 """a manually buffered input pipe
299
309
300 Python will not let us use buffered IO and lazy reading with 'polling' at
310 Python will not let us use buffered IO and lazy reading with 'polling' at
301 the same time. We cannot probe the buffer state and select will not detect
311 the same time. We cannot probe the buffer state and select will not detect
302 that data are ready to read if they are already buffered.
312 that data are ready to read if they are already buffered.
303
313
304 This class let us work around that by implementing its own buffering
314 This class let us work around that by implementing its own buffering
305 (allowing efficient readline) while offering a way to know if the buffer is
315 (allowing efficient readline) while offering a way to know if the buffer is
306 empty from the output (allowing collaboration of the buffer with polling).
316 empty from the output (allowing collaboration of the buffer with polling).
307
317
308 This class lives in the 'util' module because it makes use of the 'os'
318 This class lives in the 'util' module because it makes use of the 'os'
309 module from the python stdlib.
319 module from the python stdlib.
310 """
320 """
311
321
312 def __init__(self, input):
322 def __init__(self, input):
313 self._input = input
323 self._input = input
314 self._buffer = []
324 self._buffer = []
315 self._eof = False
325 self._eof = False
316 self._lenbuf = 0
326 self._lenbuf = 0
317
327
318 @property
328 @property
319 def hasbuffer(self):
329 def hasbuffer(self):
320 """True is any data is currently buffered
330 """True is any data is currently buffered
321
331
322 This will be used externally a pre-step for polling IO. If there is
332 This will be used externally a pre-step for polling IO. If there is
323 already data then no polling should be set in place."""
333 already data then no polling should be set in place."""
324 return bool(self._buffer)
334 return bool(self._buffer)
325
335
326 @property
336 @property
327 def closed(self):
337 def closed(self):
328 return self._input.closed
338 return self._input.closed
329
339
330 def fileno(self):
340 def fileno(self):
331 return self._input.fileno()
341 return self._input.fileno()
332
342
333 def close(self):
343 def close(self):
334 return self._input.close()
344 return self._input.close()
335
345
336 def read(self, size):
346 def read(self, size):
337 while (not self._eof) and (self._lenbuf < size):
347 while (not self._eof) and (self._lenbuf < size):
338 self._fillbuffer()
348 self._fillbuffer()
339 return self._frombuffer(size)
349 return self._frombuffer(size)
340
350
341 def readline(self, *args, **kwargs):
351 def readline(self, *args, **kwargs):
342 if 1 < len(self._buffer):
352 if 1 < len(self._buffer):
343 # this should not happen because both read and readline end with a
353 # this should not happen because both read and readline end with a
344 # _frombuffer call that collapse it.
354 # _frombuffer call that collapse it.
345 self._buffer = [''.join(self._buffer)]
355 self._buffer = [''.join(self._buffer)]
346 self._lenbuf = len(self._buffer[0])
356 self._lenbuf = len(self._buffer[0])
347 lfi = -1
357 lfi = -1
348 if self._buffer:
358 if self._buffer:
349 lfi = self._buffer[-1].find('\n')
359 lfi = self._buffer[-1].find('\n')
350 while (not self._eof) and lfi < 0:
360 while (not self._eof) and lfi < 0:
351 self._fillbuffer()
361 self._fillbuffer()
352 if self._buffer:
362 if self._buffer:
353 lfi = self._buffer[-1].find('\n')
363 lfi = self._buffer[-1].find('\n')
354 size = lfi + 1
364 size = lfi + 1
355 if lfi < 0: # end of file
365 if lfi < 0: # end of file
356 size = self._lenbuf
366 size = self._lenbuf
357 elif 1 < len(self._buffer):
367 elif 1 < len(self._buffer):
358 # we need to take previous chunks into account
368 # we need to take previous chunks into account
359 size += self._lenbuf - len(self._buffer[-1])
369 size += self._lenbuf - len(self._buffer[-1])
360 return self._frombuffer(size)
370 return self._frombuffer(size)
361
371
362 def _frombuffer(self, size):
372 def _frombuffer(self, size):
363 """return at most 'size' data from the buffer
373 """return at most 'size' data from the buffer
364
374
365 The data are removed from the buffer."""
375 The data are removed from the buffer."""
366 if size == 0 or not self._buffer:
376 if size == 0 or not self._buffer:
367 return ''
377 return ''
368 buf = self._buffer[0]
378 buf = self._buffer[0]
369 if 1 < len(self._buffer):
379 if 1 < len(self._buffer):
370 buf = ''.join(self._buffer)
380 buf = ''.join(self._buffer)
371
381
372 data = buf[:size]
382 data = buf[:size]
373 buf = buf[len(data):]
383 buf = buf[len(data):]
374 if buf:
384 if buf:
375 self._buffer = [buf]
385 self._buffer = [buf]
376 self._lenbuf = len(buf)
386 self._lenbuf = len(buf)
377 else:
387 else:
378 self._buffer = []
388 self._buffer = []
379 self._lenbuf = 0
389 self._lenbuf = 0
380 return data
390 return data
381
391
382 def _fillbuffer(self):
392 def _fillbuffer(self):
383 """read data to the buffer"""
393 """read data to the buffer"""
384 data = os.read(self._input.fileno(), _chunksize)
394 data = os.read(self._input.fileno(), _chunksize)
385 if not data:
395 if not data:
386 self._eof = True
396 self._eof = True
387 else:
397 else:
388 self._lenbuf += len(data)
398 self._lenbuf += len(data)
389 self._buffer.append(data)
399 self._buffer.append(data)
390
400
391 def popen2(cmd, env=None, newlines=False):
401 def popen2(cmd, env=None, newlines=False):
392 # Setting bufsize to -1 lets the system decide the buffer size.
402 # Setting bufsize to -1 lets the system decide the buffer size.
393 # The default for bufsize is 0, meaning unbuffered. This leads to
403 # The default for bufsize is 0, meaning unbuffered. This leads to
394 # poor performance on Mac OS X: http://bugs.python.org/issue4194
404 # poor performance on Mac OS X: http://bugs.python.org/issue4194
395 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
405 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
396 close_fds=closefds,
406 close_fds=closefds,
397 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
407 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
398 universal_newlines=newlines,
408 universal_newlines=newlines,
399 env=env)
409 env=env)
400 return p.stdin, p.stdout
410 return p.stdin, p.stdout
401
411
402 def popen3(cmd, env=None, newlines=False):
412 def popen3(cmd, env=None, newlines=False):
403 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
413 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
404 return stdin, stdout, stderr
414 return stdin, stdout, stderr
405
415
406 def popen4(cmd, env=None, newlines=False, bufsize=-1):
416 def popen4(cmd, env=None, newlines=False, bufsize=-1):
407 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
417 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
408 close_fds=closefds,
418 close_fds=closefds,
409 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
419 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
410 stderr=subprocess.PIPE,
420 stderr=subprocess.PIPE,
411 universal_newlines=newlines,
421 universal_newlines=newlines,
412 env=env)
422 env=env)
413 return p.stdin, p.stdout, p.stderr, p
423 return p.stdin, p.stdout, p.stderr, p
414
424
415 def version():
425 def version():
416 """Return version information if available."""
426 """Return version information if available."""
417 try:
427 try:
418 from . import __version__
428 from . import __version__
419 return __version__.version
429 return __version__.version
420 except ImportError:
430 except ImportError:
421 return 'unknown'
431 return 'unknown'
422
432
423 def versiontuple(v=None, n=4):
433 def versiontuple(v=None, n=4):
424 """Parses a Mercurial version string into an N-tuple.
434 """Parses a Mercurial version string into an N-tuple.
425
435
426 The version string to be parsed is specified with the ``v`` argument.
436 The version string to be parsed is specified with the ``v`` argument.
427 If it isn't defined, the current Mercurial version string will be parsed.
437 If it isn't defined, the current Mercurial version string will be parsed.
428
438
429 ``n`` can be 2, 3, or 4. Here is how some version strings map to
439 ``n`` can be 2, 3, or 4. Here is how some version strings map to
430 returned values:
440 returned values:
431
441
432 >>> v = '3.6.1+190-df9b73d2d444'
442 >>> v = '3.6.1+190-df9b73d2d444'
433 >>> versiontuple(v, 2)
443 >>> versiontuple(v, 2)
434 (3, 6)
444 (3, 6)
435 >>> versiontuple(v, 3)
445 >>> versiontuple(v, 3)
436 (3, 6, 1)
446 (3, 6, 1)
437 >>> versiontuple(v, 4)
447 >>> versiontuple(v, 4)
438 (3, 6, 1, '190-df9b73d2d444')
448 (3, 6, 1, '190-df9b73d2d444')
439
449
440 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
450 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
441 (3, 6, 1, '190-df9b73d2d444+20151118')
451 (3, 6, 1, '190-df9b73d2d444+20151118')
442
452
443 >>> v = '3.6'
453 >>> v = '3.6'
444 >>> versiontuple(v, 2)
454 >>> versiontuple(v, 2)
445 (3, 6)
455 (3, 6)
446 >>> versiontuple(v, 3)
456 >>> versiontuple(v, 3)
447 (3, 6, None)
457 (3, 6, None)
448 >>> versiontuple(v, 4)
458 >>> versiontuple(v, 4)
449 (3, 6, None, None)
459 (3, 6, None, None)
450
460
451 >>> v = '3.9-rc'
461 >>> v = '3.9-rc'
452 >>> versiontuple(v, 2)
462 >>> versiontuple(v, 2)
453 (3, 9)
463 (3, 9)
454 >>> versiontuple(v, 3)
464 >>> versiontuple(v, 3)
455 (3, 9, None)
465 (3, 9, None)
456 >>> versiontuple(v, 4)
466 >>> versiontuple(v, 4)
457 (3, 9, None, 'rc')
467 (3, 9, None, 'rc')
458
468
459 >>> v = '3.9-rc+2-02a8fea4289b'
469 >>> v = '3.9-rc+2-02a8fea4289b'
460 >>> versiontuple(v, 2)
470 >>> versiontuple(v, 2)
461 (3, 9)
471 (3, 9)
462 >>> versiontuple(v, 3)
472 >>> versiontuple(v, 3)
463 (3, 9, None)
473 (3, 9, None)
464 >>> versiontuple(v, 4)
474 >>> versiontuple(v, 4)
465 (3, 9, None, 'rc+2-02a8fea4289b')
475 (3, 9, None, 'rc+2-02a8fea4289b')
466 """
476 """
467 if not v:
477 if not v:
468 v = version()
478 v = version()
469 parts = remod.split('[\+-]', v, 1)
479 parts = remod.split('[\+-]', v, 1)
470 if len(parts) == 1:
480 if len(parts) == 1:
471 vparts, extra = parts[0], None
481 vparts, extra = parts[0], None
472 else:
482 else:
473 vparts, extra = parts
483 vparts, extra = parts
474
484
475 vints = []
485 vints = []
476 for i in vparts.split('.'):
486 for i in vparts.split('.'):
477 try:
487 try:
478 vints.append(int(i))
488 vints.append(int(i))
479 except ValueError:
489 except ValueError:
480 break
490 break
481 # (3, 6) -> (3, 6, None)
491 # (3, 6) -> (3, 6, None)
482 while len(vints) < 3:
492 while len(vints) < 3:
483 vints.append(None)
493 vints.append(None)
484
494
485 if n == 2:
495 if n == 2:
486 return (vints[0], vints[1])
496 return (vints[0], vints[1])
487 if n == 3:
497 if n == 3:
488 return (vints[0], vints[1], vints[2])
498 return (vints[0], vints[1], vints[2])
489 if n == 4:
499 if n == 4:
490 return (vints[0], vints[1], vints[2], extra)
500 return (vints[0], vints[1], vints[2], extra)
491
501
492 # used by parsedate
502 # used by parsedate
493 defaultdateformats = (
503 defaultdateformats = (
494 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
504 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
495 '%Y-%m-%dT%H:%M', # without seconds
505 '%Y-%m-%dT%H:%M', # without seconds
496 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
506 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
497 '%Y-%m-%dT%H%M', # without seconds
507 '%Y-%m-%dT%H%M', # without seconds
498 '%Y-%m-%d %H:%M:%S', # our common legal variant
508 '%Y-%m-%d %H:%M:%S', # our common legal variant
499 '%Y-%m-%d %H:%M', # without seconds
509 '%Y-%m-%d %H:%M', # without seconds
500 '%Y-%m-%d %H%M%S', # without :
510 '%Y-%m-%d %H%M%S', # without :
501 '%Y-%m-%d %H%M', # without seconds
511 '%Y-%m-%d %H%M', # without seconds
502 '%Y-%m-%d %I:%M:%S%p',
512 '%Y-%m-%d %I:%M:%S%p',
503 '%Y-%m-%d %H:%M',
513 '%Y-%m-%d %H:%M',
504 '%Y-%m-%d %I:%M%p',
514 '%Y-%m-%d %I:%M%p',
505 '%Y-%m-%d',
515 '%Y-%m-%d',
506 '%m-%d',
516 '%m-%d',
507 '%m/%d',
517 '%m/%d',
508 '%m/%d/%y',
518 '%m/%d/%y',
509 '%m/%d/%Y',
519 '%m/%d/%Y',
510 '%a %b %d %H:%M:%S %Y',
520 '%a %b %d %H:%M:%S %Y',
511 '%a %b %d %I:%M:%S%p %Y',
521 '%a %b %d %I:%M:%S%p %Y',
512 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
522 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
513 '%b %d %H:%M:%S %Y',
523 '%b %d %H:%M:%S %Y',
514 '%b %d %I:%M:%S%p %Y',
524 '%b %d %I:%M:%S%p %Y',
515 '%b %d %H:%M:%S',
525 '%b %d %H:%M:%S',
516 '%b %d %I:%M:%S%p',
526 '%b %d %I:%M:%S%p',
517 '%b %d %H:%M',
527 '%b %d %H:%M',
518 '%b %d %I:%M%p',
528 '%b %d %I:%M%p',
519 '%b %d %Y',
529 '%b %d %Y',
520 '%b %d',
530 '%b %d',
521 '%H:%M:%S',
531 '%H:%M:%S',
522 '%I:%M:%S%p',
532 '%I:%M:%S%p',
523 '%H:%M',
533 '%H:%M',
524 '%I:%M%p',
534 '%I:%M%p',
525 )
535 )
526
536
527 extendeddateformats = defaultdateformats + (
537 extendeddateformats = defaultdateformats + (
528 "%Y",
538 "%Y",
529 "%Y-%m",
539 "%Y-%m",
530 "%b",
540 "%b",
531 "%b %Y",
541 "%b %Y",
532 )
542 )
533
543
534 def cachefunc(func):
544 def cachefunc(func):
535 '''cache the result of function calls'''
545 '''cache the result of function calls'''
536 # XXX doesn't handle keywords args
546 # XXX doesn't handle keywords args
537 if func.__code__.co_argcount == 0:
547 if func.__code__.co_argcount == 0:
538 cache = []
548 cache = []
539 def f():
549 def f():
540 if len(cache) == 0:
550 if len(cache) == 0:
541 cache.append(func())
551 cache.append(func())
542 return cache[0]
552 return cache[0]
543 return f
553 return f
544 cache = {}
554 cache = {}
545 if func.__code__.co_argcount == 1:
555 if func.__code__.co_argcount == 1:
546 # we gain a small amount of time because
556 # we gain a small amount of time because
547 # we don't need to pack/unpack the list
557 # we don't need to pack/unpack the list
548 def f(arg):
558 def f(arg):
549 if arg not in cache:
559 if arg not in cache:
550 cache[arg] = func(arg)
560 cache[arg] = func(arg)
551 return cache[arg]
561 return cache[arg]
552 else:
562 else:
553 def f(*args):
563 def f(*args):
554 if args not in cache:
564 if args not in cache:
555 cache[args] = func(*args)
565 cache[args] = func(*args)
556 return cache[args]
566 return cache[args]
557
567
558 return f
568 return f
559
569
560 class sortdict(dict):
570 class sortdict(dict):
561 '''a simple sorted dictionary'''
571 '''a simple sorted dictionary'''
562 def __init__(self, data=None):
572 def __init__(self, data=None):
563 self._list = []
573 self._list = []
564 if data:
574 if data:
565 self.update(data)
575 self.update(data)
566 def copy(self):
576 def copy(self):
567 return sortdict(self)
577 return sortdict(self)
568 def __setitem__(self, key, val):
578 def __setitem__(self, key, val):
569 if key in self:
579 if key in self:
570 self._list.remove(key)
580 self._list.remove(key)
571 self._list.append(key)
581 self._list.append(key)
572 dict.__setitem__(self, key, val)
582 dict.__setitem__(self, key, val)
573 def __iter__(self):
583 def __iter__(self):
574 return self._list.__iter__()
584 return self._list.__iter__()
575 def update(self, src):
585 def update(self, src):
576 if isinstance(src, dict):
586 if isinstance(src, dict):
577 src = src.iteritems()
587 src = src.iteritems()
578 for k, v in src:
588 for k, v in src:
579 self[k] = v
589 self[k] = v
580 def clear(self):
590 def clear(self):
581 dict.clear(self)
591 dict.clear(self)
582 self._list = []
592 self._list = []
583 def items(self):
593 def items(self):
584 return [(k, self[k]) for k in self._list]
594 return [(k, self[k]) for k in self._list]
585 def __delitem__(self, key):
595 def __delitem__(self, key):
586 dict.__delitem__(self, key)
596 dict.__delitem__(self, key)
587 self._list.remove(key)
597 self._list.remove(key)
588 def pop(self, key, *args, **kwargs):
598 def pop(self, key, *args, **kwargs):
589 try:
599 try:
590 self._list.remove(key)
600 self._list.remove(key)
591 except ValueError:
601 except ValueError:
592 pass
602 pass
593 return dict.pop(self, key, *args, **kwargs)
603 return dict.pop(self, key, *args, **kwargs)
594 def keys(self):
604 def keys(self):
595 return self._list[:]
605 return self._list[:]
596 def iterkeys(self):
606 def iterkeys(self):
597 return self._list.__iter__()
607 return self._list.__iter__()
598 def iteritems(self):
608 def iteritems(self):
599 for k in self._list:
609 for k in self._list:
600 yield k, self[k]
610 yield k, self[k]
601 def insert(self, index, key, val):
611 def insert(self, index, key, val):
602 self._list.insert(index, key)
612 self._list.insert(index, key)
603 dict.__setitem__(self, key, val)
613 dict.__setitem__(self, key, val)
604 def __repr__(self):
614 def __repr__(self):
605 if not self:
615 if not self:
606 return '%s()' % self.__class__.__name__
616 return '%s()' % self.__class__.__name__
607 return '%s(%r)' % (self.__class__.__name__, self.items())
617 return '%s(%r)' % (self.__class__.__name__, self.items())
608
618
609 class _lrucachenode(object):
619 class _lrucachenode(object):
610 """A node in a doubly linked list.
620 """A node in a doubly linked list.
611
621
612 Holds a reference to nodes on either side as well as a key-value
622 Holds a reference to nodes on either side as well as a key-value
613 pair for the dictionary entry.
623 pair for the dictionary entry.
614 """
624 """
615 __slots__ = (u'next', u'prev', u'key', u'value')
625 __slots__ = (u'next', u'prev', u'key', u'value')
616
626
617 def __init__(self):
627 def __init__(self):
618 self.next = None
628 self.next = None
619 self.prev = None
629 self.prev = None
620
630
621 self.key = _notset
631 self.key = _notset
622 self.value = None
632 self.value = None
623
633
624 def markempty(self):
634 def markempty(self):
625 """Mark the node as emptied."""
635 """Mark the node as emptied."""
626 self.key = _notset
636 self.key = _notset
627
637
628 class lrucachedict(object):
638 class lrucachedict(object):
629 """Dict that caches most recent accesses and sets.
639 """Dict that caches most recent accesses and sets.
630
640
631 The dict consists of an actual backing dict - indexed by original
641 The dict consists of an actual backing dict - indexed by original
632 key - and a doubly linked circular list defining the order of entries in
642 key - and a doubly linked circular list defining the order of entries in
633 the cache.
643 the cache.
634
644
635 The head node is the newest entry in the cache. If the cache is full,
645 The head node is the newest entry in the cache. If the cache is full,
636 we recycle head.prev and make it the new head. Cache accesses result in
646 we recycle head.prev and make it the new head. Cache accesses result in
637 the node being moved to before the existing head and being marked as the
647 the node being moved to before the existing head and being marked as the
638 new head node.
648 new head node.
639 """
649 """
640 def __init__(self, max):
650 def __init__(self, max):
641 self._cache = {}
651 self._cache = {}
642
652
643 self._head = head = _lrucachenode()
653 self._head = head = _lrucachenode()
644 head.prev = head
654 head.prev = head
645 head.next = head
655 head.next = head
646 self._size = 1
656 self._size = 1
647 self._capacity = max
657 self._capacity = max
648
658
649 def __len__(self):
659 def __len__(self):
650 return len(self._cache)
660 return len(self._cache)
651
661
652 def __contains__(self, k):
662 def __contains__(self, k):
653 return k in self._cache
663 return k in self._cache
654
664
655 def __iter__(self):
665 def __iter__(self):
656 # We don't have to iterate in cache order, but why not.
666 # We don't have to iterate in cache order, but why not.
657 n = self._head
667 n = self._head
658 for i in range(len(self._cache)):
668 for i in range(len(self._cache)):
659 yield n.key
669 yield n.key
660 n = n.next
670 n = n.next
661
671
662 def __getitem__(self, k):
672 def __getitem__(self, k):
663 node = self._cache[k]
673 node = self._cache[k]
664 self._movetohead(node)
674 self._movetohead(node)
665 return node.value
675 return node.value
666
676
667 def __setitem__(self, k, v):
677 def __setitem__(self, k, v):
668 node = self._cache.get(k)
678 node = self._cache.get(k)
669 # Replace existing value and mark as newest.
679 # Replace existing value and mark as newest.
670 if node is not None:
680 if node is not None:
671 node.value = v
681 node.value = v
672 self._movetohead(node)
682 self._movetohead(node)
673 return
683 return
674
684
675 if self._size < self._capacity:
685 if self._size < self._capacity:
676 node = self._addcapacity()
686 node = self._addcapacity()
677 else:
687 else:
678 # Grab the last/oldest item.
688 # Grab the last/oldest item.
679 node = self._head.prev
689 node = self._head.prev
680
690
681 # At capacity. Kill the old entry.
691 # At capacity. Kill the old entry.
682 if node.key is not _notset:
692 if node.key is not _notset:
683 del self._cache[node.key]
693 del self._cache[node.key]
684
694
685 node.key = k
695 node.key = k
686 node.value = v
696 node.value = v
687 self._cache[k] = node
697 self._cache[k] = node
688 # And mark it as newest entry. No need to adjust order since it
698 # And mark it as newest entry. No need to adjust order since it
689 # is already self._head.prev.
699 # is already self._head.prev.
690 self._head = node
700 self._head = node
691
701
692 def __delitem__(self, k):
702 def __delitem__(self, k):
693 node = self._cache.pop(k)
703 node = self._cache.pop(k)
694 node.markempty()
704 node.markempty()
695
705
696 # Temporarily mark as newest item before re-adjusting head to make
706 # Temporarily mark as newest item before re-adjusting head to make
697 # this node the oldest item.
707 # this node the oldest item.
698 self._movetohead(node)
708 self._movetohead(node)
699 self._head = node.next
709 self._head = node.next
700
710
701 # Additional dict methods.
711 # Additional dict methods.
702
712
703 def get(self, k, default=None):
713 def get(self, k, default=None):
704 try:
714 try:
705 return self._cache[k].value
715 return self._cache[k].value
706 except KeyError:
716 except KeyError:
707 return default
717 return default
708
718
709 def clear(self):
719 def clear(self):
710 n = self._head
720 n = self._head
711 while n.key is not _notset:
721 while n.key is not _notset:
712 n.markempty()
722 n.markempty()
713 n = n.next
723 n = n.next
714
724
715 self._cache.clear()
725 self._cache.clear()
716
726
717 def copy(self):
727 def copy(self):
718 result = lrucachedict(self._capacity)
728 result = lrucachedict(self._capacity)
719 n = self._head.prev
729 n = self._head.prev
720 # Iterate in oldest-to-newest order, so the copy has the right ordering
730 # Iterate in oldest-to-newest order, so the copy has the right ordering
721 for i in range(len(self._cache)):
731 for i in range(len(self._cache)):
722 result[n.key] = n.value
732 result[n.key] = n.value
723 n = n.prev
733 n = n.prev
724 return result
734 return result
725
735
726 def _movetohead(self, node):
736 def _movetohead(self, node):
727 """Mark a node as the newest, making it the new head.
737 """Mark a node as the newest, making it the new head.
728
738
729 When a node is accessed, it becomes the freshest entry in the LRU
739 When a node is accessed, it becomes the freshest entry in the LRU
730 list, which is denoted by self._head.
740 list, which is denoted by self._head.
731
741
732 Visually, let's make ``N`` the new head node (* denotes head):
742 Visually, let's make ``N`` the new head node (* denotes head):
733
743
734 previous/oldest <-> head <-> next/next newest
744 previous/oldest <-> head <-> next/next newest
735
745
736 ----<->--- A* ---<->-----
746 ----<->--- A* ---<->-----
737 | |
747 | |
738 E <-> D <-> N <-> C <-> B
748 E <-> D <-> N <-> C <-> B
739
749
740 To:
750 To:
741
751
742 ----<->--- N* ---<->-----
752 ----<->--- N* ---<->-----
743 | |
753 | |
744 E <-> D <-> C <-> B <-> A
754 E <-> D <-> C <-> B <-> A
745
755
746 This requires the following moves:
756 This requires the following moves:
747
757
748 C.next = D (node.prev.next = node.next)
758 C.next = D (node.prev.next = node.next)
749 D.prev = C (node.next.prev = node.prev)
759 D.prev = C (node.next.prev = node.prev)
750 E.next = N (head.prev.next = node)
760 E.next = N (head.prev.next = node)
751 N.prev = E (node.prev = head.prev)
761 N.prev = E (node.prev = head.prev)
752 N.next = A (node.next = head)
762 N.next = A (node.next = head)
753 A.prev = N (head.prev = node)
763 A.prev = N (head.prev = node)
754 """
764 """
755 head = self._head
765 head = self._head
756 # C.next = D
766 # C.next = D
757 node.prev.next = node.next
767 node.prev.next = node.next
758 # D.prev = C
768 # D.prev = C
759 node.next.prev = node.prev
769 node.next.prev = node.prev
760 # N.prev = E
770 # N.prev = E
761 node.prev = head.prev
771 node.prev = head.prev
762 # N.next = A
772 # N.next = A
763 # It is tempting to do just "head" here, however if node is
773 # It is tempting to do just "head" here, however if node is
764 # adjacent to head, this will do bad things.
774 # adjacent to head, this will do bad things.
765 node.next = head.prev.next
775 node.next = head.prev.next
766 # E.next = N
776 # E.next = N
767 node.next.prev = node
777 node.next.prev = node
768 # A.prev = N
778 # A.prev = N
769 node.prev.next = node
779 node.prev.next = node
770
780
771 self._head = node
781 self._head = node
772
782
773 def _addcapacity(self):
783 def _addcapacity(self):
774 """Add a node to the circular linked list.
784 """Add a node to the circular linked list.
775
785
776 The new node is inserted before the head node.
786 The new node is inserted before the head node.
777 """
787 """
778 head = self._head
788 head = self._head
779 node = _lrucachenode()
789 node = _lrucachenode()
780 head.prev.next = node
790 head.prev.next = node
781 node.prev = head.prev
791 node.prev = head.prev
782 node.next = head
792 node.next = head
783 head.prev = node
793 head.prev = node
784 self._size += 1
794 self._size += 1
785 return node
795 return node
786
796
787 def lrucachefunc(func):
797 def lrucachefunc(func):
788 '''cache most recent results of function calls'''
798 '''cache most recent results of function calls'''
789 cache = {}
799 cache = {}
790 order = collections.deque()
800 order = collections.deque()
791 if func.__code__.co_argcount == 1:
801 if func.__code__.co_argcount == 1:
792 def f(arg):
802 def f(arg):
793 if arg not in cache:
803 if arg not in cache:
794 if len(cache) > 20:
804 if len(cache) > 20:
795 del cache[order.popleft()]
805 del cache[order.popleft()]
796 cache[arg] = func(arg)
806 cache[arg] = func(arg)
797 else:
807 else:
798 order.remove(arg)
808 order.remove(arg)
799 order.append(arg)
809 order.append(arg)
800 return cache[arg]
810 return cache[arg]
801 else:
811 else:
802 def f(*args):
812 def f(*args):
803 if args not in cache:
813 if args not in cache:
804 if len(cache) > 20:
814 if len(cache) > 20:
805 del cache[order.popleft()]
815 del cache[order.popleft()]
806 cache[args] = func(*args)
816 cache[args] = func(*args)
807 else:
817 else:
808 order.remove(args)
818 order.remove(args)
809 order.append(args)
819 order.append(args)
810 return cache[args]
820 return cache[args]
811
821
812 return f
822 return f
813
823
814 class propertycache(object):
824 class propertycache(object):
815 def __init__(self, func):
825 def __init__(self, func):
816 self.func = func
826 self.func = func
817 self.name = func.__name__
827 self.name = func.__name__
818 def __get__(self, obj, type=None):
828 def __get__(self, obj, type=None):
819 result = self.func(obj)
829 result = self.func(obj)
820 self.cachevalue(obj, result)
830 self.cachevalue(obj, result)
821 return result
831 return result
822
832
823 def cachevalue(self, obj, value):
833 def cachevalue(self, obj, value):
824 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
834 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
825 obj.__dict__[self.name] = value
835 obj.__dict__[self.name] = value
826
836
827 def pipefilter(s, cmd):
837 def pipefilter(s, cmd):
828 '''filter string S through command CMD, returning its output'''
838 '''filter string S through command CMD, returning its output'''
829 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
839 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
830 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
840 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
831 pout, perr = p.communicate(s)
841 pout, perr = p.communicate(s)
832 return pout
842 return pout
833
843
834 def tempfilter(s, cmd):
844 def tempfilter(s, cmd):
835 '''filter string S through a pair of temporary files with CMD.
845 '''filter string S through a pair of temporary files with CMD.
836 CMD is used as a template to create the real command to be run,
846 CMD is used as a template to create the real command to be run,
837 with the strings INFILE and OUTFILE replaced by the real names of
847 with the strings INFILE and OUTFILE replaced by the real names of
838 the temporary files generated.'''
848 the temporary files generated.'''
839 inname, outname = None, None
849 inname, outname = None, None
840 try:
850 try:
841 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
851 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
842 fp = os.fdopen(infd, pycompat.sysstr('wb'))
852 fp = os.fdopen(infd, pycompat.sysstr('wb'))
843 fp.write(s)
853 fp.write(s)
844 fp.close()
854 fp.close()
845 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
855 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
846 os.close(outfd)
856 os.close(outfd)
847 cmd = cmd.replace('INFILE', inname)
857 cmd = cmd.replace('INFILE', inname)
848 cmd = cmd.replace('OUTFILE', outname)
858 cmd = cmd.replace('OUTFILE', outname)
849 code = os.system(cmd)
859 code = os.system(cmd)
850 if pycompat.sysplatform == 'OpenVMS' and code & 1:
860 if pycompat.sysplatform == 'OpenVMS' and code & 1:
851 code = 0
861 code = 0
852 if code:
862 if code:
853 raise Abort(_("command '%s' failed: %s") %
863 raise Abort(_("command '%s' failed: %s") %
854 (cmd, explainexit(code)))
864 (cmd, explainexit(code)))
855 return readfile(outname)
865 return readfile(outname)
856 finally:
866 finally:
857 try:
867 try:
858 if inname:
868 if inname:
859 os.unlink(inname)
869 os.unlink(inname)
860 except OSError:
870 except OSError:
861 pass
871 pass
862 try:
872 try:
863 if outname:
873 if outname:
864 os.unlink(outname)
874 os.unlink(outname)
865 except OSError:
875 except OSError:
866 pass
876 pass
867
877
868 filtertable = {
878 filtertable = {
869 'tempfile:': tempfilter,
879 'tempfile:': tempfilter,
870 'pipe:': pipefilter,
880 'pipe:': pipefilter,
871 }
881 }
872
882
873 def filter(s, cmd):
883 def filter(s, cmd):
874 "filter a string through a command that transforms its input to its output"
884 "filter a string through a command that transforms its input to its output"
875 for name, fn in filtertable.iteritems():
885 for name, fn in filtertable.iteritems():
876 if cmd.startswith(name):
886 if cmd.startswith(name):
877 return fn(s, cmd[len(name):].lstrip())
887 return fn(s, cmd[len(name):].lstrip())
878 return pipefilter(s, cmd)
888 return pipefilter(s, cmd)
879
889
880 def binary(s):
890 def binary(s):
881 """return true if a string is binary data"""
891 """return true if a string is binary data"""
882 return bool(s and '\0' in s)
892 return bool(s and '\0' in s)
883
893
884 def increasingchunks(source, min=1024, max=65536):
894 def increasingchunks(source, min=1024, max=65536):
885 '''return no less than min bytes per chunk while data remains,
895 '''return no less than min bytes per chunk while data remains,
886 doubling min after each chunk until it reaches max'''
896 doubling min after each chunk until it reaches max'''
887 def log2(x):
897 def log2(x):
888 if not x:
898 if not x:
889 return 0
899 return 0
890 i = 0
900 i = 0
891 while x:
901 while x:
892 x >>= 1
902 x >>= 1
893 i += 1
903 i += 1
894 return i - 1
904 return i - 1
895
905
896 buf = []
906 buf = []
897 blen = 0
907 blen = 0
898 for chunk in source:
908 for chunk in source:
899 buf.append(chunk)
909 buf.append(chunk)
900 blen += len(chunk)
910 blen += len(chunk)
901 if blen >= min:
911 if blen >= min:
902 if min < max:
912 if min < max:
903 min = min << 1
913 min = min << 1
904 nmin = 1 << log2(blen)
914 nmin = 1 << log2(blen)
905 if nmin > min:
915 if nmin > min:
906 min = nmin
916 min = nmin
907 if min > max:
917 if min > max:
908 min = max
918 min = max
909 yield ''.join(buf)
919 yield ''.join(buf)
910 blen = 0
920 blen = 0
911 buf = []
921 buf = []
912 if buf:
922 if buf:
913 yield ''.join(buf)
923 yield ''.join(buf)
914
924
915 Abort = error.Abort
925 Abort = error.Abort
916
926
917 def always(fn):
927 def always(fn):
918 return True
928 return True
919
929
920 def never(fn):
930 def never(fn):
921 return False
931 return False
922
932
923 def nogc(func):
933 def nogc(func):
924 """disable garbage collector
934 """disable garbage collector
925
935
926 Python's garbage collector triggers a GC each time a certain number of
936 Python's garbage collector triggers a GC each time a certain number of
927 container objects (the number being defined by gc.get_threshold()) are
937 container objects (the number being defined by gc.get_threshold()) are
928 allocated even when marked not to be tracked by the collector. Tracking has
938 allocated even when marked not to be tracked by the collector. Tracking has
929 no effect on when GCs are triggered, only on what objects the GC looks
939 no effect on when GCs are triggered, only on what objects the GC looks
930 into. As a workaround, disable GC while building complex (huge)
940 into. As a workaround, disable GC while building complex (huge)
931 containers.
941 containers.
932
942
933 This garbage collector issue have been fixed in 2.7.
943 This garbage collector issue have been fixed in 2.7.
934 """
944 """
935 if sys.version_info >= (2, 7):
945 if sys.version_info >= (2, 7):
936 return func
946 return func
937 def wrapper(*args, **kwargs):
947 def wrapper(*args, **kwargs):
938 gcenabled = gc.isenabled()
948 gcenabled = gc.isenabled()
939 gc.disable()
949 gc.disable()
940 try:
950 try:
941 return func(*args, **kwargs)
951 return func(*args, **kwargs)
942 finally:
952 finally:
943 if gcenabled:
953 if gcenabled:
944 gc.enable()
954 gc.enable()
945 return wrapper
955 return wrapper
946
956
947 def pathto(root, n1, n2):
957 def pathto(root, n1, n2):
948 '''return the relative path from one place to another.
958 '''return the relative path from one place to another.
949 root should use os.sep to separate directories
959 root should use os.sep to separate directories
950 n1 should use os.sep to separate directories
960 n1 should use os.sep to separate directories
951 n2 should use "/" to separate directories
961 n2 should use "/" to separate directories
952 returns an os.sep-separated path.
962 returns an os.sep-separated path.
953
963
954 If n1 is a relative path, it's assumed it's
964 If n1 is a relative path, it's assumed it's
955 relative to root.
965 relative to root.
956 n2 should always be relative to root.
966 n2 should always be relative to root.
957 '''
967 '''
958 if not n1:
968 if not n1:
959 return localpath(n2)
969 return localpath(n2)
960 if os.path.isabs(n1):
970 if os.path.isabs(n1):
961 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
971 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
962 return os.path.join(root, localpath(n2))
972 return os.path.join(root, localpath(n2))
963 n2 = '/'.join((pconvert(root), n2))
973 n2 = '/'.join((pconvert(root), n2))
964 a, b = splitpath(n1), n2.split('/')
974 a, b = splitpath(n1), n2.split('/')
965 a.reverse()
975 a.reverse()
966 b.reverse()
976 b.reverse()
967 while a and b and a[-1] == b[-1]:
977 while a and b and a[-1] == b[-1]:
968 a.pop()
978 a.pop()
969 b.pop()
979 b.pop()
970 b.reverse()
980 b.reverse()
971 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
981 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
972
982
973 def mainfrozen():
983 def mainfrozen():
974 """return True if we are a frozen executable.
984 """return True if we are a frozen executable.
975
985
976 The code supports py2exe (most common, Windows only) and tools/freeze
986 The code supports py2exe (most common, Windows only) and tools/freeze
977 (portable, not much used).
987 (portable, not much used).
978 """
988 """
979 return (safehasattr(sys, "frozen") or # new py2exe
989 return (safehasattr(sys, "frozen") or # new py2exe
980 safehasattr(sys, "importers") or # old py2exe
990 safehasattr(sys, "importers") or # old py2exe
981 imp.is_frozen(u"__main__")) # tools/freeze
991 imp.is_frozen(u"__main__")) # tools/freeze
982
992
983 # the location of data files matching the source code
993 # the location of data files matching the source code
984 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
994 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
985 # executable version (py2exe) doesn't support __file__
995 # executable version (py2exe) doesn't support __file__
986 datapath = os.path.dirname(pycompat.sysexecutable)
996 datapath = os.path.dirname(pycompat.sysexecutable)
987 else:
997 else:
988 datapath = os.path.dirname(pycompat.fsencode(__file__))
998 datapath = os.path.dirname(pycompat.fsencode(__file__))
989
999
990 i18n.setdatapath(datapath)
1000 i18n.setdatapath(datapath)
991
1001
992 _hgexecutable = None
1002 _hgexecutable = None
993
1003
994 def hgexecutable():
1004 def hgexecutable():
995 """return location of the 'hg' executable.
1005 """return location of the 'hg' executable.
996
1006
997 Defaults to $HG or 'hg' in the search path.
1007 Defaults to $HG or 'hg' in the search path.
998 """
1008 """
999 if _hgexecutable is None:
1009 if _hgexecutable is None:
1000 hg = encoding.environ.get('HG')
1010 hg = encoding.environ.get('HG')
1001 mainmod = sys.modules[pycompat.sysstr('__main__')]
1011 mainmod = sys.modules[pycompat.sysstr('__main__')]
1002 if hg:
1012 if hg:
1003 _sethgexecutable(hg)
1013 _sethgexecutable(hg)
1004 elif mainfrozen():
1014 elif mainfrozen():
1005 if getattr(sys, 'frozen', None) == 'macosx_app':
1015 if getattr(sys, 'frozen', None) == 'macosx_app':
1006 # Env variable set by py2app
1016 # Env variable set by py2app
1007 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1017 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1008 else:
1018 else:
1009 _sethgexecutable(pycompat.sysexecutable)
1019 _sethgexecutable(pycompat.sysexecutable)
1010 elif (os.path.basename(
1020 elif (os.path.basename(
1011 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1021 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1012 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1022 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1013 else:
1023 else:
1014 exe = findexe('hg') or os.path.basename(sys.argv[0])
1024 exe = findexe('hg') or os.path.basename(sys.argv[0])
1015 _sethgexecutable(exe)
1025 _sethgexecutable(exe)
1016 return _hgexecutable
1026 return _hgexecutable
1017
1027
1018 def _sethgexecutable(path):
1028 def _sethgexecutable(path):
1019 """set location of the 'hg' executable"""
1029 """set location of the 'hg' executable"""
1020 global _hgexecutable
1030 global _hgexecutable
1021 _hgexecutable = path
1031 _hgexecutable = path
1022
1032
1023 def _isstdout(f):
1033 def _isstdout(f):
1024 fileno = getattr(f, 'fileno', None)
1034 fileno = getattr(f, 'fileno', None)
1025 return fileno and fileno() == sys.__stdout__.fileno()
1035 return fileno and fileno() == sys.__stdout__.fileno()
1026
1036
1027 def shellenviron(environ=None):
1037 def shellenviron(environ=None):
1028 """return environ with optional override, useful for shelling out"""
1038 """return environ with optional override, useful for shelling out"""
1029 def py2shell(val):
1039 def py2shell(val):
1030 'convert python object into string that is useful to shell'
1040 'convert python object into string that is useful to shell'
1031 if val is None or val is False:
1041 if val is None or val is False:
1032 return '0'
1042 return '0'
1033 if val is True:
1043 if val is True:
1034 return '1'
1044 return '1'
1035 return str(val)
1045 return str(val)
1036 env = dict(encoding.environ)
1046 env = dict(encoding.environ)
1037 if environ:
1047 if environ:
1038 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1048 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1039 env['HG'] = hgexecutable()
1049 env['HG'] = hgexecutable()
1040 return env
1050 return env
1041
1051
1042 def system(cmd, environ=None, cwd=None, out=None):
1052 def system(cmd, environ=None, cwd=None, out=None):
1043 '''enhanced shell command execution.
1053 '''enhanced shell command execution.
1044 run with environment maybe modified, maybe in different dir.
1054 run with environment maybe modified, maybe in different dir.
1045
1055
1046 if out is specified, it is assumed to be a file-like object that has a
1056 if out is specified, it is assumed to be a file-like object that has a
1047 write() method. stdout and stderr will be redirected to out.'''
1057 write() method. stdout and stderr will be redirected to out.'''
1048 try:
1058 try:
1049 stdout.flush()
1059 stdout.flush()
1050 except Exception:
1060 except Exception:
1051 pass
1061 pass
1052 cmd = quotecommand(cmd)
1062 cmd = quotecommand(cmd)
1053 if pycompat.sysplatform == 'plan9' and (sys.version_info[0] == 2
1063 if pycompat.sysplatform == 'plan9' and (sys.version_info[0] == 2
1054 and sys.version_info[1] < 7):
1064 and sys.version_info[1] < 7):
1055 # subprocess kludge to work around issues in half-baked Python
1065 # subprocess kludge to work around issues in half-baked Python
1056 # ports, notably bichued/python:
1066 # ports, notably bichued/python:
1057 if not cwd is None:
1067 if not cwd is None:
1058 os.chdir(cwd)
1068 os.chdir(cwd)
1059 rc = os.system(cmd)
1069 rc = os.system(cmd)
1060 else:
1070 else:
1061 env = shellenviron(environ)
1071 env = shellenviron(environ)
1062 if out is None or _isstdout(out):
1072 if out is None or _isstdout(out):
1063 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1073 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1064 env=env, cwd=cwd)
1074 env=env, cwd=cwd)
1065 else:
1075 else:
1066 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1076 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1067 env=env, cwd=cwd, stdout=subprocess.PIPE,
1077 env=env, cwd=cwd, stdout=subprocess.PIPE,
1068 stderr=subprocess.STDOUT)
1078 stderr=subprocess.STDOUT)
1069 for line in iter(proc.stdout.readline, ''):
1079 for line in iter(proc.stdout.readline, ''):
1070 out.write(line)
1080 out.write(line)
1071 proc.wait()
1081 proc.wait()
1072 rc = proc.returncode
1082 rc = proc.returncode
1073 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1083 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1074 rc = 0
1084 rc = 0
1075 return rc
1085 return rc
1076
1086
1077 def checksignature(func):
1087 def checksignature(func):
1078 '''wrap a function with code to check for calling errors'''
1088 '''wrap a function with code to check for calling errors'''
1079 def check(*args, **kwargs):
1089 def check(*args, **kwargs):
1080 try:
1090 try:
1081 return func(*args, **kwargs)
1091 return func(*args, **kwargs)
1082 except TypeError:
1092 except TypeError:
1083 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1093 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1084 raise error.SignatureError
1094 raise error.SignatureError
1085 raise
1095 raise
1086
1096
1087 return check
1097 return check
1088
1098
1089 # a whilelist of known filesystems where hardlink works reliably
1099 # a whilelist of known filesystems where hardlink works reliably
1090 _hardlinkfswhitelist = set([
1100 _hardlinkfswhitelist = set([
1091 'btrfs',
1101 'btrfs',
1092 'ext2',
1102 'ext2',
1093 'ext3',
1103 'ext3',
1094 'ext4',
1104 'ext4',
1095 'hfs',
1105 'hfs',
1096 'jfs',
1106 'jfs',
1097 'reiserfs',
1107 'reiserfs',
1098 'tmpfs',
1108 'tmpfs',
1099 'ufs',
1109 'ufs',
1100 'xfs',
1110 'xfs',
1101 'zfs',
1111 'zfs',
1102 ])
1112 ])
1103
1113
1104 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1114 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1105 '''copy a file, preserving mode and optionally other stat info like
1115 '''copy a file, preserving mode and optionally other stat info like
1106 atime/mtime
1116 atime/mtime
1107
1117
1108 checkambig argument is used with filestat, and is useful only if
1118 checkambig argument is used with filestat, and is useful only if
1109 destination file is guarded by any lock (e.g. repo.lock or
1119 destination file is guarded by any lock (e.g. repo.lock or
1110 repo.wlock).
1120 repo.wlock).
1111
1121
1112 copystat and checkambig should be exclusive.
1122 copystat and checkambig should be exclusive.
1113 '''
1123 '''
1114 assert not (copystat and checkambig)
1124 assert not (copystat and checkambig)
1115 oldstat = None
1125 oldstat = None
1116 if os.path.lexists(dest):
1126 if os.path.lexists(dest):
1117 if checkambig:
1127 if checkambig:
1118 oldstat = checkambig and filestat(dest)
1128 oldstat = checkambig and filestat(dest)
1119 unlink(dest)
1129 unlink(dest)
1120 if hardlink:
1130 if hardlink:
1121 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1131 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1122 # unless we are confident that dest is on a whitelisted filesystem.
1132 # unless we are confident that dest is on a whitelisted filesystem.
1123 try:
1133 try:
1124 fstype = getfstype(os.path.dirname(dest))
1134 fstype = getfstype(os.path.dirname(dest))
1125 except OSError:
1135 except OSError:
1126 fstype = None
1136 fstype = None
1127 if fstype not in _hardlinkfswhitelist:
1137 if fstype not in _hardlinkfswhitelist:
1128 hardlink = False
1138 hardlink = False
1129 if hardlink:
1139 if hardlink:
1130 try:
1140 try:
1131 oslink(src, dest)
1141 oslink(src, dest)
1132 return
1142 return
1133 except (IOError, OSError):
1143 except (IOError, OSError):
1134 pass # fall back to normal copy
1144 pass # fall back to normal copy
1135 if os.path.islink(src):
1145 if os.path.islink(src):
1136 os.symlink(os.readlink(src), dest)
1146 os.symlink(os.readlink(src), dest)
1137 # copytime is ignored for symlinks, but in general copytime isn't needed
1147 # copytime is ignored for symlinks, but in general copytime isn't needed
1138 # for them anyway
1148 # for them anyway
1139 else:
1149 else:
1140 try:
1150 try:
1141 shutil.copyfile(src, dest)
1151 shutil.copyfile(src, dest)
1142 if copystat:
1152 if copystat:
1143 # copystat also copies mode
1153 # copystat also copies mode
1144 shutil.copystat(src, dest)
1154 shutil.copystat(src, dest)
1145 else:
1155 else:
1146 shutil.copymode(src, dest)
1156 shutil.copymode(src, dest)
1147 if oldstat and oldstat.stat:
1157 if oldstat and oldstat.stat:
1148 newstat = filestat(dest)
1158 newstat = filestat(dest)
1149 if newstat.isambig(oldstat):
1159 if newstat.isambig(oldstat):
1150 # stat of copied file is ambiguous to original one
1160 # stat of copied file is ambiguous to original one
1151 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1161 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1152 os.utime(dest, (advanced, advanced))
1162 os.utime(dest, (advanced, advanced))
1153 except shutil.Error as inst:
1163 except shutil.Error as inst:
1154 raise Abort(str(inst))
1164 raise Abort(str(inst))
1155
1165
1156 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1166 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1157 """Copy a directory tree using hardlinks if possible."""
1167 """Copy a directory tree using hardlinks if possible."""
1158 num = 0
1168 num = 0
1159
1169
1160 gettopic = lambda: hardlink and _('linking') or _('copying')
1170 gettopic = lambda: hardlink and _('linking') or _('copying')
1161
1171
1162 if os.path.isdir(src):
1172 if os.path.isdir(src):
1163 if hardlink is None:
1173 if hardlink is None:
1164 hardlink = (os.stat(src).st_dev ==
1174 hardlink = (os.stat(src).st_dev ==
1165 os.stat(os.path.dirname(dst)).st_dev)
1175 os.stat(os.path.dirname(dst)).st_dev)
1166 topic = gettopic()
1176 topic = gettopic()
1167 os.mkdir(dst)
1177 os.mkdir(dst)
1168 for name, kind in osutil.listdir(src):
1178 for name, kind in listdir(src):
1169 srcname = os.path.join(src, name)
1179 srcname = os.path.join(src, name)
1170 dstname = os.path.join(dst, name)
1180 dstname = os.path.join(dst, name)
1171 def nprog(t, pos):
1181 def nprog(t, pos):
1172 if pos is not None:
1182 if pos is not None:
1173 return progress(t, pos + num)
1183 return progress(t, pos + num)
1174 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1184 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1175 num += n
1185 num += n
1176 else:
1186 else:
1177 if hardlink is None:
1187 if hardlink is None:
1178 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1188 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1179 os.stat(os.path.dirname(dst)).st_dev)
1189 os.stat(os.path.dirname(dst)).st_dev)
1180 topic = gettopic()
1190 topic = gettopic()
1181
1191
1182 if hardlink:
1192 if hardlink:
1183 try:
1193 try:
1184 oslink(src, dst)
1194 oslink(src, dst)
1185 except (IOError, OSError):
1195 except (IOError, OSError):
1186 hardlink = False
1196 hardlink = False
1187 shutil.copy(src, dst)
1197 shutil.copy(src, dst)
1188 else:
1198 else:
1189 shutil.copy(src, dst)
1199 shutil.copy(src, dst)
1190 num += 1
1200 num += 1
1191 progress(topic, num)
1201 progress(topic, num)
1192 progress(topic, None)
1202 progress(topic, None)
1193
1203
1194 return hardlink, num
1204 return hardlink, num
1195
1205
1196 _winreservednames = '''con prn aux nul
1206 _winreservednames = '''con prn aux nul
1197 com1 com2 com3 com4 com5 com6 com7 com8 com9
1207 com1 com2 com3 com4 com5 com6 com7 com8 com9
1198 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1208 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1199 _winreservedchars = ':*?"<>|'
1209 _winreservedchars = ':*?"<>|'
1200 def checkwinfilename(path):
1210 def checkwinfilename(path):
1201 r'''Check that the base-relative path is a valid filename on Windows.
1211 r'''Check that the base-relative path is a valid filename on Windows.
1202 Returns None if the path is ok, or a UI string describing the problem.
1212 Returns None if the path is ok, or a UI string describing the problem.
1203
1213
1204 >>> checkwinfilename("just/a/normal/path")
1214 >>> checkwinfilename("just/a/normal/path")
1205 >>> checkwinfilename("foo/bar/con.xml")
1215 >>> checkwinfilename("foo/bar/con.xml")
1206 "filename contains 'con', which is reserved on Windows"
1216 "filename contains 'con', which is reserved on Windows"
1207 >>> checkwinfilename("foo/con.xml/bar")
1217 >>> checkwinfilename("foo/con.xml/bar")
1208 "filename contains 'con', which is reserved on Windows"
1218 "filename contains 'con', which is reserved on Windows"
1209 >>> checkwinfilename("foo/bar/xml.con")
1219 >>> checkwinfilename("foo/bar/xml.con")
1210 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1220 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1211 "filename contains 'AUX', which is reserved on Windows"
1221 "filename contains 'AUX', which is reserved on Windows"
1212 >>> checkwinfilename("foo/bar/bla:.txt")
1222 >>> checkwinfilename("foo/bar/bla:.txt")
1213 "filename contains ':', which is reserved on Windows"
1223 "filename contains ':', which is reserved on Windows"
1214 >>> checkwinfilename("foo/bar/b\07la.txt")
1224 >>> checkwinfilename("foo/bar/b\07la.txt")
1215 "filename contains '\\x07', which is invalid on Windows"
1225 "filename contains '\\x07', which is invalid on Windows"
1216 >>> checkwinfilename("foo/bar/bla ")
1226 >>> checkwinfilename("foo/bar/bla ")
1217 "filename ends with ' ', which is not allowed on Windows"
1227 "filename ends with ' ', which is not allowed on Windows"
1218 >>> checkwinfilename("../bar")
1228 >>> checkwinfilename("../bar")
1219 >>> checkwinfilename("foo\\")
1229 >>> checkwinfilename("foo\\")
1220 "filename ends with '\\', which is invalid on Windows"
1230 "filename ends with '\\', which is invalid on Windows"
1221 >>> checkwinfilename("foo\\/bar")
1231 >>> checkwinfilename("foo\\/bar")
1222 "directory name ends with '\\', which is invalid on Windows"
1232 "directory name ends with '\\', which is invalid on Windows"
1223 '''
1233 '''
1224 if path.endswith('\\'):
1234 if path.endswith('\\'):
1225 return _("filename ends with '\\', which is invalid on Windows")
1235 return _("filename ends with '\\', which is invalid on Windows")
1226 if '\\/' in path:
1236 if '\\/' in path:
1227 return _("directory name ends with '\\', which is invalid on Windows")
1237 return _("directory name ends with '\\', which is invalid on Windows")
1228 for n in path.replace('\\', '/').split('/'):
1238 for n in path.replace('\\', '/').split('/'):
1229 if not n:
1239 if not n:
1230 continue
1240 continue
1231 for c in pycompat.bytestr(n):
1241 for c in pycompat.bytestr(n):
1232 if c in _winreservedchars:
1242 if c in _winreservedchars:
1233 return _("filename contains '%s', which is reserved "
1243 return _("filename contains '%s', which is reserved "
1234 "on Windows") % c
1244 "on Windows") % c
1235 if ord(c) <= 31:
1245 if ord(c) <= 31:
1236 return _("filename contains %r, which is invalid "
1246 return _("filename contains %r, which is invalid "
1237 "on Windows") % c
1247 "on Windows") % c
1238 base = n.split('.')[0]
1248 base = n.split('.')[0]
1239 if base and base.lower() in _winreservednames:
1249 if base and base.lower() in _winreservednames:
1240 return _("filename contains '%s', which is reserved "
1250 return _("filename contains '%s', which is reserved "
1241 "on Windows") % base
1251 "on Windows") % base
1242 t = n[-1]
1252 t = n[-1]
1243 if t in '. ' and n not in '..':
1253 if t in '. ' and n not in '..':
1244 return _("filename ends with '%s', which is not allowed "
1254 return _("filename ends with '%s', which is not allowed "
1245 "on Windows") % t
1255 "on Windows") % t
1246
1256
1247 if pycompat.osname == 'nt':
1257 if pycompat.osname == 'nt':
1248 checkosfilename = checkwinfilename
1258 checkosfilename = checkwinfilename
1249 timer = time.clock
1259 timer = time.clock
1250 else:
1260 else:
1251 checkosfilename = platform.checkosfilename
1261 checkosfilename = platform.checkosfilename
1252 timer = time.time
1262 timer = time.time
1253
1263
1254 if safehasattr(time, "perf_counter"):
1264 if safehasattr(time, "perf_counter"):
1255 timer = time.perf_counter
1265 timer = time.perf_counter
1256
1266
1257 def makelock(info, pathname):
1267 def makelock(info, pathname):
1258 try:
1268 try:
1259 return os.symlink(info, pathname)
1269 return os.symlink(info, pathname)
1260 except OSError as why:
1270 except OSError as why:
1261 if why.errno == errno.EEXIST:
1271 if why.errno == errno.EEXIST:
1262 raise
1272 raise
1263 except AttributeError: # no symlink in os
1273 except AttributeError: # no symlink in os
1264 pass
1274 pass
1265
1275
1266 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1276 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1267 os.write(ld, info)
1277 os.write(ld, info)
1268 os.close(ld)
1278 os.close(ld)
1269
1279
1270 def readlock(pathname):
1280 def readlock(pathname):
1271 try:
1281 try:
1272 return os.readlink(pathname)
1282 return os.readlink(pathname)
1273 except OSError as why:
1283 except OSError as why:
1274 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1284 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1275 raise
1285 raise
1276 except AttributeError: # no symlink in os
1286 except AttributeError: # no symlink in os
1277 pass
1287 pass
1278 fp = posixfile(pathname)
1288 fp = posixfile(pathname)
1279 r = fp.read()
1289 r = fp.read()
1280 fp.close()
1290 fp.close()
1281 return r
1291 return r
1282
1292
1283 def fstat(fp):
1293 def fstat(fp):
1284 '''stat file object that may not have fileno method.'''
1294 '''stat file object that may not have fileno method.'''
1285 try:
1295 try:
1286 return os.fstat(fp.fileno())
1296 return os.fstat(fp.fileno())
1287 except AttributeError:
1297 except AttributeError:
1288 return os.stat(fp.name)
1298 return os.stat(fp.name)
1289
1299
1290 # File system features
1300 # File system features
1291
1301
1292 def fscasesensitive(path):
1302 def fscasesensitive(path):
1293 """
1303 """
1294 Return true if the given path is on a case-sensitive filesystem
1304 Return true if the given path is on a case-sensitive filesystem
1295
1305
1296 Requires a path (like /foo/.hg) ending with a foldable final
1306 Requires a path (like /foo/.hg) ending with a foldable final
1297 directory component.
1307 directory component.
1298 """
1308 """
1299 s1 = os.lstat(path)
1309 s1 = os.lstat(path)
1300 d, b = os.path.split(path)
1310 d, b = os.path.split(path)
1301 b2 = b.upper()
1311 b2 = b.upper()
1302 if b == b2:
1312 if b == b2:
1303 b2 = b.lower()
1313 b2 = b.lower()
1304 if b == b2:
1314 if b == b2:
1305 return True # no evidence against case sensitivity
1315 return True # no evidence against case sensitivity
1306 p2 = os.path.join(d, b2)
1316 p2 = os.path.join(d, b2)
1307 try:
1317 try:
1308 s2 = os.lstat(p2)
1318 s2 = os.lstat(p2)
1309 if s2 == s1:
1319 if s2 == s1:
1310 return False
1320 return False
1311 return True
1321 return True
1312 except OSError:
1322 except OSError:
1313 return True
1323 return True
1314
1324
1315 try:
1325 try:
1316 import re2
1326 import re2
1317 _re2 = None
1327 _re2 = None
1318 except ImportError:
1328 except ImportError:
1319 _re2 = False
1329 _re2 = False
1320
1330
1321 class _re(object):
1331 class _re(object):
1322 def _checkre2(self):
1332 def _checkre2(self):
1323 global _re2
1333 global _re2
1324 try:
1334 try:
1325 # check if match works, see issue3964
1335 # check if match works, see issue3964
1326 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1336 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1327 except ImportError:
1337 except ImportError:
1328 _re2 = False
1338 _re2 = False
1329
1339
1330 def compile(self, pat, flags=0):
1340 def compile(self, pat, flags=0):
1331 '''Compile a regular expression, using re2 if possible
1341 '''Compile a regular expression, using re2 if possible
1332
1342
1333 For best performance, use only re2-compatible regexp features. The
1343 For best performance, use only re2-compatible regexp features. The
1334 only flags from the re module that are re2-compatible are
1344 only flags from the re module that are re2-compatible are
1335 IGNORECASE and MULTILINE.'''
1345 IGNORECASE and MULTILINE.'''
1336 if _re2 is None:
1346 if _re2 is None:
1337 self._checkre2()
1347 self._checkre2()
1338 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1348 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1339 if flags & remod.IGNORECASE:
1349 if flags & remod.IGNORECASE:
1340 pat = '(?i)' + pat
1350 pat = '(?i)' + pat
1341 if flags & remod.MULTILINE:
1351 if flags & remod.MULTILINE:
1342 pat = '(?m)' + pat
1352 pat = '(?m)' + pat
1343 try:
1353 try:
1344 return re2.compile(pat)
1354 return re2.compile(pat)
1345 except re2.error:
1355 except re2.error:
1346 pass
1356 pass
1347 return remod.compile(pat, flags)
1357 return remod.compile(pat, flags)
1348
1358
1349 @propertycache
1359 @propertycache
1350 def escape(self):
1360 def escape(self):
1351 '''Return the version of escape corresponding to self.compile.
1361 '''Return the version of escape corresponding to self.compile.
1352
1362
1353 This is imperfect because whether re2 or re is used for a particular
1363 This is imperfect because whether re2 or re is used for a particular
1354 function depends on the flags, etc, but it's the best we can do.
1364 function depends on the flags, etc, but it's the best we can do.
1355 '''
1365 '''
1356 global _re2
1366 global _re2
1357 if _re2 is None:
1367 if _re2 is None:
1358 self._checkre2()
1368 self._checkre2()
1359 if _re2:
1369 if _re2:
1360 return re2.escape
1370 return re2.escape
1361 else:
1371 else:
1362 return remod.escape
1372 return remod.escape
1363
1373
1364 re = _re()
1374 re = _re()
1365
1375
1366 _fspathcache = {}
1376 _fspathcache = {}
1367 def fspath(name, root):
1377 def fspath(name, root):
1368 '''Get name in the case stored in the filesystem
1378 '''Get name in the case stored in the filesystem
1369
1379
1370 The name should be relative to root, and be normcase-ed for efficiency.
1380 The name should be relative to root, and be normcase-ed for efficiency.
1371
1381
1372 Note that this function is unnecessary, and should not be
1382 Note that this function is unnecessary, and should not be
1373 called, for case-sensitive filesystems (simply because it's expensive).
1383 called, for case-sensitive filesystems (simply because it's expensive).
1374
1384
1375 The root should be normcase-ed, too.
1385 The root should be normcase-ed, too.
1376 '''
1386 '''
1377 def _makefspathcacheentry(dir):
1387 def _makefspathcacheentry(dir):
1378 return dict((normcase(n), n) for n in os.listdir(dir))
1388 return dict((normcase(n), n) for n in os.listdir(dir))
1379
1389
1380 seps = pycompat.ossep
1390 seps = pycompat.ossep
1381 if pycompat.osaltsep:
1391 if pycompat.osaltsep:
1382 seps = seps + pycompat.osaltsep
1392 seps = seps + pycompat.osaltsep
1383 # Protect backslashes. This gets silly very quickly.
1393 # Protect backslashes. This gets silly very quickly.
1384 seps.replace('\\','\\\\')
1394 seps.replace('\\','\\\\')
1385 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1395 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1386 dir = os.path.normpath(root)
1396 dir = os.path.normpath(root)
1387 result = []
1397 result = []
1388 for part, sep in pattern.findall(name):
1398 for part, sep in pattern.findall(name):
1389 if sep:
1399 if sep:
1390 result.append(sep)
1400 result.append(sep)
1391 continue
1401 continue
1392
1402
1393 if dir not in _fspathcache:
1403 if dir not in _fspathcache:
1394 _fspathcache[dir] = _makefspathcacheentry(dir)
1404 _fspathcache[dir] = _makefspathcacheentry(dir)
1395 contents = _fspathcache[dir]
1405 contents = _fspathcache[dir]
1396
1406
1397 found = contents.get(part)
1407 found = contents.get(part)
1398 if not found:
1408 if not found:
1399 # retry "once per directory" per "dirstate.walk" which
1409 # retry "once per directory" per "dirstate.walk" which
1400 # may take place for each patches of "hg qpush", for example
1410 # may take place for each patches of "hg qpush", for example
1401 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1411 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1402 found = contents.get(part)
1412 found = contents.get(part)
1403
1413
1404 result.append(found or part)
1414 result.append(found or part)
1405 dir = os.path.join(dir, part)
1415 dir = os.path.join(dir, part)
1406
1416
1407 return ''.join(result)
1417 return ''.join(result)
1408
1418
1409 def getfstype(dirpath):
1419 def getfstype(dirpath):
1410 '''Get the filesystem type name from a directory (best-effort)
1420 '''Get the filesystem type name from a directory (best-effort)
1411
1421
1412 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1422 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1413 '''
1423 '''
1414 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1424 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1415
1425
1416 def checknlink(testfile):
1426 def checknlink(testfile):
1417 '''check whether hardlink count reporting works properly'''
1427 '''check whether hardlink count reporting works properly'''
1418
1428
1419 # testfile may be open, so we need a separate file for checking to
1429 # testfile may be open, so we need a separate file for checking to
1420 # work around issue2543 (or testfile may get lost on Samba shares)
1430 # work around issue2543 (or testfile may get lost on Samba shares)
1421 f1 = testfile + ".hgtmp1"
1431 f1 = testfile + ".hgtmp1"
1422 if os.path.lexists(f1):
1432 if os.path.lexists(f1):
1423 return False
1433 return False
1424 try:
1434 try:
1425 posixfile(f1, 'w').close()
1435 posixfile(f1, 'w').close()
1426 except IOError:
1436 except IOError:
1427 try:
1437 try:
1428 os.unlink(f1)
1438 os.unlink(f1)
1429 except OSError:
1439 except OSError:
1430 pass
1440 pass
1431 return False
1441 return False
1432
1442
1433 f2 = testfile + ".hgtmp2"
1443 f2 = testfile + ".hgtmp2"
1434 fd = None
1444 fd = None
1435 try:
1445 try:
1436 oslink(f1, f2)
1446 oslink(f1, f2)
1437 # nlinks() may behave differently for files on Windows shares if
1447 # nlinks() may behave differently for files on Windows shares if
1438 # the file is open.
1448 # the file is open.
1439 fd = posixfile(f2)
1449 fd = posixfile(f2)
1440 return nlinks(f2) > 1
1450 return nlinks(f2) > 1
1441 except OSError:
1451 except OSError:
1442 return False
1452 return False
1443 finally:
1453 finally:
1444 if fd is not None:
1454 if fd is not None:
1445 fd.close()
1455 fd.close()
1446 for f in (f1, f2):
1456 for f in (f1, f2):
1447 try:
1457 try:
1448 os.unlink(f)
1458 os.unlink(f)
1449 except OSError:
1459 except OSError:
1450 pass
1460 pass
1451
1461
1452 def endswithsep(path):
1462 def endswithsep(path):
1453 '''Check path ends with os.sep or os.altsep.'''
1463 '''Check path ends with os.sep or os.altsep.'''
1454 return (path.endswith(pycompat.ossep)
1464 return (path.endswith(pycompat.ossep)
1455 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1465 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1456
1466
1457 def splitpath(path):
1467 def splitpath(path):
1458 '''Split path by os.sep.
1468 '''Split path by os.sep.
1459 Note that this function does not use os.altsep because this is
1469 Note that this function does not use os.altsep because this is
1460 an alternative of simple "xxx.split(os.sep)".
1470 an alternative of simple "xxx.split(os.sep)".
1461 It is recommended to use os.path.normpath() before using this
1471 It is recommended to use os.path.normpath() before using this
1462 function if need.'''
1472 function if need.'''
1463 return path.split(pycompat.ossep)
1473 return path.split(pycompat.ossep)
1464
1474
1465 def gui():
1475 def gui():
1466 '''Are we running in a GUI?'''
1476 '''Are we running in a GUI?'''
1467 if pycompat.sysplatform == 'darwin':
1477 if pycompat.sysplatform == 'darwin':
1468 if 'SSH_CONNECTION' in encoding.environ:
1478 if 'SSH_CONNECTION' in encoding.environ:
1469 # handle SSH access to a box where the user is logged in
1479 # handle SSH access to a box where the user is logged in
1470 return False
1480 return False
1471 elif getattr(osutil, 'isgui', None):
1481 elif getattr(osutil, 'isgui', None):
1472 # check if a CoreGraphics session is available
1482 # check if a CoreGraphics session is available
1473 return osutil.isgui()
1483 return osutil.isgui()
1474 else:
1484 else:
1475 # pure build; use a safe default
1485 # pure build; use a safe default
1476 return True
1486 return True
1477 else:
1487 else:
1478 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1488 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1479
1489
1480 def mktempcopy(name, emptyok=False, createmode=None):
1490 def mktempcopy(name, emptyok=False, createmode=None):
1481 """Create a temporary file with the same contents from name
1491 """Create a temporary file with the same contents from name
1482
1492
1483 The permission bits are copied from the original file.
1493 The permission bits are copied from the original file.
1484
1494
1485 If the temporary file is going to be truncated immediately, you
1495 If the temporary file is going to be truncated immediately, you
1486 can use emptyok=True as an optimization.
1496 can use emptyok=True as an optimization.
1487
1497
1488 Returns the name of the temporary file.
1498 Returns the name of the temporary file.
1489 """
1499 """
1490 d, fn = os.path.split(name)
1500 d, fn = os.path.split(name)
1491 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1501 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1492 os.close(fd)
1502 os.close(fd)
1493 # Temporary files are created with mode 0600, which is usually not
1503 # Temporary files are created with mode 0600, which is usually not
1494 # what we want. If the original file already exists, just copy
1504 # what we want. If the original file already exists, just copy
1495 # its mode. Otherwise, manually obey umask.
1505 # its mode. Otherwise, manually obey umask.
1496 copymode(name, temp, createmode)
1506 copymode(name, temp, createmode)
1497 if emptyok:
1507 if emptyok:
1498 return temp
1508 return temp
1499 try:
1509 try:
1500 try:
1510 try:
1501 ifp = posixfile(name, "rb")
1511 ifp = posixfile(name, "rb")
1502 except IOError as inst:
1512 except IOError as inst:
1503 if inst.errno == errno.ENOENT:
1513 if inst.errno == errno.ENOENT:
1504 return temp
1514 return temp
1505 if not getattr(inst, 'filename', None):
1515 if not getattr(inst, 'filename', None):
1506 inst.filename = name
1516 inst.filename = name
1507 raise
1517 raise
1508 ofp = posixfile(temp, "wb")
1518 ofp = posixfile(temp, "wb")
1509 for chunk in filechunkiter(ifp):
1519 for chunk in filechunkiter(ifp):
1510 ofp.write(chunk)
1520 ofp.write(chunk)
1511 ifp.close()
1521 ifp.close()
1512 ofp.close()
1522 ofp.close()
1513 except: # re-raises
1523 except: # re-raises
1514 try: os.unlink(temp)
1524 try: os.unlink(temp)
1515 except OSError: pass
1525 except OSError: pass
1516 raise
1526 raise
1517 return temp
1527 return temp
1518
1528
1519 class filestat(object):
1529 class filestat(object):
1520 """help to exactly detect change of a file
1530 """help to exactly detect change of a file
1521
1531
1522 'stat' attribute is result of 'os.stat()' if specified 'path'
1532 'stat' attribute is result of 'os.stat()' if specified 'path'
1523 exists. Otherwise, it is None. This can avoid preparative
1533 exists. Otherwise, it is None. This can avoid preparative
1524 'exists()' examination on client side of this class.
1534 'exists()' examination on client side of this class.
1525 """
1535 """
1526 def __init__(self, path):
1536 def __init__(self, path):
1527 try:
1537 try:
1528 self.stat = os.stat(path)
1538 self.stat = os.stat(path)
1529 except OSError as err:
1539 except OSError as err:
1530 if err.errno != errno.ENOENT:
1540 if err.errno != errno.ENOENT:
1531 raise
1541 raise
1532 self.stat = None
1542 self.stat = None
1533
1543
1534 __hash__ = object.__hash__
1544 __hash__ = object.__hash__
1535
1545
1536 def __eq__(self, old):
1546 def __eq__(self, old):
1537 try:
1547 try:
1538 # if ambiguity between stat of new and old file is
1548 # if ambiguity between stat of new and old file is
1539 # avoided, comparison of size, ctime and mtime is enough
1549 # avoided, comparison of size, ctime and mtime is enough
1540 # to exactly detect change of a file regardless of platform
1550 # to exactly detect change of a file regardless of platform
1541 return (self.stat.st_size == old.stat.st_size and
1551 return (self.stat.st_size == old.stat.st_size and
1542 self.stat.st_ctime == old.stat.st_ctime and
1552 self.stat.st_ctime == old.stat.st_ctime and
1543 self.stat.st_mtime == old.stat.st_mtime)
1553 self.stat.st_mtime == old.stat.st_mtime)
1544 except AttributeError:
1554 except AttributeError:
1545 return False
1555 return False
1546
1556
1547 def isambig(self, old):
1557 def isambig(self, old):
1548 """Examine whether new (= self) stat is ambiguous against old one
1558 """Examine whether new (= self) stat is ambiguous against old one
1549
1559
1550 "S[N]" below means stat of a file at N-th change:
1560 "S[N]" below means stat of a file at N-th change:
1551
1561
1552 - S[n-1].ctime < S[n].ctime: can detect change of a file
1562 - S[n-1].ctime < S[n].ctime: can detect change of a file
1553 - S[n-1].ctime == S[n].ctime
1563 - S[n-1].ctime == S[n].ctime
1554 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1564 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1555 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1565 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1556 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1566 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1557 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1567 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1558
1568
1559 Case (*2) above means that a file was changed twice or more at
1569 Case (*2) above means that a file was changed twice or more at
1560 same time in sec (= S[n-1].ctime), and comparison of timestamp
1570 same time in sec (= S[n-1].ctime), and comparison of timestamp
1561 is ambiguous.
1571 is ambiguous.
1562
1572
1563 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1573 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1564 timestamp is ambiguous".
1574 timestamp is ambiguous".
1565
1575
1566 But advancing mtime only in case (*2) doesn't work as
1576 But advancing mtime only in case (*2) doesn't work as
1567 expected, because naturally advanced S[n].mtime in case (*1)
1577 expected, because naturally advanced S[n].mtime in case (*1)
1568 might be equal to manually advanced S[n-1 or earlier].mtime.
1578 might be equal to manually advanced S[n-1 or earlier].mtime.
1569
1579
1570 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1580 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1571 treated as ambiguous regardless of mtime, to avoid overlooking
1581 treated as ambiguous regardless of mtime, to avoid overlooking
1572 by confliction between such mtime.
1582 by confliction between such mtime.
1573
1583
1574 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1584 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1575 S[n].mtime", even if size of a file isn't changed.
1585 S[n].mtime", even if size of a file isn't changed.
1576 """
1586 """
1577 try:
1587 try:
1578 return (self.stat.st_ctime == old.stat.st_ctime)
1588 return (self.stat.st_ctime == old.stat.st_ctime)
1579 except AttributeError:
1589 except AttributeError:
1580 return False
1590 return False
1581
1591
1582 def avoidambig(self, path, old):
1592 def avoidambig(self, path, old):
1583 """Change file stat of specified path to avoid ambiguity
1593 """Change file stat of specified path to avoid ambiguity
1584
1594
1585 'old' should be previous filestat of 'path'.
1595 'old' should be previous filestat of 'path'.
1586
1596
1587 This skips avoiding ambiguity, if a process doesn't have
1597 This skips avoiding ambiguity, if a process doesn't have
1588 appropriate privileges for 'path'.
1598 appropriate privileges for 'path'.
1589 """
1599 """
1590 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1600 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1591 try:
1601 try:
1592 os.utime(path, (advanced, advanced))
1602 os.utime(path, (advanced, advanced))
1593 except OSError as inst:
1603 except OSError as inst:
1594 if inst.errno == errno.EPERM:
1604 if inst.errno == errno.EPERM:
1595 # utime() on the file created by another user causes EPERM,
1605 # utime() on the file created by another user causes EPERM,
1596 # if a process doesn't have appropriate privileges
1606 # if a process doesn't have appropriate privileges
1597 return
1607 return
1598 raise
1608 raise
1599
1609
1600 def __ne__(self, other):
1610 def __ne__(self, other):
1601 return not self == other
1611 return not self == other
1602
1612
1603 class atomictempfile(object):
1613 class atomictempfile(object):
1604 '''writable file object that atomically updates a file
1614 '''writable file object that atomically updates a file
1605
1615
1606 All writes will go to a temporary copy of the original file. Call
1616 All writes will go to a temporary copy of the original file. Call
1607 close() when you are done writing, and atomictempfile will rename
1617 close() when you are done writing, and atomictempfile will rename
1608 the temporary copy to the original name, making the changes
1618 the temporary copy to the original name, making the changes
1609 visible. If the object is destroyed without being closed, all your
1619 visible. If the object is destroyed without being closed, all your
1610 writes are discarded.
1620 writes are discarded.
1611
1621
1612 checkambig argument of constructor is used with filestat, and is
1622 checkambig argument of constructor is used with filestat, and is
1613 useful only if target file is guarded by any lock (e.g. repo.lock
1623 useful only if target file is guarded by any lock (e.g. repo.lock
1614 or repo.wlock).
1624 or repo.wlock).
1615 '''
1625 '''
1616 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1626 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1617 self.__name = name # permanent name
1627 self.__name = name # permanent name
1618 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1628 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1619 createmode=createmode)
1629 createmode=createmode)
1620 self._fp = posixfile(self._tempname, mode)
1630 self._fp = posixfile(self._tempname, mode)
1621 self._checkambig = checkambig
1631 self._checkambig = checkambig
1622
1632
1623 # delegated methods
1633 # delegated methods
1624 self.read = self._fp.read
1634 self.read = self._fp.read
1625 self.write = self._fp.write
1635 self.write = self._fp.write
1626 self.seek = self._fp.seek
1636 self.seek = self._fp.seek
1627 self.tell = self._fp.tell
1637 self.tell = self._fp.tell
1628 self.fileno = self._fp.fileno
1638 self.fileno = self._fp.fileno
1629
1639
1630 def close(self):
1640 def close(self):
1631 if not self._fp.closed:
1641 if not self._fp.closed:
1632 self._fp.close()
1642 self._fp.close()
1633 filename = localpath(self.__name)
1643 filename = localpath(self.__name)
1634 oldstat = self._checkambig and filestat(filename)
1644 oldstat = self._checkambig and filestat(filename)
1635 if oldstat and oldstat.stat:
1645 if oldstat and oldstat.stat:
1636 rename(self._tempname, filename)
1646 rename(self._tempname, filename)
1637 newstat = filestat(filename)
1647 newstat = filestat(filename)
1638 if newstat.isambig(oldstat):
1648 if newstat.isambig(oldstat):
1639 # stat of changed file is ambiguous to original one
1649 # stat of changed file is ambiguous to original one
1640 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1650 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1641 os.utime(filename, (advanced, advanced))
1651 os.utime(filename, (advanced, advanced))
1642 else:
1652 else:
1643 rename(self._tempname, filename)
1653 rename(self._tempname, filename)
1644
1654
1645 def discard(self):
1655 def discard(self):
1646 if not self._fp.closed:
1656 if not self._fp.closed:
1647 try:
1657 try:
1648 os.unlink(self._tempname)
1658 os.unlink(self._tempname)
1649 except OSError:
1659 except OSError:
1650 pass
1660 pass
1651 self._fp.close()
1661 self._fp.close()
1652
1662
1653 def __del__(self):
1663 def __del__(self):
1654 if safehasattr(self, '_fp'): # constructor actually did something
1664 if safehasattr(self, '_fp'): # constructor actually did something
1655 self.discard()
1665 self.discard()
1656
1666
1657 def __enter__(self):
1667 def __enter__(self):
1658 return self
1668 return self
1659
1669
1660 def __exit__(self, exctype, excvalue, traceback):
1670 def __exit__(self, exctype, excvalue, traceback):
1661 if exctype is not None:
1671 if exctype is not None:
1662 self.discard()
1672 self.discard()
1663 else:
1673 else:
1664 self.close()
1674 self.close()
1665
1675
1666 def unlinkpath(f, ignoremissing=False):
1676 def unlinkpath(f, ignoremissing=False):
1667 """unlink and remove the directory if it is empty"""
1677 """unlink and remove the directory if it is empty"""
1668 if ignoremissing:
1678 if ignoremissing:
1669 tryunlink(f)
1679 tryunlink(f)
1670 else:
1680 else:
1671 unlink(f)
1681 unlink(f)
1672 # try removing directories that might now be empty
1682 # try removing directories that might now be empty
1673 try:
1683 try:
1674 removedirs(os.path.dirname(f))
1684 removedirs(os.path.dirname(f))
1675 except OSError:
1685 except OSError:
1676 pass
1686 pass
1677
1687
1678 def tryunlink(f):
1688 def tryunlink(f):
1679 """Attempt to remove a file, ignoring ENOENT errors."""
1689 """Attempt to remove a file, ignoring ENOENT errors."""
1680 try:
1690 try:
1681 unlink(f)
1691 unlink(f)
1682 except OSError as e:
1692 except OSError as e:
1683 if e.errno != errno.ENOENT:
1693 if e.errno != errno.ENOENT:
1684 raise
1694 raise
1685
1695
1686 def makedirs(name, mode=None, notindexed=False):
1696 def makedirs(name, mode=None, notindexed=False):
1687 """recursive directory creation with parent mode inheritance
1697 """recursive directory creation with parent mode inheritance
1688
1698
1689 Newly created directories are marked as "not to be indexed by
1699 Newly created directories are marked as "not to be indexed by
1690 the content indexing service", if ``notindexed`` is specified
1700 the content indexing service", if ``notindexed`` is specified
1691 for "write" mode access.
1701 for "write" mode access.
1692 """
1702 """
1693 try:
1703 try:
1694 makedir(name, notindexed)
1704 makedir(name, notindexed)
1695 except OSError as err:
1705 except OSError as err:
1696 if err.errno == errno.EEXIST:
1706 if err.errno == errno.EEXIST:
1697 return
1707 return
1698 if err.errno != errno.ENOENT or not name:
1708 if err.errno != errno.ENOENT or not name:
1699 raise
1709 raise
1700 parent = os.path.dirname(os.path.abspath(name))
1710 parent = os.path.dirname(os.path.abspath(name))
1701 if parent == name:
1711 if parent == name:
1702 raise
1712 raise
1703 makedirs(parent, mode, notindexed)
1713 makedirs(parent, mode, notindexed)
1704 try:
1714 try:
1705 makedir(name, notindexed)
1715 makedir(name, notindexed)
1706 except OSError as err:
1716 except OSError as err:
1707 # Catch EEXIST to handle races
1717 # Catch EEXIST to handle races
1708 if err.errno == errno.EEXIST:
1718 if err.errno == errno.EEXIST:
1709 return
1719 return
1710 raise
1720 raise
1711 if mode is not None:
1721 if mode is not None:
1712 os.chmod(name, mode)
1722 os.chmod(name, mode)
1713
1723
1714 def readfile(path):
1724 def readfile(path):
1715 with open(path, 'rb') as fp:
1725 with open(path, 'rb') as fp:
1716 return fp.read()
1726 return fp.read()
1717
1727
1718 def writefile(path, text):
1728 def writefile(path, text):
1719 with open(path, 'wb') as fp:
1729 with open(path, 'wb') as fp:
1720 fp.write(text)
1730 fp.write(text)
1721
1731
1722 def appendfile(path, text):
1732 def appendfile(path, text):
1723 with open(path, 'ab') as fp:
1733 with open(path, 'ab') as fp:
1724 fp.write(text)
1734 fp.write(text)
1725
1735
1726 class chunkbuffer(object):
1736 class chunkbuffer(object):
1727 """Allow arbitrary sized chunks of data to be efficiently read from an
1737 """Allow arbitrary sized chunks of data to be efficiently read from an
1728 iterator over chunks of arbitrary size."""
1738 iterator over chunks of arbitrary size."""
1729
1739
1730 def __init__(self, in_iter):
1740 def __init__(self, in_iter):
1731 """in_iter is the iterator that's iterating over the input chunks."""
1741 """in_iter is the iterator that's iterating over the input chunks."""
1732 def splitbig(chunks):
1742 def splitbig(chunks):
1733 for chunk in chunks:
1743 for chunk in chunks:
1734 if len(chunk) > 2**20:
1744 if len(chunk) > 2**20:
1735 pos = 0
1745 pos = 0
1736 while pos < len(chunk):
1746 while pos < len(chunk):
1737 end = pos + 2 ** 18
1747 end = pos + 2 ** 18
1738 yield chunk[pos:end]
1748 yield chunk[pos:end]
1739 pos = end
1749 pos = end
1740 else:
1750 else:
1741 yield chunk
1751 yield chunk
1742 self.iter = splitbig(in_iter)
1752 self.iter = splitbig(in_iter)
1743 self._queue = collections.deque()
1753 self._queue = collections.deque()
1744 self._chunkoffset = 0
1754 self._chunkoffset = 0
1745
1755
1746 def read(self, l=None):
1756 def read(self, l=None):
1747 """Read L bytes of data from the iterator of chunks of data.
1757 """Read L bytes of data from the iterator of chunks of data.
1748 Returns less than L bytes if the iterator runs dry.
1758 Returns less than L bytes if the iterator runs dry.
1749
1759
1750 If size parameter is omitted, read everything"""
1760 If size parameter is omitted, read everything"""
1751 if l is None:
1761 if l is None:
1752 return ''.join(self.iter)
1762 return ''.join(self.iter)
1753
1763
1754 left = l
1764 left = l
1755 buf = []
1765 buf = []
1756 queue = self._queue
1766 queue = self._queue
1757 while left > 0:
1767 while left > 0:
1758 # refill the queue
1768 # refill the queue
1759 if not queue:
1769 if not queue:
1760 target = 2**18
1770 target = 2**18
1761 for chunk in self.iter:
1771 for chunk in self.iter:
1762 queue.append(chunk)
1772 queue.append(chunk)
1763 target -= len(chunk)
1773 target -= len(chunk)
1764 if target <= 0:
1774 if target <= 0:
1765 break
1775 break
1766 if not queue:
1776 if not queue:
1767 break
1777 break
1768
1778
1769 # The easy way to do this would be to queue.popleft(), modify the
1779 # The easy way to do this would be to queue.popleft(), modify the
1770 # chunk (if necessary), then queue.appendleft(). However, for cases
1780 # chunk (if necessary), then queue.appendleft(). However, for cases
1771 # where we read partial chunk content, this incurs 2 dequeue
1781 # where we read partial chunk content, this incurs 2 dequeue
1772 # mutations and creates a new str for the remaining chunk in the
1782 # mutations and creates a new str for the remaining chunk in the
1773 # queue. Our code below avoids this overhead.
1783 # queue. Our code below avoids this overhead.
1774
1784
1775 chunk = queue[0]
1785 chunk = queue[0]
1776 chunkl = len(chunk)
1786 chunkl = len(chunk)
1777 offset = self._chunkoffset
1787 offset = self._chunkoffset
1778
1788
1779 # Use full chunk.
1789 # Use full chunk.
1780 if offset == 0 and left >= chunkl:
1790 if offset == 0 and left >= chunkl:
1781 left -= chunkl
1791 left -= chunkl
1782 queue.popleft()
1792 queue.popleft()
1783 buf.append(chunk)
1793 buf.append(chunk)
1784 # self._chunkoffset remains at 0.
1794 # self._chunkoffset remains at 0.
1785 continue
1795 continue
1786
1796
1787 chunkremaining = chunkl - offset
1797 chunkremaining = chunkl - offset
1788
1798
1789 # Use all of unconsumed part of chunk.
1799 # Use all of unconsumed part of chunk.
1790 if left >= chunkremaining:
1800 if left >= chunkremaining:
1791 left -= chunkremaining
1801 left -= chunkremaining
1792 queue.popleft()
1802 queue.popleft()
1793 # offset == 0 is enabled by block above, so this won't merely
1803 # offset == 0 is enabled by block above, so this won't merely
1794 # copy via ``chunk[0:]``.
1804 # copy via ``chunk[0:]``.
1795 buf.append(chunk[offset:])
1805 buf.append(chunk[offset:])
1796 self._chunkoffset = 0
1806 self._chunkoffset = 0
1797
1807
1798 # Partial chunk needed.
1808 # Partial chunk needed.
1799 else:
1809 else:
1800 buf.append(chunk[offset:offset + left])
1810 buf.append(chunk[offset:offset + left])
1801 self._chunkoffset += left
1811 self._chunkoffset += left
1802 left -= chunkremaining
1812 left -= chunkremaining
1803
1813
1804 return ''.join(buf)
1814 return ''.join(buf)
1805
1815
1806 def filechunkiter(f, size=131072, limit=None):
1816 def filechunkiter(f, size=131072, limit=None):
1807 """Create a generator that produces the data in the file size
1817 """Create a generator that produces the data in the file size
1808 (default 131072) bytes at a time, up to optional limit (default is
1818 (default 131072) bytes at a time, up to optional limit (default is
1809 to read all data). Chunks may be less than size bytes if the
1819 to read all data). Chunks may be less than size bytes if the
1810 chunk is the last chunk in the file, or the file is a socket or
1820 chunk is the last chunk in the file, or the file is a socket or
1811 some other type of file that sometimes reads less data than is
1821 some other type of file that sometimes reads less data than is
1812 requested."""
1822 requested."""
1813 assert size >= 0
1823 assert size >= 0
1814 assert limit is None or limit >= 0
1824 assert limit is None or limit >= 0
1815 while True:
1825 while True:
1816 if limit is None:
1826 if limit is None:
1817 nbytes = size
1827 nbytes = size
1818 else:
1828 else:
1819 nbytes = min(limit, size)
1829 nbytes = min(limit, size)
1820 s = nbytes and f.read(nbytes)
1830 s = nbytes and f.read(nbytes)
1821 if not s:
1831 if not s:
1822 break
1832 break
1823 if limit:
1833 if limit:
1824 limit -= len(s)
1834 limit -= len(s)
1825 yield s
1835 yield s
1826
1836
1827 def makedate(timestamp=None):
1837 def makedate(timestamp=None):
1828 '''Return a unix timestamp (or the current time) as a (unixtime,
1838 '''Return a unix timestamp (or the current time) as a (unixtime,
1829 offset) tuple based off the local timezone.'''
1839 offset) tuple based off the local timezone.'''
1830 if timestamp is None:
1840 if timestamp is None:
1831 timestamp = time.time()
1841 timestamp = time.time()
1832 if timestamp < 0:
1842 if timestamp < 0:
1833 hint = _("check your clock")
1843 hint = _("check your clock")
1834 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1844 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1835 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1845 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1836 datetime.datetime.fromtimestamp(timestamp))
1846 datetime.datetime.fromtimestamp(timestamp))
1837 tz = delta.days * 86400 + delta.seconds
1847 tz = delta.days * 86400 + delta.seconds
1838 return timestamp, tz
1848 return timestamp, tz
1839
1849
1840 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1850 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1841 """represent a (unixtime, offset) tuple as a localized time.
1851 """represent a (unixtime, offset) tuple as a localized time.
1842 unixtime is seconds since the epoch, and offset is the time zone's
1852 unixtime is seconds since the epoch, and offset is the time zone's
1843 number of seconds away from UTC.
1853 number of seconds away from UTC.
1844
1854
1845 >>> datestr((0, 0))
1855 >>> datestr((0, 0))
1846 'Thu Jan 01 00:00:00 1970 +0000'
1856 'Thu Jan 01 00:00:00 1970 +0000'
1847 >>> datestr((42, 0))
1857 >>> datestr((42, 0))
1848 'Thu Jan 01 00:00:42 1970 +0000'
1858 'Thu Jan 01 00:00:42 1970 +0000'
1849 >>> datestr((-42, 0))
1859 >>> datestr((-42, 0))
1850 'Wed Dec 31 23:59:18 1969 +0000'
1860 'Wed Dec 31 23:59:18 1969 +0000'
1851 >>> datestr((0x7fffffff, 0))
1861 >>> datestr((0x7fffffff, 0))
1852 'Tue Jan 19 03:14:07 2038 +0000'
1862 'Tue Jan 19 03:14:07 2038 +0000'
1853 >>> datestr((-0x80000000, 0))
1863 >>> datestr((-0x80000000, 0))
1854 'Fri Dec 13 20:45:52 1901 +0000'
1864 'Fri Dec 13 20:45:52 1901 +0000'
1855 """
1865 """
1856 t, tz = date or makedate()
1866 t, tz = date or makedate()
1857 if "%1" in format or "%2" in format or "%z" in format:
1867 if "%1" in format or "%2" in format or "%z" in format:
1858 sign = (tz > 0) and "-" or "+"
1868 sign = (tz > 0) and "-" or "+"
1859 minutes = abs(tz) // 60
1869 minutes = abs(tz) // 60
1860 q, r = divmod(minutes, 60)
1870 q, r = divmod(minutes, 60)
1861 format = format.replace("%z", "%1%2")
1871 format = format.replace("%z", "%1%2")
1862 format = format.replace("%1", "%c%02d" % (sign, q))
1872 format = format.replace("%1", "%c%02d" % (sign, q))
1863 format = format.replace("%2", "%02d" % r)
1873 format = format.replace("%2", "%02d" % r)
1864 d = t - tz
1874 d = t - tz
1865 if d > 0x7fffffff:
1875 if d > 0x7fffffff:
1866 d = 0x7fffffff
1876 d = 0x7fffffff
1867 elif d < -0x80000000:
1877 elif d < -0x80000000:
1868 d = -0x80000000
1878 d = -0x80000000
1869 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1879 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1870 # because they use the gmtime() system call which is buggy on Windows
1880 # because they use the gmtime() system call which is buggy on Windows
1871 # for negative values.
1881 # for negative values.
1872 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1882 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1873 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1883 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1874 return s
1884 return s
1875
1885
1876 def shortdate(date=None):
1886 def shortdate(date=None):
1877 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1887 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1878 return datestr(date, format='%Y-%m-%d')
1888 return datestr(date, format='%Y-%m-%d')
1879
1889
1880 def parsetimezone(s):
1890 def parsetimezone(s):
1881 """find a trailing timezone, if any, in string, and return a
1891 """find a trailing timezone, if any, in string, and return a
1882 (offset, remainder) pair"""
1892 (offset, remainder) pair"""
1883
1893
1884 if s.endswith("GMT") or s.endswith("UTC"):
1894 if s.endswith("GMT") or s.endswith("UTC"):
1885 return 0, s[:-3].rstrip()
1895 return 0, s[:-3].rstrip()
1886
1896
1887 # Unix-style timezones [+-]hhmm
1897 # Unix-style timezones [+-]hhmm
1888 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1898 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1889 sign = (s[-5] == "+") and 1 or -1
1899 sign = (s[-5] == "+") and 1 or -1
1890 hours = int(s[-4:-2])
1900 hours = int(s[-4:-2])
1891 minutes = int(s[-2:])
1901 minutes = int(s[-2:])
1892 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1902 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1893
1903
1894 # ISO8601 trailing Z
1904 # ISO8601 trailing Z
1895 if s.endswith("Z") and s[-2:-1].isdigit():
1905 if s.endswith("Z") and s[-2:-1].isdigit():
1896 return 0, s[:-1]
1906 return 0, s[:-1]
1897
1907
1898 # ISO8601-style [+-]hh:mm
1908 # ISO8601-style [+-]hh:mm
1899 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1909 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1900 s[-5:-3].isdigit() and s[-2:].isdigit()):
1910 s[-5:-3].isdigit() and s[-2:].isdigit()):
1901 sign = (s[-6] == "+") and 1 or -1
1911 sign = (s[-6] == "+") and 1 or -1
1902 hours = int(s[-5:-3])
1912 hours = int(s[-5:-3])
1903 minutes = int(s[-2:])
1913 minutes = int(s[-2:])
1904 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1914 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1905
1915
1906 return None, s
1916 return None, s
1907
1917
1908 def strdate(string, format, defaults=None):
1918 def strdate(string, format, defaults=None):
1909 """parse a localized time string and return a (unixtime, offset) tuple.
1919 """parse a localized time string and return a (unixtime, offset) tuple.
1910 if the string cannot be parsed, ValueError is raised."""
1920 if the string cannot be parsed, ValueError is raised."""
1911 if defaults is None:
1921 if defaults is None:
1912 defaults = {}
1922 defaults = {}
1913
1923
1914 # NOTE: unixtime = localunixtime + offset
1924 # NOTE: unixtime = localunixtime + offset
1915 offset, date = parsetimezone(string)
1925 offset, date = parsetimezone(string)
1916
1926
1917 # add missing elements from defaults
1927 # add missing elements from defaults
1918 usenow = False # default to using biased defaults
1928 usenow = False # default to using biased defaults
1919 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1929 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1920 part = pycompat.bytestr(part)
1930 part = pycompat.bytestr(part)
1921 found = [True for p in part if ("%"+p) in format]
1931 found = [True for p in part if ("%"+p) in format]
1922 if not found:
1932 if not found:
1923 date += "@" + defaults[part][usenow]
1933 date += "@" + defaults[part][usenow]
1924 format += "@%" + part[0]
1934 format += "@%" + part[0]
1925 else:
1935 else:
1926 # We've found a specific time element, less specific time
1936 # We've found a specific time element, less specific time
1927 # elements are relative to today
1937 # elements are relative to today
1928 usenow = True
1938 usenow = True
1929
1939
1930 timetuple = time.strptime(date, format)
1940 timetuple = time.strptime(date, format)
1931 localunixtime = int(calendar.timegm(timetuple))
1941 localunixtime = int(calendar.timegm(timetuple))
1932 if offset is None:
1942 if offset is None:
1933 # local timezone
1943 # local timezone
1934 unixtime = int(time.mktime(timetuple))
1944 unixtime = int(time.mktime(timetuple))
1935 offset = unixtime - localunixtime
1945 offset = unixtime - localunixtime
1936 else:
1946 else:
1937 unixtime = localunixtime + offset
1947 unixtime = localunixtime + offset
1938 return unixtime, offset
1948 return unixtime, offset
1939
1949
1940 def parsedate(date, formats=None, bias=None):
1950 def parsedate(date, formats=None, bias=None):
1941 """parse a localized date/time and return a (unixtime, offset) tuple.
1951 """parse a localized date/time and return a (unixtime, offset) tuple.
1942
1952
1943 The date may be a "unixtime offset" string or in one of the specified
1953 The date may be a "unixtime offset" string or in one of the specified
1944 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1954 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1945
1955
1946 >>> parsedate(' today ') == parsedate(\
1956 >>> parsedate(' today ') == parsedate(\
1947 datetime.date.today().strftime('%b %d'))
1957 datetime.date.today().strftime('%b %d'))
1948 True
1958 True
1949 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1959 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1950 datetime.timedelta(days=1)\
1960 datetime.timedelta(days=1)\
1951 ).strftime('%b %d'))
1961 ).strftime('%b %d'))
1952 True
1962 True
1953 >>> now, tz = makedate()
1963 >>> now, tz = makedate()
1954 >>> strnow, strtz = parsedate('now')
1964 >>> strnow, strtz = parsedate('now')
1955 >>> (strnow - now) < 1
1965 >>> (strnow - now) < 1
1956 True
1966 True
1957 >>> tz == strtz
1967 >>> tz == strtz
1958 True
1968 True
1959 """
1969 """
1960 if bias is None:
1970 if bias is None:
1961 bias = {}
1971 bias = {}
1962 if not date:
1972 if not date:
1963 return 0, 0
1973 return 0, 0
1964 if isinstance(date, tuple) and len(date) == 2:
1974 if isinstance(date, tuple) and len(date) == 2:
1965 return date
1975 return date
1966 if not formats:
1976 if not formats:
1967 formats = defaultdateformats
1977 formats = defaultdateformats
1968 date = date.strip()
1978 date = date.strip()
1969
1979
1970 if date == 'now' or date == _('now'):
1980 if date == 'now' or date == _('now'):
1971 return makedate()
1981 return makedate()
1972 if date == 'today' or date == _('today'):
1982 if date == 'today' or date == _('today'):
1973 date = datetime.date.today().strftime('%b %d')
1983 date = datetime.date.today().strftime('%b %d')
1974 elif date == 'yesterday' or date == _('yesterday'):
1984 elif date == 'yesterday' or date == _('yesterday'):
1975 date = (datetime.date.today() -
1985 date = (datetime.date.today() -
1976 datetime.timedelta(days=1)).strftime('%b %d')
1986 datetime.timedelta(days=1)).strftime('%b %d')
1977
1987
1978 try:
1988 try:
1979 when, offset = map(int, date.split(' '))
1989 when, offset = map(int, date.split(' '))
1980 except ValueError:
1990 except ValueError:
1981 # fill out defaults
1991 # fill out defaults
1982 now = makedate()
1992 now = makedate()
1983 defaults = {}
1993 defaults = {}
1984 for part in ("d", "mb", "yY", "HI", "M", "S"):
1994 for part in ("d", "mb", "yY", "HI", "M", "S"):
1985 # this piece is for rounding the specific end of unknowns
1995 # this piece is for rounding the specific end of unknowns
1986 b = bias.get(part)
1996 b = bias.get(part)
1987 if b is None:
1997 if b is None:
1988 if part[0:1] in "HMS":
1998 if part[0:1] in "HMS":
1989 b = "00"
1999 b = "00"
1990 else:
2000 else:
1991 b = "0"
2001 b = "0"
1992
2002
1993 # this piece is for matching the generic end to today's date
2003 # this piece is for matching the generic end to today's date
1994 n = datestr(now, "%" + part[0:1])
2004 n = datestr(now, "%" + part[0:1])
1995
2005
1996 defaults[part] = (b, n)
2006 defaults[part] = (b, n)
1997
2007
1998 for format in formats:
2008 for format in formats:
1999 try:
2009 try:
2000 when, offset = strdate(date, format, defaults)
2010 when, offset = strdate(date, format, defaults)
2001 except (ValueError, OverflowError):
2011 except (ValueError, OverflowError):
2002 pass
2012 pass
2003 else:
2013 else:
2004 break
2014 break
2005 else:
2015 else:
2006 raise Abort(_('invalid date: %r') % date)
2016 raise Abort(_('invalid date: %r') % date)
2007 # validate explicit (probably user-specified) date and
2017 # validate explicit (probably user-specified) date and
2008 # time zone offset. values must fit in signed 32 bits for
2018 # time zone offset. values must fit in signed 32 bits for
2009 # current 32-bit linux runtimes. timezones go from UTC-12
2019 # current 32-bit linux runtimes. timezones go from UTC-12
2010 # to UTC+14
2020 # to UTC+14
2011 if when < -0x80000000 or when > 0x7fffffff:
2021 if when < -0x80000000 or when > 0x7fffffff:
2012 raise Abort(_('date exceeds 32 bits: %d') % when)
2022 raise Abort(_('date exceeds 32 bits: %d') % when)
2013 if offset < -50400 or offset > 43200:
2023 if offset < -50400 or offset > 43200:
2014 raise Abort(_('impossible time zone offset: %d') % offset)
2024 raise Abort(_('impossible time zone offset: %d') % offset)
2015 return when, offset
2025 return when, offset
2016
2026
2017 def matchdate(date):
2027 def matchdate(date):
2018 """Return a function that matches a given date match specifier
2028 """Return a function that matches a given date match specifier
2019
2029
2020 Formats include:
2030 Formats include:
2021
2031
2022 '{date}' match a given date to the accuracy provided
2032 '{date}' match a given date to the accuracy provided
2023
2033
2024 '<{date}' on or before a given date
2034 '<{date}' on or before a given date
2025
2035
2026 '>{date}' on or after a given date
2036 '>{date}' on or after a given date
2027
2037
2028 >>> p1 = parsedate("10:29:59")
2038 >>> p1 = parsedate("10:29:59")
2029 >>> p2 = parsedate("10:30:00")
2039 >>> p2 = parsedate("10:30:00")
2030 >>> p3 = parsedate("10:30:59")
2040 >>> p3 = parsedate("10:30:59")
2031 >>> p4 = parsedate("10:31:00")
2041 >>> p4 = parsedate("10:31:00")
2032 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2042 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2033 >>> f = matchdate("10:30")
2043 >>> f = matchdate("10:30")
2034 >>> f(p1[0])
2044 >>> f(p1[0])
2035 False
2045 False
2036 >>> f(p2[0])
2046 >>> f(p2[0])
2037 True
2047 True
2038 >>> f(p3[0])
2048 >>> f(p3[0])
2039 True
2049 True
2040 >>> f(p4[0])
2050 >>> f(p4[0])
2041 False
2051 False
2042 >>> f(p5[0])
2052 >>> f(p5[0])
2043 False
2053 False
2044 """
2054 """
2045
2055
2046 def lower(date):
2056 def lower(date):
2047 d = {'mb': "1", 'd': "1"}
2057 d = {'mb': "1", 'd': "1"}
2048 return parsedate(date, extendeddateformats, d)[0]
2058 return parsedate(date, extendeddateformats, d)[0]
2049
2059
2050 def upper(date):
2060 def upper(date):
2051 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2061 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2052 for days in ("31", "30", "29"):
2062 for days in ("31", "30", "29"):
2053 try:
2063 try:
2054 d["d"] = days
2064 d["d"] = days
2055 return parsedate(date, extendeddateformats, d)[0]
2065 return parsedate(date, extendeddateformats, d)[0]
2056 except Abort:
2066 except Abort:
2057 pass
2067 pass
2058 d["d"] = "28"
2068 d["d"] = "28"
2059 return parsedate(date, extendeddateformats, d)[0]
2069 return parsedate(date, extendeddateformats, d)[0]
2060
2070
2061 date = date.strip()
2071 date = date.strip()
2062
2072
2063 if not date:
2073 if not date:
2064 raise Abort(_("dates cannot consist entirely of whitespace"))
2074 raise Abort(_("dates cannot consist entirely of whitespace"))
2065 elif date[0] == "<":
2075 elif date[0] == "<":
2066 if not date[1:]:
2076 if not date[1:]:
2067 raise Abort(_("invalid day spec, use '<DATE'"))
2077 raise Abort(_("invalid day spec, use '<DATE'"))
2068 when = upper(date[1:])
2078 when = upper(date[1:])
2069 return lambda x: x <= when
2079 return lambda x: x <= when
2070 elif date[0] == ">":
2080 elif date[0] == ">":
2071 if not date[1:]:
2081 if not date[1:]:
2072 raise Abort(_("invalid day spec, use '>DATE'"))
2082 raise Abort(_("invalid day spec, use '>DATE'"))
2073 when = lower(date[1:])
2083 when = lower(date[1:])
2074 return lambda x: x >= when
2084 return lambda x: x >= when
2075 elif date[0] == "-":
2085 elif date[0] == "-":
2076 try:
2086 try:
2077 days = int(date[1:])
2087 days = int(date[1:])
2078 except ValueError:
2088 except ValueError:
2079 raise Abort(_("invalid day spec: %s") % date[1:])
2089 raise Abort(_("invalid day spec: %s") % date[1:])
2080 if days < 0:
2090 if days < 0:
2081 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2091 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2082 % date[1:])
2092 % date[1:])
2083 when = makedate()[0] - days * 3600 * 24
2093 when = makedate()[0] - days * 3600 * 24
2084 return lambda x: x >= when
2094 return lambda x: x >= when
2085 elif " to " in date:
2095 elif " to " in date:
2086 a, b = date.split(" to ")
2096 a, b = date.split(" to ")
2087 start, stop = lower(a), upper(b)
2097 start, stop = lower(a), upper(b)
2088 return lambda x: x >= start and x <= stop
2098 return lambda x: x >= start and x <= stop
2089 else:
2099 else:
2090 start, stop = lower(date), upper(date)
2100 start, stop = lower(date), upper(date)
2091 return lambda x: x >= start and x <= stop
2101 return lambda x: x >= start and x <= stop
2092
2102
2093 def stringmatcher(pattern, casesensitive=True):
2103 def stringmatcher(pattern, casesensitive=True):
2094 """
2104 """
2095 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2105 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2096 returns the matcher name, pattern, and matcher function.
2106 returns the matcher name, pattern, and matcher function.
2097 missing or unknown prefixes are treated as literal matches.
2107 missing or unknown prefixes are treated as literal matches.
2098
2108
2099 helper for tests:
2109 helper for tests:
2100 >>> def test(pattern, *tests):
2110 >>> def test(pattern, *tests):
2101 ... kind, pattern, matcher = stringmatcher(pattern)
2111 ... kind, pattern, matcher = stringmatcher(pattern)
2102 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2112 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2103 >>> def itest(pattern, *tests):
2113 >>> def itest(pattern, *tests):
2104 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2114 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2105 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2115 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2106
2116
2107 exact matching (no prefix):
2117 exact matching (no prefix):
2108 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2118 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2109 ('literal', 'abcdefg', [False, False, True])
2119 ('literal', 'abcdefg', [False, False, True])
2110
2120
2111 regex matching ('re:' prefix)
2121 regex matching ('re:' prefix)
2112 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2122 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2113 ('re', 'a.+b', [False, False, True])
2123 ('re', 'a.+b', [False, False, True])
2114
2124
2115 force exact matches ('literal:' prefix)
2125 force exact matches ('literal:' prefix)
2116 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2126 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2117 ('literal', 're:foobar', [False, True])
2127 ('literal', 're:foobar', [False, True])
2118
2128
2119 unknown prefixes are ignored and treated as literals
2129 unknown prefixes are ignored and treated as literals
2120 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2130 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2121 ('literal', 'foo:bar', [False, False, True])
2131 ('literal', 'foo:bar', [False, False, True])
2122
2132
2123 case insensitive regex matches
2133 case insensitive regex matches
2124 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2134 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2125 ('re', 'A.+b', [False, False, True])
2135 ('re', 'A.+b', [False, False, True])
2126
2136
2127 case insensitive literal matches
2137 case insensitive literal matches
2128 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2138 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2129 ('literal', 'ABCDEFG', [False, False, True])
2139 ('literal', 'ABCDEFG', [False, False, True])
2130 """
2140 """
2131 if pattern.startswith('re:'):
2141 if pattern.startswith('re:'):
2132 pattern = pattern[3:]
2142 pattern = pattern[3:]
2133 try:
2143 try:
2134 flags = 0
2144 flags = 0
2135 if not casesensitive:
2145 if not casesensitive:
2136 flags = remod.I
2146 flags = remod.I
2137 regex = remod.compile(pattern, flags)
2147 regex = remod.compile(pattern, flags)
2138 except remod.error as e:
2148 except remod.error as e:
2139 raise error.ParseError(_('invalid regular expression: %s')
2149 raise error.ParseError(_('invalid regular expression: %s')
2140 % e)
2150 % e)
2141 return 're', pattern, regex.search
2151 return 're', pattern, regex.search
2142 elif pattern.startswith('literal:'):
2152 elif pattern.startswith('literal:'):
2143 pattern = pattern[8:]
2153 pattern = pattern[8:]
2144
2154
2145 match = pattern.__eq__
2155 match = pattern.__eq__
2146
2156
2147 if not casesensitive:
2157 if not casesensitive:
2148 ipat = encoding.lower(pattern)
2158 ipat = encoding.lower(pattern)
2149 match = lambda s: ipat == encoding.lower(s)
2159 match = lambda s: ipat == encoding.lower(s)
2150 return 'literal', pattern, match
2160 return 'literal', pattern, match
2151
2161
2152 def shortuser(user):
2162 def shortuser(user):
2153 """Return a short representation of a user name or email address."""
2163 """Return a short representation of a user name or email address."""
2154 f = user.find('@')
2164 f = user.find('@')
2155 if f >= 0:
2165 if f >= 0:
2156 user = user[:f]
2166 user = user[:f]
2157 f = user.find('<')
2167 f = user.find('<')
2158 if f >= 0:
2168 if f >= 0:
2159 user = user[f + 1:]
2169 user = user[f + 1:]
2160 f = user.find(' ')
2170 f = user.find(' ')
2161 if f >= 0:
2171 if f >= 0:
2162 user = user[:f]
2172 user = user[:f]
2163 f = user.find('.')
2173 f = user.find('.')
2164 if f >= 0:
2174 if f >= 0:
2165 user = user[:f]
2175 user = user[:f]
2166 return user
2176 return user
2167
2177
2168 def emailuser(user):
2178 def emailuser(user):
2169 """Return the user portion of an email address."""
2179 """Return the user portion of an email address."""
2170 f = user.find('@')
2180 f = user.find('@')
2171 if f >= 0:
2181 if f >= 0:
2172 user = user[:f]
2182 user = user[:f]
2173 f = user.find('<')
2183 f = user.find('<')
2174 if f >= 0:
2184 if f >= 0:
2175 user = user[f + 1:]
2185 user = user[f + 1:]
2176 return user
2186 return user
2177
2187
2178 def email(author):
2188 def email(author):
2179 '''get email of author.'''
2189 '''get email of author.'''
2180 r = author.find('>')
2190 r = author.find('>')
2181 if r == -1:
2191 if r == -1:
2182 r = None
2192 r = None
2183 return author[author.find('<') + 1:r]
2193 return author[author.find('<') + 1:r]
2184
2194
2185 def ellipsis(text, maxlength=400):
2195 def ellipsis(text, maxlength=400):
2186 """Trim string to at most maxlength (default: 400) columns in display."""
2196 """Trim string to at most maxlength (default: 400) columns in display."""
2187 return encoding.trim(text, maxlength, ellipsis='...')
2197 return encoding.trim(text, maxlength, ellipsis='...')
2188
2198
2189 def unitcountfn(*unittable):
2199 def unitcountfn(*unittable):
2190 '''return a function that renders a readable count of some quantity'''
2200 '''return a function that renders a readable count of some quantity'''
2191
2201
2192 def go(count):
2202 def go(count):
2193 for multiplier, divisor, format in unittable:
2203 for multiplier, divisor, format in unittable:
2194 if abs(count) >= divisor * multiplier:
2204 if abs(count) >= divisor * multiplier:
2195 return format % (count / float(divisor))
2205 return format % (count / float(divisor))
2196 return unittable[-1][2] % count
2206 return unittable[-1][2] % count
2197
2207
2198 return go
2208 return go
2199
2209
2200 def processlinerange(fromline, toline):
2210 def processlinerange(fromline, toline):
2201 """Check that linerange <fromline>:<toline> makes sense and return a
2211 """Check that linerange <fromline>:<toline> makes sense and return a
2202 0-based range.
2212 0-based range.
2203
2213
2204 >>> processlinerange(10, 20)
2214 >>> processlinerange(10, 20)
2205 (9, 20)
2215 (9, 20)
2206 >>> processlinerange(2, 1)
2216 >>> processlinerange(2, 1)
2207 Traceback (most recent call last):
2217 Traceback (most recent call last):
2208 ...
2218 ...
2209 ParseError: line range must be positive
2219 ParseError: line range must be positive
2210 >>> processlinerange(0, 5)
2220 >>> processlinerange(0, 5)
2211 Traceback (most recent call last):
2221 Traceback (most recent call last):
2212 ...
2222 ...
2213 ParseError: fromline must be strictly positive
2223 ParseError: fromline must be strictly positive
2214 """
2224 """
2215 if toline - fromline < 0:
2225 if toline - fromline < 0:
2216 raise error.ParseError(_("line range must be positive"))
2226 raise error.ParseError(_("line range must be positive"))
2217 if fromline < 1:
2227 if fromline < 1:
2218 raise error.ParseError(_("fromline must be strictly positive"))
2228 raise error.ParseError(_("fromline must be strictly positive"))
2219 return fromline - 1, toline
2229 return fromline - 1, toline
2220
2230
2221 bytecount = unitcountfn(
2231 bytecount = unitcountfn(
2222 (100, 1 << 30, _('%.0f GB')),
2232 (100, 1 << 30, _('%.0f GB')),
2223 (10, 1 << 30, _('%.1f GB')),
2233 (10, 1 << 30, _('%.1f GB')),
2224 (1, 1 << 30, _('%.2f GB')),
2234 (1, 1 << 30, _('%.2f GB')),
2225 (100, 1 << 20, _('%.0f MB')),
2235 (100, 1 << 20, _('%.0f MB')),
2226 (10, 1 << 20, _('%.1f MB')),
2236 (10, 1 << 20, _('%.1f MB')),
2227 (1, 1 << 20, _('%.2f MB')),
2237 (1, 1 << 20, _('%.2f MB')),
2228 (100, 1 << 10, _('%.0f KB')),
2238 (100, 1 << 10, _('%.0f KB')),
2229 (10, 1 << 10, _('%.1f KB')),
2239 (10, 1 << 10, _('%.1f KB')),
2230 (1, 1 << 10, _('%.2f KB')),
2240 (1, 1 << 10, _('%.2f KB')),
2231 (1, 1, _('%.0f bytes')),
2241 (1, 1, _('%.0f bytes')),
2232 )
2242 )
2233
2243
2234 # Matches a single EOL which can either be a CRLF where repeated CR
2244 # Matches a single EOL which can either be a CRLF where repeated CR
2235 # are removed or a LF. We do not care about old Macintosh files, so a
2245 # are removed or a LF. We do not care about old Macintosh files, so a
2236 # stray CR is an error.
2246 # stray CR is an error.
2237 _eolre = remod.compile(br'\r*\n')
2247 _eolre = remod.compile(br'\r*\n')
2238
2248
2239 def tolf(s):
2249 def tolf(s):
2240 return _eolre.sub('\n', s)
2250 return _eolre.sub('\n', s)
2241
2251
2242 def tocrlf(s):
2252 def tocrlf(s):
2243 return _eolre.sub('\r\n', s)
2253 return _eolre.sub('\r\n', s)
2244
2254
2245 if pycompat.oslinesep == '\r\n':
2255 if pycompat.oslinesep == '\r\n':
2246 tonativeeol = tocrlf
2256 tonativeeol = tocrlf
2247 fromnativeeol = tolf
2257 fromnativeeol = tolf
2248 else:
2258 else:
2249 tonativeeol = pycompat.identity
2259 tonativeeol = pycompat.identity
2250 fromnativeeol = pycompat.identity
2260 fromnativeeol = pycompat.identity
2251
2261
2252 def escapestr(s):
2262 def escapestr(s):
2253 # call underlying function of s.encode('string_escape') directly for
2263 # call underlying function of s.encode('string_escape') directly for
2254 # Python 3 compatibility
2264 # Python 3 compatibility
2255 return codecs.escape_encode(s)[0]
2265 return codecs.escape_encode(s)[0]
2256
2266
2257 def unescapestr(s):
2267 def unescapestr(s):
2258 return codecs.escape_decode(s)[0]
2268 return codecs.escape_decode(s)[0]
2259
2269
2260 def uirepr(s):
2270 def uirepr(s):
2261 # Avoid double backslash in Windows path repr()
2271 # Avoid double backslash in Windows path repr()
2262 return repr(s).replace('\\\\', '\\')
2272 return repr(s).replace('\\\\', '\\')
2263
2273
2264 # delay import of textwrap
2274 # delay import of textwrap
2265 def MBTextWrapper(**kwargs):
2275 def MBTextWrapper(**kwargs):
2266 class tw(textwrap.TextWrapper):
2276 class tw(textwrap.TextWrapper):
2267 """
2277 """
2268 Extend TextWrapper for width-awareness.
2278 Extend TextWrapper for width-awareness.
2269
2279
2270 Neither number of 'bytes' in any encoding nor 'characters' is
2280 Neither number of 'bytes' in any encoding nor 'characters' is
2271 appropriate to calculate terminal columns for specified string.
2281 appropriate to calculate terminal columns for specified string.
2272
2282
2273 Original TextWrapper implementation uses built-in 'len()' directly,
2283 Original TextWrapper implementation uses built-in 'len()' directly,
2274 so overriding is needed to use width information of each characters.
2284 so overriding is needed to use width information of each characters.
2275
2285
2276 In addition, characters classified into 'ambiguous' width are
2286 In addition, characters classified into 'ambiguous' width are
2277 treated as wide in East Asian area, but as narrow in other.
2287 treated as wide in East Asian area, but as narrow in other.
2278
2288
2279 This requires use decision to determine width of such characters.
2289 This requires use decision to determine width of such characters.
2280 """
2290 """
2281 def _cutdown(self, ucstr, space_left):
2291 def _cutdown(self, ucstr, space_left):
2282 l = 0
2292 l = 0
2283 colwidth = encoding.ucolwidth
2293 colwidth = encoding.ucolwidth
2284 for i in xrange(len(ucstr)):
2294 for i in xrange(len(ucstr)):
2285 l += colwidth(ucstr[i])
2295 l += colwidth(ucstr[i])
2286 if space_left < l:
2296 if space_left < l:
2287 return (ucstr[:i], ucstr[i:])
2297 return (ucstr[:i], ucstr[i:])
2288 return ucstr, ''
2298 return ucstr, ''
2289
2299
2290 # overriding of base class
2300 # overriding of base class
2291 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2301 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2292 space_left = max(width - cur_len, 1)
2302 space_left = max(width - cur_len, 1)
2293
2303
2294 if self.break_long_words:
2304 if self.break_long_words:
2295 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2305 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2296 cur_line.append(cut)
2306 cur_line.append(cut)
2297 reversed_chunks[-1] = res
2307 reversed_chunks[-1] = res
2298 elif not cur_line:
2308 elif not cur_line:
2299 cur_line.append(reversed_chunks.pop())
2309 cur_line.append(reversed_chunks.pop())
2300
2310
2301 # this overriding code is imported from TextWrapper of Python 2.6
2311 # this overriding code is imported from TextWrapper of Python 2.6
2302 # to calculate columns of string by 'encoding.ucolwidth()'
2312 # to calculate columns of string by 'encoding.ucolwidth()'
2303 def _wrap_chunks(self, chunks):
2313 def _wrap_chunks(self, chunks):
2304 colwidth = encoding.ucolwidth
2314 colwidth = encoding.ucolwidth
2305
2315
2306 lines = []
2316 lines = []
2307 if self.width <= 0:
2317 if self.width <= 0:
2308 raise ValueError("invalid width %r (must be > 0)" % self.width)
2318 raise ValueError("invalid width %r (must be > 0)" % self.width)
2309
2319
2310 # Arrange in reverse order so items can be efficiently popped
2320 # Arrange in reverse order so items can be efficiently popped
2311 # from a stack of chucks.
2321 # from a stack of chucks.
2312 chunks.reverse()
2322 chunks.reverse()
2313
2323
2314 while chunks:
2324 while chunks:
2315
2325
2316 # Start the list of chunks that will make up the current line.
2326 # Start the list of chunks that will make up the current line.
2317 # cur_len is just the length of all the chunks in cur_line.
2327 # cur_len is just the length of all the chunks in cur_line.
2318 cur_line = []
2328 cur_line = []
2319 cur_len = 0
2329 cur_len = 0
2320
2330
2321 # Figure out which static string will prefix this line.
2331 # Figure out which static string will prefix this line.
2322 if lines:
2332 if lines:
2323 indent = self.subsequent_indent
2333 indent = self.subsequent_indent
2324 else:
2334 else:
2325 indent = self.initial_indent
2335 indent = self.initial_indent
2326
2336
2327 # Maximum width for this line.
2337 # Maximum width for this line.
2328 width = self.width - len(indent)
2338 width = self.width - len(indent)
2329
2339
2330 # First chunk on line is whitespace -- drop it, unless this
2340 # First chunk on line is whitespace -- drop it, unless this
2331 # is the very beginning of the text (i.e. no lines started yet).
2341 # is the very beginning of the text (i.e. no lines started yet).
2332 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2342 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2333 del chunks[-1]
2343 del chunks[-1]
2334
2344
2335 while chunks:
2345 while chunks:
2336 l = colwidth(chunks[-1])
2346 l = colwidth(chunks[-1])
2337
2347
2338 # Can at least squeeze this chunk onto the current line.
2348 # Can at least squeeze this chunk onto the current line.
2339 if cur_len + l <= width:
2349 if cur_len + l <= width:
2340 cur_line.append(chunks.pop())
2350 cur_line.append(chunks.pop())
2341 cur_len += l
2351 cur_len += l
2342
2352
2343 # Nope, this line is full.
2353 # Nope, this line is full.
2344 else:
2354 else:
2345 break
2355 break
2346
2356
2347 # The current line is full, and the next chunk is too big to
2357 # The current line is full, and the next chunk is too big to
2348 # fit on *any* line (not just this one).
2358 # fit on *any* line (not just this one).
2349 if chunks and colwidth(chunks[-1]) > width:
2359 if chunks and colwidth(chunks[-1]) > width:
2350 self._handle_long_word(chunks, cur_line, cur_len, width)
2360 self._handle_long_word(chunks, cur_line, cur_len, width)
2351
2361
2352 # If the last chunk on this line is all whitespace, drop it.
2362 # If the last chunk on this line is all whitespace, drop it.
2353 if (self.drop_whitespace and
2363 if (self.drop_whitespace and
2354 cur_line and cur_line[-1].strip() == ''):
2364 cur_line and cur_line[-1].strip() == ''):
2355 del cur_line[-1]
2365 del cur_line[-1]
2356
2366
2357 # Convert current line back to a string and store it in list
2367 # Convert current line back to a string and store it in list
2358 # of all lines (return value).
2368 # of all lines (return value).
2359 if cur_line:
2369 if cur_line:
2360 lines.append(indent + ''.join(cur_line))
2370 lines.append(indent + ''.join(cur_line))
2361
2371
2362 return lines
2372 return lines
2363
2373
2364 global MBTextWrapper
2374 global MBTextWrapper
2365 MBTextWrapper = tw
2375 MBTextWrapper = tw
2366 return tw(**kwargs)
2376 return tw(**kwargs)
2367
2377
2368 def wrap(line, width, initindent='', hangindent=''):
2378 def wrap(line, width, initindent='', hangindent=''):
2369 maxindent = max(len(hangindent), len(initindent))
2379 maxindent = max(len(hangindent), len(initindent))
2370 if width <= maxindent:
2380 if width <= maxindent:
2371 # adjust for weird terminal size
2381 # adjust for weird terminal size
2372 width = max(78, maxindent + 1)
2382 width = max(78, maxindent + 1)
2373 line = line.decode(pycompat.sysstr(encoding.encoding),
2383 line = line.decode(pycompat.sysstr(encoding.encoding),
2374 pycompat.sysstr(encoding.encodingmode))
2384 pycompat.sysstr(encoding.encodingmode))
2375 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2385 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2376 pycompat.sysstr(encoding.encodingmode))
2386 pycompat.sysstr(encoding.encodingmode))
2377 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2387 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2378 pycompat.sysstr(encoding.encodingmode))
2388 pycompat.sysstr(encoding.encodingmode))
2379 wrapper = MBTextWrapper(width=width,
2389 wrapper = MBTextWrapper(width=width,
2380 initial_indent=initindent,
2390 initial_indent=initindent,
2381 subsequent_indent=hangindent)
2391 subsequent_indent=hangindent)
2382 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2392 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2383
2393
2384 if (pyplatform.python_implementation() == 'CPython' and
2394 if (pyplatform.python_implementation() == 'CPython' and
2385 sys.version_info < (3, 0)):
2395 sys.version_info < (3, 0)):
2386 # There is an issue in CPython that some IO methods do not handle EINTR
2396 # There is an issue in CPython that some IO methods do not handle EINTR
2387 # correctly. The following table shows what CPython version (and functions)
2397 # correctly. The following table shows what CPython version (and functions)
2388 # are affected (buggy: has the EINTR bug, okay: otherwise):
2398 # are affected (buggy: has the EINTR bug, okay: otherwise):
2389 #
2399 #
2390 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2400 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2391 # --------------------------------------------------
2401 # --------------------------------------------------
2392 # fp.__iter__ | buggy | buggy | okay
2402 # fp.__iter__ | buggy | buggy | okay
2393 # fp.read* | buggy | okay [1] | okay
2403 # fp.read* | buggy | okay [1] | okay
2394 #
2404 #
2395 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2405 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2396 #
2406 #
2397 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2407 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2398 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2408 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2399 #
2409 #
2400 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2410 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2401 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2411 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2402 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2412 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2403 # fp.__iter__ but not other fp.read* methods.
2413 # fp.__iter__ but not other fp.read* methods.
2404 #
2414 #
2405 # On modern systems like Linux, the "read" syscall cannot be interrupted
2415 # On modern systems like Linux, the "read" syscall cannot be interrupted
2406 # when reading "fast" files like on-disk files. So the EINTR issue only
2416 # when reading "fast" files like on-disk files. So the EINTR issue only
2407 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2417 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2408 # files approximately as "fast" files and use the fast (unsafe) code path,
2418 # files approximately as "fast" files and use the fast (unsafe) code path,
2409 # to minimize the performance impact.
2419 # to minimize the performance impact.
2410 if sys.version_info >= (2, 7, 4):
2420 if sys.version_info >= (2, 7, 4):
2411 # fp.readline deals with EINTR correctly, use it as a workaround.
2421 # fp.readline deals with EINTR correctly, use it as a workaround.
2412 def _safeiterfile(fp):
2422 def _safeiterfile(fp):
2413 return iter(fp.readline, '')
2423 return iter(fp.readline, '')
2414 else:
2424 else:
2415 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2425 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2416 # note: this may block longer than necessary because of bufsize.
2426 # note: this may block longer than necessary because of bufsize.
2417 def _safeiterfile(fp, bufsize=4096):
2427 def _safeiterfile(fp, bufsize=4096):
2418 fd = fp.fileno()
2428 fd = fp.fileno()
2419 line = ''
2429 line = ''
2420 while True:
2430 while True:
2421 try:
2431 try:
2422 buf = os.read(fd, bufsize)
2432 buf = os.read(fd, bufsize)
2423 except OSError as ex:
2433 except OSError as ex:
2424 # os.read only raises EINTR before any data is read
2434 # os.read only raises EINTR before any data is read
2425 if ex.errno == errno.EINTR:
2435 if ex.errno == errno.EINTR:
2426 continue
2436 continue
2427 else:
2437 else:
2428 raise
2438 raise
2429 line += buf
2439 line += buf
2430 if '\n' in buf:
2440 if '\n' in buf:
2431 splitted = line.splitlines(True)
2441 splitted = line.splitlines(True)
2432 line = ''
2442 line = ''
2433 for l in splitted:
2443 for l in splitted:
2434 if l[-1] == '\n':
2444 if l[-1] == '\n':
2435 yield l
2445 yield l
2436 else:
2446 else:
2437 line = l
2447 line = l
2438 if not buf:
2448 if not buf:
2439 break
2449 break
2440 if line:
2450 if line:
2441 yield line
2451 yield line
2442
2452
2443 def iterfile(fp):
2453 def iterfile(fp):
2444 fastpath = True
2454 fastpath = True
2445 if type(fp) is file:
2455 if type(fp) is file:
2446 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2456 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2447 if fastpath:
2457 if fastpath:
2448 return fp
2458 return fp
2449 else:
2459 else:
2450 return _safeiterfile(fp)
2460 return _safeiterfile(fp)
2451 else:
2461 else:
2452 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2462 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2453 def iterfile(fp):
2463 def iterfile(fp):
2454 return fp
2464 return fp
2455
2465
2456 def iterlines(iterator):
2466 def iterlines(iterator):
2457 for chunk in iterator:
2467 for chunk in iterator:
2458 for line in chunk.splitlines():
2468 for line in chunk.splitlines():
2459 yield line
2469 yield line
2460
2470
2461 def expandpath(path):
2471 def expandpath(path):
2462 return os.path.expanduser(os.path.expandvars(path))
2472 return os.path.expanduser(os.path.expandvars(path))
2463
2473
2464 def hgcmd():
2474 def hgcmd():
2465 """Return the command used to execute current hg
2475 """Return the command used to execute current hg
2466
2476
2467 This is different from hgexecutable() because on Windows we want
2477 This is different from hgexecutable() because on Windows we want
2468 to avoid things opening new shell windows like batch files, so we
2478 to avoid things opening new shell windows like batch files, so we
2469 get either the python call or current executable.
2479 get either the python call or current executable.
2470 """
2480 """
2471 if mainfrozen():
2481 if mainfrozen():
2472 if getattr(sys, 'frozen', None) == 'macosx_app':
2482 if getattr(sys, 'frozen', None) == 'macosx_app':
2473 # Env variable set by py2app
2483 # Env variable set by py2app
2474 return [encoding.environ['EXECUTABLEPATH']]
2484 return [encoding.environ['EXECUTABLEPATH']]
2475 else:
2485 else:
2476 return [pycompat.sysexecutable]
2486 return [pycompat.sysexecutable]
2477 return gethgcmd()
2487 return gethgcmd()
2478
2488
2479 def rundetached(args, condfn):
2489 def rundetached(args, condfn):
2480 """Execute the argument list in a detached process.
2490 """Execute the argument list in a detached process.
2481
2491
2482 condfn is a callable which is called repeatedly and should return
2492 condfn is a callable which is called repeatedly and should return
2483 True once the child process is known to have started successfully.
2493 True once the child process is known to have started successfully.
2484 At this point, the child process PID is returned. If the child
2494 At this point, the child process PID is returned. If the child
2485 process fails to start or finishes before condfn() evaluates to
2495 process fails to start or finishes before condfn() evaluates to
2486 True, return -1.
2496 True, return -1.
2487 """
2497 """
2488 # Windows case is easier because the child process is either
2498 # Windows case is easier because the child process is either
2489 # successfully starting and validating the condition or exiting
2499 # successfully starting and validating the condition or exiting
2490 # on failure. We just poll on its PID. On Unix, if the child
2500 # on failure. We just poll on its PID. On Unix, if the child
2491 # process fails to start, it will be left in a zombie state until
2501 # process fails to start, it will be left in a zombie state until
2492 # the parent wait on it, which we cannot do since we expect a long
2502 # the parent wait on it, which we cannot do since we expect a long
2493 # running process on success. Instead we listen for SIGCHLD telling
2503 # running process on success. Instead we listen for SIGCHLD telling
2494 # us our child process terminated.
2504 # us our child process terminated.
2495 terminated = set()
2505 terminated = set()
2496 def handler(signum, frame):
2506 def handler(signum, frame):
2497 terminated.add(os.wait())
2507 terminated.add(os.wait())
2498 prevhandler = None
2508 prevhandler = None
2499 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2509 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2500 if SIGCHLD is not None:
2510 if SIGCHLD is not None:
2501 prevhandler = signal.signal(SIGCHLD, handler)
2511 prevhandler = signal.signal(SIGCHLD, handler)
2502 try:
2512 try:
2503 pid = spawndetached(args)
2513 pid = spawndetached(args)
2504 while not condfn():
2514 while not condfn():
2505 if ((pid in terminated or not testpid(pid))
2515 if ((pid in terminated or not testpid(pid))
2506 and not condfn()):
2516 and not condfn()):
2507 return -1
2517 return -1
2508 time.sleep(0.1)
2518 time.sleep(0.1)
2509 return pid
2519 return pid
2510 finally:
2520 finally:
2511 if prevhandler is not None:
2521 if prevhandler is not None:
2512 signal.signal(signal.SIGCHLD, prevhandler)
2522 signal.signal(signal.SIGCHLD, prevhandler)
2513
2523
2514 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2524 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2515 """Return the result of interpolating items in the mapping into string s.
2525 """Return the result of interpolating items in the mapping into string s.
2516
2526
2517 prefix is a single character string, or a two character string with
2527 prefix is a single character string, or a two character string with
2518 a backslash as the first character if the prefix needs to be escaped in
2528 a backslash as the first character if the prefix needs to be escaped in
2519 a regular expression.
2529 a regular expression.
2520
2530
2521 fn is an optional function that will be applied to the replacement text
2531 fn is an optional function that will be applied to the replacement text
2522 just before replacement.
2532 just before replacement.
2523
2533
2524 escape_prefix is an optional flag that allows using doubled prefix for
2534 escape_prefix is an optional flag that allows using doubled prefix for
2525 its escaping.
2535 its escaping.
2526 """
2536 """
2527 fn = fn or (lambda s: s)
2537 fn = fn or (lambda s: s)
2528 patterns = '|'.join(mapping.keys())
2538 patterns = '|'.join(mapping.keys())
2529 if escape_prefix:
2539 if escape_prefix:
2530 patterns += '|' + prefix
2540 patterns += '|' + prefix
2531 if len(prefix) > 1:
2541 if len(prefix) > 1:
2532 prefix_char = prefix[1:]
2542 prefix_char = prefix[1:]
2533 else:
2543 else:
2534 prefix_char = prefix
2544 prefix_char = prefix
2535 mapping[prefix_char] = prefix_char
2545 mapping[prefix_char] = prefix_char
2536 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2546 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2537 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2547 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2538
2548
2539 def getport(port):
2549 def getport(port):
2540 """Return the port for a given network service.
2550 """Return the port for a given network service.
2541
2551
2542 If port is an integer, it's returned as is. If it's a string, it's
2552 If port is an integer, it's returned as is. If it's a string, it's
2543 looked up using socket.getservbyname(). If there's no matching
2553 looked up using socket.getservbyname(). If there's no matching
2544 service, error.Abort is raised.
2554 service, error.Abort is raised.
2545 """
2555 """
2546 try:
2556 try:
2547 return int(port)
2557 return int(port)
2548 except ValueError:
2558 except ValueError:
2549 pass
2559 pass
2550
2560
2551 try:
2561 try:
2552 return socket.getservbyname(port)
2562 return socket.getservbyname(port)
2553 except socket.error:
2563 except socket.error:
2554 raise Abort(_("no port number associated with service '%s'") % port)
2564 raise Abort(_("no port number associated with service '%s'") % port)
2555
2565
2556 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2566 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2557 '0': False, 'no': False, 'false': False, 'off': False,
2567 '0': False, 'no': False, 'false': False, 'off': False,
2558 'never': False}
2568 'never': False}
2559
2569
2560 def parsebool(s):
2570 def parsebool(s):
2561 """Parse s into a boolean.
2571 """Parse s into a boolean.
2562
2572
2563 If s is not a valid boolean, returns None.
2573 If s is not a valid boolean, returns None.
2564 """
2574 """
2565 return _booleans.get(s.lower(), None)
2575 return _booleans.get(s.lower(), None)
2566
2576
2567 _hextochr = dict((a + b, chr(int(a + b, 16)))
2577 _hextochr = dict((a + b, chr(int(a + b, 16)))
2568 for a in string.hexdigits for b in string.hexdigits)
2578 for a in string.hexdigits for b in string.hexdigits)
2569
2579
2570 class url(object):
2580 class url(object):
2571 r"""Reliable URL parser.
2581 r"""Reliable URL parser.
2572
2582
2573 This parses URLs and provides attributes for the following
2583 This parses URLs and provides attributes for the following
2574 components:
2584 components:
2575
2585
2576 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2586 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2577
2587
2578 Missing components are set to None. The only exception is
2588 Missing components are set to None. The only exception is
2579 fragment, which is set to '' if present but empty.
2589 fragment, which is set to '' if present but empty.
2580
2590
2581 If parsefragment is False, fragment is included in query. If
2591 If parsefragment is False, fragment is included in query. If
2582 parsequery is False, query is included in path. If both are
2592 parsequery is False, query is included in path. If both are
2583 False, both fragment and query are included in path.
2593 False, both fragment and query are included in path.
2584
2594
2585 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2595 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2586
2596
2587 Note that for backward compatibility reasons, bundle URLs do not
2597 Note that for backward compatibility reasons, bundle URLs do not
2588 take host names. That means 'bundle://../' has a path of '../'.
2598 take host names. That means 'bundle://../' has a path of '../'.
2589
2599
2590 Examples:
2600 Examples:
2591
2601
2592 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2602 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2593 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2603 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2594 >>> url('ssh://[::1]:2200//home/joe/repo')
2604 >>> url('ssh://[::1]:2200//home/joe/repo')
2595 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2605 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2596 >>> url('file:///home/joe/repo')
2606 >>> url('file:///home/joe/repo')
2597 <url scheme: 'file', path: '/home/joe/repo'>
2607 <url scheme: 'file', path: '/home/joe/repo'>
2598 >>> url('file:///c:/temp/foo/')
2608 >>> url('file:///c:/temp/foo/')
2599 <url scheme: 'file', path: 'c:/temp/foo/'>
2609 <url scheme: 'file', path: 'c:/temp/foo/'>
2600 >>> url('bundle:foo')
2610 >>> url('bundle:foo')
2601 <url scheme: 'bundle', path: 'foo'>
2611 <url scheme: 'bundle', path: 'foo'>
2602 >>> url('bundle://../foo')
2612 >>> url('bundle://../foo')
2603 <url scheme: 'bundle', path: '../foo'>
2613 <url scheme: 'bundle', path: '../foo'>
2604 >>> url(r'c:\foo\bar')
2614 >>> url(r'c:\foo\bar')
2605 <url path: 'c:\\foo\\bar'>
2615 <url path: 'c:\\foo\\bar'>
2606 >>> url(r'\\blah\blah\blah')
2616 >>> url(r'\\blah\blah\blah')
2607 <url path: '\\\\blah\\blah\\blah'>
2617 <url path: '\\\\blah\\blah\\blah'>
2608 >>> url(r'\\blah\blah\blah#baz')
2618 >>> url(r'\\blah\blah\blah#baz')
2609 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2619 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2610 >>> url(r'file:///C:\users\me')
2620 >>> url(r'file:///C:\users\me')
2611 <url scheme: 'file', path: 'C:\\users\\me'>
2621 <url scheme: 'file', path: 'C:\\users\\me'>
2612
2622
2613 Authentication credentials:
2623 Authentication credentials:
2614
2624
2615 >>> url('ssh://joe:xyz@x/repo')
2625 >>> url('ssh://joe:xyz@x/repo')
2616 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2626 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2617 >>> url('ssh://joe@x/repo')
2627 >>> url('ssh://joe@x/repo')
2618 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2628 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2619
2629
2620 Query strings and fragments:
2630 Query strings and fragments:
2621
2631
2622 >>> url('http://host/a?b#c')
2632 >>> url('http://host/a?b#c')
2623 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2633 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2624 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2634 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2625 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2635 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2626
2636
2627 Empty path:
2637 Empty path:
2628
2638
2629 >>> url('')
2639 >>> url('')
2630 <url path: ''>
2640 <url path: ''>
2631 >>> url('#a')
2641 >>> url('#a')
2632 <url path: '', fragment: 'a'>
2642 <url path: '', fragment: 'a'>
2633 >>> url('http://host/')
2643 >>> url('http://host/')
2634 <url scheme: 'http', host: 'host', path: ''>
2644 <url scheme: 'http', host: 'host', path: ''>
2635 >>> url('http://host/#a')
2645 >>> url('http://host/#a')
2636 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2646 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2637
2647
2638 Only scheme:
2648 Only scheme:
2639
2649
2640 >>> url('http:')
2650 >>> url('http:')
2641 <url scheme: 'http'>
2651 <url scheme: 'http'>
2642 """
2652 """
2643
2653
2644 _safechars = "!~*'()+"
2654 _safechars = "!~*'()+"
2645 _safepchars = "/!~*'()+:\\"
2655 _safepchars = "/!~*'()+:\\"
2646 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2656 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2647
2657
2648 def __init__(self, path, parsequery=True, parsefragment=True):
2658 def __init__(self, path, parsequery=True, parsefragment=True):
2649 # We slowly chomp away at path until we have only the path left
2659 # We slowly chomp away at path until we have only the path left
2650 self.scheme = self.user = self.passwd = self.host = None
2660 self.scheme = self.user = self.passwd = self.host = None
2651 self.port = self.path = self.query = self.fragment = None
2661 self.port = self.path = self.query = self.fragment = None
2652 self._localpath = True
2662 self._localpath = True
2653 self._hostport = ''
2663 self._hostport = ''
2654 self._origpath = path
2664 self._origpath = path
2655
2665
2656 if parsefragment and '#' in path:
2666 if parsefragment and '#' in path:
2657 path, self.fragment = path.split('#', 1)
2667 path, self.fragment = path.split('#', 1)
2658
2668
2659 # special case for Windows drive letters and UNC paths
2669 # special case for Windows drive letters and UNC paths
2660 if hasdriveletter(path) or path.startswith('\\\\'):
2670 if hasdriveletter(path) or path.startswith('\\\\'):
2661 self.path = path
2671 self.path = path
2662 return
2672 return
2663
2673
2664 # For compatibility reasons, we can't handle bundle paths as
2674 # For compatibility reasons, we can't handle bundle paths as
2665 # normal URLS
2675 # normal URLS
2666 if path.startswith('bundle:'):
2676 if path.startswith('bundle:'):
2667 self.scheme = 'bundle'
2677 self.scheme = 'bundle'
2668 path = path[7:]
2678 path = path[7:]
2669 if path.startswith('//'):
2679 if path.startswith('//'):
2670 path = path[2:]
2680 path = path[2:]
2671 self.path = path
2681 self.path = path
2672 return
2682 return
2673
2683
2674 if self._matchscheme(path):
2684 if self._matchscheme(path):
2675 parts = path.split(':', 1)
2685 parts = path.split(':', 1)
2676 if parts[0]:
2686 if parts[0]:
2677 self.scheme, path = parts
2687 self.scheme, path = parts
2678 self._localpath = False
2688 self._localpath = False
2679
2689
2680 if not path:
2690 if not path:
2681 path = None
2691 path = None
2682 if self._localpath:
2692 if self._localpath:
2683 self.path = ''
2693 self.path = ''
2684 return
2694 return
2685 else:
2695 else:
2686 if self._localpath:
2696 if self._localpath:
2687 self.path = path
2697 self.path = path
2688 return
2698 return
2689
2699
2690 if parsequery and '?' in path:
2700 if parsequery and '?' in path:
2691 path, self.query = path.split('?', 1)
2701 path, self.query = path.split('?', 1)
2692 if not path:
2702 if not path:
2693 path = None
2703 path = None
2694 if not self.query:
2704 if not self.query:
2695 self.query = None
2705 self.query = None
2696
2706
2697 # // is required to specify a host/authority
2707 # // is required to specify a host/authority
2698 if path and path.startswith('//'):
2708 if path and path.startswith('//'):
2699 parts = path[2:].split('/', 1)
2709 parts = path[2:].split('/', 1)
2700 if len(parts) > 1:
2710 if len(parts) > 1:
2701 self.host, path = parts
2711 self.host, path = parts
2702 else:
2712 else:
2703 self.host = parts[0]
2713 self.host = parts[0]
2704 path = None
2714 path = None
2705 if not self.host:
2715 if not self.host:
2706 self.host = None
2716 self.host = None
2707 # path of file:///d is /d
2717 # path of file:///d is /d
2708 # path of file:///d:/ is d:/, not /d:/
2718 # path of file:///d:/ is d:/, not /d:/
2709 if path and not hasdriveletter(path):
2719 if path and not hasdriveletter(path):
2710 path = '/' + path
2720 path = '/' + path
2711
2721
2712 if self.host and '@' in self.host:
2722 if self.host and '@' in self.host:
2713 self.user, self.host = self.host.rsplit('@', 1)
2723 self.user, self.host = self.host.rsplit('@', 1)
2714 if ':' in self.user:
2724 if ':' in self.user:
2715 self.user, self.passwd = self.user.split(':', 1)
2725 self.user, self.passwd = self.user.split(':', 1)
2716 if not self.host:
2726 if not self.host:
2717 self.host = None
2727 self.host = None
2718
2728
2719 # Don't split on colons in IPv6 addresses without ports
2729 # Don't split on colons in IPv6 addresses without ports
2720 if (self.host and ':' in self.host and
2730 if (self.host and ':' in self.host and
2721 not (self.host.startswith('[') and self.host.endswith(']'))):
2731 not (self.host.startswith('[') and self.host.endswith(']'))):
2722 self._hostport = self.host
2732 self._hostport = self.host
2723 self.host, self.port = self.host.rsplit(':', 1)
2733 self.host, self.port = self.host.rsplit(':', 1)
2724 if not self.host:
2734 if not self.host:
2725 self.host = None
2735 self.host = None
2726
2736
2727 if (self.host and self.scheme == 'file' and
2737 if (self.host and self.scheme == 'file' and
2728 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2738 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2729 raise Abort(_('file:// URLs can only refer to localhost'))
2739 raise Abort(_('file:// URLs can only refer to localhost'))
2730
2740
2731 self.path = path
2741 self.path = path
2732
2742
2733 # leave the query string escaped
2743 # leave the query string escaped
2734 for a in ('user', 'passwd', 'host', 'port',
2744 for a in ('user', 'passwd', 'host', 'port',
2735 'path', 'fragment'):
2745 'path', 'fragment'):
2736 v = getattr(self, a)
2746 v = getattr(self, a)
2737 if v is not None:
2747 if v is not None:
2738 setattr(self, a, urlreq.unquote(v))
2748 setattr(self, a, urlreq.unquote(v))
2739
2749
2740 def __repr__(self):
2750 def __repr__(self):
2741 attrs = []
2751 attrs = []
2742 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2752 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2743 'query', 'fragment'):
2753 'query', 'fragment'):
2744 v = getattr(self, a)
2754 v = getattr(self, a)
2745 if v is not None:
2755 if v is not None:
2746 attrs.append('%s: %r' % (a, v))
2756 attrs.append('%s: %r' % (a, v))
2747 return '<url %s>' % ', '.join(attrs)
2757 return '<url %s>' % ', '.join(attrs)
2748
2758
2749 def __str__(self):
2759 def __str__(self):
2750 r"""Join the URL's components back into a URL string.
2760 r"""Join the URL's components back into a URL string.
2751
2761
2752 Examples:
2762 Examples:
2753
2763
2754 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2764 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2755 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2765 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2756 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2766 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2757 'http://user:pw@host:80/?foo=bar&baz=42'
2767 'http://user:pw@host:80/?foo=bar&baz=42'
2758 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2768 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2759 'http://user:pw@host:80/?foo=bar%3dbaz'
2769 'http://user:pw@host:80/?foo=bar%3dbaz'
2760 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2770 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2761 'ssh://user:pw@[::1]:2200//home/joe#'
2771 'ssh://user:pw@[::1]:2200//home/joe#'
2762 >>> str(url('http://localhost:80//'))
2772 >>> str(url('http://localhost:80//'))
2763 'http://localhost:80//'
2773 'http://localhost:80//'
2764 >>> str(url('http://localhost:80/'))
2774 >>> str(url('http://localhost:80/'))
2765 'http://localhost:80/'
2775 'http://localhost:80/'
2766 >>> str(url('http://localhost:80'))
2776 >>> str(url('http://localhost:80'))
2767 'http://localhost:80/'
2777 'http://localhost:80/'
2768 >>> str(url('bundle:foo'))
2778 >>> str(url('bundle:foo'))
2769 'bundle:foo'
2779 'bundle:foo'
2770 >>> str(url('bundle://../foo'))
2780 >>> str(url('bundle://../foo'))
2771 'bundle:../foo'
2781 'bundle:../foo'
2772 >>> str(url('path'))
2782 >>> str(url('path'))
2773 'path'
2783 'path'
2774 >>> str(url('file:///tmp/foo/bar'))
2784 >>> str(url('file:///tmp/foo/bar'))
2775 'file:///tmp/foo/bar'
2785 'file:///tmp/foo/bar'
2776 >>> str(url('file:///c:/tmp/foo/bar'))
2786 >>> str(url('file:///c:/tmp/foo/bar'))
2777 'file:///c:/tmp/foo/bar'
2787 'file:///c:/tmp/foo/bar'
2778 >>> print url(r'bundle:foo\bar')
2788 >>> print url(r'bundle:foo\bar')
2779 bundle:foo\bar
2789 bundle:foo\bar
2780 >>> print url(r'file:///D:\data\hg')
2790 >>> print url(r'file:///D:\data\hg')
2781 file:///D:\data\hg
2791 file:///D:\data\hg
2782 """
2792 """
2783 return encoding.strfromlocal(self.__bytes__())
2793 return encoding.strfromlocal(self.__bytes__())
2784
2794
2785 def __bytes__(self):
2795 def __bytes__(self):
2786 if self._localpath:
2796 if self._localpath:
2787 s = self.path
2797 s = self.path
2788 if self.scheme == 'bundle':
2798 if self.scheme == 'bundle':
2789 s = 'bundle:' + s
2799 s = 'bundle:' + s
2790 if self.fragment:
2800 if self.fragment:
2791 s += '#' + self.fragment
2801 s += '#' + self.fragment
2792 return s
2802 return s
2793
2803
2794 s = self.scheme + ':'
2804 s = self.scheme + ':'
2795 if self.user or self.passwd or self.host:
2805 if self.user or self.passwd or self.host:
2796 s += '//'
2806 s += '//'
2797 elif self.scheme and (not self.path or self.path.startswith('/')
2807 elif self.scheme and (not self.path or self.path.startswith('/')
2798 or hasdriveletter(self.path)):
2808 or hasdriveletter(self.path)):
2799 s += '//'
2809 s += '//'
2800 if hasdriveletter(self.path):
2810 if hasdriveletter(self.path):
2801 s += '/'
2811 s += '/'
2802 if self.user:
2812 if self.user:
2803 s += urlreq.quote(self.user, safe=self._safechars)
2813 s += urlreq.quote(self.user, safe=self._safechars)
2804 if self.passwd:
2814 if self.passwd:
2805 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2815 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2806 if self.user or self.passwd:
2816 if self.user or self.passwd:
2807 s += '@'
2817 s += '@'
2808 if self.host:
2818 if self.host:
2809 if not (self.host.startswith('[') and self.host.endswith(']')):
2819 if not (self.host.startswith('[') and self.host.endswith(']')):
2810 s += urlreq.quote(self.host)
2820 s += urlreq.quote(self.host)
2811 else:
2821 else:
2812 s += self.host
2822 s += self.host
2813 if self.port:
2823 if self.port:
2814 s += ':' + urlreq.quote(self.port)
2824 s += ':' + urlreq.quote(self.port)
2815 if self.host:
2825 if self.host:
2816 s += '/'
2826 s += '/'
2817 if self.path:
2827 if self.path:
2818 # TODO: similar to the query string, we should not unescape the
2828 # TODO: similar to the query string, we should not unescape the
2819 # path when we store it, the path might contain '%2f' = '/',
2829 # path when we store it, the path might contain '%2f' = '/',
2820 # which we should *not* escape.
2830 # which we should *not* escape.
2821 s += urlreq.quote(self.path, safe=self._safepchars)
2831 s += urlreq.quote(self.path, safe=self._safepchars)
2822 if self.query:
2832 if self.query:
2823 # we store the query in escaped form.
2833 # we store the query in escaped form.
2824 s += '?' + self.query
2834 s += '?' + self.query
2825 if self.fragment is not None:
2835 if self.fragment is not None:
2826 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2836 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2827 return s
2837 return s
2828
2838
2829 def authinfo(self):
2839 def authinfo(self):
2830 user, passwd = self.user, self.passwd
2840 user, passwd = self.user, self.passwd
2831 try:
2841 try:
2832 self.user, self.passwd = None, None
2842 self.user, self.passwd = None, None
2833 s = bytes(self)
2843 s = bytes(self)
2834 finally:
2844 finally:
2835 self.user, self.passwd = user, passwd
2845 self.user, self.passwd = user, passwd
2836 if not self.user:
2846 if not self.user:
2837 return (s, None)
2847 return (s, None)
2838 # authinfo[1] is passed to urllib2 password manager, and its
2848 # authinfo[1] is passed to urllib2 password manager, and its
2839 # URIs must not contain credentials. The host is passed in the
2849 # URIs must not contain credentials. The host is passed in the
2840 # URIs list because Python < 2.4.3 uses only that to search for
2850 # URIs list because Python < 2.4.3 uses only that to search for
2841 # a password.
2851 # a password.
2842 return (s, (None, (s, self.host),
2852 return (s, (None, (s, self.host),
2843 self.user, self.passwd or ''))
2853 self.user, self.passwd or ''))
2844
2854
2845 def isabs(self):
2855 def isabs(self):
2846 if self.scheme and self.scheme != 'file':
2856 if self.scheme and self.scheme != 'file':
2847 return True # remote URL
2857 return True # remote URL
2848 if hasdriveletter(self.path):
2858 if hasdriveletter(self.path):
2849 return True # absolute for our purposes - can't be joined()
2859 return True # absolute for our purposes - can't be joined()
2850 if self.path.startswith(r'\\'):
2860 if self.path.startswith(r'\\'):
2851 return True # Windows UNC path
2861 return True # Windows UNC path
2852 if self.path.startswith('/'):
2862 if self.path.startswith('/'):
2853 return True # POSIX-style
2863 return True # POSIX-style
2854 return False
2864 return False
2855
2865
2856 def localpath(self):
2866 def localpath(self):
2857 if self.scheme == 'file' or self.scheme == 'bundle':
2867 if self.scheme == 'file' or self.scheme == 'bundle':
2858 path = self.path or '/'
2868 path = self.path or '/'
2859 # For Windows, we need to promote hosts containing drive
2869 # For Windows, we need to promote hosts containing drive
2860 # letters to paths with drive letters.
2870 # letters to paths with drive letters.
2861 if hasdriveletter(self._hostport):
2871 if hasdriveletter(self._hostport):
2862 path = self._hostport + '/' + self.path
2872 path = self._hostport + '/' + self.path
2863 elif (self.host is not None and self.path
2873 elif (self.host is not None and self.path
2864 and not hasdriveletter(path)):
2874 and not hasdriveletter(path)):
2865 path = '/' + path
2875 path = '/' + path
2866 return path
2876 return path
2867 return self._origpath
2877 return self._origpath
2868
2878
2869 def islocal(self):
2879 def islocal(self):
2870 '''whether localpath will return something that posixfile can open'''
2880 '''whether localpath will return something that posixfile can open'''
2871 return (not self.scheme or self.scheme == 'file'
2881 return (not self.scheme or self.scheme == 'file'
2872 or self.scheme == 'bundle')
2882 or self.scheme == 'bundle')
2873
2883
2874 def hasscheme(path):
2884 def hasscheme(path):
2875 return bool(url(path).scheme)
2885 return bool(url(path).scheme)
2876
2886
2877 def hasdriveletter(path):
2887 def hasdriveletter(path):
2878 return path and path[1:2] == ':' and path[0:1].isalpha()
2888 return path and path[1:2] == ':' and path[0:1].isalpha()
2879
2889
2880 def urllocalpath(path):
2890 def urllocalpath(path):
2881 return url(path, parsequery=False, parsefragment=False).localpath()
2891 return url(path, parsequery=False, parsefragment=False).localpath()
2882
2892
2883 def hidepassword(u):
2893 def hidepassword(u):
2884 '''hide user credential in a url string'''
2894 '''hide user credential in a url string'''
2885 u = url(u)
2895 u = url(u)
2886 if u.passwd:
2896 if u.passwd:
2887 u.passwd = '***'
2897 u.passwd = '***'
2888 return bytes(u)
2898 return bytes(u)
2889
2899
2890 def removeauth(u):
2900 def removeauth(u):
2891 '''remove all authentication information from a url string'''
2901 '''remove all authentication information from a url string'''
2892 u = url(u)
2902 u = url(u)
2893 u.user = u.passwd = None
2903 u.user = u.passwd = None
2894 return str(u)
2904 return str(u)
2895
2905
2896 timecount = unitcountfn(
2906 timecount = unitcountfn(
2897 (1, 1e3, _('%.0f s')),
2907 (1, 1e3, _('%.0f s')),
2898 (100, 1, _('%.1f s')),
2908 (100, 1, _('%.1f s')),
2899 (10, 1, _('%.2f s')),
2909 (10, 1, _('%.2f s')),
2900 (1, 1, _('%.3f s')),
2910 (1, 1, _('%.3f s')),
2901 (100, 0.001, _('%.1f ms')),
2911 (100, 0.001, _('%.1f ms')),
2902 (10, 0.001, _('%.2f ms')),
2912 (10, 0.001, _('%.2f ms')),
2903 (1, 0.001, _('%.3f ms')),
2913 (1, 0.001, _('%.3f ms')),
2904 (100, 0.000001, _('%.1f us')),
2914 (100, 0.000001, _('%.1f us')),
2905 (10, 0.000001, _('%.2f us')),
2915 (10, 0.000001, _('%.2f us')),
2906 (1, 0.000001, _('%.3f us')),
2916 (1, 0.000001, _('%.3f us')),
2907 (100, 0.000000001, _('%.1f ns')),
2917 (100, 0.000000001, _('%.1f ns')),
2908 (10, 0.000000001, _('%.2f ns')),
2918 (10, 0.000000001, _('%.2f ns')),
2909 (1, 0.000000001, _('%.3f ns')),
2919 (1, 0.000000001, _('%.3f ns')),
2910 )
2920 )
2911
2921
2912 _timenesting = [0]
2922 _timenesting = [0]
2913
2923
2914 def timed(func):
2924 def timed(func):
2915 '''Report the execution time of a function call to stderr.
2925 '''Report the execution time of a function call to stderr.
2916
2926
2917 During development, use as a decorator when you need to measure
2927 During development, use as a decorator when you need to measure
2918 the cost of a function, e.g. as follows:
2928 the cost of a function, e.g. as follows:
2919
2929
2920 @util.timed
2930 @util.timed
2921 def foo(a, b, c):
2931 def foo(a, b, c):
2922 pass
2932 pass
2923 '''
2933 '''
2924
2934
2925 def wrapper(*args, **kwargs):
2935 def wrapper(*args, **kwargs):
2926 start = timer()
2936 start = timer()
2927 indent = 2
2937 indent = 2
2928 _timenesting[0] += indent
2938 _timenesting[0] += indent
2929 try:
2939 try:
2930 return func(*args, **kwargs)
2940 return func(*args, **kwargs)
2931 finally:
2941 finally:
2932 elapsed = timer() - start
2942 elapsed = timer() - start
2933 _timenesting[0] -= indent
2943 _timenesting[0] -= indent
2934 stderr.write('%s%s: %s\n' %
2944 stderr.write('%s%s: %s\n' %
2935 (' ' * _timenesting[0], func.__name__,
2945 (' ' * _timenesting[0], func.__name__,
2936 timecount(elapsed)))
2946 timecount(elapsed)))
2937 return wrapper
2947 return wrapper
2938
2948
2939 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2949 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2940 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2950 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2941
2951
2942 def sizetoint(s):
2952 def sizetoint(s):
2943 '''Convert a space specifier to a byte count.
2953 '''Convert a space specifier to a byte count.
2944
2954
2945 >>> sizetoint('30')
2955 >>> sizetoint('30')
2946 30
2956 30
2947 >>> sizetoint('2.2kb')
2957 >>> sizetoint('2.2kb')
2948 2252
2958 2252
2949 >>> sizetoint('6M')
2959 >>> sizetoint('6M')
2950 6291456
2960 6291456
2951 '''
2961 '''
2952 t = s.strip().lower()
2962 t = s.strip().lower()
2953 try:
2963 try:
2954 for k, u in _sizeunits:
2964 for k, u in _sizeunits:
2955 if t.endswith(k):
2965 if t.endswith(k):
2956 return int(float(t[:-len(k)]) * u)
2966 return int(float(t[:-len(k)]) * u)
2957 return int(t)
2967 return int(t)
2958 except ValueError:
2968 except ValueError:
2959 raise error.ParseError(_("couldn't parse size: %s") % s)
2969 raise error.ParseError(_("couldn't parse size: %s") % s)
2960
2970
2961 class hooks(object):
2971 class hooks(object):
2962 '''A collection of hook functions that can be used to extend a
2972 '''A collection of hook functions that can be used to extend a
2963 function's behavior. Hooks are called in lexicographic order,
2973 function's behavior. Hooks are called in lexicographic order,
2964 based on the names of their sources.'''
2974 based on the names of their sources.'''
2965
2975
2966 def __init__(self):
2976 def __init__(self):
2967 self._hooks = []
2977 self._hooks = []
2968
2978
2969 def add(self, source, hook):
2979 def add(self, source, hook):
2970 self._hooks.append((source, hook))
2980 self._hooks.append((source, hook))
2971
2981
2972 def __call__(self, *args):
2982 def __call__(self, *args):
2973 self._hooks.sort(key=lambda x: x[0])
2983 self._hooks.sort(key=lambda x: x[0])
2974 results = []
2984 results = []
2975 for source, hook in self._hooks:
2985 for source, hook in self._hooks:
2976 results.append(hook(*args))
2986 results.append(hook(*args))
2977 return results
2987 return results
2978
2988
2979 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
2989 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
2980 '''Yields lines for a nicely formatted stacktrace.
2990 '''Yields lines for a nicely formatted stacktrace.
2981 Skips the 'skip' last entries, then return the last 'depth' entries.
2991 Skips the 'skip' last entries, then return the last 'depth' entries.
2982 Each file+linenumber is formatted according to fileline.
2992 Each file+linenumber is formatted according to fileline.
2983 Each line is formatted according to line.
2993 Each line is formatted according to line.
2984 If line is None, it yields:
2994 If line is None, it yields:
2985 length of longest filepath+line number,
2995 length of longest filepath+line number,
2986 filepath+linenumber,
2996 filepath+linenumber,
2987 function
2997 function
2988
2998
2989 Not be used in production code but very convenient while developing.
2999 Not be used in production code but very convenient while developing.
2990 '''
3000 '''
2991 entries = [(fileline % (fn, ln), func)
3001 entries = [(fileline % (fn, ln), func)
2992 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3002 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
2993 ][-depth:]
3003 ][-depth:]
2994 if entries:
3004 if entries:
2995 fnmax = max(len(entry[0]) for entry in entries)
3005 fnmax = max(len(entry[0]) for entry in entries)
2996 for fnln, func in entries:
3006 for fnln, func in entries:
2997 if line is None:
3007 if line is None:
2998 yield (fnmax, fnln, func)
3008 yield (fnmax, fnln, func)
2999 else:
3009 else:
3000 yield line % (fnmax, fnln, func)
3010 yield line % (fnmax, fnln, func)
3001
3011
3002 def debugstacktrace(msg='stacktrace', skip=0,
3012 def debugstacktrace(msg='stacktrace', skip=0,
3003 f=stderr, otherf=stdout, depth=0):
3013 f=stderr, otherf=stdout, depth=0):
3004 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3014 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3005 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3015 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3006 By default it will flush stdout first.
3016 By default it will flush stdout first.
3007 It can be used everywhere and intentionally does not require an ui object.
3017 It can be used everywhere and intentionally does not require an ui object.
3008 Not be used in production code but very convenient while developing.
3018 Not be used in production code but very convenient while developing.
3009 '''
3019 '''
3010 if otherf:
3020 if otherf:
3011 otherf.flush()
3021 otherf.flush()
3012 f.write('%s at:\n' % msg.rstrip())
3022 f.write('%s at:\n' % msg.rstrip())
3013 for line in getstackframes(skip + 1, depth=depth):
3023 for line in getstackframes(skip + 1, depth=depth):
3014 f.write(line)
3024 f.write(line)
3015 f.flush()
3025 f.flush()
3016
3026
3017 class dirs(object):
3027 class dirs(object):
3018 '''a multiset of directory names from a dirstate or manifest'''
3028 '''a multiset of directory names from a dirstate or manifest'''
3019
3029
3020 def __init__(self, map, skip=None):
3030 def __init__(self, map, skip=None):
3021 self._dirs = {}
3031 self._dirs = {}
3022 addpath = self.addpath
3032 addpath = self.addpath
3023 if safehasattr(map, 'iteritems') and skip is not None:
3033 if safehasattr(map, 'iteritems') and skip is not None:
3024 for f, s in map.iteritems():
3034 for f, s in map.iteritems():
3025 if s[0] != skip:
3035 if s[0] != skip:
3026 addpath(f)
3036 addpath(f)
3027 else:
3037 else:
3028 for f in map:
3038 for f in map:
3029 addpath(f)
3039 addpath(f)
3030
3040
3031 def addpath(self, path):
3041 def addpath(self, path):
3032 dirs = self._dirs
3042 dirs = self._dirs
3033 for base in finddirs(path):
3043 for base in finddirs(path):
3034 if base in dirs:
3044 if base in dirs:
3035 dirs[base] += 1
3045 dirs[base] += 1
3036 return
3046 return
3037 dirs[base] = 1
3047 dirs[base] = 1
3038
3048
3039 def delpath(self, path):
3049 def delpath(self, path):
3040 dirs = self._dirs
3050 dirs = self._dirs
3041 for base in finddirs(path):
3051 for base in finddirs(path):
3042 if dirs[base] > 1:
3052 if dirs[base] > 1:
3043 dirs[base] -= 1
3053 dirs[base] -= 1
3044 return
3054 return
3045 del dirs[base]
3055 del dirs[base]
3046
3056
3047 def __iter__(self):
3057 def __iter__(self):
3048 return iter(self._dirs)
3058 return iter(self._dirs)
3049
3059
3050 def __contains__(self, d):
3060 def __contains__(self, d):
3051 return d in self._dirs
3061 return d in self._dirs
3052
3062
3053 if safehasattr(parsers, 'dirs'):
3063 if safehasattr(parsers, 'dirs'):
3054 dirs = parsers.dirs
3064 dirs = parsers.dirs
3055
3065
3056 def finddirs(path):
3066 def finddirs(path):
3057 pos = path.rfind('/')
3067 pos = path.rfind('/')
3058 while pos != -1:
3068 while pos != -1:
3059 yield path[:pos]
3069 yield path[:pos]
3060 pos = path.rfind('/', 0, pos)
3070 pos = path.rfind('/', 0, pos)
3061
3071
3062 class ctxmanager(object):
3072 class ctxmanager(object):
3063 '''A context manager for use in 'with' blocks to allow multiple
3073 '''A context manager for use in 'with' blocks to allow multiple
3064 contexts to be entered at once. This is both safer and more
3074 contexts to be entered at once. This is both safer and more
3065 flexible than contextlib.nested.
3075 flexible than contextlib.nested.
3066
3076
3067 Once Mercurial supports Python 2.7+, this will become mostly
3077 Once Mercurial supports Python 2.7+, this will become mostly
3068 unnecessary.
3078 unnecessary.
3069 '''
3079 '''
3070
3080
3071 def __init__(self, *args):
3081 def __init__(self, *args):
3072 '''Accepts a list of no-argument functions that return context
3082 '''Accepts a list of no-argument functions that return context
3073 managers. These will be invoked at __call__ time.'''
3083 managers. These will be invoked at __call__ time.'''
3074 self._pending = args
3084 self._pending = args
3075 self._atexit = []
3085 self._atexit = []
3076
3086
3077 def __enter__(self):
3087 def __enter__(self):
3078 return self
3088 return self
3079
3089
3080 def enter(self):
3090 def enter(self):
3081 '''Create and enter context managers in the order in which they were
3091 '''Create and enter context managers in the order in which they were
3082 passed to the constructor.'''
3092 passed to the constructor.'''
3083 values = []
3093 values = []
3084 for func in self._pending:
3094 for func in self._pending:
3085 obj = func()
3095 obj = func()
3086 values.append(obj.__enter__())
3096 values.append(obj.__enter__())
3087 self._atexit.append(obj.__exit__)
3097 self._atexit.append(obj.__exit__)
3088 del self._pending
3098 del self._pending
3089 return values
3099 return values
3090
3100
3091 def atexit(self, func, *args, **kwargs):
3101 def atexit(self, func, *args, **kwargs):
3092 '''Add a function to call when this context manager exits. The
3102 '''Add a function to call when this context manager exits. The
3093 ordering of multiple atexit calls is unspecified, save that
3103 ordering of multiple atexit calls is unspecified, save that
3094 they will happen before any __exit__ functions.'''
3104 they will happen before any __exit__ functions.'''
3095 def wrapper(exc_type, exc_val, exc_tb):
3105 def wrapper(exc_type, exc_val, exc_tb):
3096 func(*args, **kwargs)
3106 func(*args, **kwargs)
3097 self._atexit.append(wrapper)
3107 self._atexit.append(wrapper)
3098 return func
3108 return func
3099
3109
3100 def __exit__(self, exc_type, exc_val, exc_tb):
3110 def __exit__(self, exc_type, exc_val, exc_tb):
3101 '''Context managers are exited in the reverse order from which
3111 '''Context managers are exited in the reverse order from which
3102 they were created.'''
3112 they were created.'''
3103 received = exc_type is not None
3113 received = exc_type is not None
3104 suppressed = False
3114 suppressed = False
3105 pending = None
3115 pending = None
3106 self._atexit.reverse()
3116 self._atexit.reverse()
3107 for exitfunc in self._atexit:
3117 for exitfunc in self._atexit:
3108 try:
3118 try:
3109 if exitfunc(exc_type, exc_val, exc_tb):
3119 if exitfunc(exc_type, exc_val, exc_tb):
3110 suppressed = True
3120 suppressed = True
3111 exc_type = None
3121 exc_type = None
3112 exc_val = None
3122 exc_val = None
3113 exc_tb = None
3123 exc_tb = None
3114 except BaseException:
3124 except BaseException:
3115 pending = sys.exc_info()
3125 pending = sys.exc_info()
3116 exc_type, exc_val, exc_tb = pending = sys.exc_info()
3126 exc_type, exc_val, exc_tb = pending = sys.exc_info()
3117 del self._atexit
3127 del self._atexit
3118 if pending:
3128 if pending:
3119 raise exc_val
3129 raise exc_val
3120 return received and suppressed
3130 return received and suppressed
3121
3131
3122 # compression code
3132 # compression code
3123
3133
3124 SERVERROLE = 'server'
3134 SERVERROLE = 'server'
3125 CLIENTROLE = 'client'
3135 CLIENTROLE = 'client'
3126
3136
3127 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3137 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3128 (u'name', u'serverpriority',
3138 (u'name', u'serverpriority',
3129 u'clientpriority'))
3139 u'clientpriority'))
3130
3140
3131 class compressormanager(object):
3141 class compressormanager(object):
3132 """Holds registrations of various compression engines.
3142 """Holds registrations of various compression engines.
3133
3143
3134 This class essentially abstracts the differences between compression
3144 This class essentially abstracts the differences between compression
3135 engines to allow new compression formats to be added easily, possibly from
3145 engines to allow new compression formats to be added easily, possibly from
3136 extensions.
3146 extensions.
3137
3147
3138 Compressors are registered against the global instance by calling its
3148 Compressors are registered against the global instance by calling its
3139 ``register()`` method.
3149 ``register()`` method.
3140 """
3150 """
3141 def __init__(self):
3151 def __init__(self):
3142 self._engines = {}
3152 self._engines = {}
3143 # Bundle spec human name to engine name.
3153 # Bundle spec human name to engine name.
3144 self._bundlenames = {}
3154 self._bundlenames = {}
3145 # Internal bundle identifier to engine name.
3155 # Internal bundle identifier to engine name.
3146 self._bundletypes = {}
3156 self._bundletypes = {}
3147 # Revlog header to engine name.
3157 # Revlog header to engine name.
3148 self._revlogheaders = {}
3158 self._revlogheaders = {}
3149 # Wire proto identifier to engine name.
3159 # Wire proto identifier to engine name.
3150 self._wiretypes = {}
3160 self._wiretypes = {}
3151
3161
3152 def __getitem__(self, key):
3162 def __getitem__(self, key):
3153 return self._engines[key]
3163 return self._engines[key]
3154
3164
3155 def __contains__(self, key):
3165 def __contains__(self, key):
3156 return key in self._engines
3166 return key in self._engines
3157
3167
3158 def __iter__(self):
3168 def __iter__(self):
3159 return iter(self._engines.keys())
3169 return iter(self._engines.keys())
3160
3170
3161 def register(self, engine):
3171 def register(self, engine):
3162 """Register a compression engine with the manager.
3172 """Register a compression engine with the manager.
3163
3173
3164 The argument must be a ``compressionengine`` instance.
3174 The argument must be a ``compressionengine`` instance.
3165 """
3175 """
3166 if not isinstance(engine, compressionengine):
3176 if not isinstance(engine, compressionengine):
3167 raise ValueError(_('argument must be a compressionengine'))
3177 raise ValueError(_('argument must be a compressionengine'))
3168
3178
3169 name = engine.name()
3179 name = engine.name()
3170
3180
3171 if name in self._engines:
3181 if name in self._engines:
3172 raise error.Abort(_('compression engine %s already registered') %
3182 raise error.Abort(_('compression engine %s already registered') %
3173 name)
3183 name)
3174
3184
3175 bundleinfo = engine.bundletype()
3185 bundleinfo = engine.bundletype()
3176 if bundleinfo:
3186 if bundleinfo:
3177 bundlename, bundletype = bundleinfo
3187 bundlename, bundletype = bundleinfo
3178
3188
3179 if bundlename in self._bundlenames:
3189 if bundlename in self._bundlenames:
3180 raise error.Abort(_('bundle name %s already registered') %
3190 raise error.Abort(_('bundle name %s already registered') %
3181 bundlename)
3191 bundlename)
3182 if bundletype in self._bundletypes:
3192 if bundletype in self._bundletypes:
3183 raise error.Abort(_('bundle type %s already registered by %s') %
3193 raise error.Abort(_('bundle type %s already registered by %s') %
3184 (bundletype, self._bundletypes[bundletype]))
3194 (bundletype, self._bundletypes[bundletype]))
3185
3195
3186 # No external facing name declared.
3196 # No external facing name declared.
3187 if bundlename:
3197 if bundlename:
3188 self._bundlenames[bundlename] = name
3198 self._bundlenames[bundlename] = name
3189
3199
3190 self._bundletypes[bundletype] = name
3200 self._bundletypes[bundletype] = name
3191
3201
3192 wiresupport = engine.wireprotosupport()
3202 wiresupport = engine.wireprotosupport()
3193 if wiresupport:
3203 if wiresupport:
3194 wiretype = wiresupport.name
3204 wiretype = wiresupport.name
3195 if wiretype in self._wiretypes:
3205 if wiretype in self._wiretypes:
3196 raise error.Abort(_('wire protocol compression %s already '
3206 raise error.Abort(_('wire protocol compression %s already '
3197 'registered by %s') %
3207 'registered by %s') %
3198 (wiretype, self._wiretypes[wiretype]))
3208 (wiretype, self._wiretypes[wiretype]))
3199
3209
3200 self._wiretypes[wiretype] = name
3210 self._wiretypes[wiretype] = name
3201
3211
3202 revlogheader = engine.revlogheader()
3212 revlogheader = engine.revlogheader()
3203 if revlogheader and revlogheader in self._revlogheaders:
3213 if revlogheader and revlogheader in self._revlogheaders:
3204 raise error.Abort(_('revlog header %s already registered by %s') %
3214 raise error.Abort(_('revlog header %s already registered by %s') %
3205 (revlogheader, self._revlogheaders[revlogheader]))
3215 (revlogheader, self._revlogheaders[revlogheader]))
3206
3216
3207 if revlogheader:
3217 if revlogheader:
3208 self._revlogheaders[revlogheader] = name
3218 self._revlogheaders[revlogheader] = name
3209
3219
3210 self._engines[name] = engine
3220 self._engines[name] = engine
3211
3221
3212 @property
3222 @property
3213 def supportedbundlenames(self):
3223 def supportedbundlenames(self):
3214 return set(self._bundlenames.keys())
3224 return set(self._bundlenames.keys())
3215
3225
3216 @property
3226 @property
3217 def supportedbundletypes(self):
3227 def supportedbundletypes(self):
3218 return set(self._bundletypes.keys())
3228 return set(self._bundletypes.keys())
3219
3229
3220 def forbundlename(self, bundlename):
3230 def forbundlename(self, bundlename):
3221 """Obtain a compression engine registered to a bundle name.
3231 """Obtain a compression engine registered to a bundle name.
3222
3232
3223 Will raise KeyError if the bundle type isn't registered.
3233 Will raise KeyError if the bundle type isn't registered.
3224
3234
3225 Will abort if the engine is known but not available.
3235 Will abort if the engine is known but not available.
3226 """
3236 """
3227 engine = self._engines[self._bundlenames[bundlename]]
3237 engine = self._engines[self._bundlenames[bundlename]]
3228 if not engine.available():
3238 if not engine.available():
3229 raise error.Abort(_('compression engine %s could not be loaded') %
3239 raise error.Abort(_('compression engine %s could not be loaded') %
3230 engine.name())
3240 engine.name())
3231 return engine
3241 return engine
3232
3242
3233 def forbundletype(self, bundletype):
3243 def forbundletype(self, bundletype):
3234 """Obtain a compression engine registered to a bundle type.
3244 """Obtain a compression engine registered to a bundle type.
3235
3245
3236 Will raise KeyError if the bundle type isn't registered.
3246 Will raise KeyError if the bundle type isn't registered.
3237
3247
3238 Will abort if the engine is known but not available.
3248 Will abort if the engine is known but not available.
3239 """
3249 """
3240 engine = self._engines[self._bundletypes[bundletype]]
3250 engine = self._engines[self._bundletypes[bundletype]]
3241 if not engine.available():
3251 if not engine.available():
3242 raise error.Abort(_('compression engine %s could not be loaded') %
3252 raise error.Abort(_('compression engine %s could not be loaded') %
3243 engine.name())
3253 engine.name())
3244 return engine
3254 return engine
3245
3255
3246 def supportedwireengines(self, role, onlyavailable=True):
3256 def supportedwireengines(self, role, onlyavailable=True):
3247 """Obtain compression engines that support the wire protocol.
3257 """Obtain compression engines that support the wire protocol.
3248
3258
3249 Returns a list of engines in prioritized order, most desired first.
3259 Returns a list of engines in prioritized order, most desired first.
3250
3260
3251 If ``onlyavailable`` is set, filter out engines that can't be
3261 If ``onlyavailable`` is set, filter out engines that can't be
3252 loaded.
3262 loaded.
3253 """
3263 """
3254 assert role in (SERVERROLE, CLIENTROLE)
3264 assert role in (SERVERROLE, CLIENTROLE)
3255
3265
3256 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3266 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3257
3267
3258 engines = [self._engines[e] for e in self._wiretypes.values()]
3268 engines = [self._engines[e] for e in self._wiretypes.values()]
3259 if onlyavailable:
3269 if onlyavailable:
3260 engines = [e for e in engines if e.available()]
3270 engines = [e for e in engines if e.available()]
3261
3271
3262 def getkey(e):
3272 def getkey(e):
3263 # Sort first by priority, highest first. In case of tie, sort
3273 # Sort first by priority, highest first. In case of tie, sort
3264 # alphabetically. This is arbitrary, but ensures output is
3274 # alphabetically. This is arbitrary, but ensures output is
3265 # stable.
3275 # stable.
3266 w = e.wireprotosupport()
3276 w = e.wireprotosupport()
3267 return -1 * getattr(w, attr), w.name
3277 return -1 * getattr(w, attr), w.name
3268
3278
3269 return list(sorted(engines, key=getkey))
3279 return list(sorted(engines, key=getkey))
3270
3280
3271 def forwiretype(self, wiretype):
3281 def forwiretype(self, wiretype):
3272 engine = self._engines[self._wiretypes[wiretype]]
3282 engine = self._engines[self._wiretypes[wiretype]]
3273 if not engine.available():
3283 if not engine.available():
3274 raise error.Abort(_('compression engine %s could not be loaded') %
3284 raise error.Abort(_('compression engine %s could not be loaded') %
3275 engine.name())
3285 engine.name())
3276 return engine
3286 return engine
3277
3287
3278 def forrevlogheader(self, header):
3288 def forrevlogheader(self, header):
3279 """Obtain a compression engine registered to a revlog header.
3289 """Obtain a compression engine registered to a revlog header.
3280
3290
3281 Will raise KeyError if the revlog header value isn't registered.
3291 Will raise KeyError if the revlog header value isn't registered.
3282 """
3292 """
3283 return self._engines[self._revlogheaders[header]]
3293 return self._engines[self._revlogheaders[header]]
3284
3294
3285 compengines = compressormanager()
3295 compengines = compressormanager()
3286
3296
3287 class compressionengine(object):
3297 class compressionengine(object):
3288 """Base class for compression engines.
3298 """Base class for compression engines.
3289
3299
3290 Compression engines must implement the interface defined by this class.
3300 Compression engines must implement the interface defined by this class.
3291 """
3301 """
3292 def name(self):
3302 def name(self):
3293 """Returns the name of the compression engine.
3303 """Returns the name of the compression engine.
3294
3304
3295 This is the key the engine is registered under.
3305 This is the key the engine is registered under.
3296
3306
3297 This method must be implemented.
3307 This method must be implemented.
3298 """
3308 """
3299 raise NotImplementedError()
3309 raise NotImplementedError()
3300
3310
3301 def available(self):
3311 def available(self):
3302 """Whether the compression engine is available.
3312 """Whether the compression engine is available.
3303
3313
3304 The intent of this method is to allow optional compression engines
3314 The intent of this method is to allow optional compression engines
3305 that may not be available in all installations (such as engines relying
3315 that may not be available in all installations (such as engines relying
3306 on C extensions that may not be present).
3316 on C extensions that may not be present).
3307 """
3317 """
3308 return True
3318 return True
3309
3319
3310 def bundletype(self):
3320 def bundletype(self):
3311 """Describes bundle identifiers for this engine.
3321 """Describes bundle identifiers for this engine.
3312
3322
3313 If this compression engine isn't supported for bundles, returns None.
3323 If this compression engine isn't supported for bundles, returns None.
3314
3324
3315 If this engine can be used for bundles, returns a 2-tuple of strings of
3325 If this engine can be used for bundles, returns a 2-tuple of strings of
3316 the user-facing "bundle spec" compression name and an internal
3326 the user-facing "bundle spec" compression name and an internal
3317 identifier used to denote the compression format within bundles. To
3327 identifier used to denote the compression format within bundles. To
3318 exclude the name from external usage, set the first element to ``None``.
3328 exclude the name from external usage, set the first element to ``None``.
3319
3329
3320 If bundle compression is supported, the class must also implement
3330 If bundle compression is supported, the class must also implement
3321 ``compressstream`` and `decompressorreader``.
3331 ``compressstream`` and `decompressorreader``.
3322
3332
3323 The docstring of this method is used in the help system to tell users
3333 The docstring of this method is used in the help system to tell users
3324 about this engine.
3334 about this engine.
3325 """
3335 """
3326 return None
3336 return None
3327
3337
3328 def wireprotosupport(self):
3338 def wireprotosupport(self):
3329 """Declare support for this compression format on the wire protocol.
3339 """Declare support for this compression format on the wire protocol.
3330
3340
3331 If this compression engine isn't supported for compressing wire
3341 If this compression engine isn't supported for compressing wire
3332 protocol payloads, returns None.
3342 protocol payloads, returns None.
3333
3343
3334 Otherwise, returns ``compenginewireprotosupport`` with the following
3344 Otherwise, returns ``compenginewireprotosupport`` with the following
3335 fields:
3345 fields:
3336
3346
3337 * String format identifier
3347 * String format identifier
3338 * Integer priority for the server
3348 * Integer priority for the server
3339 * Integer priority for the client
3349 * Integer priority for the client
3340
3350
3341 The integer priorities are used to order the advertisement of format
3351 The integer priorities are used to order the advertisement of format
3342 support by server and client. The highest integer is advertised
3352 support by server and client. The highest integer is advertised
3343 first. Integers with non-positive values aren't advertised.
3353 first. Integers with non-positive values aren't advertised.
3344
3354
3345 The priority values are somewhat arbitrary and only used for default
3355 The priority values are somewhat arbitrary and only used for default
3346 ordering. The relative order can be changed via config options.
3356 ordering. The relative order can be changed via config options.
3347
3357
3348 If wire protocol compression is supported, the class must also implement
3358 If wire protocol compression is supported, the class must also implement
3349 ``compressstream`` and ``decompressorreader``.
3359 ``compressstream`` and ``decompressorreader``.
3350 """
3360 """
3351 return None
3361 return None
3352
3362
3353 def revlogheader(self):
3363 def revlogheader(self):
3354 """Header added to revlog chunks that identifies this engine.
3364 """Header added to revlog chunks that identifies this engine.
3355
3365
3356 If this engine can be used to compress revlogs, this method should
3366 If this engine can be used to compress revlogs, this method should
3357 return the bytes used to identify chunks compressed with this engine.
3367 return the bytes used to identify chunks compressed with this engine.
3358 Else, the method should return ``None`` to indicate it does not
3368 Else, the method should return ``None`` to indicate it does not
3359 participate in revlog compression.
3369 participate in revlog compression.
3360 """
3370 """
3361 return None
3371 return None
3362
3372
3363 def compressstream(self, it, opts=None):
3373 def compressstream(self, it, opts=None):
3364 """Compress an iterator of chunks.
3374 """Compress an iterator of chunks.
3365
3375
3366 The method receives an iterator (ideally a generator) of chunks of
3376 The method receives an iterator (ideally a generator) of chunks of
3367 bytes to be compressed. It returns an iterator (ideally a generator)
3377 bytes to be compressed. It returns an iterator (ideally a generator)
3368 of bytes of chunks representing the compressed output.
3378 of bytes of chunks representing the compressed output.
3369
3379
3370 Optionally accepts an argument defining how to perform compression.
3380 Optionally accepts an argument defining how to perform compression.
3371 Each engine treats this argument differently.
3381 Each engine treats this argument differently.
3372 """
3382 """
3373 raise NotImplementedError()
3383 raise NotImplementedError()
3374
3384
3375 def decompressorreader(self, fh):
3385 def decompressorreader(self, fh):
3376 """Perform decompression on a file object.
3386 """Perform decompression on a file object.
3377
3387
3378 Argument is an object with a ``read(size)`` method that returns
3388 Argument is an object with a ``read(size)`` method that returns
3379 compressed data. Return value is an object with a ``read(size)`` that
3389 compressed data. Return value is an object with a ``read(size)`` that
3380 returns uncompressed data.
3390 returns uncompressed data.
3381 """
3391 """
3382 raise NotImplementedError()
3392 raise NotImplementedError()
3383
3393
3384 def revlogcompressor(self, opts=None):
3394 def revlogcompressor(self, opts=None):
3385 """Obtain an object that can be used to compress revlog entries.
3395 """Obtain an object that can be used to compress revlog entries.
3386
3396
3387 The object has a ``compress(data)`` method that compresses binary
3397 The object has a ``compress(data)`` method that compresses binary
3388 data. This method returns compressed binary data or ``None`` if
3398 data. This method returns compressed binary data or ``None`` if
3389 the data could not be compressed (too small, not compressible, etc).
3399 the data could not be compressed (too small, not compressible, etc).
3390 The returned data should have a header uniquely identifying this
3400 The returned data should have a header uniquely identifying this
3391 compression format so decompression can be routed to this engine.
3401 compression format so decompression can be routed to this engine.
3392 This header should be identified by the ``revlogheader()`` return
3402 This header should be identified by the ``revlogheader()`` return
3393 value.
3403 value.
3394
3404
3395 The object has a ``decompress(data)`` method that decompresses
3405 The object has a ``decompress(data)`` method that decompresses
3396 data. The method will only be called if ``data`` begins with
3406 data. The method will only be called if ``data`` begins with
3397 ``revlogheader()``. The method should return the raw, uncompressed
3407 ``revlogheader()``. The method should return the raw, uncompressed
3398 data or raise a ``RevlogError``.
3408 data or raise a ``RevlogError``.
3399
3409
3400 The object is reusable but is not thread safe.
3410 The object is reusable but is not thread safe.
3401 """
3411 """
3402 raise NotImplementedError()
3412 raise NotImplementedError()
3403
3413
3404 class _zlibengine(compressionengine):
3414 class _zlibengine(compressionengine):
3405 def name(self):
3415 def name(self):
3406 return 'zlib'
3416 return 'zlib'
3407
3417
3408 def bundletype(self):
3418 def bundletype(self):
3409 """zlib compression using the DEFLATE algorithm.
3419 """zlib compression using the DEFLATE algorithm.
3410
3420
3411 All Mercurial clients should support this format. The compression
3421 All Mercurial clients should support this format. The compression
3412 algorithm strikes a reasonable balance between compression ratio
3422 algorithm strikes a reasonable balance between compression ratio
3413 and size.
3423 and size.
3414 """
3424 """
3415 return 'gzip', 'GZ'
3425 return 'gzip', 'GZ'
3416
3426
3417 def wireprotosupport(self):
3427 def wireprotosupport(self):
3418 return compewireprotosupport('zlib', 20, 20)
3428 return compewireprotosupport('zlib', 20, 20)
3419
3429
3420 def revlogheader(self):
3430 def revlogheader(self):
3421 return 'x'
3431 return 'x'
3422
3432
3423 def compressstream(self, it, opts=None):
3433 def compressstream(self, it, opts=None):
3424 opts = opts or {}
3434 opts = opts or {}
3425
3435
3426 z = zlib.compressobj(opts.get('level', -1))
3436 z = zlib.compressobj(opts.get('level', -1))
3427 for chunk in it:
3437 for chunk in it:
3428 data = z.compress(chunk)
3438 data = z.compress(chunk)
3429 # Not all calls to compress emit data. It is cheaper to inspect
3439 # Not all calls to compress emit data. It is cheaper to inspect
3430 # here than to feed empty chunks through generator.
3440 # here than to feed empty chunks through generator.
3431 if data:
3441 if data:
3432 yield data
3442 yield data
3433
3443
3434 yield z.flush()
3444 yield z.flush()
3435
3445
3436 def decompressorreader(self, fh):
3446 def decompressorreader(self, fh):
3437 def gen():
3447 def gen():
3438 d = zlib.decompressobj()
3448 d = zlib.decompressobj()
3439 for chunk in filechunkiter(fh):
3449 for chunk in filechunkiter(fh):
3440 while chunk:
3450 while chunk:
3441 # Limit output size to limit memory.
3451 # Limit output size to limit memory.
3442 yield d.decompress(chunk, 2 ** 18)
3452 yield d.decompress(chunk, 2 ** 18)
3443 chunk = d.unconsumed_tail
3453 chunk = d.unconsumed_tail
3444
3454
3445 return chunkbuffer(gen())
3455 return chunkbuffer(gen())
3446
3456
3447 class zlibrevlogcompressor(object):
3457 class zlibrevlogcompressor(object):
3448 def compress(self, data):
3458 def compress(self, data):
3449 insize = len(data)
3459 insize = len(data)
3450 # Caller handles empty input case.
3460 # Caller handles empty input case.
3451 assert insize > 0
3461 assert insize > 0
3452
3462
3453 if insize < 44:
3463 if insize < 44:
3454 return None
3464 return None
3455
3465
3456 elif insize <= 1000000:
3466 elif insize <= 1000000:
3457 compressed = zlib.compress(data)
3467 compressed = zlib.compress(data)
3458 if len(compressed) < insize:
3468 if len(compressed) < insize:
3459 return compressed
3469 return compressed
3460 return None
3470 return None
3461
3471
3462 # zlib makes an internal copy of the input buffer, doubling
3472 # zlib makes an internal copy of the input buffer, doubling
3463 # memory usage for large inputs. So do streaming compression
3473 # memory usage for large inputs. So do streaming compression
3464 # on large inputs.
3474 # on large inputs.
3465 else:
3475 else:
3466 z = zlib.compressobj()
3476 z = zlib.compressobj()
3467 parts = []
3477 parts = []
3468 pos = 0
3478 pos = 0
3469 while pos < insize:
3479 while pos < insize:
3470 pos2 = pos + 2**20
3480 pos2 = pos + 2**20
3471 parts.append(z.compress(data[pos:pos2]))
3481 parts.append(z.compress(data[pos:pos2]))
3472 pos = pos2
3482 pos = pos2
3473 parts.append(z.flush())
3483 parts.append(z.flush())
3474
3484
3475 if sum(map(len, parts)) < insize:
3485 if sum(map(len, parts)) < insize:
3476 return ''.join(parts)
3486 return ''.join(parts)
3477 return None
3487 return None
3478
3488
3479 def decompress(self, data):
3489 def decompress(self, data):
3480 try:
3490 try:
3481 return zlib.decompress(data)
3491 return zlib.decompress(data)
3482 except zlib.error as e:
3492 except zlib.error as e:
3483 raise error.RevlogError(_('revlog decompress error: %s') %
3493 raise error.RevlogError(_('revlog decompress error: %s') %
3484 str(e))
3494 str(e))
3485
3495
3486 def revlogcompressor(self, opts=None):
3496 def revlogcompressor(self, opts=None):
3487 return self.zlibrevlogcompressor()
3497 return self.zlibrevlogcompressor()
3488
3498
3489 compengines.register(_zlibengine())
3499 compengines.register(_zlibengine())
3490
3500
3491 class _bz2engine(compressionengine):
3501 class _bz2engine(compressionengine):
3492 def name(self):
3502 def name(self):
3493 return 'bz2'
3503 return 'bz2'
3494
3504
3495 def bundletype(self):
3505 def bundletype(self):
3496 """An algorithm that produces smaller bundles than ``gzip``.
3506 """An algorithm that produces smaller bundles than ``gzip``.
3497
3507
3498 All Mercurial clients should support this format.
3508 All Mercurial clients should support this format.
3499
3509
3500 This engine will likely produce smaller bundles than ``gzip`` but
3510 This engine will likely produce smaller bundles than ``gzip`` but
3501 will be significantly slower, both during compression and
3511 will be significantly slower, both during compression and
3502 decompression.
3512 decompression.
3503
3513
3504 If available, the ``zstd`` engine can yield similar or better
3514 If available, the ``zstd`` engine can yield similar or better
3505 compression at much higher speeds.
3515 compression at much higher speeds.
3506 """
3516 """
3507 return 'bzip2', 'BZ'
3517 return 'bzip2', 'BZ'
3508
3518
3509 # We declare a protocol name but don't advertise by default because
3519 # We declare a protocol name but don't advertise by default because
3510 # it is slow.
3520 # it is slow.
3511 def wireprotosupport(self):
3521 def wireprotosupport(self):
3512 return compewireprotosupport('bzip2', 0, 0)
3522 return compewireprotosupport('bzip2', 0, 0)
3513
3523
3514 def compressstream(self, it, opts=None):
3524 def compressstream(self, it, opts=None):
3515 opts = opts or {}
3525 opts = opts or {}
3516 z = bz2.BZ2Compressor(opts.get('level', 9))
3526 z = bz2.BZ2Compressor(opts.get('level', 9))
3517 for chunk in it:
3527 for chunk in it:
3518 data = z.compress(chunk)
3528 data = z.compress(chunk)
3519 if data:
3529 if data:
3520 yield data
3530 yield data
3521
3531
3522 yield z.flush()
3532 yield z.flush()
3523
3533
3524 def decompressorreader(self, fh):
3534 def decompressorreader(self, fh):
3525 def gen():
3535 def gen():
3526 d = bz2.BZ2Decompressor()
3536 d = bz2.BZ2Decompressor()
3527 for chunk in filechunkiter(fh):
3537 for chunk in filechunkiter(fh):
3528 yield d.decompress(chunk)
3538 yield d.decompress(chunk)
3529
3539
3530 return chunkbuffer(gen())
3540 return chunkbuffer(gen())
3531
3541
3532 compengines.register(_bz2engine())
3542 compengines.register(_bz2engine())
3533
3543
3534 class _truncatedbz2engine(compressionengine):
3544 class _truncatedbz2engine(compressionengine):
3535 def name(self):
3545 def name(self):
3536 return 'bz2truncated'
3546 return 'bz2truncated'
3537
3547
3538 def bundletype(self):
3548 def bundletype(self):
3539 return None, '_truncatedBZ'
3549 return None, '_truncatedBZ'
3540
3550
3541 # We don't implement compressstream because it is hackily handled elsewhere.
3551 # We don't implement compressstream because it is hackily handled elsewhere.
3542
3552
3543 def decompressorreader(self, fh):
3553 def decompressorreader(self, fh):
3544 def gen():
3554 def gen():
3545 # The input stream doesn't have the 'BZ' header. So add it back.
3555 # The input stream doesn't have the 'BZ' header. So add it back.
3546 d = bz2.BZ2Decompressor()
3556 d = bz2.BZ2Decompressor()
3547 d.decompress('BZ')
3557 d.decompress('BZ')
3548 for chunk in filechunkiter(fh):
3558 for chunk in filechunkiter(fh):
3549 yield d.decompress(chunk)
3559 yield d.decompress(chunk)
3550
3560
3551 return chunkbuffer(gen())
3561 return chunkbuffer(gen())
3552
3562
3553 compengines.register(_truncatedbz2engine())
3563 compengines.register(_truncatedbz2engine())
3554
3564
3555 class _noopengine(compressionengine):
3565 class _noopengine(compressionengine):
3556 def name(self):
3566 def name(self):
3557 return 'none'
3567 return 'none'
3558
3568
3559 def bundletype(self):
3569 def bundletype(self):
3560 """No compression is performed.
3570 """No compression is performed.
3561
3571
3562 Use this compression engine to explicitly disable compression.
3572 Use this compression engine to explicitly disable compression.
3563 """
3573 """
3564 return 'none', 'UN'
3574 return 'none', 'UN'
3565
3575
3566 # Clients always support uncompressed payloads. Servers don't because
3576 # Clients always support uncompressed payloads. Servers don't because
3567 # unless you are on a fast network, uncompressed payloads can easily
3577 # unless you are on a fast network, uncompressed payloads can easily
3568 # saturate your network pipe.
3578 # saturate your network pipe.
3569 def wireprotosupport(self):
3579 def wireprotosupport(self):
3570 return compewireprotosupport('none', 0, 10)
3580 return compewireprotosupport('none', 0, 10)
3571
3581
3572 # We don't implement revlogheader because it is handled specially
3582 # We don't implement revlogheader because it is handled specially
3573 # in the revlog class.
3583 # in the revlog class.
3574
3584
3575 def compressstream(self, it, opts=None):
3585 def compressstream(self, it, opts=None):
3576 return it
3586 return it
3577
3587
3578 def decompressorreader(self, fh):
3588 def decompressorreader(self, fh):
3579 return fh
3589 return fh
3580
3590
3581 class nooprevlogcompressor(object):
3591 class nooprevlogcompressor(object):
3582 def compress(self, data):
3592 def compress(self, data):
3583 return None
3593 return None
3584
3594
3585 def revlogcompressor(self, opts=None):
3595 def revlogcompressor(self, opts=None):
3586 return self.nooprevlogcompressor()
3596 return self.nooprevlogcompressor()
3587
3597
3588 compengines.register(_noopengine())
3598 compengines.register(_noopengine())
3589
3599
3590 class _zstdengine(compressionengine):
3600 class _zstdengine(compressionengine):
3591 def name(self):
3601 def name(self):
3592 return 'zstd'
3602 return 'zstd'
3593
3603
3594 @propertycache
3604 @propertycache
3595 def _module(self):
3605 def _module(self):
3596 # Not all installs have the zstd module available. So defer importing
3606 # Not all installs have the zstd module available. So defer importing
3597 # until first access.
3607 # until first access.
3598 try:
3608 try:
3599 from . import zstd
3609 from . import zstd
3600 # Force delayed import.
3610 # Force delayed import.
3601 zstd.__version__
3611 zstd.__version__
3602 return zstd
3612 return zstd
3603 except ImportError:
3613 except ImportError:
3604 return None
3614 return None
3605
3615
3606 def available(self):
3616 def available(self):
3607 return bool(self._module)
3617 return bool(self._module)
3608
3618
3609 def bundletype(self):
3619 def bundletype(self):
3610 """A modern compression algorithm that is fast and highly flexible.
3620 """A modern compression algorithm that is fast and highly flexible.
3611
3621
3612 Only supported by Mercurial 4.1 and newer clients.
3622 Only supported by Mercurial 4.1 and newer clients.
3613
3623
3614 With the default settings, zstd compression is both faster and yields
3624 With the default settings, zstd compression is both faster and yields
3615 better compression than ``gzip``. It also frequently yields better
3625 better compression than ``gzip``. It also frequently yields better
3616 compression than ``bzip2`` while operating at much higher speeds.
3626 compression than ``bzip2`` while operating at much higher speeds.
3617
3627
3618 If this engine is available and backwards compatibility is not a
3628 If this engine is available and backwards compatibility is not a
3619 concern, it is likely the best available engine.
3629 concern, it is likely the best available engine.
3620 """
3630 """
3621 return 'zstd', 'ZS'
3631 return 'zstd', 'ZS'
3622
3632
3623 def wireprotosupport(self):
3633 def wireprotosupport(self):
3624 return compewireprotosupport('zstd', 50, 50)
3634 return compewireprotosupport('zstd', 50, 50)
3625
3635
3626 def revlogheader(self):
3636 def revlogheader(self):
3627 return '\x28'
3637 return '\x28'
3628
3638
3629 def compressstream(self, it, opts=None):
3639 def compressstream(self, it, opts=None):
3630 opts = opts or {}
3640 opts = opts or {}
3631 # zstd level 3 is almost always significantly faster than zlib
3641 # zstd level 3 is almost always significantly faster than zlib
3632 # while providing no worse compression. It strikes a good balance
3642 # while providing no worse compression. It strikes a good balance
3633 # between speed and compression.
3643 # between speed and compression.
3634 level = opts.get('level', 3)
3644 level = opts.get('level', 3)
3635
3645
3636 zstd = self._module
3646 zstd = self._module
3637 z = zstd.ZstdCompressor(level=level).compressobj()
3647 z = zstd.ZstdCompressor(level=level).compressobj()
3638 for chunk in it:
3648 for chunk in it:
3639 data = z.compress(chunk)
3649 data = z.compress(chunk)
3640 if data:
3650 if data:
3641 yield data
3651 yield data
3642
3652
3643 yield z.flush()
3653 yield z.flush()
3644
3654
3645 def decompressorreader(self, fh):
3655 def decompressorreader(self, fh):
3646 zstd = self._module
3656 zstd = self._module
3647 dctx = zstd.ZstdDecompressor()
3657 dctx = zstd.ZstdDecompressor()
3648 return chunkbuffer(dctx.read_from(fh))
3658 return chunkbuffer(dctx.read_from(fh))
3649
3659
3650 class zstdrevlogcompressor(object):
3660 class zstdrevlogcompressor(object):
3651 def __init__(self, zstd, level=3):
3661 def __init__(self, zstd, level=3):
3652 # Writing the content size adds a few bytes to the output. However,
3662 # Writing the content size adds a few bytes to the output. However,
3653 # it allows decompression to be more optimal since we can
3663 # it allows decompression to be more optimal since we can
3654 # pre-allocate a buffer to hold the result.
3664 # pre-allocate a buffer to hold the result.
3655 self._cctx = zstd.ZstdCompressor(level=level,
3665 self._cctx = zstd.ZstdCompressor(level=level,
3656 write_content_size=True)
3666 write_content_size=True)
3657 self._dctx = zstd.ZstdDecompressor()
3667 self._dctx = zstd.ZstdDecompressor()
3658 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3668 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3659 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3669 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3660
3670
3661 def compress(self, data):
3671 def compress(self, data):
3662 insize = len(data)
3672 insize = len(data)
3663 # Caller handles empty input case.
3673 # Caller handles empty input case.
3664 assert insize > 0
3674 assert insize > 0
3665
3675
3666 if insize < 50:
3676 if insize < 50:
3667 return None
3677 return None
3668
3678
3669 elif insize <= 1000000:
3679 elif insize <= 1000000:
3670 compressed = self._cctx.compress(data)
3680 compressed = self._cctx.compress(data)
3671 if len(compressed) < insize:
3681 if len(compressed) < insize:
3672 return compressed
3682 return compressed
3673 return None
3683 return None
3674 else:
3684 else:
3675 z = self._cctx.compressobj()
3685 z = self._cctx.compressobj()
3676 chunks = []
3686 chunks = []
3677 pos = 0
3687 pos = 0
3678 while pos < insize:
3688 while pos < insize:
3679 pos2 = pos + self._compinsize
3689 pos2 = pos + self._compinsize
3680 chunk = z.compress(data[pos:pos2])
3690 chunk = z.compress(data[pos:pos2])
3681 if chunk:
3691 if chunk:
3682 chunks.append(chunk)
3692 chunks.append(chunk)
3683 pos = pos2
3693 pos = pos2
3684 chunks.append(z.flush())
3694 chunks.append(z.flush())
3685
3695
3686 if sum(map(len, chunks)) < insize:
3696 if sum(map(len, chunks)) < insize:
3687 return ''.join(chunks)
3697 return ''.join(chunks)
3688 return None
3698 return None
3689
3699
3690 def decompress(self, data):
3700 def decompress(self, data):
3691 insize = len(data)
3701 insize = len(data)
3692
3702
3693 try:
3703 try:
3694 # This was measured to be faster than other streaming
3704 # This was measured to be faster than other streaming
3695 # decompressors.
3705 # decompressors.
3696 dobj = self._dctx.decompressobj()
3706 dobj = self._dctx.decompressobj()
3697 chunks = []
3707 chunks = []
3698 pos = 0
3708 pos = 0
3699 while pos < insize:
3709 while pos < insize:
3700 pos2 = pos + self._decompinsize
3710 pos2 = pos + self._decompinsize
3701 chunk = dobj.decompress(data[pos:pos2])
3711 chunk = dobj.decompress(data[pos:pos2])
3702 if chunk:
3712 if chunk:
3703 chunks.append(chunk)
3713 chunks.append(chunk)
3704 pos = pos2
3714 pos = pos2
3705 # Frame should be exhausted, so no finish() API.
3715 # Frame should be exhausted, so no finish() API.
3706
3716
3707 return ''.join(chunks)
3717 return ''.join(chunks)
3708 except Exception as e:
3718 except Exception as e:
3709 raise error.RevlogError(_('revlog decompress error: %s') %
3719 raise error.RevlogError(_('revlog decompress error: %s') %
3710 str(e))
3720 str(e))
3711
3721
3712 def revlogcompressor(self, opts=None):
3722 def revlogcompressor(self, opts=None):
3713 opts = opts or {}
3723 opts = opts or {}
3714 return self.zstdrevlogcompressor(self._module,
3724 return self.zstdrevlogcompressor(self._module,
3715 level=opts.get('level', 3))
3725 level=opts.get('level', 3))
3716
3726
3717 compengines.register(_zstdengine())
3727 compengines.register(_zstdengine())
3718
3728
3719 def bundlecompressiontopics():
3729 def bundlecompressiontopics():
3720 """Obtains a list of available bundle compressions for use in help."""
3730 """Obtains a list of available bundle compressions for use in help."""
3721 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3731 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3722 items = {}
3732 items = {}
3723
3733
3724 # We need to format the docstring. So use a dummy object/type to hold it
3734 # We need to format the docstring. So use a dummy object/type to hold it
3725 # rather than mutating the original.
3735 # rather than mutating the original.
3726 class docobject(object):
3736 class docobject(object):
3727 pass
3737 pass
3728
3738
3729 for name in compengines:
3739 for name in compengines:
3730 engine = compengines[name]
3740 engine = compengines[name]
3731
3741
3732 if not engine.available():
3742 if not engine.available():
3733 continue
3743 continue
3734
3744
3735 bt = engine.bundletype()
3745 bt = engine.bundletype()
3736 if not bt or not bt[0]:
3746 if not bt or not bt[0]:
3737 continue
3747 continue
3738
3748
3739 doc = pycompat.sysstr('``%s``\n %s') % (
3749 doc = pycompat.sysstr('``%s``\n %s') % (
3740 bt[0], engine.bundletype.__doc__)
3750 bt[0], engine.bundletype.__doc__)
3741
3751
3742 value = docobject()
3752 value = docobject()
3743 value.__doc__ = doc
3753 value.__doc__ = doc
3744
3754
3745 items[bt[0]] = value
3755 items[bt[0]] = value
3746
3756
3747 return items
3757 return items
3748
3758
3749 # convenient shortcut
3759 # convenient shortcut
3750 dst = debugstacktrace
3760 dst = debugstacktrace
@@ -1,637 +1,636
1 # vfs.py - Mercurial 'vfs' classes
1 # vfs.py - Mercurial 'vfs' classes
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
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 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import errno
10 import errno
11 import os
11 import os
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import tempfile
14 import tempfile
15 import threading
15 import threading
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 error,
19 error,
20 osutil,
21 pathutil,
20 pathutil,
22 pycompat,
21 pycompat,
23 util,
22 util,
24 )
23 )
25
24
26 class abstractvfs(object):
25 class abstractvfs(object):
27 """Abstract base class; cannot be instantiated"""
26 """Abstract base class; cannot be instantiated"""
28
27
29 def __init__(self, *args, **kwargs):
28 def __init__(self, *args, **kwargs):
30 '''Prevent instantiation; don't call this from subclasses.'''
29 '''Prevent instantiation; don't call this from subclasses.'''
31 raise NotImplementedError('attempted instantiating ' + str(type(self)))
30 raise NotImplementedError('attempted instantiating ' + str(type(self)))
32
31
33 def tryread(self, path):
32 def tryread(self, path):
34 '''gracefully return an empty string for missing files'''
33 '''gracefully return an empty string for missing files'''
35 try:
34 try:
36 return self.read(path)
35 return self.read(path)
37 except IOError as inst:
36 except IOError as inst:
38 if inst.errno != errno.ENOENT:
37 if inst.errno != errno.ENOENT:
39 raise
38 raise
40 return ""
39 return ""
41
40
42 def tryreadlines(self, path, mode='rb'):
41 def tryreadlines(self, path, mode='rb'):
43 '''gracefully return an empty array for missing files'''
42 '''gracefully return an empty array for missing files'''
44 try:
43 try:
45 return self.readlines(path, mode=mode)
44 return self.readlines(path, mode=mode)
46 except IOError as inst:
45 except IOError as inst:
47 if inst.errno != errno.ENOENT:
46 if inst.errno != errno.ENOENT:
48 raise
47 raise
49 return []
48 return []
50
49
51 @util.propertycache
50 @util.propertycache
52 def open(self):
51 def open(self):
53 '''Open ``path`` file, which is relative to vfs root.
52 '''Open ``path`` file, which is relative to vfs root.
54
53
55 Newly created directories are marked as "not to be indexed by
54 Newly created directories are marked as "not to be indexed by
56 the content indexing service", if ``notindexed`` is specified
55 the content indexing service", if ``notindexed`` is specified
57 for "write" mode access.
56 for "write" mode access.
58 '''
57 '''
59 return self.__call__
58 return self.__call__
60
59
61 def read(self, path):
60 def read(self, path):
62 with self(path, 'rb') as fp:
61 with self(path, 'rb') as fp:
63 return fp.read()
62 return fp.read()
64
63
65 def readlines(self, path, mode='rb'):
64 def readlines(self, path, mode='rb'):
66 with self(path, mode=mode) as fp:
65 with self(path, mode=mode) as fp:
67 return fp.readlines()
66 return fp.readlines()
68
67
69 def write(self, path, data, backgroundclose=False):
68 def write(self, path, data, backgroundclose=False):
70 with self(path, 'wb', backgroundclose=backgroundclose) as fp:
69 with self(path, 'wb', backgroundclose=backgroundclose) as fp:
71 return fp.write(data)
70 return fp.write(data)
72
71
73 def writelines(self, path, data, mode='wb', notindexed=False):
72 def writelines(self, path, data, mode='wb', notindexed=False):
74 with self(path, mode=mode, notindexed=notindexed) as fp:
73 with self(path, mode=mode, notindexed=notindexed) as fp:
75 return fp.writelines(data)
74 return fp.writelines(data)
76
75
77 def append(self, path, data):
76 def append(self, path, data):
78 with self(path, 'ab') as fp:
77 with self(path, 'ab') as fp:
79 return fp.write(data)
78 return fp.write(data)
80
79
81 def basename(self, path):
80 def basename(self, path):
82 """return base element of a path (as os.path.basename would do)
81 """return base element of a path (as os.path.basename would do)
83
82
84 This exists to allow handling of strange encoding if needed."""
83 This exists to allow handling of strange encoding if needed."""
85 return os.path.basename(path)
84 return os.path.basename(path)
86
85
87 def chmod(self, path, mode):
86 def chmod(self, path, mode):
88 return os.chmod(self.join(path), mode)
87 return os.chmod(self.join(path), mode)
89
88
90 def dirname(self, path):
89 def dirname(self, path):
91 """return dirname element of a path (as os.path.dirname would do)
90 """return dirname element of a path (as os.path.dirname would do)
92
91
93 This exists to allow handling of strange encoding if needed."""
92 This exists to allow handling of strange encoding if needed."""
94 return os.path.dirname(path)
93 return os.path.dirname(path)
95
94
96 def exists(self, path=None):
95 def exists(self, path=None):
97 return os.path.exists(self.join(path))
96 return os.path.exists(self.join(path))
98
97
99 def fstat(self, fp):
98 def fstat(self, fp):
100 return util.fstat(fp)
99 return util.fstat(fp)
101
100
102 def isdir(self, path=None):
101 def isdir(self, path=None):
103 return os.path.isdir(self.join(path))
102 return os.path.isdir(self.join(path))
104
103
105 def isfile(self, path=None):
104 def isfile(self, path=None):
106 return os.path.isfile(self.join(path))
105 return os.path.isfile(self.join(path))
107
106
108 def islink(self, path=None):
107 def islink(self, path=None):
109 return os.path.islink(self.join(path))
108 return os.path.islink(self.join(path))
110
109
111 def isfileorlink(self, path=None):
110 def isfileorlink(self, path=None):
112 '''return whether path is a regular file or a symlink
111 '''return whether path is a regular file or a symlink
113
112
114 Unlike isfile, this doesn't follow symlinks.'''
113 Unlike isfile, this doesn't follow symlinks.'''
115 try:
114 try:
116 st = self.lstat(path)
115 st = self.lstat(path)
117 except OSError:
116 except OSError:
118 return False
117 return False
119 mode = st.st_mode
118 mode = st.st_mode
120 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
119 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
121
120
122 def reljoin(self, *paths):
121 def reljoin(self, *paths):
123 """join various elements of a path together (as os.path.join would do)
122 """join various elements of a path together (as os.path.join would do)
124
123
125 The vfs base is not injected so that path stay relative. This exists
124 The vfs base is not injected so that path stay relative. This exists
126 to allow handling of strange encoding if needed."""
125 to allow handling of strange encoding if needed."""
127 return os.path.join(*paths)
126 return os.path.join(*paths)
128
127
129 def split(self, path):
128 def split(self, path):
130 """split top-most element of a path (as os.path.split would do)
129 """split top-most element of a path (as os.path.split would do)
131
130
132 This exists to allow handling of strange encoding if needed."""
131 This exists to allow handling of strange encoding if needed."""
133 return os.path.split(path)
132 return os.path.split(path)
134
133
135 def lexists(self, path=None):
134 def lexists(self, path=None):
136 return os.path.lexists(self.join(path))
135 return os.path.lexists(self.join(path))
137
136
138 def lstat(self, path=None):
137 def lstat(self, path=None):
139 return os.lstat(self.join(path))
138 return os.lstat(self.join(path))
140
139
141 def listdir(self, path=None):
140 def listdir(self, path=None):
142 return os.listdir(self.join(path))
141 return os.listdir(self.join(path))
143
142
144 def makedir(self, path=None, notindexed=True):
143 def makedir(self, path=None, notindexed=True):
145 return util.makedir(self.join(path), notindexed)
144 return util.makedir(self.join(path), notindexed)
146
145
147 def makedirs(self, path=None, mode=None):
146 def makedirs(self, path=None, mode=None):
148 return util.makedirs(self.join(path), mode)
147 return util.makedirs(self.join(path), mode)
149
148
150 def makelock(self, info, path):
149 def makelock(self, info, path):
151 return util.makelock(info, self.join(path))
150 return util.makelock(info, self.join(path))
152
151
153 def mkdir(self, path=None):
152 def mkdir(self, path=None):
154 return os.mkdir(self.join(path))
153 return os.mkdir(self.join(path))
155
154
156 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
155 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
157 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
156 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
158 dir=self.join(dir), text=text)
157 dir=self.join(dir), text=text)
159 dname, fname = util.split(name)
158 dname, fname = util.split(name)
160 if dir:
159 if dir:
161 return fd, os.path.join(dir, fname)
160 return fd, os.path.join(dir, fname)
162 else:
161 else:
163 return fd, fname
162 return fd, fname
164
163
165 def readdir(self, path=None, stat=None, skip=None):
164 def readdir(self, path=None, stat=None, skip=None):
166 return osutil.listdir(self.join(path), stat, skip)
165 return util.listdir(self.join(path), stat, skip)
167
166
168 def readlock(self, path):
167 def readlock(self, path):
169 return util.readlock(self.join(path))
168 return util.readlock(self.join(path))
170
169
171 def rename(self, src, dst, checkambig=False):
170 def rename(self, src, dst, checkambig=False):
172 """Rename from src to dst
171 """Rename from src to dst
173
172
174 checkambig argument is used with util.filestat, and is useful
173 checkambig argument is used with util.filestat, and is useful
175 only if destination file is guarded by any lock
174 only if destination file is guarded by any lock
176 (e.g. repo.lock or repo.wlock).
175 (e.g. repo.lock or repo.wlock).
177 """
176 """
178 dstpath = self.join(dst)
177 dstpath = self.join(dst)
179 oldstat = checkambig and util.filestat(dstpath)
178 oldstat = checkambig and util.filestat(dstpath)
180 if oldstat and oldstat.stat:
179 if oldstat and oldstat.stat:
181 ret = util.rename(self.join(src), dstpath)
180 ret = util.rename(self.join(src), dstpath)
182 newstat = util.filestat(dstpath)
181 newstat = util.filestat(dstpath)
183 if newstat.isambig(oldstat):
182 if newstat.isambig(oldstat):
184 # stat of renamed file is ambiguous to original one
183 # stat of renamed file is ambiguous to original one
185 newstat.avoidambig(dstpath, oldstat)
184 newstat.avoidambig(dstpath, oldstat)
186 return ret
185 return ret
187 return util.rename(self.join(src), dstpath)
186 return util.rename(self.join(src), dstpath)
188
187
189 def readlink(self, path):
188 def readlink(self, path):
190 return os.readlink(self.join(path))
189 return os.readlink(self.join(path))
191
190
192 def removedirs(self, path=None):
191 def removedirs(self, path=None):
193 """Remove a leaf directory and all empty intermediate ones
192 """Remove a leaf directory and all empty intermediate ones
194 """
193 """
195 return util.removedirs(self.join(path))
194 return util.removedirs(self.join(path))
196
195
197 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
196 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
198 """Remove a directory tree recursively
197 """Remove a directory tree recursively
199
198
200 If ``forcibly``, this tries to remove READ-ONLY files, too.
199 If ``forcibly``, this tries to remove READ-ONLY files, too.
201 """
200 """
202 if forcibly:
201 if forcibly:
203 def onerror(function, path, excinfo):
202 def onerror(function, path, excinfo):
204 if function is not os.remove:
203 if function is not os.remove:
205 raise
204 raise
206 # read-only files cannot be unlinked under Windows
205 # read-only files cannot be unlinked under Windows
207 s = os.stat(path)
206 s = os.stat(path)
208 if (s.st_mode & stat.S_IWRITE) != 0:
207 if (s.st_mode & stat.S_IWRITE) != 0:
209 raise
208 raise
210 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
209 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
211 os.remove(path)
210 os.remove(path)
212 else:
211 else:
213 onerror = None
212 onerror = None
214 return shutil.rmtree(self.join(path),
213 return shutil.rmtree(self.join(path),
215 ignore_errors=ignore_errors, onerror=onerror)
214 ignore_errors=ignore_errors, onerror=onerror)
216
215
217 def setflags(self, path, l, x):
216 def setflags(self, path, l, x):
218 return util.setflags(self.join(path), l, x)
217 return util.setflags(self.join(path), l, x)
219
218
220 def stat(self, path=None):
219 def stat(self, path=None):
221 return os.stat(self.join(path))
220 return os.stat(self.join(path))
222
221
223 def unlink(self, path=None):
222 def unlink(self, path=None):
224 return util.unlink(self.join(path))
223 return util.unlink(self.join(path))
225
224
226 def tryunlink(self, path=None):
225 def tryunlink(self, path=None):
227 """Attempt to remove a file, ignoring missing file errors."""
226 """Attempt to remove a file, ignoring missing file errors."""
228 util.tryunlink(self.join(path))
227 util.tryunlink(self.join(path))
229
228
230 def unlinkpath(self, path=None, ignoremissing=False):
229 def unlinkpath(self, path=None, ignoremissing=False):
231 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
230 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
232
231
233 def utime(self, path=None, t=None):
232 def utime(self, path=None, t=None):
234 return os.utime(self.join(path), t)
233 return os.utime(self.join(path), t)
235
234
236 def walk(self, path=None, onerror=None):
235 def walk(self, path=None, onerror=None):
237 """Yield (dirpath, dirs, files) tuple for each directories under path
236 """Yield (dirpath, dirs, files) tuple for each directories under path
238
237
239 ``dirpath`` is relative one from the root of this vfs. This
238 ``dirpath`` is relative one from the root of this vfs. This
240 uses ``os.sep`` as path separator, even you specify POSIX
239 uses ``os.sep`` as path separator, even you specify POSIX
241 style ``path``.
240 style ``path``.
242
241
243 "The root of this vfs" is represented as empty ``dirpath``.
242 "The root of this vfs" is represented as empty ``dirpath``.
244 """
243 """
245 root = os.path.normpath(self.join(None))
244 root = os.path.normpath(self.join(None))
246 # when dirpath == root, dirpath[prefixlen:] becomes empty
245 # when dirpath == root, dirpath[prefixlen:] becomes empty
247 # because len(dirpath) < prefixlen.
246 # because len(dirpath) < prefixlen.
248 prefixlen = len(pathutil.normasprefix(root))
247 prefixlen = len(pathutil.normasprefix(root))
249 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
248 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
250 yield (dirpath[prefixlen:], dirs, files)
249 yield (dirpath[prefixlen:], dirs, files)
251
250
252 @contextlib.contextmanager
251 @contextlib.contextmanager
253 def backgroundclosing(self, ui, expectedcount=-1):
252 def backgroundclosing(self, ui, expectedcount=-1):
254 """Allow files to be closed asynchronously.
253 """Allow files to be closed asynchronously.
255
254
256 When this context manager is active, ``backgroundclose`` can be passed
255 When this context manager is active, ``backgroundclose`` can be passed
257 to ``__call__``/``open`` to result in the file possibly being closed
256 to ``__call__``/``open`` to result in the file possibly being closed
258 asynchronously, on a background thread.
257 asynchronously, on a background thread.
259 """
258 """
260 # This is an arbitrary restriction and could be changed if we ever
259 # This is an arbitrary restriction and could be changed if we ever
261 # have a use case.
260 # have a use case.
262 vfs = getattr(self, 'vfs', self)
261 vfs = getattr(self, 'vfs', self)
263 if getattr(vfs, '_backgroundfilecloser', None):
262 if getattr(vfs, '_backgroundfilecloser', None):
264 raise error.Abort(
263 raise error.Abort(
265 _('can only have 1 active background file closer'))
264 _('can only have 1 active background file closer'))
266
265
267 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
266 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
268 try:
267 try:
269 vfs._backgroundfilecloser = bfc
268 vfs._backgroundfilecloser = bfc
270 yield bfc
269 yield bfc
271 finally:
270 finally:
272 vfs._backgroundfilecloser = None
271 vfs._backgroundfilecloser = None
273
272
274 class vfs(abstractvfs):
273 class vfs(abstractvfs):
275 '''Operate files relative to a base directory
274 '''Operate files relative to a base directory
276
275
277 This class is used to hide the details of COW semantics and
276 This class is used to hide the details of COW semantics and
278 remote file access from higher level code.
277 remote file access from higher level code.
279 '''
278 '''
280 def __init__(self, base, audit=True, expandpath=False, realpath=False):
279 def __init__(self, base, audit=True, expandpath=False, realpath=False):
281 if expandpath:
280 if expandpath:
282 base = util.expandpath(base)
281 base = util.expandpath(base)
283 if realpath:
282 if realpath:
284 base = os.path.realpath(base)
283 base = os.path.realpath(base)
285 self.base = base
284 self.base = base
286 self.mustaudit = audit
285 self.mustaudit = audit
287 self.createmode = None
286 self.createmode = None
288 self._trustnlink = None
287 self._trustnlink = None
289
288
290 @property
289 @property
291 def mustaudit(self):
290 def mustaudit(self):
292 return self._audit
291 return self._audit
293
292
294 @mustaudit.setter
293 @mustaudit.setter
295 def mustaudit(self, onoff):
294 def mustaudit(self, onoff):
296 self._audit = onoff
295 self._audit = onoff
297 if onoff:
296 if onoff:
298 self.audit = pathutil.pathauditor(self.base)
297 self.audit = pathutil.pathauditor(self.base)
299 else:
298 else:
300 self.audit = util.always
299 self.audit = util.always
301
300
302 @util.propertycache
301 @util.propertycache
303 def _cansymlink(self):
302 def _cansymlink(self):
304 return util.checklink(self.base)
303 return util.checklink(self.base)
305
304
306 @util.propertycache
305 @util.propertycache
307 def _chmod(self):
306 def _chmod(self):
308 return util.checkexec(self.base)
307 return util.checkexec(self.base)
309
308
310 def _fixfilemode(self, name):
309 def _fixfilemode(self, name):
311 if self.createmode is None or not self._chmod:
310 if self.createmode is None or not self._chmod:
312 return
311 return
313 os.chmod(name, self.createmode & 0o666)
312 os.chmod(name, self.createmode & 0o666)
314
313
315 def __call__(self, path, mode="r", text=False, atomictemp=False,
314 def __call__(self, path, mode="r", text=False, atomictemp=False,
316 notindexed=False, backgroundclose=False, checkambig=False):
315 notindexed=False, backgroundclose=False, checkambig=False):
317 '''Open ``path`` file, which is relative to vfs root.
316 '''Open ``path`` file, which is relative to vfs root.
318
317
319 Newly created directories are marked as "not to be indexed by
318 Newly created directories are marked as "not to be indexed by
320 the content indexing service", if ``notindexed`` is specified
319 the content indexing service", if ``notindexed`` is specified
321 for "write" mode access.
320 for "write" mode access.
322
321
323 If ``backgroundclose`` is passed, the file may be closed asynchronously.
322 If ``backgroundclose`` is passed, the file may be closed asynchronously.
324 It can only be used if the ``self.backgroundclosing()`` context manager
323 It can only be used if the ``self.backgroundclosing()`` context manager
325 is active. This should only be specified if the following criteria hold:
324 is active. This should only be specified if the following criteria hold:
326
325
327 1. There is a potential for writing thousands of files. Unless you
326 1. There is a potential for writing thousands of files. Unless you
328 are writing thousands of files, the performance benefits of
327 are writing thousands of files, the performance benefits of
329 asynchronously closing files is not realized.
328 asynchronously closing files is not realized.
330 2. Files are opened exactly once for the ``backgroundclosing``
329 2. Files are opened exactly once for the ``backgroundclosing``
331 active duration and are therefore free of race conditions between
330 active duration and are therefore free of race conditions between
332 closing a file on a background thread and reopening it. (If the
331 closing a file on a background thread and reopening it. (If the
333 file were opened multiple times, there could be unflushed data
332 file were opened multiple times, there could be unflushed data
334 because the original file handle hasn't been flushed/closed yet.)
333 because the original file handle hasn't been flushed/closed yet.)
335
334
336 ``checkambig`` argument is passed to atomictemplfile (valid
335 ``checkambig`` argument is passed to atomictemplfile (valid
337 only for writing), and is useful only if target file is
336 only for writing), and is useful only if target file is
338 guarded by any lock (e.g. repo.lock or repo.wlock).
337 guarded by any lock (e.g. repo.lock or repo.wlock).
339 '''
338 '''
340 if self._audit:
339 if self._audit:
341 r = util.checkosfilename(path)
340 r = util.checkosfilename(path)
342 if r:
341 if r:
343 raise error.Abort("%s: %r" % (r, path))
342 raise error.Abort("%s: %r" % (r, path))
344 self.audit(path)
343 self.audit(path)
345 f = self.join(path)
344 f = self.join(path)
346
345
347 if not text and "b" not in mode:
346 if not text and "b" not in mode:
348 mode += "b" # for that other OS
347 mode += "b" # for that other OS
349
348
350 nlink = -1
349 nlink = -1
351 if mode not in ('r', 'rb'):
350 if mode not in ('r', 'rb'):
352 dirname, basename = util.split(f)
351 dirname, basename = util.split(f)
353 # If basename is empty, then the path is malformed because it points
352 # If basename is empty, then the path is malformed because it points
354 # to a directory. Let the posixfile() call below raise IOError.
353 # to a directory. Let the posixfile() call below raise IOError.
355 if basename:
354 if basename:
356 if atomictemp:
355 if atomictemp:
357 util.makedirs(dirname, self.createmode, notindexed)
356 util.makedirs(dirname, self.createmode, notindexed)
358 return util.atomictempfile(f, mode, self.createmode,
357 return util.atomictempfile(f, mode, self.createmode,
359 checkambig=checkambig)
358 checkambig=checkambig)
360 try:
359 try:
361 if 'w' in mode:
360 if 'w' in mode:
362 util.unlink(f)
361 util.unlink(f)
363 nlink = 0
362 nlink = 0
364 else:
363 else:
365 # nlinks() may behave differently for files on Windows
364 # nlinks() may behave differently for files on Windows
366 # shares if the file is open.
365 # shares if the file is open.
367 with util.posixfile(f):
366 with util.posixfile(f):
368 nlink = util.nlinks(f)
367 nlink = util.nlinks(f)
369 if nlink < 1:
368 if nlink < 1:
370 nlink = 2 # force mktempcopy (issue1922)
369 nlink = 2 # force mktempcopy (issue1922)
371 except (OSError, IOError) as e:
370 except (OSError, IOError) as e:
372 if e.errno != errno.ENOENT:
371 if e.errno != errno.ENOENT:
373 raise
372 raise
374 nlink = 0
373 nlink = 0
375 util.makedirs(dirname, self.createmode, notindexed)
374 util.makedirs(dirname, self.createmode, notindexed)
376 if nlink > 0:
375 if nlink > 0:
377 if self._trustnlink is None:
376 if self._trustnlink is None:
378 self._trustnlink = nlink > 1 or util.checknlink(f)
377 self._trustnlink = nlink > 1 or util.checknlink(f)
379 if nlink > 1 or not self._trustnlink:
378 if nlink > 1 or not self._trustnlink:
380 util.rename(util.mktempcopy(f), f)
379 util.rename(util.mktempcopy(f), f)
381 fp = util.posixfile(f, mode)
380 fp = util.posixfile(f, mode)
382 if nlink == 0:
381 if nlink == 0:
383 self._fixfilemode(f)
382 self._fixfilemode(f)
384
383
385 if checkambig:
384 if checkambig:
386 if mode in ('r', 'rb'):
385 if mode in ('r', 'rb'):
387 raise error.Abort(_('implementation error: mode %s is not'
386 raise error.Abort(_('implementation error: mode %s is not'
388 ' valid for checkambig=True') % mode)
387 ' valid for checkambig=True') % mode)
389 fp = checkambigatclosing(fp)
388 fp = checkambigatclosing(fp)
390
389
391 if backgroundclose:
390 if backgroundclose:
392 if not self._backgroundfilecloser:
391 if not self._backgroundfilecloser:
393 raise error.Abort(_('backgroundclose can only be used when a '
392 raise error.Abort(_('backgroundclose can only be used when a '
394 'backgroundclosing context manager is active')
393 'backgroundclosing context manager is active')
395 )
394 )
396
395
397 fp = delayclosedfile(fp, self._backgroundfilecloser)
396 fp = delayclosedfile(fp, self._backgroundfilecloser)
398
397
399 return fp
398 return fp
400
399
401 def symlink(self, src, dst):
400 def symlink(self, src, dst):
402 self.audit(dst)
401 self.audit(dst)
403 linkname = self.join(dst)
402 linkname = self.join(dst)
404 util.tryunlink(linkname)
403 util.tryunlink(linkname)
405
404
406 util.makedirs(os.path.dirname(linkname), self.createmode)
405 util.makedirs(os.path.dirname(linkname), self.createmode)
407
406
408 if self._cansymlink:
407 if self._cansymlink:
409 try:
408 try:
410 os.symlink(src, linkname)
409 os.symlink(src, linkname)
411 except OSError as err:
410 except OSError as err:
412 raise OSError(err.errno, _('could not symlink to %r: %s') %
411 raise OSError(err.errno, _('could not symlink to %r: %s') %
413 (src, err.strerror), linkname)
412 (src, err.strerror), linkname)
414 else:
413 else:
415 self.write(dst, src)
414 self.write(dst, src)
416
415
417 def join(self, path, *insidef):
416 def join(self, path, *insidef):
418 if path:
417 if path:
419 return os.path.join(self.base, path, *insidef)
418 return os.path.join(self.base, path, *insidef)
420 else:
419 else:
421 return self.base
420 return self.base
422
421
423 opener = vfs
422 opener = vfs
424
423
425 class auditvfs(object):
424 class auditvfs(object):
426 def __init__(self, vfs):
425 def __init__(self, vfs):
427 self.vfs = vfs
426 self.vfs = vfs
428
427
429 @property
428 @property
430 def mustaudit(self):
429 def mustaudit(self):
431 return self.vfs.mustaudit
430 return self.vfs.mustaudit
432
431
433 @mustaudit.setter
432 @mustaudit.setter
434 def mustaudit(self, onoff):
433 def mustaudit(self, onoff):
435 self.vfs.mustaudit = onoff
434 self.vfs.mustaudit = onoff
436
435
437 @property
436 @property
438 def options(self):
437 def options(self):
439 return self.vfs.options
438 return self.vfs.options
440
439
441 @options.setter
440 @options.setter
442 def options(self, value):
441 def options(self, value):
443 self.vfs.options = value
442 self.vfs.options = value
444
443
445 class filtervfs(abstractvfs, auditvfs):
444 class filtervfs(abstractvfs, auditvfs):
446 '''Wrapper vfs for filtering filenames with a function.'''
445 '''Wrapper vfs for filtering filenames with a function.'''
447
446
448 def __init__(self, vfs, filter):
447 def __init__(self, vfs, filter):
449 auditvfs.__init__(self, vfs)
448 auditvfs.__init__(self, vfs)
450 self._filter = filter
449 self._filter = filter
451
450
452 def __call__(self, path, *args, **kwargs):
451 def __call__(self, path, *args, **kwargs):
453 return self.vfs(self._filter(path), *args, **kwargs)
452 return self.vfs(self._filter(path), *args, **kwargs)
454
453
455 def join(self, path, *insidef):
454 def join(self, path, *insidef):
456 if path:
455 if path:
457 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
456 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
458 else:
457 else:
459 return self.vfs.join(path)
458 return self.vfs.join(path)
460
459
461 filteropener = filtervfs
460 filteropener = filtervfs
462
461
463 class readonlyvfs(abstractvfs, auditvfs):
462 class readonlyvfs(abstractvfs, auditvfs):
464 '''Wrapper vfs preventing any writing.'''
463 '''Wrapper vfs preventing any writing.'''
465
464
466 def __init__(self, vfs):
465 def __init__(self, vfs):
467 auditvfs.__init__(self, vfs)
466 auditvfs.__init__(self, vfs)
468
467
469 def __call__(self, path, mode='r', *args, **kw):
468 def __call__(self, path, mode='r', *args, **kw):
470 if mode not in ('r', 'rb'):
469 if mode not in ('r', 'rb'):
471 raise error.Abort(_('this vfs is read only'))
470 raise error.Abort(_('this vfs is read only'))
472 return self.vfs(path, mode, *args, **kw)
471 return self.vfs(path, mode, *args, **kw)
473
472
474 def join(self, path, *insidef):
473 def join(self, path, *insidef):
475 return self.vfs.join(path, *insidef)
474 return self.vfs.join(path, *insidef)
476
475
477 class closewrapbase(object):
476 class closewrapbase(object):
478 """Base class of wrapper, which hooks closing
477 """Base class of wrapper, which hooks closing
479
478
480 Do not instantiate outside of the vfs layer.
479 Do not instantiate outside of the vfs layer.
481 """
480 """
482 def __init__(self, fh):
481 def __init__(self, fh):
483 object.__setattr__(self, r'_origfh', fh)
482 object.__setattr__(self, r'_origfh', fh)
484
483
485 def __getattr__(self, attr):
484 def __getattr__(self, attr):
486 return getattr(self._origfh, attr)
485 return getattr(self._origfh, attr)
487
486
488 def __setattr__(self, attr, value):
487 def __setattr__(self, attr, value):
489 return setattr(self._origfh, attr, value)
488 return setattr(self._origfh, attr, value)
490
489
491 def __delattr__(self, attr):
490 def __delattr__(self, attr):
492 return delattr(self._origfh, attr)
491 return delattr(self._origfh, attr)
493
492
494 def __enter__(self):
493 def __enter__(self):
495 return self._origfh.__enter__()
494 return self._origfh.__enter__()
496
495
497 def __exit__(self, exc_type, exc_value, exc_tb):
496 def __exit__(self, exc_type, exc_value, exc_tb):
498 raise NotImplementedError('attempted instantiating ' + str(type(self)))
497 raise NotImplementedError('attempted instantiating ' + str(type(self)))
499
498
500 def close(self):
499 def close(self):
501 raise NotImplementedError('attempted instantiating ' + str(type(self)))
500 raise NotImplementedError('attempted instantiating ' + str(type(self)))
502
501
503 class delayclosedfile(closewrapbase):
502 class delayclosedfile(closewrapbase):
504 """Proxy for a file object whose close is delayed.
503 """Proxy for a file object whose close is delayed.
505
504
506 Do not instantiate outside of the vfs layer.
505 Do not instantiate outside of the vfs layer.
507 """
506 """
508 def __init__(self, fh, closer):
507 def __init__(self, fh, closer):
509 super(delayclosedfile, self).__init__(fh)
508 super(delayclosedfile, self).__init__(fh)
510 object.__setattr__(self, r'_closer', closer)
509 object.__setattr__(self, r'_closer', closer)
511
510
512 def __exit__(self, exc_type, exc_value, exc_tb):
511 def __exit__(self, exc_type, exc_value, exc_tb):
513 self._closer.close(self._origfh)
512 self._closer.close(self._origfh)
514
513
515 def close(self):
514 def close(self):
516 self._closer.close(self._origfh)
515 self._closer.close(self._origfh)
517
516
518 class backgroundfilecloser(object):
517 class backgroundfilecloser(object):
519 """Coordinates background closing of file handles on multiple threads."""
518 """Coordinates background closing of file handles on multiple threads."""
520 def __init__(self, ui, expectedcount=-1):
519 def __init__(self, ui, expectedcount=-1):
521 self._running = False
520 self._running = False
522 self._entered = False
521 self._entered = False
523 self._threads = []
522 self._threads = []
524 self._threadexception = None
523 self._threadexception = None
525
524
526 # Only Windows/NTFS has slow file closing. So only enable by default
525 # Only Windows/NTFS has slow file closing. So only enable by default
527 # on that platform. But allow to be enabled elsewhere for testing.
526 # on that platform. But allow to be enabled elsewhere for testing.
528 defaultenabled = pycompat.osname == 'nt'
527 defaultenabled = pycompat.osname == 'nt'
529 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
528 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
530
529
531 if not enabled:
530 if not enabled:
532 return
531 return
533
532
534 # There is overhead to starting and stopping the background threads.
533 # There is overhead to starting and stopping the background threads.
535 # Don't do background processing unless the file count is large enough
534 # Don't do background processing unless the file count is large enough
536 # to justify it.
535 # to justify it.
537 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount',
536 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount',
538 2048)
537 2048)
539 # FUTURE dynamically start background threads after minfilecount closes.
538 # FUTURE dynamically start background threads after minfilecount closes.
540 # (We don't currently have any callers that don't know their file count)
539 # (We don't currently have any callers that don't know their file count)
541 if expectedcount > 0 and expectedcount < minfilecount:
540 if expectedcount > 0 and expectedcount < minfilecount:
542 return
541 return
543
542
544 # Windows defaults to a limit of 512 open files. A buffer of 128
543 # Windows defaults to a limit of 512 open files. A buffer of 128
545 # should give us enough headway.
544 # should give us enough headway.
546 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384)
545 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384)
547 threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4)
546 threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4)
548
547
549 ui.debug('starting %d threads for background file closing\n' %
548 ui.debug('starting %d threads for background file closing\n' %
550 threadcount)
549 threadcount)
551
550
552 self._queue = util.queue(maxsize=maxqueue)
551 self._queue = util.queue(maxsize=maxqueue)
553 self._running = True
552 self._running = True
554
553
555 for i in range(threadcount):
554 for i in range(threadcount):
556 t = threading.Thread(target=self._worker, name='backgroundcloser')
555 t = threading.Thread(target=self._worker, name='backgroundcloser')
557 self._threads.append(t)
556 self._threads.append(t)
558 t.start()
557 t.start()
559
558
560 def __enter__(self):
559 def __enter__(self):
561 self._entered = True
560 self._entered = True
562 return self
561 return self
563
562
564 def __exit__(self, exc_type, exc_value, exc_tb):
563 def __exit__(self, exc_type, exc_value, exc_tb):
565 self._running = False
564 self._running = False
566
565
567 # Wait for threads to finish closing so open files don't linger for
566 # Wait for threads to finish closing so open files don't linger for
568 # longer than lifetime of context manager.
567 # longer than lifetime of context manager.
569 for t in self._threads:
568 for t in self._threads:
570 t.join()
569 t.join()
571
570
572 def _worker(self):
571 def _worker(self):
573 """Main routine for worker thread."""
572 """Main routine for worker thread."""
574 while True:
573 while True:
575 try:
574 try:
576 fh = self._queue.get(block=True, timeout=0.100)
575 fh = self._queue.get(block=True, timeout=0.100)
577 # Need to catch or the thread will terminate and
576 # Need to catch or the thread will terminate and
578 # we could orphan file descriptors.
577 # we could orphan file descriptors.
579 try:
578 try:
580 fh.close()
579 fh.close()
581 except Exception as e:
580 except Exception as e:
582 # Stash so can re-raise from main thread later.
581 # Stash so can re-raise from main thread later.
583 self._threadexception = e
582 self._threadexception = e
584 except util.empty:
583 except util.empty:
585 if not self._running:
584 if not self._running:
586 break
585 break
587
586
588 def close(self, fh):
587 def close(self, fh):
589 """Schedule a file for closing."""
588 """Schedule a file for closing."""
590 if not self._entered:
589 if not self._entered:
591 raise error.Abort(_('can only call close() when context manager '
590 raise error.Abort(_('can only call close() when context manager '
592 'active'))
591 'active'))
593
592
594 # If a background thread encountered an exception, raise now so we fail
593 # If a background thread encountered an exception, raise now so we fail
595 # fast. Otherwise we may potentially go on for minutes until the error
594 # fast. Otherwise we may potentially go on for minutes until the error
596 # is acted on.
595 # is acted on.
597 if self._threadexception:
596 if self._threadexception:
598 e = self._threadexception
597 e = self._threadexception
599 self._threadexception = None
598 self._threadexception = None
600 raise e
599 raise e
601
600
602 # If we're not actively running, close synchronously.
601 # If we're not actively running, close synchronously.
603 if not self._running:
602 if not self._running:
604 fh.close()
603 fh.close()
605 return
604 return
606
605
607 self._queue.put(fh, block=True, timeout=None)
606 self._queue.put(fh, block=True, timeout=None)
608
607
609 class checkambigatclosing(closewrapbase):
608 class checkambigatclosing(closewrapbase):
610 """Proxy for a file object, to avoid ambiguity of file stat
609 """Proxy for a file object, to avoid ambiguity of file stat
611
610
612 See also util.filestat for detail about "ambiguity of file stat".
611 See also util.filestat for detail about "ambiguity of file stat".
613
612
614 This proxy is useful only if the target file is guarded by any
613 This proxy is useful only if the target file is guarded by any
615 lock (e.g. repo.lock or repo.wlock)
614 lock (e.g. repo.lock or repo.wlock)
616
615
617 Do not instantiate outside of the vfs layer.
616 Do not instantiate outside of the vfs layer.
618 """
617 """
619 def __init__(self, fh):
618 def __init__(self, fh):
620 super(checkambigatclosing, self).__init__(fh)
619 super(checkambigatclosing, self).__init__(fh)
621 object.__setattr__(self, r'_oldstat', util.filestat(fh.name))
620 object.__setattr__(self, r'_oldstat', util.filestat(fh.name))
622
621
623 def _checkambig(self):
622 def _checkambig(self):
624 oldstat = self._oldstat
623 oldstat = self._oldstat
625 if oldstat.stat:
624 if oldstat.stat:
626 newstat = util.filestat(self._origfh.name)
625 newstat = util.filestat(self._origfh.name)
627 if newstat.isambig(oldstat):
626 if newstat.isambig(oldstat):
628 # stat of changed file is ambiguous to original one
627 # stat of changed file is ambiguous to original one
629 newstat.avoidambig(self._origfh.name, oldstat)
628 newstat.avoidambig(self._origfh.name, oldstat)
630
629
631 def __exit__(self, exc_type, exc_value, exc_tb):
630 def __exit__(self, exc_type, exc_value, exc_tb):
632 self._origfh.__exit__(exc_type, exc_value, exc_tb)
631 self._origfh.__exit__(exc_type, exc_value, exc_tb)
633 self._checkambig()
632 self._checkambig()
634
633
635 def close(self):
634 def close(self):
636 self._origfh.close()
635 self._origfh.close()
637 self._checkambig()
636 self._checkambig()
@@ -1,472 +1,475
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import msvcrt
11 import msvcrt
12 import os
12 import os
13 import re
13 import re
14 import stat
14 import stat
15 import sys
15 import sys
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 osutil,
20 osutil,
21 pycompat,
21 pycompat,
22 win32,
22 win32,
23 )
23 )
24
24
25 try:
25 try:
26 import _winreg as winreg
26 import _winreg as winreg
27 winreg.CloseKey
27 winreg.CloseKey
28 except ImportError:
28 except ImportError:
29 import winreg
29 import winreg
30
30
31 executablepath = win32.executablepath
31 executablepath = win32.executablepath
32 getuser = win32.getuser
32 getuser = win32.getuser
33 hidewindow = win32.hidewindow
33 hidewindow = win32.hidewindow
34 makedir = win32.makedir
34 makedir = win32.makedir
35 nlinks = win32.nlinks
35 nlinks = win32.nlinks
36 oslink = win32.oslink
36 oslink = win32.oslink
37 samedevice = win32.samedevice
37 samedevice = win32.samedevice
38 samefile = win32.samefile
38 samefile = win32.samefile
39 setsignalhandler = win32.setsignalhandler
39 setsignalhandler = win32.setsignalhandler
40 spawndetached = win32.spawndetached
40 spawndetached = win32.spawndetached
41 split = os.path.split
41 split = os.path.split
42 testpid = win32.testpid
42 testpid = win32.testpid
43 unlink = win32.unlink
43 unlink = win32.unlink
44
44
45 umask = 0o022
45 umask = 0o022
46
46
47 class mixedfilemodewrapper(object):
47 class mixedfilemodewrapper(object):
48 """Wraps a file handle when it is opened in read/write mode.
48 """Wraps a file handle when it is opened in read/write mode.
49
49
50 fopen() and fdopen() on Windows have a specific-to-Windows requirement
50 fopen() and fdopen() on Windows have a specific-to-Windows requirement
51 that files opened with mode r+, w+, or a+ make a call to a file positioning
51 that files opened with mode r+, w+, or a+ make a call to a file positioning
52 function when switching between reads and writes. Without this extra call,
52 function when switching between reads and writes. Without this extra call,
53 Python will raise a not very intuitive "IOError: [Errno 0] Error."
53 Python will raise a not very intuitive "IOError: [Errno 0] Error."
54
54
55 This class wraps posixfile instances when the file is opened in read/write
55 This class wraps posixfile instances when the file is opened in read/write
56 mode and automatically adds checks or inserts appropriate file positioning
56 mode and automatically adds checks or inserts appropriate file positioning
57 calls when necessary.
57 calls when necessary.
58 """
58 """
59 OPNONE = 0
59 OPNONE = 0
60 OPREAD = 1
60 OPREAD = 1
61 OPWRITE = 2
61 OPWRITE = 2
62
62
63 def __init__(self, fp):
63 def __init__(self, fp):
64 object.__setattr__(self, r'_fp', fp)
64 object.__setattr__(self, r'_fp', fp)
65 object.__setattr__(self, r'_lastop', 0)
65 object.__setattr__(self, r'_lastop', 0)
66
66
67 def __enter__(self):
67 def __enter__(self):
68 return self._fp.__enter__()
68 return self._fp.__enter__()
69
69
70 def __exit__(self, exc_type, exc_val, exc_tb):
70 def __exit__(self, exc_type, exc_val, exc_tb):
71 self._fp.__exit__(exc_type, exc_val, exc_tb)
71 self._fp.__exit__(exc_type, exc_val, exc_tb)
72
72
73 def __getattr__(self, name):
73 def __getattr__(self, name):
74 return getattr(self._fp, name)
74 return getattr(self._fp, name)
75
75
76 def __setattr__(self, name, value):
76 def __setattr__(self, name, value):
77 return self._fp.__setattr__(name, value)
77 return self._fp.__setattr__(name, value)
78
78
79 def _noopseek(self):
79 def _noopseek(self):
80 self._fp.seek(0, os.SEEK_CUR)
80 self._fp.seek(0, os.SEEK_CUR)
81
81
82 def seek(self, *args, **kwargs):
82 def seek(self, *args, **kwargs):
83 object.__setattr__(self, r'_lastop', self.OPNONE)
83 object.__setattr__(self, r'_lastop', self.OPNONE)
84 return self._fp.seek(*args, **kwargs)
84 return self._fp.seek(*args, **kwargs)
85
85
86 def write(self, d):
86 def write(self, d):
87 if self._lastop == self.OPREAD:
87 if self._lastop == self.OPREAD:
88 self._noopseek()
88 self._noopseek()
89
89
90 object.__setattr__(self, r'_lastop', self.OPWRITE)
90 object.__setattr__(self, r'_lastop', self.OPWRITE)
91 return self._fp.write(d)
91 return self._fp.write(d)
92
92
93 def writelines(self, *args, **kwargs):
93 def writelines(self, *args, **kwargs):
94 if self._lastop == self.OPREAD:
94 if self._lastop == self.OPREAD:
95 self._noopeseek()
95 self._noopeseek()
96
96
97 object.__setattr__(self, r'_lastop', self.OPWRITE)
97 object.__setattr__(self, r'_lastop', self.OPWRITE)
98 return self._fp.writelines(*args, **kwargs)
98 return self._fp.writelines(*args, **kwargs)
99
99
100 def read(self, *args, **kwargs):
100 def read(self, *args, **kwargs):
101 if self._lastop == self.OPWRITE:
101 if self._lastop == self.OPWRITE:
102 self._noopseek()
102 self._noopseek()
103
103
104 object.__setattr__(self, r'_lastop', self.OPREAD)
104 object.__setattr__(self, r'_lastop', self.OPREAD)
105 return self._fp.read(*args, **kwargs)
105 return self._fp.read(*args, **kwargs)
106
106
107 def readline(self, *args, **kwargs):
107 def readline(self, *args, **kwargs):
108 if self._lastop == self.OPWRITE:
108 if self._lastop == self.OPWRITE:
109 self._noopseek()
109 self._noopseek()
110
110
111 object.__setattr__(self, r'_lastop', self.OPREAD)
111 object.__setattr__(self, r'_lastop', self.OPREAD)
112 return self._fp.readline(*args, **kwargs)
112 return self._fp.readline(*args, **kwargs)
113
113
114 def readlines(self, *args, **kwargs):
114 def readlines(self, *args, **kwargs):
115 if self._lastop == self.OPWRITE:
115 if self._lastop == self.OPWRITE:
116 self._noopseek()
116 self._noopseek()
117
117
118 object.__setattr__(self, r'_lastop', self.OPREAD)
118 object.__setattr__(self, r'_lastop', self.OPREAD)
119 return self._fp.readlines(*args, **kwargs)
119 return self._fp.readlines(*args, **kwargs)
120
120
121 def posixfile(name, mode='r', buffering=-1):
121 def posixfile(name, mode='r', buffering=-1):
122 '''Open a file with even more POSIX-like semantics'''
122 '''Open a file with even more POSIX-like semantics'''
123 try:
123 try:
124 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
124 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
125
125
126 # The position when opening in append mode is implementation defined, so
126 # The position when opening in append mode is implementation defined, so
127 # make it consistent with other platforms, which position at EOF.
127 # make it consistent with other platforms, which position at EOF.
128 if 'a' in mode:
128 if 'a' in mode:
129 fp.seek(0, os.SEEK_END)
129 fp.seek(0, os.SEEK_END)
130
130
131 if '+' in mode:
131 if '+' in mode:
132 return mixedfilemodewrapper(fp)
132 return mixedfilemodewrapper(fp)
133
133
134 return fp
134 return fp
135 except WindowsError as err:
135 except WindowsError as err:
136 # convert to a friendlier exception
136 # convert to a friendlier exception
137 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
137 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
138
138
139 # may be wrapped by win32mbcs extension
140 listdir = osutil.listdir
141
139 class winstdout(object):
142 class winstdout(object):
140 '''stdout on windows misbehaves if sent through a pipe'''
143 '''stdout on windows misbehaves if sent through a pipe'''
141
144
142 def __init__(self, fp):
145 def __init__(self, fp):
143 self.fp = fp
146 self.fp = fp
144
147
145 def __getattr__(self, key):
148 def __getattr__(self, key):
146 return getattr(self.fp, key)
149 return getattr(self.fp, key)
147
150
148 def close(self):
151 def close(self):
149 try:
152 try:
150 self.fp.close()
153 self.fp.close()
151 except IOError:
154 except IOError:
152 pass
155 pass
153
156
154 def write(self, s):
157 def write(self, s):
155 try:
158 try:
156 # This is workaround for "Not enough space" error on
159 # This is workaround for "Not enough space" error on
157 # writing large size of data to console.
160 # writing large size of data to console.
158 limit = 16000
161 limit = 16000
159 l = len(s)
162 l = len(s)
160 start = 0
163 start = 0
161 self.softspace = 0
164 self.softspace = 0
162 while start < l:
165 while start < l:
163 end = start + limit
166 end = start + limit
164 self.fp.write(s[start:end])
167 self.fp.write(s[start:end])
165 start = end
168 start = end
166 except IOError as inst:
169 except IOError as inst:
167 if inst.errno != 0:
170 if inst.errno != 0:
168 raise
171 raise
169 self.close()
172 self.close()
170 raise IOError(errno.EPIPE, 'Broken pipe')
173 raise IOError(errno.EPIPE, 'Broken pipe')
171
174
172 def flush(self):
175 def flush(self):
173 try:
176 try:
174 return self.fp.flush()
177 return self.fp.flush()
175 except IOError as inst:
178 except IOError as inst:
176 if inst.errno != errno.EINVAL:
179 if inst.errno != errno.EINVAL:
177 raise
180 raise
178 self.close()
181 self.close()
179 raise IOError(errno.EPIPE, 'Broken pipe')
182 raise IOError(errno.EPIPE, 'Broken pipe')
180
183
181 def _is_win_9x():
184 def _is_win_9x():
182 '''return true if run on windows 95, 98 or me.'''
185 '''return true if run on windows 95, 98 or me.'''
183 try:
186 try:
184 return sys.getwindowsversion()[3] == 1
187 return sys.getwindowsversion()[3] == 1
185 except AttributeError:
188 except AttributeError:
186 return 'command' in encoding.environ.get('comspec', '')
189 return 'command' in encoding.environ.get('comspec', '')
187
190
188 def openhardlinks():
191 def openhardlinks():
189 return not _is_win_9x()
192 return not _is_win_9x()
190
193
191 def parsepatchoutput(output_line):
194 def parsepatchoutput(output_line):
192 """parses the output produced by patch and returns the filename"""
195 """parses the output produced by patch and returns the filename"""
193 pf = output_line[14:]
196 pf = output_line[14:]
194 if pf[0] == '`':
197 if pf[0] == '`':
195 pf = pf[1:-1] # Remove the quotes
198 pf = pf[1:-1] # Remove the quotes
196 return pf
199 return pf
197
200
198 def sshargs(sshcmd, host, user, port):
201 def sshargs(sshcmd, host, user, port):
199 '''Build argument list for ssh or Plink'''
202 '''Build argument list for ssh or Plink'''
200 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
203 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
201 args = user and ("%s@%s" % (user, host)) or host
204 args = user and ("%s@%s" % (user, host)) or host
202 return port and ("%s %s %s" % (args, pflag, port)) or args
205 return port and ("%s %s %s" % (args, pflag, port)) or args
203
206
204 def setflags(f, l, x):
207 def setflags(f, l, x):
205 pass
208 pass
206
209
207 def copymode(src, dst, mode=None):
210 def copymode(src, dst, mode=None):
208 pass
211 pass
209
212
210 def checkexec(path):
213 def checkexec(path):
211 return False
214 return False
212
215
213 def checklink(path):
216 def checklink(path):
214 return False
217 return False
215
218
216 def setbinary(fd):
219 def setbinary(fd):
217 # When run without console, pipes may expose invalid
220 # When run without console, pipes may expose invalid
218 # fileno(), usually set to -1.
221 # fileno(), usually set to -1.
219 fno = getattr(fd, 'fileno', None)
222 fno = getattr(fd, 'fileno', None)
220 if fno is not None and fno() >= 0:
223 if fno is not None and fno() >= 0:
221 msvcrt.setmode(fno(), os.O_BINARY)
224 msvcrt.setmode(fno(), os.O_BINARY)
222
225
223 def pconvert(path):
226 def pconvert(path):
224 return path.replace(pycompat.ossep, '/')
227 return path.replace(pycompat.ossep, '/')
225
228
226 def localpath(path):
229 def localpath(path):
227 return path.replace('/', '\\')
230 return path.replace('/', '\\')
228
231
229 def normpath(path):
232 def normpath(path):
230 return pconvert(os.path.normpath(path))
233 return pconvert(os.path.normpath(path))
231
234
232 def normcase(path):
235 def normcase(path):
233 return encoding.upper(path) # NTFS compares via upper()
236 return encoding.upper(path) # NTFS compares via upper()
234
237
235 # see posix.py for definitions
238 # see posix.py for definitions
236 normcasespec = encoding.normcasespecs.upper
239 normcasespec = encoding.normcasespecs.upper
237 normcasefallback = encoding.upperfallback
240 normcasefallback = encoding.upperfallback
238
241
239 def samestat(s1, s2):
242 def samestat(s1, s2):
240 return False
243 return False
241
244
242 # A sequence of backslashes is special iff it precedes a double quote:
245 # A sequence of backslashes is special iff it precedes a double quote:
243 # - if there's an even number of backslashes, the double quote is not
246 # - if there's an even number of backslashes, the double quote is not
244 # quoted (i.e. it ends the quoted region)
247 # quoted (i.e. it ends the quoted region)
245 # - if there's an odd number of backslashes, the double quote is quoted
248 # - if there's an odd number of backslashes, the double quote is quoted
246 # - in both cases, every pair of backslashes is unquoted into a single
249 # - in both cases, every pair of backslashes is unquoted into a single
247 # backslash
250 # backslash
248 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
251 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
249 # So, to quote a string, we must surround it in double quotes, double
252 # So, to quote a string, we must surround it in double quotes, double
250 # the number of backslashes that precede double quotes and add another
253 # the number of backslashes that precede double quotes and add another
251 # backslash before every double quote (being careful with the double
254 # backslash before every double quote (being careful with the double
252 # quote we've appended to the end)
255 # quote we've appended to the end)
253 _quotere = None
256 _quotere = None
254 _needsshellquote = None
257 _needsshellquote = None
255 def shellquote(s):
258 def shellquote(s):
256 r"""
259 r"""
257 >>> shellquote(r'C:\Users\xyz')
260 >>> shellquote(r'C:\Users\xyz')
258 '"C:\\Users\\xyz"'
261 '"C:\\Users\\xyz"'
259 >>> shellquote(r'C:\Users\xyz/mixed')
262 >>> shellquote(r'C:\Users\xyz/mixed')
260 '"C:\\Users\\xyz/mixed"'
263 '"C:\\Users\\xyz/mixed"'
261 >>> # Would be safe not to quote too, since it is all double backslashes
264 >>> # Would be safe not to quote too, since it is all double backslashes
262 >>> shellquote(r'C:\\Users\\xyz')
265 >>> shellquote(r'C:\\Users\\xyz')
263 '"C:\\\\Users\\\\xyz"'
266 '"C:\\\\Users\\\\xyz"'
264 >>> # But this must be quoted
267 >>> # But this must be quoted
265 >>> shellquote(r'C:\\Users\\xyz/abc')
268 >>> shellquote(r'C:\\Users\\xyz/abc')
266 '"C:\\\\Users\\\\xyz/abc"'
269 '"C:\\\\Users\\\\xyz/abc"'
267 """
270 """
268 global _quotere
271 global _quotere
269 if _quotere is None:
272 if _quotere is None:
270 _quotere = re.compile(r'(\\*)("|\\$)')
273 _quotere = re.compile(r'(\\*)("|\\$)')
271 global _needsshellquote
274 global _needsshellquote
272 if _needsshellquote is None:
275 if _needsshellquote is None:
273 # ":" is also treated as "safe character", because it is used as a part
276 # ":" is also treated as "safe character", because it is used as a part
274 # of path name on Windows. "\" is also part of a path name, but isn't
277 # of path name on Windows. "\" is also part of a path name, but isn't
275 # safe because shlex.split() (kind of) treats it as an escape char and
278 # safe because shlex.split() (kind of) treats it as an escape char and
276 # drops it. It will leave the next character, even if it is another
279 # drops it. It will leave the next character, even if it is another
277 # "\".
280 # "\".
278 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
281 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
279 if s and not _needsshellquote(s) and not _quotere.search(s):
282 if s and not _needsshellquote(s) and not _quotere.search(s):
280 # "s" shouldn't have to be quoted
283 # "s" shouldn't have to be quoted
281 return s
284 return s
282 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
285 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
283
286
284 def quotecommand(cmd):
287 def quotecommand(cmd):
285 """Build a command string suitable for os.popen* calls."""
288 """Build a command string suitable for os.popen* calls."""
286 if sys.version_info < (2, 7, 1):
289 if sys.version_info < (2, 7, 1):
287 # Python versions since 2.7.1 do this extra quoting themselves
290 # Python versions since 2.7.1 do this extra quoting themselves
288 return '"' + cmd + '"'
291 return '"' + cmd + '"'
289 return cmd
292 return cmd
290
293
291 def popen(command, mode='r'):
294 def popen(command, mode='r'):
292 # Work around "popen spawned process may not write to stdout
295 # Work around "popen spawned process may not write to stdout
293 # under windows"
296 # under windows"
294 # http://bugs.python.org/issue1366
297 # http://bugs.python.org/issue1366
295 command += " 2> %s" % os.devnull
298 command += " 2> %s" % os.devnull
296 return os.popen(quotecommand(command), mode)
299 return os.popen(quotecommand(command), mode)
297
300
298 def explainexit(code):
301 def explainexit(code):
299 return _("exited with status %d") % code, code
302 return _("exited with status %d") % code, code
300
303
301 # if you change this stub into a real check, please try to implement the
304 # if you change this stub into a real check, please try to implement the
302 # username and groupname functions above, too.
305 # username and groupname functions above, too.
303 def isowner(st):
306 def isowner(st):
304 return True
307 return True
305
308
306 def findexe(command):
309 def findexe(command):
307 '''Find executable for command searching like cmd.exe does.
310 '''Find executable for command searching like cmd.exe does.
308 If command is a basename then PATH is searched for command.
311 If command is a basename then PATH is searched for command.
309 PATH isn't searched if command is an absolute or relative path.
312 PATH isn't searched if command is an absolute or relative path.
310 An extension from PATHEXT is found and added if not present.
313 An extension from PATHEXT is found and added if not present.
311 If command isn't found None is returned.'''
314 If command isn't found None is returned.'''
312 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
315 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
313 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
316 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
314 if os.path.splitext(command)[1].lower() in pathexts:
317 if os.path.splitext(command)[1].lower() in pathexts:
315 pathexts = ['']
318 pathexts = ['']
316
319
317 def findexisting(pathcommand):
320 def findexisting(pathcommand):
318 'Will append extension (if needed) and return existing file'
321 'Will append extension (if needed) and return existing file'
319 for ext in pathexts:
322 for ext in pathexts:
320 executable = pathcommand + ext
323 executable = pathcommand + ext
321 if os.path.exists(executable):
324 if os.path.exists(executable):
322 return executable
325 return executable
323 return None
326 return None
324
327
325 if pycompat.ossep in command:
328 if pycompat.ossep in command:
326 return findexisting(command)
329 return findexisting(command)
327
330
328 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
331 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
329 executable = findexisting(os.path.join(path, command))
332 executable = findexisting(os.path.join(path, command))
330 if executable is not None:
333 if executable is not None:
331 return executable
334 return executable
332 return findexisting(os.path.expanduser(os.path.expandvars(command)))
335 return findexisting(os.path.expanduser(os.path.expandvars(command)))
333
336
334 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
337 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
335
338
336 def statfiles(files):
339 def statfiles(files):
337 '''Stat each file in files. Yield each stat, or None if a file
340 '''Stat each file in files. Yield each stat, or None if a file
338 does not exist or has a type we don't care about.
341 does not exist or has a type we don't care about.
339
342
340 Cluster and cache stat per directory to minimize number of OS stat calls.'''
343 Cluster and cache stat per directory to minimize number of OS stat calls.'''
341 dircache = {} # dirname -> filename -> status | None if file does not exist
344 dircache = {} # dirname -> filename -> status | None if file does not exist
342 getkind = stat.S_IFMT
345 getkind = stat.S_IFMT
343 for nf in files:
346 for nf in files:
344 nf = normcase(nf)
347 nf = normcase(nf)
345 dir, base = os.path.split(nf)
348 dir, base = os.path.split(nf)
346 if not dir:
349 if not dir:
347 dir = '.'
350 dir = '.'
348 cache = dircache.get(dir, None)
351 cache = dircache.get(dir, None)
349 if cache is None:
352 if cache is None:
350 try:
353 try:
351 dmap = dict([(normcase(n), s)
354 dmap = dict([(normcase(n), s)
352 for n, k, s in osutil.listdir(dir, True)
355 for n, k, s in listdir(dir, True)
353 if getkind(s.st_mode) in _wantedkinds])
356 if getkind(s.st_mode) in _wantedkinds])
354 except OSError as err:
357 except OSError as err:
355 # Python >= 2.5 returns ENOENT and adds winerror field
358 # Python >= 2.5 returns ENOENT and adds winerror field
356 # EINVAL is raised if dir is not a directory.
359 # EINVAL is raised if dir is not a directory.
357 if err.errno not in (errno.ENOENT, errno.EINVAL,
360 if err.errno not in (errno.ENOENT, errno.EINVAL,
358 errno.ENOTDIR):
361 errno.ENOTDIR):
359 raise
362 raise
360 dmap = {}
363 dmap = {}
361 cache = dircache.setdefault(dir, dmap)
364 cache = dircache.setdefault(dir, dmap)
362 yield cache.get(base, None)
365 yield cache.get(base, None)
363
366
364 def username(uid=None):
367 def username(uid=None):
365 """Return the name of the user with the given uid.
368 """Return the name of the user with the given uid.
366
369
367 If uid is None, return the name of the current user."""
370 If uid is None, return the name of the current user."""
368 return None
371 return None
369
372
370 def groupname(gid=None):
373 def groupname(gid=None):
371 """Return the name of the group with the given gid.
374 """Return the name of the group with the given gid.
372
375
373 If gid is None, return the name of the current group."""
376 If gid is None, return the name of the current group."""
374 return None
377 return None
375
378
376 def removedirs(name):
379 def removedirs(name):
377 """special version of os.removedirs that does not remove symlinked
380 """special version of os.removedirs that does not remove symlinked
378 directories or junction points if they actually contain files"""
381 directories or junction points if they actually contain files"""
379 if osutil.listdir(name):
382 if listdir(name):
380 return
383 return
381 os.rmdir(name)
384 os.rmdir(name)
382 head, tail = os.path.split(name)
385 head, tail = os.path.split(name)
383 if not tail:
386 if not tail:
384 head, tail = os.path.split(head)
387 head, tail = os.path.split(head)
385 while head and tail:
388 while head and tail:
386 try:
389 try:
387 if osutil.listdir(head):
390 if listdir(head):
388 return
391 return
389 os.rmdir(head)
392 os.rmdir(head)
390 except (ValueError, OSError):
393 except (ValueError, OSError):
391 break
394 break
392 head, tail = os.path.split(head)
395 head, tail = os.path.split(head)
393
396
394 def rename(src, dst):
397 def rename(src, dst):
395 '''atomically rename file src to dst, replacing dst if it exists'''
398 '''atomically rename file src to dst, replacing dst if it exists'''
396 try:
399 try:
397 os.rename(src, dst)
400 os.rename(src, dst)
398 except OSError as e:
401 except OSError as e:
399 if e.errno != errno.EEXIST:
402 if e.errno != errno.EEXIST:
400 raise
403 raise
401 unlink(dst)
404 unlink(dst)
402 os.rename(src, dst)
405 os.rename(src, dst)
403
406
404 def gethgcmd():
407 def gethgcmd():
405 return [sys.executable] + sys.argv[:1]
408 return [sys.executable] + sys.argv[:1]
406
409
407 def groupmembers(name):
410 def groupmembers(name):
408 # Don't support groups on Windows for now
411 # Don't support groups on Windows for now
409 raise KeyError
412 raise KeyError
410
413
411 def isexec(f):
414 def isexec(f):
412 return False
415 return False
413
416
414 class cachestat(object):
417 class cachestat(object):
415 def __init__(self, path):
418 def __init__(self, path):
416 pass
419 pass
417
420
418 def cacheable(self):
421 def cacheable(self):
419 return False
422 return False
420
423
421 def lookupreg(key, valname=None, scope=None):
424 def lookupreg(key, valname=None, scope=None):
422 ''' Look up a key/value name in the Windows registry.
425 ''' Look up a key/value name in the Windows registry.
423
426
424 valname: value name. If unspecified, the default value for the key
427 valname: value name. If unspecified, the default value for the key
425 is used.
428 is used.
426 scope: optionally specify scope for registry lookup, this can be
429 scope: optionally specify scope for registry lookup, this can be
427 a sequence of scopes to look up in order. Default (CURRENT_USER,
430 a sequence of scopes to look up in order. Default (CURRENT_USER,
428 LOCAL_MACHINE).
431 LOCAL_MACHINE).
429 '''
432 '''
430 if scope is None:
433 if scope is None:
431 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
434 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
432 elif not isinstance(scope, (list, tuple)):
435 elif not isinstance(scope, (list, tuple)):
433 scope = (scope,)
436 scope = (scope,)
434 for s in scope:
437 for s in scope:
435 try:
438 try:
436 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
439 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
437 # never let a Unicode string escape into the wild
440 # never let a Unicode string escape into the wild
438 return encoding.unitolocal(val)
441 return encoding.unitolocal(val)
439 except EnvironmentError:
442 except EnvironmentError:
440 pass
443 pass
441
444
442 expandglobs = True
445 expandglobs = True
443
446
444 def statislink(st):
447 def statislink(st):
445 '''check whether a stat result is a symlink'''
448 '''check whether a stat result is a symlink'''
446 return False
449 return False
447
450
448 def statisexec(st):
451 def statisexec(st):
449 '''check whether a stat result is an executable file'''
452 '''check whether a stat result is an executable file'''
450 return False
453 return False
451
454
452 def poll(fds):
455 def poll(fds):
453 # see posix.py for description
456 # see posix.py for description
454 raise NotImplementedError()
457 raise NotImplementedError()
455
458
456 def readpipe(pipe):
459 def readpipe(pipe):
457 """Read all available data from a pipe."""
460 """Read all available data from a pipe."""
458 chunks = []
461 chunks = []
459 while True:
462 while True:
460 size = win32.peekpipe(pipe)
463 size = win32.peekpipe(pipe)
461 if not size:
464 if not size:
462 break
465 break
463
466
464 s = pipe.read(size)
467 s = pipe.read(size)
465 if not s:
468 if not s:
466 break
469 break
467 chunks.append(s)
470 chunks.append(s)
468
471
469 return ''.join(chunks)
472 return ''.join(chunks)
470
473
471 def bindunixsocket(sock, path):
474 def bindunixsocket(sock, path):
472 raise NotImplementedError('unsupported platform')
475 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now