##// 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 1 # win32mbcs.py -- MBCS filename support for Mercurial
2 2 #
3 3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
4 4 #
5 5 # Version: 0.3
6 6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
7 7 #
8 8 # This software may be used and distributed according to the terms of the
9 9 # GNU General Public License version 2 or any later version.
10 10 #
11 11
12 12 '''allow the use of MBCS paths with problematic encodings
13 13
14 14 Some MBCS encodings are not good for some path operations (i.e.
15 15 splitting path, case conversion, etc.) with its encoded bytes. We call
16 16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
17 17 This extension can be used to fix the issue with those encodings by
18 18 wrapping some functions to convert to Unicode string before path
19 19 operation.
20 20
21 21 This extension is useful for:
22 22
23 23 - Japanese Windows users using shift_jis encoding.
24 24 - Chinese Windows users using big5 encoding.
25 25 - All users who use a repository with one of problematic encodings on
26 26 case-insensitive file system.
27 27
28 28 This extension is not needed for:
29 29
30 30 - Any user who use only ASCII chars in path.
31 31 - Any user who do not use any of problematic encodings.
32 32
33 33 Note that there are some limitations on using this extension:
34 34
35 35 - You should use single encoding in one repository.
36 36 - If the repository path ends with 0x5c, .hg/hgrc cannot be read.
37 37 - win32mbcs is not compatible with fixutf8 extension.
38 38
39 39 By default, win32mbcs uses encoding.encoding decided by Mercurial.
40 40 You can specify the encoding by config option::
41 41
42 42 [win32mbcs]
43 43 encoding = sjis
44 44
45 45 It is useful for the users who want to commit with UTF-8 log message.
46 46 '''
47 47 from __future__ import absolute_import
48 48
49 49 import os
50 50 import sys
51 51
52 52 from mercurial.i18n import _
53 53 from mercurial import (
54 54 encoding,
55 55 error,
56 56 pycompat,
57 57 )
58 58
59 59 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
60 60 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
61 61 # be specifying the version(s) of Mercurial they are tested with, or
62 62 # leave the attribute unspecified.
63 63 testedwith = 'ships-with-hg-core'
64 64
65 65 _encoding = None # see extsetup
66 66
67 67 def decode(arg):
68 68 if isinstance(arg, str):
69 69 uarg = arg.decode(_encoding)
70 70 if arg == uarg.encode(_encoding):
71 71 return uarg
72 72 raise UnicodeError("Not local encoding")
73 73 elif isinstance(arg, tuple):
74 74 return tuple(map(decode, arg))
75 75 elif isinstance(arg, list):
76 76 return map(decode, arg)
77 77 elif isinstance(arg, dict):
78 78 for k, v in arg.items():
79 79 arg[k] = decode(v)
80 80 return arg
81 81
82 82 def encode(arg):
83 83 if isinstance(arg, unicode):
84 84 return arg.encode(_encoding)
85 85 elif isinstance(arg, tuple):
86 86 return tuple(map(encode, arg))
87 87 elif isinstance(arg, list):
88 88 return map(encode, arg)
89 89 elif isinstance(arg, dict):
90 90 for k, v in arg.items():
91 91 arg[k] = encode(v)
92 92 return arg
93 93
94 94 def appendsep(s):
95 95 # ensure the path ends with os.sep, appending it if necessary.
96 96 try:
97 97 us = decode(s)
98 98 except UnicodeError:
99 99 us = s
100 100 if us and us[-1] not in ':/\\':
101 101 s += pycompat.ossep
102 102 return s
103 103
104 104
105 105 def basewrapper(func, argtype, enc, dec, args, kwds):
106 106 # check check already converted, then call original
107 107 for arg in args:
108 108 if isinstance(arg, argtype):
109 109 return func(*args, **kwds)
110 110
111 111 try:
112 112 # convert string arguments, call func, then convert back the
113 113 # return value.
114 114 return enc(func(*dec(args), **dec(kwds)))
115 115 except UnicodeError:
116 116 raise error.Abort(_("[win32mbcs] filename conversion failed with"
117 117 " %s encoding\n") % (_encoding))
118 118
119 119 def wrapper(func, args, kwds):
120 120 return basewrapper(func, unicode, encode, decode, args, kwds)
121 121
122 122
123 123 def reversewrapper(func, args, kwds):
124 124 return basewrapper(func, str, decode, encode, args, kwds)
125 125
126 126 def wrapperforlistdir(func, args, kwds):
127 127 # Ensure 'path' argument ends with os.sep to avoids
128 128 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
129 129 if args:
130 130 args = list(args)
131 131 args[0] = appendsep(args[0])
132 132 if 'path' in kwds:
133 133 kwds['path'] = appendsep(kwds['path'])
134 134 return func(*args, **kwds)
135 135
136 136 def wrapname(name, wrapper):
137 137 module, name = name.rsplit('.', 1)
138 138 module = sys.modules[module]
139 139 func = getattr(module, name)
140 140 def f(*args, **kwds):
141 141 return wrapper(func, args, kwds)
142 142 f.__name__ = func.__name__
143 143 setattr(module, name, f)
144 144
145 145 # List of functions to be wrapped.
146 146 # NOTE: os.path.dirname() and os.path.basename() are safe because
147 147 # they use result of os.path.split()
148 148 funcs = '''os.path.join os.path.split os.path.splitext
149 149 os.path.normpath os.makedirs mercurial.util.endswithsep
150 150 mercurial.util.splitpath mercurial.util.fscasesensitive
151 151 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
152 152 mercurial.util.checkwinfilename mercurial.util.checkosfilename
153 153 mercurial.util.split'''
154 154
155 155 # These functions are required to be called with local encoded string
156 156 # because they expects argument is local encoded string and cause
157 157 # problem with unicode string.
158 158 rfuncs = '''mercurial.encoding.upper mercurial.encoding.lower'''
159 159
160 160 # List of Windows specific functions to be wrapped.
161 161 winfuncs = '''os.path.splitunc'''
162 162
163 163 # codec and alias names of sjis and big5 to be faked.
164 164 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
165 165 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
166 166 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
167 167 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
168 168
169 169 def extsetup(ui):
170 170 # TODO: decide use of config section for this extension
171 171 if ((not os.path.supports_unicode_filenames) and
172 172 (pycompat.sysplatform != 'cygwin')):
173 173 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
174 174 return
175 175 # determine encoding for filename
176 176 global _encoding
177 177 _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
178 178 # fake is only for relevant environment.
179 179 if _encoding.lower() in problematic_encodings.split():
180 180 for f in funcs.split():
181 181 wrapname(f, wrapper)
182 182 if pycompat.osname == 'nt':
183 183 for f in winfuncs.split():
184 184 wrapname(f, wrapper)
185 wrapname("mercurial.osutil.listdir", wrapperforlistdir)
185 wrapname("mercurial.util.listdir", wrapperforlistdir)
186 wrapname("mercurial.windows.listdir", wrapperforlistdir)
186 187 # wrap functions to be called with local byte string arguments
187 188 for f in rfuncs.split():
188 189 wrapname(f, reversewrapper)
189 190 # Check sys.args manually instead of using ui.debug() because
190 191 # command line options is not yet applied when
191 192 # extensions.loadall() is called.
192 193 if '--debug' in sys.argv:
193 194 ui.write(("[win32mbcs] activated with encoding: %s\n")
194 195 % _encoding)
@@ -1,578 +1,577
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """command server extension for cHg
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'setenv' command
20 20 replace os.environ completely
21 21
22 22 'setumask' command
23 23 set umask
24 24
25 25 'validate' command
26 26 reload the config and check if the server is up to date
27 27
28 28 Config
29 29 ------
30 30
31 31 ::
32 32
33 33 [chgserver]
34 34 # how long (in seconds) should an idle chg server exit
35 35 idletimeout = 3600
36 36
37 37 # whether to skip config or env change checks
38 38 skiphash = False
39 39 """
40 40
41 41 from __future__ import absolute_import
42 42
43 43 import hashlib
44 44 import inspect
45 45 import os
46 46 import re
47 47 import struct
48 48 import time
49 49
50 50 from .i18n import _
51 51
52 52 from . import (
53 53 commandserver,
54 54 encoding,
55 55 error,
56 56 extensions,
57 osutil,
58 57 pycompat,
59 58 util,
60 59 )
61 60
62 61 _log = commandserver.log
63 62
64 63 def _hashlist(items):
65 64 """return sha1 hexdigest for a list"""
66 65 return hashlib.sha1(str(items)).hexdigest()
67 66
68 67 # sensitive config sections affecting confighash
69 68 _configsections = [
70 69 'alias', # affects global state commands.table
71 70 'extdiff', # uisetup will register new commands
72 71 'extensions',
73 72 ]
74 73
75 74 # sensitive environment variables affecting confighash
76 75 _envre = re.compile(r'''\A(?:
77 76 CHGHG
78 77 |HG(?:[A-Z].*)?
79 78 |LANG(?:UAGE)?
80 79 |LC_.*
81 80 |LD_.*
82 81 |PATH
83 82 |PYTHON.*
84 83 |TERM(?:INFO)?
85 84 |TZ
86 85 )\Z''', re.X)
87 86
88 87 def _confighash(ui):
89 88 """return a quick hash for detecting config/env changes
90 89
91 90 confighash is the hash of sensitive config items and environment variables.
92 91
93 92 for chgserver, it is designed that once confighash changes, the server is
94 93 not qualified to serve its client and should redirect the client to a new
95 94 server. different from mtimehash, confighash change will not mark the
96 95 server outdated and exit since the user can have different configs at the
97 96 same time.
98 97 """
99 98 sectionitems = []
100 99 for section in _configsections:
101 100 sectionitems.append(ui.configitems(section))
102 101 sectionhash = _hashlist(sectionitems)
103 102 envitems = [(k, v) for k, v in encoding.environ.iteritems()
104 103 if _envre.match(k)]
105 104 envhash = _hashlist(sorted(envitems))
106 105 return sectionhash[:6] + envhash[:6]
107 106
108 107 def _getmtimepaths(ui):
109 108 """get a list of paths that should be checked to detect change
110 109
111 110 The list will include:
112 111 - extensions (will not cover all files for complex extensions)
113 112 - mercurial/__version__.py
114 113 - python binary
115 114 """
116 115 modules = [m for n, m in extensions.extensions(ui)]
117 116 try:
118 117 from . import __version__
119 118 modules.append(__version__)
120 119 except ImportError:
121 120 pass
122 121 files = [pycompat.sysexecutable]
123 122 for m in modules:
124 123 try:
125 124 files.append(inspect.getabsfile(m))
126 125 except TypeError:
127 126 pass
128 127 return sorted(set(files))
129 128
130 129 def _mtimehash(paths):
131 130 """return a quick hash for detecting file changes
132 131
133 132 mtimehash calls stat on given paths and calculate a hash based on size and
134 133 mtime of each file. mtimehash does not read file content because reading is
135 134 expensive. therefore it's not 100% reliable for detecting content changes.
136 135 it's possible to return different hashes for same file contents.
137 136 it's also possible to return a same hash for different file contents for
138 137 some carefully crafted situation.
139 138
140 139 for chgserver, it is designed that once mtimehash changes, the server is
141 140 considered outdated immediately and should no longer provide service.
142 141
143 142 mtimehash is not included in confighash because we only know the paths of
144 143 extensions after importing them (there is imp.find_module but that faces
145 144 race conditions). We need to calculate confighash without importing.
146 145 """
147 146 def trystat(path):
148 147 try:
149 148 st = os.stat(path)
150 149 return (st.st_mtime, st.st_size)
151 150 except OSError:
152 151 # could be ENOENT, EPERM etc. not fatal in any case
153 152 pass
154 153 return _hashlist(map(trystat, paths))[:12]
155 154
156 155 class hashstate(object):
157 156 """a structure storing confighash, mtimehash, paths used for mtimehash"""
158 157 def __init__(self, confighash, mtimehash, mtimepaths):
159 158 self.confighash = confighash
160 159 self.mtimehash = mtimehash
161 160 self.mtimepaths = mtimepaths
162 161
163 162 @staticmethod
164 163 def fromui(ui, mtimepaths=None):
165 164 if mtimepaths is None:
166 165 mtimepaths = _getmtimepaths(ui)
167 166 confighash = _confighash(ui)
168 167 mtimehash = _mtimehash(mtimepaths)
169 168 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
170 169 return hashstate(confighash, mtimehash, mtimepaths)
171 170
172 171 def _newchgui(srcui, csystem, attachio):
173 172 class chgui(srcui.__class__):
174 173 def __init__(self, src=None):
175 174 super(chgui, self).__init__(src)
176 175 if src:
177 176 self._csystem = getattr(src, '_csystem', csystem)
178 177 else:
179 178 self._csystem = csystem
180 179
181 180 def _runsystem(self, cmd, environ, cwd, out):
182 181 # fallback to the original system method if the output needs to be
183 182 # captured (to self._buffers), or the output stream is not stdout
184 183 # (e.g. stderr, cStringIO), because the chg client is not aware of
185 184 # these situations and will behave differently (write to stdout).
186 185 if (out is not self.fout
187 186 or not util.safehasattr(self.fout, 'fileno')
188 187 or self.fout.fileno() != util.stdout.fileno()):
189 188 return util.system(cmd, environ=environ, cwd=cwd, out=out)
190 189 self.flush()
191 190 return self._csystem(cmd, util.shellenviron(environ), cwd)
192 191
193 192 def _runpager(self, cmd, env=None):
194 193 self._csystem(cmd, util.shellenviron(env), type='pager',
195 194 cmdtable={'attachio': attachio})
196 195 return True
197 196
198 197 return chgui(srcui)
199 198
200 199 def _loadnewui(srcui, args):
201 200 from . import dispatch # avoid cycle
202 201
203 202 newui = srcui.__class__.load()
204 203 for a in ['fin', 'fout', 'ferr', 'environ']:
205 204 setattr(newui, a, getattr(srcui, a))
206 205 if util.safehasattr(srcui, '_csystem'):
207 206 newui._csystem = srcui._csystem
208 207
209 208 # command line args
210 209 args = args[:]
211 210 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
212 211
213 212 # stolen from tortoisehg.util.copydynamicconfig()
214 213 for section, name, value in srcui.walkconfig():
215 214 source = srcui.configsource(section, name)
216 215 if ':' in source or source == '--config' or source.startswith('$'):
217 216 # path:line or command line, or environ
218 217 continue
219 218 newui.setconfig(section, name, value, source)
220 219
221 220 # load wd and repo config, copied from dispatch.py
222 221 cwds = dispatch._earlygetopt(['--cwd'], args)
223 222 cwd = cwds and os.path.realpath(cwds[-1]) or None
224 223 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
225 224 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
226 225
227 226 return (newui, newlui)
228 227
229 228 class channeledsystem(object):
230 229 """Propagate ui.system() request in the following format:
231 230
232 231 payload length (unsigned int),
233 232 type, '\0',
234 233 cmd, '\0',
235 234 cwd, '\0',
236 235 envkey, '=', val, '\0',
237 236 ...
238 237 envkey, '=', val
239 238
240 239 if type == 'system', waits for:
241 240
242 241 exitcode length (unsigned int),
243 242 exitcode (int)
244 243
245 244 if type == 'pager', repetitively waits for a command name ending with '\n'
246 245 and executes it defined by cmdtable, or exits the loop if the command name
247 246 is empty.
248 247 """
249 248 def __init__(self, in_, out, channel):
250 249 self.in_ = in_
251 250 self.out = out
252 251 self.channel = channel
253 252
254 253 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
255 254 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
256 255 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
257 256 data = '\0'.join(args)
258 257 self.out.write(struct.pack('>cI', self.channel, len(data)))
259 258 self.out.write(data)
260 259 self.out.flush()
261 260
262 261 if type == 'system':
263 262 length = self.in_.read(4)
264 263 length, = struct.unpack('>I', length)
265 264 if length != 4:
266 265 raise error.Abort(_('invalid response'))
267 266 rc, = struct.unpack('>i', self.in_.read(4))
268 267 return rc
269 268 elif type == 'pager':
270 269 while True:
271 270 cmd = self.in_.readline()[:-1]
272 271 if not cmd:
273 272 break
274 273 if cmdtable and cmd in cmdtable:
275 274 _log('pager subcommand: %s' % cmd)
276 275 cmdtable[cmd]()
277 276 else:
278 277 raise error.Abort(_('unexpected command: %s') % cmd)
279 278 else:
280 279 raise error.ProgrammingError('invalid S channel type: %s' % type)
281 280
282 281 _iochannels = [
283 282 # server.ch, ui.fp, mode
284 283 ('cin', 'fin', pycompat.sysstr('rb')),
285 284 ('cout', 'fout', pycompat.sysstr('wb')),
286 285 ('cerr', 'ferr', pycompat.sysstr('wb')),
287 286 ]
288 287
289 288 class chgcmdserver(commandserver.server):
290 289 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
291 290 super(chgcmdserver, self).__init__(
292 291 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
293 292 repo, fin, fout)
294 293 self.clientsock = sock
295 294 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
296 295 self.hashstate = hashstate
297 296 self.baseaddress = baseaddress
298 297 if hashstate is not None:
299 298 self.capabilities = self.capabilities.copy()
300 299 self.capabilities['validate'] = chgcmdserver.validate
301 300
302 301 def cleanup(self):
303 302 super(chgcmdserver, self).cleanup()
304 303 # dispatch._runcatch() does not flush outputs if exception is not
305 304 # handled by dispatch._dispatch()
306 305 self.ui.flush()
307 306 self._restoreio()
308 307
309 308 def attachio(self):
310 309 """Attach to client's stdio passed via unix domain socket; all
311 310 channels except cresult will no longer be used
312 311 """
313 312 # tell client to sendmsg() with 1-byte payload, which makes it
314 313 # distinctive from "attachio\n" command consumed by client.read()
315 314 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
316 clientfds = osutil.recvfds(self.clientsock.fileno())
315 clientfds = util.recvfds(self.clientsock.fileno())
317 316 _log('received fds: %r\n' % clientfds)
318 317
319 318 ui = self.ui
320 319 ui.flush()
321 320 first = self._saveio()
322 321 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
323 322 assert fd > 0
324 323 fp = getattr(ui, fn)
325 324 os.dup2(fd, fp.fileno())
326 325 os.close(fd)
327 326 if not first:
328 327 continue
329 328 # reset buffering mode when client is first attached. as we want
330 329 # to see output immediately on pager, the mode stays unchanged
331 330 # when client re-attached. ferr is unchanged because it should
332 331 # be unbuffered no matter if it is a tty or not.
333 332 if fn == 'ferr':
334 333 newfp = fp
335 334 else:
336 335 # make it line buffered explicitly because the default is
337 336 # decided on first write(), where fout could be a pager.
338 337 if fp.isatty():
339 338 bufsize = 1 # line buffered
340 339 else:
341 340 bufsize = -1 # system default
342 341 newfp = os.fdopen(fp.fileno(), mode, bufsize)
343 342 setattr(ui, fn, newfp)
344 343 setattr(self, cn, newfp)
345 344
346 345 self.cresult.write(struct.pack('>i', len(clientfds)))
347 346
348 347 def _saveio(self):
349 348 if self._oldios:
350 349 return False
351 350 ui = self.ui
352 351 for cn, fn, _mode in _iochannels:
353 352 ch = getattr(self, cn)
354 353 fp = getattr(ui, fn)
355 354 fd = os.dup(fp.fileno())
356 355 self._oldios.append((ch, fp, fd))
357 356 return True
358 357
359 358 def _restoreio(self):
360 359 ui = self.ui
361 360 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
362 361 newfp = getattr(ui, fn)
363 362 # close newfp while it's associated with client; otherwise it
364 363 # would be closed when newfp is deleted
365 364 if newfp is not fp:
366 365 newfp.close()
367 366 # restore original fd: fp is open again
368 367 os.dup2(fd, fp.fileno())
369 368 os.close(fd)
370 369 setattr(self, cn, ch)
371 370 setattr(ui, fn, fp)
372 371 del self._oldios[:]
373 372
374 373 def validate(self):
375 374 """Reload the config and check if the server is up to date
376 375
377 376 Read a list of '\0' separated arguments.
378 377 Write a non-empty list of '\0' separated instruction strings or '\0'
379 378 if the list is empty.
380 379 An instruction string could be either:
381 380 - "unlink $path", the client should unlink the path to stop the
382 381 outdated server.
383 382 - "redirect $path", the client should attempt to connect to $path
384 383 first. If it does not work, start a new server. It implies
385 384 "reconnect".
386 385 - "exit $n", the client should exit directly with code n.
387 386 This may happen if we cannot parse the config.
388 387 - "reconnect", the client should close the connection and
389 388 reconnect.
390 389 If neither "reconnect" nor "redirect" is included in the instruction
391 390 list, the client can continue with this server after completing all
392 391 the instructions.
393 392 """
394 393 from . import dispatch # avoid cycle
395 394
396 395 args = self._readlist()
397 396 try:
398 397 self.ui, lui = _loadnewui(self.ui, args)
399 398 except error.ParseError as inst:
400 399 dispatch._formatparse(self.ui.warn, inst)
401 400 self.ui.flush()
402 401 self.cresult.write('exit 255')
403 402 return
404 403 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
405 404 insts = []
406 405 if newhash.mtimehash != self.hashstate.mtimehash:
407 406 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
408 407 insts.append('unlink %s' % addr)
409 408 # mtimehash is empty if one or more extensions fail to load.
410 409 # to be compatible with hg, still serve the client this time.
411 410 if self.hashstate.mtimehash:
412 411 insts.append('reconnect')
413 412 if newhash.confighash != self.hashstate.confighash:
414 413 addr = _hashaddress(self.baseaddress, newhash.confighash)
415 414 insts.append('redirect %s' % addr)
416 415 _log('validate: %s\n' % insts)
417 416 self.cresult.write('\0'.join(insts) or '\0')
418 417
419 418 def chdir(self):
420 419 """Change current directory
421 420
422 421 Note that the behavior of --cwd option is bit different from this.
423 422 It does not affect --config parameter.
424 423 """
425 424 path = self._readstr()
426 425 if not path:
427 426 return
428 427 _log('chdir to %r\n' % path)
429 428 os.chdir(path)
430 429
431 430 def setumask(self):
432 431 """Change umask"""
433 432 mask = struct.unpack('>I', self._read(4))[0]
434 433 _log('setumask %r\n' % mask)
435 434 os.umask(mask)
436 435
437 436 def runcommand(self):
438 437 return super(chgcmdserver, self).runcommand()
439 438
440 439 def setenv(self):
441 440 """Clear and update os.environ
442 441
443 442 Note that not all variables can make an effect on the running process.
444 443 """
445 444 l = self._readlist()
446 445 try:
447 446 newenv = dict(s.split('=', 1) for s in l)
448 447 except ValueError:
449 448 raise ValueError('unexpected value in setenv request')
450 449 _log('setenv: %r\n' % sorted(newenv.keys()))
451 450 encoding.environ.clear()
452 451 encoding.environ.update(newenv)
453 452
454 453 capabilities = commandserver.server.capabilities.copy()
455 454 capabilities.update({'attachio': attachio,
456 455 'chdir': chdir,
457 456 'runcommand': runcommand,
458 457 'setenv': setenv,
459 458 'setumask': setumask})
460 459
461 if util.safehasattr(osutil, 'setprocname'):
460 if util.safehasattr(util, 'setprocname'):
462 461 def setprocname(self):
463 462 """Change process title"""
464 463 name = self._readstr()
465 464 _log('setprocname: %r\n' % name)
466 osutil.setprocname(name)
465 util.setprocname(name)
467 466 capabilities['setprocname'] = setprocname
468 467
469 468 def _tempaddress(address):
470 469 return '%s.%d.tmp' % (address, os.getpid())
471 470
472 471 def _hashaddress(address, hashstr):
473 472 # if the basename of address contains '.', use only the left part. this
474 473 # makes it possible for the client to pass 'server.tmp$PID' and follow by
475 474 # an atomic rename to avoid locking when spawning new servers.
476 475 dirname, basename = os.path.split(address)
477 476 basename = basename.split('.', 1)[0]
478 477 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
479 478
480 479 class chgunixservicehandler(object):
481 480 """Set of operations for chg services"""
482 481
483 482 pollinterval = 1 # [sec]
484 483
485 484 def __init__(self, ui):
486 485 self.ui = ui
487 486 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
488 487 self._lastactive = time.time()
489 488
490 489 def bindsocket(self, sock, address):
491 490 self._inithashstate(address)
492 491 self._checkextensions()
493 492 self._bind(sock)
494 493 self._createsymlink()
495 494
496 495 def _inithashstate(self, address):
497 496 self._baseaddress = address
498 497 if self.ui.configbool('chgserver', 'skiphash', False):
499 498 self._hashstate = None
500 499 self._realaddress = address
501 500 return
502 501 self._hashstate = hashstate.fromui(self.ui)
503 502 self._realaddress = _hashaddress(address, self._hashstate.confighash)
504 503
505 504 def _checkextensions(self):
506 505 if not self._hashstate:
507 506 return
508 507 if extensions.notloaded():
509 508 # one or more extensions failed to load. mtimehash becomes
510 509 # meaningless because we do not know the paths of those extensions.
511 510 # set mtimehash to an illegal hash value to invalidate the server.
512 511 self._hashstate.mtimehash = ''
513 512
514 513 def _bind(self, sock):
515 514 # use a unique temp address so we can stat the file and do ownership
516 515 # check later
517 516 tempaddress = _tempaddress(self._realaddress)
518 517 util.bindunixsocket(sock, tempaddress)
519 518 self._socketstat = os.stat(tempaddress)
520 519 # rename will replace the old socket file if exists atomically. the
521 520 # old server will detect ownership change and exit.
522 521 util.rename(tempaddress, self._realaddress)
523 522
524 523 def _createsymlink(self):
525 524 if self._baseaddress == self._realaddress:
526 525 return
527 526 tempaddress = _tempaddress(self._baseaddress)
528 527 os.symlink(os.path.basename(self._realaddress), tempaddress)
529 528 util.rename(tempaddress, self._baseaddress)
530 529
531 530 def _issocketowner(self):
532 531 try:
533 532 stat = os.stat(self._realaddress)
534 533 return (stat.st_ino == self._socketstat.st_ino and
535 534 stat.st_mtime == self._socketstat.st_mtime)
536 535 except OSError:
537 536 return False
538 537
539 538 def unlinksocket(self, address):
540 539 if not self._issocketowner():
541 540 return
542 541 # it is possible to have a race condition here that we may
543 542 # remove another server's socket file. but that's okay
544 543 # since that server will detect and exit automatically and
545 544 # the client will start a new server on demand.
546 545 util.tryunlink(self._realaddress)
547 546
548 547 def printbanner(self, address):
549 548 # no "listening at" message should be printed to simulate hg behavior
550 549 pass
551 550
552 551 def shouldexit(self):
553 552 if not self._issocketowner():
554 553 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
555 554 return True
556 555 if time.time() - self._lastactive > self._idletimeout:
557 556 self.ui.debug('being idle too long. exiting.\n')
558 557 return True
559 558 return False
560 559
561 560 def newconnection(self):
562 561 self._lastactive = time.time()
563 562
564 563 def createcmdserver(self, repo, conn, fin, fout):
565 564 return chgcmdserver(self.ui, repo, fin, fout, conn,
566 565 self._hashstate, self._baseaddress)
567 566
568 567 def chgunixservice(ui, repo, opts):
569 568 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
570 569 # start another chg. drop it to avoid possible side effects.
571 570 if 'CHGINTERNALMARK' in encoding.environ:
572 571 del encoding.environ['CHGINTERNALMARK']
573 572
574 573 if repo:
575 574 # one chgserver can serve multiple repos. drop repo information
576 575 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
577 576 h = chgunixservicehandler(ui)
578 577 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,1288 +1,1287
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import errno
12 12 import os
13 13 import stat
14 14
15 15 from .i18n import _
16 16 from .node import nullid
17 17 from . import (
18 18 encoding,
19 19 error,
20 20 match as matchmod,
21 osutil,
22 21 parsers,
23 22 pathutil,
24 23 pycompat,
25 24 scmutil,
26 25 txnutil,
27 26 util,
28 27 )
29 28
30 29 propertycache = util.propertycache
31 30 filecache = scmutil.filecache
32 31 _rangemask = 0x7fffffff
33 32
34 33 dirstatetuple = parsers.dirstatetuple
35 34
36 35 class repocache(filecache):
37 36 """filecache for files in .hg/"""
38 37 def join(self, obj, fname):
39 38 return obj._opener.join(fname)
40 39
41 40 class rootcache(filecache):
42 41 """filecache for files in the repository root"""
43 42 def join(self, obj, fname):
44 43 return obj._join(fname)
45 44
46 45 def _getfsnow(vfs):
47 46 '''Get "now" timestamp on filesystem'''
48 47 tmpfd, tmpname = vfs.mkstemp()
49 48 try:
50 49 return os.fstat(tmpfd).st_mtime
51 50 finally:
52 51 os.close(tmpfd)
53 52 vfs.unlink(tmpname)
54 53
55 54 def nonnormalentries(dmap):
56 55 '''Compute the nonnormal dirstate entries from the dmap'''
57 56 try:
58 57 return parsers.nonnormalotherparententries(dmap)
59 58 except AttributeError:
60 59 nonnorm = set()
61 60 otherparent = set()
62 61 for fname, e in dmap.iteritems():
63 62 if e[0] != 'n' or e[3] == -1:
64 63 nonnorm.add(fname)
65 64 if e[0] == 'n' and e[2] == -2:
66 65 otherparent.add(fname)
67 66 return nonnorm, otherparent
68 67
69 68 class dirstate(object):
70 69
71 70 def __init__(self, opener, ui, root, validate):
72 71 '''Create a new dirstate object.
73 72
74 73 opener is an open()-like callable that can be used to open the
75 74 dirstate file; root is the root of the directory tracked by
76 75 the dirstate.
77 76 '''
78 77 self._opener = opener
79 78 self._validate = validate
80 79 self._root = root
81 80 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
82 81 # UNC path pointing to root share (issue4557)
83 82 self._rootdir = pathutil.normasprefix(root)
84 83 # internal config: ui.forcecwd
85 84 forcecwd = ui.config('ui', 'forcecwd')
86 85 if forcecwd:
87 86 self._cwd = forcecwd
88 87 self._dirty = False
89 88 self._dirtypl = False
90 89 self._lastnormaltime = 0
91 90 self._ui = ui
92 91 self._filecache = {}
93 92 self._parentwriters = 0
94 93 self._filename = 'dirstate'
95 94 self._pendingfilename = '%s.pending' % self._filename
96 95 self._plchangecallbacks = {}
97 96 self._origpl = None
98 97 self._updatedfiles = set()
99 98
100 99 # for consistent view between _pl() and _read() invocations
101 100 self._pendingmode = None
102 101
103 102 def beginparentchange(self):
104 103 '''Marks the beginning of a set of changes that involve changing
105 104 the dirstate parents. If there is an exception during this time,
106 105 the dirstate will not be written when the wlock is released. This
107 106 prevents writing an incoherent dirstate where the parent doesn't
108 107 match the contents.
109 108 '''
110 109 self._parentwriters += 1
111 110
112 111 def endparentchange(self):
113 112 '''Marks the end of a set of changes that involve changing the
114 113 dirstate parents. Once all parent changes have been marked done,
115 114 the wlock will be free to write the dirstate on release.
116 115 '''
117 116 if self._parentwriters > 0:
118 117 self._parentwriters -= 1
119 118
120 119 def pendingparentchange(self):
121 120 '''Returns true if the dirstate is in the middle of a set of changes
122 121 that modify the dirstate parent.
123 122 '''
124 123 return self._parentwriters > 0
125 124
126 125 @propertycache
127 126 def _map(self):
128 127 '''Return the dirstate contents as a map from filename to
129 128 (state, mode, size, time).'''
130 129 self._read()
131 130 return self._map
132 131
133 132 @propertycache
134 133 def _copymap(self):
135 134 self._read()
136 135 return self._copymap
137 136
138 137 @propertycache
139 138 def _nonnormalset(self):
140 139 nonnorm, otherparents = nonnormalentries(self._map)
141 140 self._otherparentset = otherparents
142 141 return nonnorm
143 142
144 143 @propertycache
145 144 def _otherparentset(self):
146 145 nonnorm, otherparents = nonnormalentries(self._map)
147 146 self._nonnormalset = nonnorm
148 147 return otherparents
149 148
150 149 @propertycache
151 150 def _filefoldmap(self):
152 151 try:
153 152 makefilefoldmap = parsers.make_file_foldmap
154 153 except AttributeError:
155 154 pass
156 155 else:
157 156 return makefilefoldmap(self._map, util.normcasespec,
158 157 util.normcasefallback)
159 158
160 159 f = {}
161 160 normcase = util.normcase
162 161 for name, s in self._map.iteritems():
163 162 if s[0] != 'r':
164 163 f[normcase(name)] = name
165 164 f['.'] = '.' # prevents useless util.fspath() invocation
166 165 return f
167 166
168 167 @propertycache
169 168 def _dirfoldmap(self):
170 169 f = {}
171 170 normcase = util.normcase
172 171 for name in self._dirs:
173 172 f[normcase(name)] = name
174 173 return f
175 174
176 175 @repocache('branch')
177 176 def _branch(self):
178 177 try:
179 178 return self._opener.read("branch").strip() or "default"
180 179 except IOError as inst:
181 180 if inst.errno != errno.ENOENT:
182 181 raise
183 182 return "default"
184 183
185 184 @propertycache
186 185 def _pl(self):
187 186 try:
188 187 fp = self._opendirstatefile()
189 188 st = fp.read(40)
190 189 fp.close()
191 190 l = len(st)
192 191 if l == 40:
193 192 return st[:20], st[20:40]
194 193 elif l > 0 and l < 40:
195 194 raise error.Abort(_('working directory state appears damaged!'))
196 195 except IOError as err:
197 196 if err.errno != errno.ENOENT:
198 197 raise
199 198 return [nullid, nullid]
200 199
201 200 @propertycache
202 201 def _dirs(self):
203 202 return util.dirs(self._map, 'r')
204 203
205 204 def dirs(self):
206 205 return self._dirs
207 206
208 207 @rootcache('.hgignore')
209 208 def _ignore(self):
210 209 files = self._ignorefiles()
211 210 if not files:
212 211 return util.never
213 212
214 213 pats = ['include:%s' % f for f in files]
215 214 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
216 215
217 216 @propertycache
218 217 def _slash(self):
219 218 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
220 219
221 220 @propertycache
222 221 def _checklink(self):
223 222 return util.checklink(self._root)
224 223
225 224 @propertycache
226 225 def _checkexec(self):
227 226 return util.checkexec(self._root)
228 227
229 228 @propertycache
230 229 def _checkcase(self):
231 230 return not util.fscasesensitive(self._join('.hg'))
232 231
233 232 def _join(self, f):
234 233 # much faster than os.path.join()
235 234 # it's safe because f is always a relative path
236 235 return self._rootdir + f
237 236
238 237 def flagfunc(self, buildfallback):
239 238 if self._checklink and self._checkexec:
240 239 def f(x):
241 240 try:
242 241 st = os.lstat(self._join(x))
243 242 if util.statislink(st):
244 243 return 'l'
245 244 if util.statisexec(st):
246 245 return 'x'
247 246 except OSError:
248 247 pass
249 248 return ''
250 249 return f
251 250
252 251 fallback = buildfallback()
253 252 if self._checklink:
254 253 def f(x):
255 254 if os.path.islink(self._join(x)):
256 255 return 'l'
257 256 if 'x' in fallback(x):
258 257 return 'x'
259 258 return ''
260 259 return f
261 260 if self._checkexec:
262 261 def f(x):
263 262 if 'l' in fallback(x):
264 263 return 'l'
265 264 if util.isexec(self._join(x)):
266 265 return 'x'
267 266 return ''
268 267 return f
269 268 else:
270 269 return fallback
271 270
272 271 @propertycache
273 272 def _cwd(self):
274 273 return pycompat.getcwd()
275 274
276 275 def getcwd(self):
277 276 '''Return the path from which a canonical path is calculated.
278 277
279 278 This path should be used to resolve file patterns or to convert
280 279 canonical paths back to file paths for display. It shouldn't be
281 280 used to get real file paths. Use vfs functions instead.
282 281 '''
283 282 cwd = self._cwd
284 283 if cwd == self._root:
285 284 return ''
286 285 # self._root ends with a path separator if self._root is '/' or 'C:\'
287 286 rootsep = self._root
288 287 if not util.endswithsep(rootsep):
289 288 rootsep += pycompat.ossep
290 289 if cwd.startswith(rootsep):
291 290 return cwd[len(rootsep):]
292 291 else:
293 292 # we're outside the repo. return an absolute path.
294 293 return cwd
295 294
296 295 def pathto(self, f, cwd=None):
297 296 if cwd is None:
298 297 cwd = self.getcwd()
299 298 path = util.pathto(self._root, cwd, f)
300 299 if self._slash:
301 300 return util.pconvert(path)
302 301 return path
303 302
304 303 def __getitem__(self, key):
305 304 '''Return the current state of key (a filename) in the dirstate.
306 305
307 306 States are:
308 307 n normal
309 308 m needs merging
310 309 r marked for removal
311 310 a marked for addition
312 311 ? not tracked
313 312 '''
314 313 return self._map.get(key, ("?",))[0]
315 314
316 315 def __contains__(self, key):
317 316 return key in self._map
318 317
319 318 def __iter__(self):
320 319 for x in sorted(self._map):
321 320 yield x
322 321
323 322 def iteritems(self):
324 323 return self._map.iteritems()
325 324
326 325 def parents(self):
327 326 return [self._validate(p) for p in self._pl]
328 327
329 328 def p1(self):
330 329 return self._validate(self._pl[0])
331 330
332 331 def p2(self):
333 332 return self._validate(self._pl[1])
334 333
335 334 def branch(self):
336 335 return encoding.tolocal(self._branch)
337 336
338 337 def setparents(self, p1, p2=nullid):
339 338 """Set dirstate parents to p1 and p2.
340 339
341 340 When moving from two parents to one, 'm' merged entries a
342 341 adjusted to normal and previous copy records discarded and
343 342 returned by the call.
344 343
345 344 See localrepo.setparents()
346 345 """
347 346 if self._parentwriters == 0:
348 347 raise ValueError("cannot set dirstate parent without "
349 348 "calling dirstate.beginparentchange")
350 349
351 350 self._dirty = self._dirtypl = True
352 351 oldp2 = self._pl[1]
353 352 if self._origpl is None:
354 353 self._origpl = self._pl
355 354 self._pl = p1, p2
356 355 copies = {}
357 356 if oldp2 != nullid and p2 == nullid:
358 357 candidatefiles = self._nonnormalset.union(self._otherparentset)
359 358 for f in candidatefiles:
360 359 s = self._map.get(f)
361 360 if s is None:
362 361 continue
363 362
364 363 # Discard 'm' markers when moving away from a merge state
365 364 if s[0] == 'm':
366 365 if f in self._copymap:
367 366 copies[f] = self._copymap[f]
368 367 self.normallookup(f)
369 368 # Also fix up otherparent markers
370 369 elif s[0] == 'n' and s[2] == -2:
371 370 if f in self._copymap:
372 371 copies[f] = self._copymap[f]
373 372 self.add(f)
374 373 return copies
375 374
376 375 def setbranch(self, branch):
377 376 self._branch = encoding.fromlocal(branch)
378 377 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
379 378 try:
380 379 f.write(self._branch + '\n')
381 380 f.close()
382 381
383 382 # make sure filecache has the correct stat info for _branch after
384 383 # replacing the underlying file
385 384 ce = self._filecache['_branch']
386 385 if ce:
387 386 ce.refresh()
388 387 except: # re-raises
389 388 f.discard()
390 389 raise
391 390
392 391 def _opendirstatefile(self):
393 392 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
394 393 if self._pendingmode is not None and self._pendingmode != mode:
395 394 fp.close()
396 395 raise error.Abort(_('working directory state may be '
397 396 'changed parallelly'))
398 397 self._pendingmode = mode
399 398 return fp
400 399
401 400 def _read(self):
402 401 self._map = {}
403 402 self._copymap = {}
404 403 try:
405 404 fp = self._opendirstatefile()
406 405 try:
407 406 st = fp.read()
408 407 finally:
409 408 fp.close()
410 409 except IOError as err:
411 410 if err.errno != errno.ENOENT:
412 411 raise
413 412 return
414 413 if not st:
415 414 return
416 415
417 416 if util.safehasattr(parsers, 'dict_new_presized'):
418 417 # Make an estimate of the number of files in the dirstate based on
419 418 # its size. From a linear regression on a set of real-world repos,
420 419 # all over 10,000 files, the size of a dirstate entry is 85
421 420 # bytes. The cost of resizing is significantly higher than the cost
422 421 # of filling in a larger presized dict, so subtract 20% from the
423 422 # size.
424 423 #
425 424 # This heuristic is imperfect in many ways, so in a future dirstate
426 425 # format update it makes sense to just record the number of entries
427 426 # on write.
428 427 self._map = parsers.dict_new_presized(len(st) / 71)
429 428
430 429 # Python's garbage collector triggers a GC each time a certain number
431 430 # of container objects (the number being defined by
432 431 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
433 432 # for each file in the dirstate. The C version then immediately marks
434 433 # them as not to be tracked by the collector. However, this has no
435 434 # effect on when GCs are triggered, only on what objects the GC looks
436 435 # into. This means that O(number of files) GCs are unavoidable.
437 436 # Depending on when in the process's lifetime the dirstate is parsed,
438 437 # this can get very expensive. As a workaround, disable GC while
439 438 # parsing the dirstate.
440 439 #
441 440 # (we cannot decorate the function directly since it is in a C module)
442 441 parse_dirstate = util.nogc(parsers.parse_dirstate)
443 442 p = parse_dirstate(self._map, self._copymap, st)
444 443 if not self._dirtypl:
445 444 self._pl = p
446 445
447 446 def invalidate(self):
448 447 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
449 448 "_pl", "_dirs", "_ignore", "_nonnormalset",
450 449 "_otherparentset"):
451 450 if a in self.__dict__:
452 451 delattr(self, a)
453 452 self._lastnormaltime = 0
454 453 self._dirty = False
455 454 self._updatedfiles.clear()
456 455 self._parentwriters = 0
457 456 self._origpl = None
458 457
459 458 def copy(self, source, dest):
460 459 """Mark dest as a copy of source. Unmark dest if source is None."""
461 460 if source == dest:
462 461 return
463 462 self._dirty = True
464 463 if source is not None:
465 464 self._copymap[dest] = source
466 465 self._updatedfiles.add(source)
467 466 self._updatedfiles.add(dest)
468 467 elif dest in self._copymap:
469 468 del self._copymap[dest]
470 469 self._updatedfiles.add(dest)
471 470
472 471 def copied(self, file):
473 472 return self._copymap.get(file, None)
474 473
475 474 def copies(self):
476 475 return self._copymap
477 476
478 477 def _droppath(self, f):
479 478 if self[f] not in "?r" and "_dirs" in self.__dict__:
480 479 self._dirs.delpath(f)
481 480
482 481 if "_filefoldmap" in self.__dict__:
483 482 normed = util.normcase(f)
484 483 if normed in self._filefoldmap:
485 484 del self._filefoldmap[normed]
486 485
487 486 self._updatedfiles.add(f)
488 487
489 488 def _addpath(self, f, state, mode, size, mtime):
490 489 oldstate = self[f]
491 490 if state == 'a' or oldstate == 'r':
492 491 scmutil.checkfilename(f)
493 492 if f in self._dirs:
494 493 raise error.Abort(_('directory %r already in dirstate') % f)
495 494 # shadows
496 495 for d in util.finddirs(f):
497 496 if d in self._dirs:
498 497 break
499 498 if d in self._map and self[d] != 'r':
500 499 raise error.Abort(
501 500 _('file %r in dirstate clashes with %r') % (d, f))
502 501 if oldstate in "?r" and "_dirs" in self.__dict__:
503 502 self._dirs.addpath(f)
504 503 self._dirty = True
505 504 self._updatedfiles.add(f)
506 505 self._map[f] = dirstatetuple(state, mode, size, mtime)
507 506 if state != 'n' or mtime == -1:
508 507 self._nonnormalset.add(f)
509 508 if size == -2:
510 509 self._otherparentset.add(f)
511 510
512 511 def normal(self, f):
513 512 '''Mark a file normal and clean.'''
514 513 s = os.lstat(self._join(f))
515 514 mtime = s.st_mtime
516 515 self._addpath(f, 'n', s.st_mode,
517 516 s.st_size & _rangemask, mtime & _rangemask)
518 517 if f in self._copymap:
519 518 del self._copymap[f]
520 519 if f in self._nonnormalset:
521 520 self._nonnormalset.remove(f)
522 521 if mtime > self._lastnormaltime:
523 522 # Remember the most recent modification timeslot for status(),
524 523 # to make sure we won't miss future size-preserving file content
525 524 # modifications that happen within the same timeslot.
526 525 self._lastnormaltime = mtime
527 526
528 527 def normallookup(self, f):
529 528 '''Mark a file normal, but possibly dirty.'''
530 529 if self._pl[1] != nullid and f in self._map:
531 530 # if there is a merge going on and the file was either
532 531 # in state 'm' (-1) or coming from other parent (-2) before
533 532 # being removed, restore that state.
534 533 entry = self._map[f]
535 534 if entry[0] == 'r' and entry[2] in (-1, -2):
536 535 source = self._copymap.get(f)
537 536 if entry[2] == -1:
538 537 self.merge(f)
539 538 elif entry[2] == -2:
540 539 self.otherparent(f)
541 540 if source:
542 541 self.copy(source, f)
543 542 return
544 543 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
545 544 return
546 545 self._addpath(f, 'n', 0, -1, -1)
547 546 if f in self._copymap:
548 547 del self._copymap[f]
549 548 if f in self._nonnormalset:
550 549 self._nonnormalset.remove(f)
551 550
552 551 def otherparent(self, f):
553 552 '''Mark as coming from the other parent, always dirty.'''
554 553 if self._pl[1] == nullid:
555 554 raise error.Abort(_("setting %r to other parent "
556 555 "only allowed in merges") % f)
557 556 if f in self and self[f] == 'n':
558 557 # merge-like
559 558 self._addpath(f, 'm', 0, -2, -1)
560 559 else:
561 560 # add-like
562 561 self._addpath(f, 'n', 0, -2, -1)
563 562
564 563 if f in self._copymap:
565 564 del self._copymap[f]
566 565
567 566 def add(self, f):
568 567 '''Mark a file added.'''
569 568 self._addpath(f, 'a', 0, -1, -1)
570 569 if f in self._copymap:
571 570 del self._copymap[f]
572 571
573 572 def remove(self, f):
574 573 '''Mark a file removed.'''
575 574 self._dirty = True
576 575 self._droppath(f)
577 576 size = 0
578 577 if self._pl[1] != nullid and f in self._map:
579 578 # backup the previous state
580 579 entry = self._map[f]
581 580 if entry[0] == 'm': # merge
582 581 size = -1
583 582 elif entry[0] == 'n' and entry[2] == -2: # other parent
584 583 size = -2
585 584 self._otherparentset.add(f)
586 585 self._map[f] = dirstatetuple('r', 0, size, 0)
587 586 self._nonnormalset.add(f)
588 587 if size == 0 and f in self._copymap:
589 588 del self._copymap[f]
590 589
591 590 def merge(self, f):
592 591 '''Mark a file merged.'''
593 592 if self._pl[1] == nullid:
594 593 return self.normallookup(f)
595 594 return self.otherparent(f)
596 595
597 596 def drop(self, f):
598 597 '''Drop a file from the dirstate'''
599 598 if f in self._map:
600 599 self._dirty = True
601 600 self._droppath(f)
602 601 del self._map[f]
603 602 if f in self._nonnormalset:
604 603 self._nonnormalset.remove(f)
605 604 if f in self._copymap:
606 605 del self._copymap[f]
607 606
608 607 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
609 608 if exists is None:
610 609 exists = os.path.lexists(os.path.join(self._root, path))
611 610 if not exists:
612 611 # Maybe a path component exists
613 612 if not ignoremissing and '/' in path:
614 613 d, f = path.rsplit('/', 1)
615 614 d = self._normalize(d, False, ignoremissing, None)
616 615 folded = d + "/" + f
617 616 else:
618 617 # No path components, preserve original case
619 618 folded = path
620 619 else:
621 620 # recursively normalize leading directory components
622 621 # against dirstate
623 622 if '/' in normed:
624 623 d, f = normed.rsplit('/', 1)
625 624 d = self._normalize(d, False, ignoremissing, True)
626 625 r = self._root + "/" + d
627 626 folded = d + "/" + util.fspath(f, r)
628 627 else:
629 628 folded = util.fspath(normed, self._root)
630 629 storemap[normed] = folded
631 630
632 631 return folded
633 632
634 633 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
635 634 normed = util.normcase(path)
636 635 folded = self._filefoldmap.get(normed, None)
637 636 if folded is None:
638 637 if isknown:
639 638 folded = path
640 639 else:
641 640 folded = self._discoverpath(path, normed, ignoremissing, exists,
642 641 self._filefoldmap)
643 642 return folded
644 643
645 644 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
646 645 normed = util.normcase(path)
647 646 folded = self._filefoldmap.get(normed, None)
648 647 if folded is None:
649 648 folded = self._dirfoldmap.get(normed, None)
650 649 if folded is None:
651 650 if isknown:
652 651 folded = path
653 652 else:
654 653 # store discovered result in dirfoldmap so that future
655 654 # normalizefile calls don't start matching directories
656 655 folded = self._discoverpath(path, normed, ignoremissing, exists,
657 656 self._dirfoldmap)
658 657 return folded
659 658
660 659 def normalize(self, path, isknown=False, ignoremissing=False):
661 660 '''
662 661 normalize the case of a pathname when on a casefolding filesystem
663 662
664 663 isknown specifies whether the filename came from walking the
665 664 disk, to avoid extra filesystem access.
666 665
667 666 If ignoremissing is True, missing path are returned
668 667 unchanged. Otherwise, we try harder to normalize possibly
669 668 existing path components.
670 669
671 670 The normalized case is determined based on the following precedence:
672 671
673 672 - version of name already stored in the dirstate
674 673 - version of name stored on disk
675 674 - version provided via command arguments
676 675 '''
677 676
678 677 if self._checkcase:
679 678 return self._normalize(path, isknown, ignoremissing)
680 679 return path
681 680
682 681 def clear(self):
683 682 self._map = {}
684 683 self._nonnormalset = set()
685 684 self._otherparentset = set()
686 685 if "_dirs" in self.__dict__:
687 686 delattr(self, "_dirs")
688 687 self._copymap = {}
689 688 self._pl = [nullid, nullid]
690 689 self._lastnormaltime = 0
691 690 self._updatedfiles.clear()
692 691 self._dirty = True
693 692
694 693 def rebuild(self, parent, allfiles, changedfiles=None):
695 694 if changedfiles is None:
696 695 # Rebuild entire dirstate
697 696 changedfiles = allfiles
698 697 lastnormaltime = self._lastnormaltime
699 698 self.clear()
700 699 self._lastnormaltime = lastnormaltime
701 700
702 701 if self._origpl is None:
703 702 self._origpl = self._pl
704 703 self._pl = (parent, nullid)
705 704 for f in changedfiles:
706 705 if f in allfiles:
707 706 self.normallookup(f)
708 707 else:
709 708 self.drop(f)
710 709
711 710 self._dirty = True
712 711
713 712 def write(self, tr):
714 713 if not self._dirty:
715 714 return
716 715
717 716 filename = self._filename
718 717 if tr:
719 718 # 'dirstate.write()' is not only for writing in-memory
720 719 # changes out, but also for dropping ambiguous timestamp.
721 720 # delayed writing re-raise "ambiguous timestamp issue".
722 721 # See also the wiki page below for detail:
723 722 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
724 723
725 724 # emulate dropping timestamp in 'parsers.pack_dirstate'
726 725 now = _getfsnow(self._opener)
727 726 dmap = self._map
728 727 for f in self._updatedfiles:
729 728 e = dmap.get(f)
730 729 if e is not None and e[0] == 'n' and e[3] == now:
731 730 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
732 731 self._nonnormalset.add(f)
733 732
734 733 # emulate that all 'dirstate.normal' results are written out
735 734 self._lastnormaltime = 0
736 735 self._updatedfiles.clear()
737 736
738 737 # delay writing in-memory changes out
739 738 tr.addfilegenerator('dirstate', (self._filename,),
740 739 self._writedirstate, location='plain')
741 740 return
742 741
743 742 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
744 743 self._writedirstate(st)
745 744
746 745 def addparentchangecallback(self, category, callback):
747 746 """add a callback to be called when the wd parents are changed
748 747
749 748 Callback will be called with the following arguments:
750 749 dirstate, (oldp1, oldp2), (newp1, newp2)
751 750
752 751 Category is a unique identifier to allow overwriting an old callback
753 752 with a newer callback.
754 753 """
755 754 self._plchangecallbacks[category] = callback
756 755
757 756 def _writedirstate(self, st):
758 757 # notify callbacks about parents change
759 758 if self._origpl is not None and self._origpl != self._pl:
760 759 for c, callback in sorted(self._plchangecallbacks.iteritems()):
761 760 callback(self, self._origpl, self._pl)
762 761 self._origpl = None
763 762 # use the modification time of the newly created temporary file as the
764 763 # filesystem's notion of 'now'
765 764 now = util.fstat(st).st_mtime & _rangemask
766 765
767 766 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
768 767 # timestamp of each entries in dirstate, because of 'now > mtime'
769 768 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
770 769 if delaywrite > 0:
771 770 # do we have any files to delay for?
772 771 for f, e in self._map.iteritems():
773 772 if e[0] == 'n' and e[3] == now:
774 773 import time # to avoid useless import
775 774 # rather than sleep n seconds, sleep until the next
776 775 # multiple of n seconds
777 776 clock = time.time()
778 777 start = int(clock) - (int(clock) % delaywrite)
779 778 end = start + delaywrite
780 779 time.sleep(end - clock)
781 780 now = end # trust our estimate that the end is near now
782 781 break
783 782
784 783 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
785 784 self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
786 785 st.close()
787 786 self._lastnormaltime = 0
788 787 self._dirty = self._dirtypl = False
789 788
790 789 def _dirignore(self, f):
791 790 if f == '.':
792 791 return False
793 792 if self._ignore(f):
794 793 return True
795 794 for p in util.finddirs(f):
796 795 if self._ignore(p):
797 796 return True
798 797 return False
799 798
800 799 def _ignorefiles(self):
801 800 files = []
802 801 if os.path.exists(self._join('.hgignore')):
803 802 files.append(self._join('.hgignore'))
804 803 for name, path in self._ui.configitems("ui"):
805 804 if name == 'ignore' or name.startswith('ignore.'):
806 805 # we need to use os.path.join here rather than self._join
807 806 # because path is arbitrary and user-specified
808 807 files.append(os.path.join(self._rootdir, util.expandpath(path)))
809 808 return files
810 809
811 810 def _ignorefileandline(self, f):
812 811 files = collections.deque(self._ignorefiles())
813 812 visited = set()
814 813 while files:
815 814 i = files.popleft()
816 815 patterns = matchmod.readpatternfile(i, self._ui.warn,
817 816 sourceinfo=True)
818 817 for pattern, lineno, line in patterns:
819 818 kind, p = matchmod._patsplit(pattern, 'glob')
820 819 if kind == "subinclude":
821 820 if p not in visited:
822 821 files.append(p)
823 822 continue
824 823 m = matchmod.match(self._root, '', [], [pattern],
825 824 warn=self._ui.warn)
826 825 if m(f):
827 826 return (i, lineno, line)
828 827 visited.add(i)
829 828 return (None, -1, "")
830 829
831 830 def _walkexplicit(self, match, subrepos):
832 831 '''Get stat data about the files explicitly specified by match.
833 832
834 833 Return a triple (results, dirsfound, dirsnotfound).
835 834 - results is a mapping from filename to stat result. It also contains
836 835 listings mapping subrepos and .hg to None.
837 836 - dirsfound is a list of files found to be directories.
838 837 - dirsnotfound is a list of files that the dirstate thinks are
839 838 directories and that were not found.'''
840 839
841 840 def badtype(mode):
842 841 kind = _('unknown')
843 842 if stat.S_ISCHR(mode):
844 843 kind = _('character device')
845 844 elif stat.S_ISBLK(mode):
846 845 kind = _('block device')
847 846 elif stat.S_ISFIFO(mode):
848 847 kind = _('fifo')
849 848 elif stat.S_ISSOCK(mode):
850 849 kind = _('socket')
851 850 elif stat.S_ISDIR(mode):
852 851 kind = _('directory')
853 852 return _('unsupported file type (type is %s)') % kind
854 853
855 854 matchedir = match.explicitdir
856 855 badfn = match.bad
857 856 dmap = self._map
858 857 lstat = os.lstat
859 858 getkind = stat.S_IFMT
860 859 dirkind = stat.S_IFDIR
861 860 regkind = stat.S_IFREG
862 861 lnkkind = stat.S_IFLNK
863 862 join = self._join
864 863 dirsfound = []
865 864 foundadd = dirsfound.append
866 865 dirsnotfound = []
867 866 notfoundadd = dirsnotfound.append
868 867
869 868 if not match.isexact() and self._checkcase:
870 869 normalize = self._normalize
871 870 else:
872 871 normalize = None
873 872
874 873 files = sorted(match.files())
875 874 subrepos.sort()
876 875 i, j = 0, 0
877 876 while i < len(files) and j < len(subrepos):
878 877 subpath = subrepos[j] + "/"
879 878 if files[i] < subpath:
880 879 i += 1
881 880 continue
882 881 while i < len(files) and files[i].startswith(subpath):
883 882 del files[i]
884 883 j += 1
885 884
886 885 if not files or '.' in files:
887 886 files = ['.']
888 887 results = dict.fromkeys(subrepos)
889 888 results['.hg'] = None
890 889
891 890 alldirs = None
892 891 for ff in files:
893 892 # constructing the foldmap is expensive, so don't do it for the
894 893 # common case where files is ['.']
895 894 if normalize and ff != '.':
896 895 nf = normalize(ff, False, True)
897 896 else:
898 897 nf = ff
899 898 if nf in results:
900 899 continue
901 900
902 901 try:
903 902 st = lstat(join(nf))
904 903 kind = getkind(st.st_mode)
905 904 if kind == dirkind:
906 905 if nf in dmap:
907 906 # file replaced by dir on disk but still in dirstate
908 907 results[nf] = None
909 908 if matchedir:
910 909 matchedir(nf)
911 910 foundadd((nf, ff))
912 911 elif kind == regkind or kind == lnkkind:
913 912 results[nf] = st
914 913 else:
915 914 badfn(ff, badtype(kind))
916 915 if nf in dmap:
917 916 results[nf] = None
918 917 except OSError as inst: # nf not found on disk - it is dirstate only
919 918 if nf in dmap: # does it exactly match a missing file?
920 919 results[nf] = None
921 920 else: # does it match a missing directory?
922 921 if alldirs is None:
923 922 alldirs = util.dirs(dmap)
924 923 if nf in alldirs:
925 924 if matchedir:
926 925 matchedir(nf)
927 926 notfoundadd(nf)
928 927 else:
929 928 badfn(ff, inst.strerror)
930 929
931 930 # Case insensitive filesystems cannot rely on lstat() failing to detect
932 931 # a case-only rename. Prune the stat object for any file that does not
933 932 # match the case in the filesystem, if there are multiple files that
934 933 # normalize to the same path.
935 934 if match.isexact() and self._checkcase:
936 935 normed = {}
937 936
938 937 for f, st in results.iteritems():
939 938 if st is None:
940 939 continue
941 940
942 941 nc = util.normcase(f)
943 942 paths = normed.get(nc)
944 943
945 944 if paths is None:
946 945 paths = set()
947 946 normed[nc] = paths
948 947
949 948 paths.add(f)
950 949
951 950 for norm, paths in normed.iteritems():
952 951 if len(paths) > 1:
953 952 for path in paths:
954 953 folded = self._discoverpath(path, norm, True, None,
955 954 self._dirfoldmap)
956 955 if path != folded:
957 956 results[path] = None
958 957
959 958 return results, dirsfound, dirsnotfound
960 959
961 960 def walk(self, match, subrepos, unknown, ignored, full=True):
962 961 '''
963 962 Walk recursively through the directory tree, finding all files
964 963 matched by match.
965 964
966 965 If full is False, maybe skip some known-clean files.
967 966
968 967 Return a dict mapping filename to stat-like object (either
969 968 mercurial.osutil.stat instance or return value of os.stat()).
970 969
971 970 '''
972 971 # full is a flag that extensions that hook into walk can use -- this
973 972 # implementation doesn't use it at all. This satisfies the contract
974 973 # because we only guarantee a "maybe".
975 974
976 975 if ignored:
977 976 ignore = util.never
978 977 dirignore = util.never
979 978 elif unknown:
980 979 ignore = self._ignore
981 980 dirignore = self._dirignore
982 981 else:
983 982 # if not unknown and not ignored, drop dir recursion and step 2
984 983 ignore = util.always
985 984 dirignore = util.always
986 985
987 986 matchfn = match.matchfn
988 987 matchalways = match.always()
989 988 matchtdir = match.traversedir
990 989 dmap = self._map
991 listdir = osutil.listdir
990 listdir = util.listdir
992 991 lstat = os.lstat
993 992 dirkind = stat.S_IFDIR
994 993 regkind = stat.S_IFREG
995 994 lnkkind = stat.S_IFLNK
996 995 join = self._join
997 996
998 997 exact = skipstep3 = False
999 998 if match.isexact(): # match.exact
1000 999 exact = True
1001 1000 dirignore = util.always # skip step 2
1002 1001 elif match.prefix(): # match.match, no patterns
1003 1002 skipstep3 = True
1004 1003
1005 1004 if not exact and self._checkcase:
1006 1005 normalize = self._normalize
1007 1006 normalizefile = self._normalizefile
1008 1007 skipstep3 = False
1009 1008 else:
1010 1009 normalize = self._normalize
1011 1010 normalizefile = None
1012 1011
1013 1012 # step 1: find all explicit files
1014 1013 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1015 1014
1016 1015 skipstep3 = skipstep3 and not (work or dirsnotfound)
1017 1016 work = [d for d in work if not dirignore(d[0])]
1018 1017
1019 1018 # step 2: visit subdirectories
1020 1019 def traverse(work, alreadynormed):
1021 1020 wadd = work.append
1022 1021 while work:
1023 1022 nd = work.pop()
1024 1023 if not match.visitdir(nd):
1025 1024 continue
1026 1025 skip = None
1027 1026 if nd == '.':
1028 1027 nd = ''
1029 1028 else:
1030 1029 skip = '.hg'
1031 1030 try:
1032 1031 entries = listdir(join(nd), stat=True, skip=skip)
1033 1032 except OSError as inst:
1034 1033 if inst.errno in (errno.EACCES, errno.ENOENT):
1035 1034 match.bad(self.pathto(nd), inst.strerror)
1036 1035 continue
1037 1036 raise
1038 1037 for f, kind, st in entries:
1039 1038 if normalizefile:
1040 1039 # even though f might be a directory, we're only
1041 1040 # interested in comparing it to files currently in the
1042 1041 # dmap -- therefore normalizefile is enough
1043 1042 nf = normalizefile(nd and (nd + "/" + f) or f, True,
1044 1043 True)
1045 1044 else:
1046 1045 nf = nd and (nd + "/" + f) or f
1047 1046 if nf not in results:
1048 1047 if kind == dirkind:
1049 1048 if not ignore(nf):
1050 1049 if matchtdir:
1051 1050 matchtdir(nf)
1052 1051 wadd(nf)
1053 1052 if nf in dmap and (matchalways or matchfn(nf)):
1054 1053 results[nf] = None
1055 1054 elif kind == regkind or kind == lnkkind:
1056 1055 if nf in dmap:
1057 1056 if matchalways or matchfn(nf):
1058 1057 results[nf] = st
1059 1058 elif ((matchalways or matchfn(nf))
1060 1059 and not ignore(nf)):
1061 1060 # unknown file -- normalize if necessary
1062 1061 if not alreadynormed:
1063 1062 nf = normalize(nf, False, True)
1064 1063 results[nf] = st
1065 1064 elif nf in dmap and (matchalways or matchfn(nf)):
1066 1065 results[nf] = None
1067 1066
1068 1067 for nd, d in work:
1069 1068 # alreadynormed means that processwork doesn't have to do any
1070 1069 # expensive directory normalization
1071 1070 alreadynormed = not normalize or nd == d
1072 1071 traverse([d], alreadynormed)
1073 1072
1074 1073 for s in subrepos:
1075 1074 del results[s]
1076 1075 del results['.hg']
1077 1076
1078 1077 # step 3: visit remaining files from dmap
1079 1078 if not skipstep3 and not exact:
1080 1079 # If a dmap file is not in results yet, it was either
1081 1080 # a) not matching matchfn b) ignored, c) missing, or d) under a
1082 1081 # symlink directory.
1083 1082 if not results and matchalways:
1084 1083 visit = [f for f in dmap]
1085 1084 else:
1086 1085 visit = [f for f in dmap if f not in results and matchfn(f)]
1087 1086 visit.sort()
1088 1087
1089 1088 if unknown:
1090 1089 # unknown == True means we walked all dirs under the roots
1091 1090 # that wasn't ignored, and everything that matched was stat'ed
1092 1091 # and is already in results.
1093 1092 # The rest must thus be ignored or under a symlink.
1094 1093 audit_path = pathutil.pathauditor(self._root)
1095 1094
1096 1095 for nf in iter(visit):
1097 1096 # If a stat for the same file was already added with a
1098 1097 # different case, don't add one for this, since that would
1099 1098 # make it appear as if the file exists under both names
1100 1099 # on disk.
1101 1100 if (normalizefile and
1102 1101 normalizefile(nf, True, True) in results):
1103 1102 results[nf] = None
1104 1103 # Report ignored items in the dmap as long as they are not
1105 1104 # under a symlink directory.
1106 1105 elif audit_path.check(nf):
1107 1106 try:
1108 1107 results[nf] = lstat(join(nf))
1109 1108 # file was just ignored, no links, and exists
1110 1109 except OSError:
1111 1110 # file doesn't exist
1112 1111 results[nf] = None
1113 1112 else:
1114 1113 # It's either missing or under a symlink directory
1115 1114 # which we in this case report as missing
1116 1115 results[nf] = None
1117 1116 else:
1118 1117 # We may not have walked the full directory tree above,
1119 1118 # so stat and check everything we missed.
1120 1119 iv = iter(visit)
1121 1120 for st in util.statfiles([join(i) for i in visit]):
1122 1121 results[next(iv)] = st
1123 1122 return results
1124 1123
1125 1124 def status(self, match, subrepos, ignored, clean, unknown):
1126 1125 '''Determine the status of the working copy relative to the
1127 1126 dirstate and return a pair of (unsure, status), where status is of type
1128 1127 scmutil.status and:
1129 1128
1130 1129 unsure:
1131 1130 files that might have been modified since the dirstate was
1132 1131 written, but need to be read to be sure (size is the same
1133 1132 but mtime differs)
1134 1133 status.modified:
1135 1134 files that have definitely been modified since the dirstate
1136 1135 was written (different size or mode)
1137 1136 status.clean:
1138 1137 files that have definitely not been modified since the
1139 1138 dirstate was written
1140 1139 '''
1141 1140 listignored, listclean, listunknown = ignored, clean, unknown
1142 1141 lookup, modified, added, unknown, ignored = [], [], [], [], []
1143 1142 removed, deleted, clean = [], [], []
1144 1143
1145 1144 dmap = self._map
1146 1145 ladd = lookup.append # aka "unsure"
1147 1146 madd = modified.append
1148 1147 aadd = added.append
1149 1148 uadd = unknown.append
1150 1149 iadd = ignored.append
1151 1150 radd = removed.append
1152 1151 dadd = deleted.append
1153 1152 cadd = clean.append
1154 1153 mexact = match.exact
1155 1154 dirignore = self._dirignore
1156 1155 checkexec = self._checkexec
1157 1156 copymap = self._copymap
1158 1157 lastnormaltime = self._lastnormaltime
1159 1158
1160 1159 # We need to do full walks when either
1161 1160 # - we're listing all clean files, or
1162 1161 # - match.traversedir does something, because match.traversedir should
1163 1162 # be called for every dir in the working dir
1164 1163 full = listclean or match.traversedir is not None
1165 1164 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1166 1165 full=full).iteritems():
1167 1166 if fn not in dmap:
1168 1167 if (listignored or mexact(fn)) and dirignore(fn):
1169 1168 if listignored:
1170 1169 iadd(fn)
1171 1170 else:
1172 1171 uadd(fn)
1173 1172 continue
1174 1173
1175 1174 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1176 1175 # written like that for performance reasons. dmap[fn] is not a
1177 1176 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1178 1177 # opcode has fast paths when the value to be unpacked is a tuple or
1179 1178 # a list, but falls back to creating a full-fledged iterator in
1180 1179 # general. That is much slower than simply accessing and storing the
1181 1180 # tuple members one by one.
1182 1181 t = dmap[fn]
1183 1182 state = t[0]
1184 1183 mode = t[1]
1185 1184 size = t[2]
1186 1185 time = t[3]
1187 1186
1188 1187 if not st and state in "nma":
1189 1188 dadd(fn)
1190 1189 elif state == 'n':
1191 1190 if (size >= 0 and
1192 1191 ((size != st.st_size and size != st.st_size & _rangemask)
1193 1192 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1194 1193 or size == -2 # other parent
1195 1194 or fn in copymap):
1196 1195 madd(fn)
1197 1196 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1198 1197 ladd(fn)
1199 1198 elif st.st_mtime == lastnormaltime:
1200 1199 # fn may have just been marked as normal and it may have
1201 1200 # changed in the same second without changing its size.
1202 1201 # This can happen if we quickly do multiple commits.
1203 1202 # Force lookup, so we don't miss such a racy file change.
1204 1203 ladd(fn)
1205 1204 elif listclean:
1206 1205 cadd(fn)
1207 1206 elif state == 'm':
1208 1207 madd(fn)
1209 1208 elif state == 'a':
1210 1209 aadd(fn)
1211 1210 elif state == 'r':
1212 1211 radd(fn)
1213 1212
1214 1213 return (lookup, scmutil.status(modified, added, removed, deleted,
1215 1214 unknown, ignored, clean))
1216 1215
1217 1216 def matches(self, match):
1218 1217 '''
1219 1218 return files in the dirstate (in whatever state) filtered by match
1220 1219 '''
1221 1220 dmap = self._map
1222 1221 if match.always():
1223 1222 return dmap.keys()
1224 1223 files = match.files()
1225 1224 if match.isexact():
1226 1225 # fast path -- filter the other way around, since typically files is
1227 1226 # much smaller than dmap
1228 1227 return [f for f in files if f in dmap]
1229 1228 if match.prefix() and all(fn in dmap for fn in files):
1230 1229 # fast path -- all the values are known to be files, so just return
1231 1230 # that
1232 1231 return list(files)
1233 1232 return [f for f in dmap if match(f)]
1234 1233
1235 1234 def _actualfilename(self, tr):
1236 1235 if tr:
1237 1236 return self._pendingfilename
1238 1237 else:
1239 1238 return self._filename
1240 1239
1241 1240 def savebackup(self, tr, suffix='', prefix=''):
1242 1241 '''Save current dirstate into backup file with suffix'''
1243 1242 assert len(suffix) > 0 or len(prefix) > 0
1244 1243 filename = self._actualfilename(tr)
1245 1244
1246 1245 # use '_writedirstate' instead of 'write' to write changes certainly,
1247 1246 # because the latter omits writing out if transaction is running.
1248 1247 # output file will be used to create backup of dirstate at this point.
1249 1248 if self._dirty or not self._opener.exists(filename):
1250 1249 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1251 1250 checkambig=True))
1252 1251
1253 1252 if tr:
1254 1253 # ensure that subsequent tr.writepending returns True for
1255 1254 # changes written out above, even if dirstate is never
1256 1255 # changed after this
1257 1256 tr.addfilegenerator('dirstate', (self._filename,),
1258 1257 self._writedirstate, location='plain')
1259 1258
1260 1259 # ensure that pending file written above is unlinked at
1261 1260 # failure, even if tr.writepending isn't invoked until the
1262 1261 # end of this transaction
1263 1262 tr.registertmp(filename, location='plain')
1264 1263
1265 1264 backupname = prefix + self._filename + suffix
1266 1265 assert backupname != filename
1267 1266 self._opener.tryunlink(backupname)
1268 1267 # hardlink backup is okay because _writedirstate is always called
1269 1268 # with an "atomictemp=True" file.
1270 1269 util.copyfile(self._opener.join(filename),
1271 1270 self._opener.join(backupname), hardlink=True)
1272 1271
1273 1272 def restorebackup(self, tr, suffix='', prefix=''):
1274 1273 '''Restore dirstate by backup file with suffix'''
1275 1274 assert len(suffix) > 0 or len(prefix) > 0
1276 1275 # this "invalidate()" prevents "wlock.release()" from writing
1277 1276 # changes of dirstate out after restoring from backup file
1278 1277 self.invalidate()
1279 1278 filename = self._actualfilename(tr)
1280 1279 # using self._filename to avoid having "pending" in the backup filename
1281 1280 self._opener.rename(prefix + self._filename + suffix, filename,
1282 1281 checkambig=True)
1283 1282
1284 1283 def clearbackup(self, tr, suffix='', prefix=''):
1285 1284 '''Clear backup file with suffix'''
1286 1285 assert len(suffix) > 0 or len(prefix) > 0
1287 1286 # using self._filename to avoid having "pending" in the backup filename
1288 1287 self._opener.unlink(prefix + self._filename + suffix)
@@ -1,99 +1,98
1 1 # rcutil.py - utilities about config paths, special config sections etc.
2 2 #
3 3 # Copyright Mercurial Contributors
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11
12 12 from . import (
13 13 encoding,
14 osutil,
15 14 pycompat,
16 15 util,
17 16 )
18 17
19 18 if pycompat.osname == 'nt':
20 19 from . import scmwindows as scmplatform
21 20 else:
22 21 from . import scmposix as scmplatform
23 22
24 23 fallbackpager = scmplatform.fallbackpager
25 24 systemrcpath = scmplatform.systemrcpath
26 25 userrcpath = scmplatform.userrcpath
27 26
28 27 def _expandrcpath(path):
29 28 '''path could be a file or a directory. return a list of file paths'''
30 29 p = util.expandpath(path)
31 30 if os.path.isdir(p):
32 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 33 return [p]
35 34
36 35 def envrcitems(env=None):
37 36 '''Return [(section, name, value, source)] config items.
38 37
39 38 The config items are extracted from environment variables specified by env,
40 39 used to override systemrc, but not userrc.
41 40
42 41 If env is not provided, encoding.environ will be used.
43 42 '''
44 43 if env is None:
45 44 env = encoding.environ
46 45 checklist = [
47 46 ('EDITOR', 'ui', 'editor'),
48 47 ('VISUAL', 'ui', 'editor'),
49 48 ('PAGER', 'pager', 'pager'),
50 49 ]
51 50 result = []
52 51 for envname, section, configname in checklist:
53 52 if envname not in env:
54 53 continue
55 54 result.append((section, configname, env[envname], '$%s' % envname))
56 55 return result
57 56
58 57 def defaultrcpath():
59 58 '''return rc paths in default.d'''
60 59 path = []
61 60 defaultpath = os.path.join(util.datapath, 'default.d')
62 61 if os.path.isdir(defaultpath):
63 62 path = _expandrcpath(defaultpath)
64 63 return path
65 64
66 65 def rccomponents():
67 66 '''return an ordered [(type, obj)] about where to load configs.
68 67
69 68 respect $HGRCPATH. if $HGRCPATH is empty, only .hg/hgrc of current repo is
70 69 used. if $HGRCPATH is not set, the platform default will be used.
71 70
72 71 if a directory is provided, *.rc files under it will be used.
73 72
74 73 type could be either 'path' or 'items', if type is 'path', obj is a string,
75 74 and is the config file path. if type is 'items', obj is a list of (section,
76 75 name, value, source) that should fill the config directly.
77 76 '''
78 77 envrc = ('items', envrcitems())
79 78
80 79 if 'HGRCPATH' in encoding.environ:
81 80 # assume HGRCPATH is all about user configs so environments can be
82 81 # overridden.
83 82 _rccomponents = [envrc]
84 83 for p in encoding.environ['HGRCPATH'].split(pycompat.ospathsep):
85 84 if not p:
86 85 continue
87 86 _rccomponents.extend(('path', p) for p in _expandrcpath(p))
88 87 else:
89 88 normpaths = lambda paths: [('path', os.path.normpath(p)) for p in paths]
90 89 _rccomponents = normpaths(defaultrcpath() + systemrcpath())
91 90 _rccomponents.append(envrc)
92 91 _rccomponents.extend(normpaths(userrcpath()))
93 92 return _rccomponents
94 93
95 94 def defaultpagerenv():
96 95 '''return a dict of default environment variables and their values,
97 96 intended to be set before starting a pager.
98 97 '''
99 98 return {'LESS': 'FRX', 'LV': '-c'}
@@ -1,85 +1,85
1 1 from __future__ import absolute_import
2 2
3 3 import array
4 4 import errno
5 5 import fcntl
6 6 import os
7 7 import sys
8 8
9 9 from . import (
10 10 encoding,
11 osutil,
12 11 pycompat,
12 util,
13 13 )
14 14
15 15 # BSD 'more' escapes ANSI color sequences by default. This can be disabled by
16 16 # $MORE variable, but there's no compatible option with Linux 'more'. Given
17 17 # OS X is widely used and most modern Unix systems would have 'less', setting
18 18 # 'less' as the default seems reasonable.
19 19 fallbackpager = 'less'
20 20
21 21 def _rcfiles(path):
22 22 rcs = [os.path.join(path, 'hgrc')]
23 23 rcdir = os.path.join(path, 'hgrc.d')
24 24 try:
25 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 27 if f.endswith(".rc")])
28 28 except OSError:
29 29 pass
30 30 return rcs
31 31
32 32 def systemrcpath():
33 33 path = []
34 34 if pycompat.sysplatform == 'plan9':
35 35 root = 'lib/mercurial'
36 36 else:
37 37 root = 'etc/mercurial'
38 38 # old mod_python does not set sys.argv
39 39 if len(getattr(sys, 'argv', [])) > 0:
40 40 p = os.path.dirname(os.path.dirname(pycompat.sysargv[0]))
41 41 if p != '/':
42 42 path.extend(_rcfiles(os.path.join(p, root)))
43 43 path.extend(_rcfiles('/' + root))
44 44 return path
45 45
46 46 def userrcpath():
47 47 if pycompat.sysplatform == 'plan9':
48 48 return [encoding.environ['home'] + '/lib/hgrc']
49 49 elif pycompat.sysplatform == 'darwin':
50 50 return [os.path.expanduser('~/.hgrc')]
51 51 else:
52 52 confighome = encoding.environ.get('XDG_CONFIG_HOME')
53 53 if confighome is None or not os.path.isabs(confighome):
54 54 confighome = os.path.expanduser('~/.config')
55 55
56 56 return [os.path.expanduser('~/.hgrc'),
57 57 os.path.join(confighome, 'hg', 'hgrc')]
58 58
59 59 def termsize(ui):
60 60 try:
61 61 import termios
62 62 TIOCGWINSZ = termios.TIOCGWINSZ # unavailable on IRIX (issue3449)
63 63 except (AttributeError, ImportError):
64 64 return 80, 24
65 65
66 66 for dev in (ui.ferr, ui.fout, ui.fin):
67 67 try:
68 68 try:
69 69 fd = dev.fileno()
70 70 except AttributeError:
71 71 continue
72 72 if not os.isatty(fd):
73 73 continue
74 74 arri = fcntl.ioctl(fd, TIOCGWINSZ, '\0' * 8)
75 75 height, width = array.array(r'h', arri)[:2]
76 76 if width > 0 and height > 0:
77 77 return width, height
78 78 except ValueError:
79 79 pass
80 80 except IOError as e:
81 81 if e[0] == errno.EINVAL:
82 82 pass
83 83 else:
84 84 raise
85 85 return 80, 24
@@ -1,62 +1,61
1 1 from __future__ import absolute_import
2 2
3 3 import os
4 4
5 5 from . import (
6 6 encoding,
7 osutil,
8 7 pycompat,
9 8 util,
10 9 win32,
11 10 )
12 11
13 12 try:
14 13 import _winreg as winreg
15 14 winreg.CloseKey
16 15 except ImportError:
17 16 import winreg
18 17
19 18 # MS-DOS 'more' is the only pager available by default on Windows.
20 19 fallbackpager = 'more'
21 20
22 21 def systemrcpath():
23 22 '''return default os-specific hgrc search path'''
24 23 rcpath = []
25 24 filename = util.executablepath()
26 25 # Use mercurial.ini found in directory with hg.exe
27 26 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
28 27 rcpath.append(progrc)
29 28 # Use hgrc.d found in directory with hg.exe
30 29 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
31 30 if os.path.isdir(progrcd):
32 for f, kind in osutil.listdir(progrcd):
31 for f, kind in util.listdir(progrcd):
33 32 if f.endswith('.rc'):
34 33 rcpath.append(os.path.join(progrcd, f))
35 34 # else look for a system rcpath in the registry
36 35 value = util.lookupreg('SOFTWARE\\Mercurial', None,
37 36 winreg.HKEY_LOCAL_MACHINE)
38 37 if not isinstance(value, str) or not value:
39 38 return rcpath
40 39 value = util.localpath(value)
41 40 for p in value.split(pycompat.ospathsep):
42 41 if p.lower().endswith('mercurial.ini'):
43 42 rcpath.append(p)
44 43 elif os.path.isdir(p):
45 for f, kind in osutil.listdir(p):
44 for f, kind in util.listdir(p):
46 45 if f.endswith('.rc'):
47 46 rcpath.append(os.path.join(p, f))
48 47 return rcpath
49 48
50 49 def userrcpath():
51 50 '''return os-specific hgrc search path to the user dir'''
52 51 home = os.path.expanduser('~')
53 52 path = [os.path.join(home, 'mercurial.ini'),
54 53 os.path.join(home, '.hgrc')]
55 54 userprofile = encoding.environ.get('USERPROFILE')
56 55 if userprofile and userprofile != home:
57 56 path.append(os.path.join(userprofile, 'mercurial.ini'))
58 57 path.append(os.path.join(userprofile, '.hgrc'))
59 58 return path
60 59
61 60 def termsize(ui):
62 61 return win32.termsize()
@@ -1,3750 +1,3760
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import bz2
19 19 import calendar
20 20 import codecs
21 21 import collections
22 22 import datetime
23 23 import errno
24 24 import gc
25 25 import hashlib
26 26 import imp
27 27 import os
28 28 import platform as pyplatform
29 29 import re as remod
30 30 import shutil
31 31 import signal
32 32 import socket
33 33 import stat
34 34 import string
35 35 import subprocess
36 36 import sys
37 37 import tempfile
38 38 import textwrap
39 39 import time
40 40 import traceback
41 41 import warnings
42 42 import zlib
43 43
44 44 from . import (
45 45 base85,
46 46 encoding,
47 47 error,
48 48 i18n,
49 49 osutil,
50 50 parsers,
51 51 pycompat,
52 52 )
53 53
54 54 b85decode = base85.b85decode
55 55 b85encode = base85.b85encode
56 56
57 57 cookielib = pycompat.cookielib
58 58 empty = pycompat.empty
59 59 httplib = pycompat.httplib
60 60 httpserver = pycompat.httpserver
61 61 pickle = pycompat.pickle
62 62 queue = pycompat.queue
63 63 socketserver = pycompat.socketserver
64 64 stderr = pycompat.stderr
65 65 stdin = pycompat.stdin
66 66 stdout = pycompat.stdout
67 67 stringio = pycompat.stringio
68 68 urlerr = pycompat.urlerr
69 69 urlreq = pycompat.urlreq
70 70 xmlrpclib = pycompat.xmlrpclib
71 71
72 72 def isatty(fp):
73 73 try:
74 74 return fp.isatty()
75 75 except AttributeError:
76 76 return False
77 77
78 78 # glibc determines buffering on first write to stdout - if we replace a TTY
79 79 # destined stdout with a pipe destined stdout (e.g. pager), we want line
80 80 # buffering
81 81 if isatty(stdout):
82 82 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
83 83
84 84 if pycompat.osname == 'nt':
85 85 from . import windows as platform
86 86 stdout = platform.winstdout(stdout)
87 87 else:
88 88 from . import posix as platform
89 89
90 90 _ = i18n._
91 91
92 92 bindunixsocket = platform.bindunixsocket
93 93 cachestat = platform.cachestat
94 94 checkexec = platform.checkexec
95 95 checklink = platform.checklink
96 96 copymode = platform.copymode
97 97 executablepath = platform.executablepath
98 98 expandglobs = platform.expandglobs
99 99 explainexit = platform.explainexit
100 100 findexe = platform.findexe
101 101 gethgcmd = platform.gethgcmd
102 102 getuser = platform.getuser
103 103 getpid = os.getpid
104 104 groupmembers = platform.groupmembers
105 105 groupname = platform.groupname
106 106 hidewindow = platform.hidewindow
107 107 isexec = platform.isexec
108 108 isowner = platform.isowner
109 listdir = osutil.listdir
109 110 localpath = platform.localpath
110 111 lookupreg = platform.lookupreg
111 112 makedir = platform.makedir
112 113 nlinks = platform.nlinks
113 114 normpath = platform.normpath
114 115 normcase = platform.normcase
115 116 normcasespec = platform.normcasespec
116 117 normcasefallback = platform.normcasefallback
117 118 openhardlinks = platform.openhardlinks
118 119 oslink = platform.oslink
119 120 parsepatchoutput = platform.parsepatchoutput
120 121 pconvert = platform.pconvert
121 122 poll = platform.poll
122 123 popen = platform.popen
123 124 posixfile = platform.posixfile
124 125 quotecommand = platform.quotecommand
125 126 readpipe = platform.readpipe
126 127 rename = platform.rename
127 128 removedirs = platform.removedirs
128 129 samedevice = platform.samedevice
129 130 samefile = platform.samefile
130 131 samestat = platform.samestat
131 132 setbinary = platform.setbinary
132 133 setflags = platform.setflags
133 134 setsignalhandler = platform.setsignalhandler
134 135 shellquote = platform.shellquote
135 136 spawndetached = platform.spawndetached
136 137 split = platform.split
137 138 sshargs = platform.sshargs
138 139 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
139 140 statisexec = platform.statisexec
140 141 statislink = platform.statislink
141 142 testpid = platform.testpid
142 143 umask = platform.umask
143 144 unlink = platform.unlink
144 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 156 # Python compatibility
147 157
148 158 _notset = object()
149 159
150 160 # disable Python's problematic floating point timestamps (issue4836)
151 161 # (Python hypocritically says you shouldn't change this behavior in
152 162 # libraries, and sure enough Mercurial is not a library.)
153 163 os.stat_float_times(False)
154 164
155 165 def safehasattr(thing, attr):
156 166 return getattr(thing, attr, _notset) is not _notset
157 167
158 168 def bitsfrom(container):
159 169 bits = 0
160 170 for bit in container:
161 171 bits |= bit
162 172 return bits
163 173
164 174 # python 2.6 still have deprecation warning enabled by default. We do not want
165 175 # to display anything to standard user so detect if we are running test and
166 176 # only use python deprecation warning in this case.
167 177 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
168 178 if _dowarn:
169 179 # explicitly unfilter our warning for python 2.7
170 180 #
171 181 # The option of setting PYTHONWARNINGS in the test runner was investigated.
172 182 # However, module name set through PYTHONWARNINGS was exactly matched, so
173 183 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
174 184 # makes the whole PYTHONWARNINGS thing useless for our usecase.
175 185 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
176 186 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
177 187 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
178 188
179 189 def nouideprecwarn(msg, version, stacklevel=1):
180 190 """Issue an python native deprecation warning
181 191
182 192 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
183 193 """
184 194 if _dowarn:
185 195 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
186 196 " update your code.)") % version
187 197 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
188 198
189 199 DIGESTS = {
190 200 'md5': hashlib.md5,
191 201 'sha1': hashlib.sha1,
192 202 'sha512': hashlib.sha512,
193 203 }
194 204 # List of digest types from strongest to weakest
195 205 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
196 206
197 207 for k in DIGESTS_BY_STRENGTH:
198 208 assert k in DIGESTS
199 209
200 210 class digester(object):
201 211 """helper to compute digests.
202 212
203 213 This helper can be used to compute one or more digests given their name.
204 214
205 215 >>> d = digester(['md5', 'sha1'])
206 216 >>> d.update('foo')
207 217 >>> [k for k in sorted(d)]
208 218 ['md5', 'sha1']
209 219 >>> d['md5']
210 220 'acbd18db4cc2f85cedef654fccc4a4d8'
211 221 >>> d['sha1']
212 222 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
213 223 >>> digester.preferred(['md5', 'sha1'])
214 224 'sha1'
215 225 """
216 226
217 227 def __init__(self, digests, s=''):
218 228 self._hashes = {}
219 229 for k in digests:
220 230 if k not in DIGESTS:
221 231 raise Abort(_('unknown digest type: %s') % k)
222 232 self._hashes[k] = DIGESTS[k]()
223 233 if s:
224 234 self.update(s)
225 235
226 236 def update(self, data):
227 237 for h in self._hashes.values():
228 238 h.update(data)
229 239
230 240 def __getitem__(self, key):
231 241 if key not in DIGESTS:
232 242 raise Abort(_('unknown digest type: %s') % k)
233 243 return self._hashes[key].hexdigest()
234 244
235 245 def __iter__(self):
236 246 return iter(self._hashes)
237 247
238 248 @staticmethod
239 249 def preferred(supported):
240 250 """returns the strongest digest type in both supported and DIGESTS."""
241 251
242 252 for k in DIGESTS_BY_STRENGTH:
243 253 if k in supported:
244 254 return k
245 255 return None
246 256
247 257 class digestchecker(object):
248 258 """file handle wrapper that additionally checks content against a given
249 259 size and digests.
250 260
251 261 d = digestchecker(fh, size, {'md5': '...'})
252 262
253 263 When multiple digests are given, all of them are validated.
254 264 """
255 265
256 266 def __init__(self, fh, size, digests):
257 267 self._fh = fh
258 268 self._size = size
259 269 self._got = 0
260 270 self._digests = dict(digests)
261 271 self._digester = digester(self._digests.keys())
262 272
263 273 def read(self, length=-1):
264 274 content = self._fh.read(length)
265 275 self._digester.update(content)
266 276 self._got += len(content)
267 277 return content
268 278
269 279 def validate(self):
270 280 if self._size != self._got:
271 281 raise Abort(_('size mismatch: expected %d, got %d') %
272 282 (self._size, self._got))
273 283 for k, v in self._digests.items():
274 284 if v != self._digester[k]:
275 285 # i18n: first parameter is a digest name
276 286 raise Abort(_('%s mismatch: expected %s, got %s') %
277 287 (k, v, self._digester[k]))
278 288
279 289 try:
280 290 buffer = buffer
281 291 except NameError:
282 292 if not pycompat.ispy3:
283 293 def buffer(sliceable, offset=0, length=None):
284 294 if length is not None:
285 295 return sliceable[offset:offset + length]
286 296 return sliceable[offset:]
287 297 else:
288 298 def buffer(sliceable, offset=0, length=None):
289 299 if length is not None:
290 300 return memoryview(sliceable)[offset:offset + length]
291 301 return memoryview(sliceable)[offset:]
292 302
293 303 closefds = pycompat.osname == 'posix'
294 304
295 305 _chunksize = 4096
296 306
297 307 class bufferedinputpipe(object):
298 308 """a manually buffered input pipe
299 309
300 310 Python will not let us use buffered IO and lazy reading with 'polling' at
301 311 the same time. We cannot probe the buffer state and select will not detect
302 312 that data are ready to read if they are already buffered.
303 313
304 314 This class let us work around that by implementing its own buffering
305 315 (allowing efficient readline) while offering a way to know if the buffer is
306 316 empty from the output (allowing collaboration of the buffer with polling).
307 317
308 318 This class lives in the 'util' module because it makes use of the 'os'
309 319 module from the python stdlib.
310 320 """
311 321
312 322 def __init__(self, input):
313 323 self._input = input
314 324 self._buffer = []
315 325 self._eof = False
316 326 self._lenbuf = 0
317 327
318 328 @property
319 329 def hasbuffer(self):
320 330 """True is any data is currently buffered
321 331
322 332 This will be used externally a pre-step for polling IO. If there is
323 333 already data then no polling should be set in place."""
324 334 return bool(self._buffer)
325 335
326 336 @property
327 337 def closed(self):
328 338 return self._input.closed
329 339
330 340 def fileno(self):
331 341 return self._input.fileno()
332 342
333 343 def close(self):
334 344 return self._input.close()
335 345
336 346 def read(self, size):
337 347 while (not self._eof) and (self._lenbuf < size):
338 348 self._fillbuffer()
339 349 return self._frombuffer(size)
340 350
341 351 def readline(self, *args, **kwargs):
342 352 if 1 < len(self._buffer):
343 353 # this should not happen because both read and readline end with a
344 354 # _frombuffer call that collapse it.
345 355 self._buffer = [''.join(self._buffer)]
346 356 self._lenbuf = len(self._buffer[0])
347 357 lfi = -1
348 358 if self._buffer:
349 359 lfi = self._buffer[-1].find('\n')
350 360 while (not self._eof) and lfi < 0:
351 361 self._fillbuffer()
352 362 if self._buffer:
353 363 lfi = self._buffer[-1].find('\n')
354 364 size = lfi + 1
355 365 if lfi < 0: # end of file
356 366 size = self._lenbuf
357 367 elif 1 < len(self._buffer):
358 368 # we need to take previous chunks into account
359 369 size += self._lenbuf - len(self._buffer[-1])
360 370 return self._frombuffer(size)
361 371
362 372 def _frombuffer(self, size):
363 373 """return at most 'size' data from the buffer
364 374
365 375 The data are removed from the buffer."""
366 376 if size == 0 or not self._buffer:
367 377 return ''
368 378 buf = self._buffer[0]
369 379 if 1 < len(self._buffer):
370 380 buf = ''.join(self._buffer)
371 381
372 382 data = buf[:size]
373 383 buf = buf[len(data):]
374 384 if buf:
375 385 self._buffer = [buf]
376 386 self._lenbuf = len(buf)
377 387 else:
378 388 self._buffer = []
379 389 self._lenbuf = 0
380 390 return data
381 391
382 392 def _fillbuffer(self):
383 393 """read data to the buffer"""
384 394 data = os.read(self._input.fileno(), _chunksize)
385 395 if not data:
386 396 self._eof = True
387 397 else:
388 398 self._lenbuf += len(data)
389 399 self._buffer.append(data)
390 400
391 401 def popen2(cmd, env=None, newlines=False):
392 402 # Setting bufsize to -1 lets the system decide the buffer size.
393 403 # The default for bufsize is 0, meaning unbuffered. This leads to
394 404 # poor performance on Mac OS X: http://bugs.python.org/issue4194
395 405 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
396 406 close_fds=closefds,
397 407 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
398 408 universal_newlines=newlines,
399 409 env=env)
400 410 return p.stdin, p.stdout
401 411
402 412 def popen3(cmd, env=None, newlines=False):
403 413 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
404 414 return stdin, stdout, stderr
405 415
406 416 def popen4(cmd, env=None, newlines=False, bufsize=-1):
407 417 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
408 418 close_fds=closefds,
409 419 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
410 420 stderr=subprocess.PIPE,
411 421 universal_newlines=newlines,
412 422 env=env)
413 423 return p.stdin, p.stdout, p.stderr, p
414 424
415 425 def version():
416 426 """Return version information if available."""
417 427 try:
418 428 from . import __version__
419 429 return __version__.version
420 430 except ImportError:
421 431 return 'unknown'
422 432
423 433 def versiontuple(v=None, n=4):
424 434 """Parses a Mercurial version string into an N-tuple.
425 435
426 436 The version string to be parsed is specified with the ``v`` argument.
427 437 If it isn't defined, the current Mercurial version string will be parsed.
428 438
429 439 ``n`` can be 2, 3, or 4. Here is how some version strings map to
430 440 returned values:
431 441
432 442 >>> v = '3.6.1+190-df9b73d2d444'
433 443 >>> versiontuple(v, 2)
434 444 (3, 6)
435 445 >>> versiontuple(v, 3)
436 446 (3, 6, 1)
437 447 >>> versiontuple(v, 4)
438 448 (3, 6, 1, '190-df9b73d2d444')
439 449
440 450 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
441 451 (3, 6, 1, '190-df9b73d2d444+20151118')
442 452
443 453 >>> v = '3.6'
444 454 >>> versiontuple(v, 2)
445 455 (3, 6)
446 456 >>> versiontuple(v, 3)
447 457 (3, 6, None)
448 458 >>> versiontuple(v, 4)
449 459 (3, 6, None, None)
450 460
451 461 >>> v = '3.9-rc'
452 462 >>> versiontuple(v, 2)
453 463 (3, 9)
454 464 >>> versiontuple(v, 3)
455 465 (3, 9, None)
456 466 >>> versiontuple(v, 4)
457 467 (3, 9, None, 'rc')
458 468
459 469 >>> v = '3.9-rc+2-02a8fea4289b'
460 470 >>> versiontuple(v, 2)
461 471 (3, 9)
462 472 >>> versiontuple(v, 3)
463 473 (3, 9, None)
464 474 >>> versiontuple(v, 4)
465 475 (3, 9, None, 'rc+2-02a8fea4289b')
466 476 """
467 477 if not v:
468 478 v = version()
469 479 parts = remod.split('[\+-]', v, 1)
470 480 if len(parts) == 1:
471 481 vparts, extra = parts[0], None
472 482 else:
473 483 vparts, extra = parts
474 484
475 485 vints = []
476 486 for i in vparts.split('.'):
477 487 try:
478 488 vints.append(int(i))
479 489 except ValueError:
480 490 break
481 491 # (3, 6) -> (3, 6, None)
482 492 while len(vints) < 3:
483 493 vints.append(None)
484 494
485 495 if n == 2:
486 496 return (vints[0], vints[1])
487 497 if n == 3:
488 498 return (vints[0], vints[1], vints[2])
489 499 if n == 4:
490 500 return (vints[0], vints[1], vints[2], extra)
491 501
492 502 # used by parsedate
493 503 defaultdateformats = (
494 504 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
495 505 '%Y-%m-%dT%H:%M', # without seconds
496 506 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
497 507 '%Y-%m-%dT%H%M', # without seconds
498 508 '%Y-%m-%d %H:%M:%S', # our common legal variant
499 509 '%Y-%m-%d %H:%M', # without seconds
500 510 '%Y-%m-%d %H%M%S', # without :
501 511 '%Y-%m-%d %H%M', # without seconds
502 512 '%Y-%m-%d %I:%M:%S%p',
503 513 '%Y-%m-%d %H:%M',
504 514 '%Y-%m-%d %I:%M%p',
505 515 '%Y-%m-%d',
506 516 '%m-%d',
507 517 '%m/%d',
508 518 '%m/%d/%y',
509 519 '%m/%d/%Y',
510 520 '%a %b %d %H:%M:%S %Y',
511 521 '%a %b %d %I:%M:%S%p %Y',
512 522 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
513 523 '%b %d %H:%M:%S %Y',
514 524 '%b %d %I:%M:%S%p %Y',
515 525 '%b %d %H:%M:%S',
516 526 '%b %d %I:%M:%S%p',
517 527 '%b %d %H:%M',
518 528 '%b %d %I:%M%p',
519 529 '%b %d %Y',
520 530 '%b %d',
521 531 '%H:%M:%S',
522 532 '%I:%M:%S%p',
523 533 '%H:%M',
524 534 '%I:%M%p',
525 535 )
526 536
527 537 extendeddateformats = defaultdateformats + (
528 538 "%Y",
529 539 "%Y-%m",
530 540 "%b",
531 541 "%b %Y",
532 542 )
533 543
534 544 def cachefunc(func):
535 545 '''cache the result of function calls'''
536 546 # XXX doesn't handle keywords args
537 547 if func.__code__.co_argcount == 0:
538 548 cache = []
539 549 def f():
540 550 if len(cache) == 0:
541 551 cache.append(func())
542 552 return cache[0]
543 553 return f
544 554 cache = {}
545 555 if func.__code__.co_argcount == 1:
546 556 # we gain a small amount of time because
547 557 # we don't need to pack/unpack the list
548 558 def f(arg):
549 559 if arg not in cache:
550 560 cache[arg] = func(arg)
551 561 return cache[arg]
552 562 else:
553 563 def f(*args):
554 564 if args not in cache:
555 565 cache[args] = func(*args)
556 566 return cache[args]
557 567
558 568 return f
559 569
560 570 class sortdict(dict):
561 571 '''a simple sorted dictionary'''
562 572 def __init__(self, data=None):
563 573 self._list = []
564 574 if data:
565 575 self.update(data)
566 576 def copy(self):
567 577 return sortdict(self)
568 578 def __setitem__(self, key, val):
569 579 if key in self:
570 580 self._list.remove(key)
571 581 self._list.append(key)
572 582 dict.__setitem__(self, key, val)
573 583 def __iter__(self):
574 584 return self._list.__iter__()
575 585 def update(self, src):
576 586 if isinstance(src, dict):
577 587 src = src.iteritems()
578 588 for k, v in src:
579 589 self[k] = v
580 590 def clear(self):
581 591 dict.clear(self)
582 592 self._list = []
583 593 def items(self):
584 594 return [(k, self[k]) for k in self._list]
585 595 def __delitem__(self, key):
586 596 dict.__delitem__(self, key)
587 597 self._list.remove(key)
588 598 def pop(self, key, *args, **kwargs):
589 599 try:
590 600 self._list.remove(key)
591 601 except ValueError:
592 602 pass
593 603 return dict.pop(self, key, *args, **kwargs)
594 604 def keys(self):
595 605 return self._list[:]
596 606 def iterkeys(self):
597 607 return self._list.__iter__()
598 608 def iteritems(self):
599 609 for k in self._list:
600 610 yield k, self[k]
601 611 def insert(self, index, key, val):
602 612 self._list.insert(index, key)
603 613 dict.__setitem__(self, key, val)
604 614 def __repr__(self):
605 615 if not self:
606 616 return '%s()' % self.__class__.__name__
607 617 return '%s(%r)' % (self.__class__.__name__, self.items())
608 618
609 619 class _lrucachenode(object):
610 620 """A node in a doubly linked list.
611 621
612 622 Holds a reference to nodes on either side as well as a key-value
613 623 pair for the dictionary entry.
614 624 """
615 625 __slots__ = (u'next', u'prev', u'key', u'value')
616 626
617 627 def __init__(self):
618 628 self.next = None
619 629 self.prev = None
620 630
621 631 self.key = _notset
622 632 self.value = None
623 633
624 634 def markempty(self):
625 635 """Mark the node as emptied."""
626 636 self.key = _notset
627 637
628 638 class lrucachedict(object):
629 639 """Dict that caches most recent accesses and sets.
630 640
631 641 The dict consists of an actual backing dict - indexed by original
632 642 key - and a doubly linked circular list defining the order of entries in
633 643 the cache.
634 644
635 645 The head node is the newest entry in the cache. If the cache is full,
636 646 we recycle head.prev and make it the new head. Cache accesses result in
637 647 the node being moved to before the existing head and being marked as the
638 648 new head node.
639 649 """
640 650 def __init__(self, max):
641 651 self._cache = {}
642 652
643 653 self._head = head = _lrucachenode()
644 654 head.prev = head
645 655 head.next = head
646 656 self._size = 1
647 657 self._capacity = max
648 658
649 659 def __len__(self):
650 660 return len(self._cache)
651 661
652 662 def __contains__(self, k):
653 663 return k in self._cache
654 664
655 665 def __iter__(self):
656 666 # We don't have to iterate in cache order, but why not.
657 667 n = self._head
658 668 for i in range(len(self._cache)):
659 669 yield n.key
660 670 n = n.next
661 671
662 672 def __getitem__(self, k):
663 673 node = self._cache[k]
664 674 self._movetohead(node)
665 675 return node.value
666 676
667 677 def __setitem__(self, k, v):
668 678 node = self._cache.get(k)
669 679 # Replace existing value and mark as newest.
670 680 if node is not None:
671 681 node.value = v
672 682 self._movetohead(node)
673 683 return
674 684
675 685 if self._size < self._capacity:
676 686 node = self._addcapacity()
677 687 else:
678 688 # Grab the last/oldest item.
679 689 node = self._head.prev
680 690
681 691 # At capacity. Kill the old entry.
682 692 if node.key is not _notset:
683 693 del self._cache[node.key]
684 694
685 695 node.key = k
686 696 node.value = v
687 697 self._cache[k] = node
688 698 # And mark it as newest entry. No need to adjust order since it
689 699 # is already self._head.prev.
690 700 self._head = node
691 701
692 702 def __delitem__(self, k):
693 703 node = self._cache.pop(k)
694 704 node.markempty()
695 705
696 706 # Temporarily mark as newest item before re-adjusting head to make
697 707 # this node the oldest item.
698 708 self._movetohead(node)
699 709 self._head = node.next
700 710
701 711 # Additional dict methods.
702 712
703 713 def get(self, k, default=None):
704 714 try:
705 715 return self._cache[k].value
706 716 except KeyError:
707 717 return default
708 718
709 719 def clear(self):
710 720 n = self._head
711 721 while n.key is not _notset:
712 722 n.markempty()
713 723 n = n.next
714 724
715 725 self._cache.clear()
716 726
717 727 def copy(self):
718 728 result = lrucachedict(self._capacity)
719 729 n = self._head.prev
720 730 # Iterate in oldest-to-newest order, so the copy has the right ordering
721 731 for i in range(len(self._cache)):
722 732 result[n.key] = n.value
723 733 n = n.prev
724 734 return result
725 735
726 736 def _movetohead(self, node):
727 737 """Mark a node as the newest, making it the new head.
728 738
729 739 When a node is accessed, it becomes the freshest entry in the LRU
730 740 list, which is denoted by self._head.
731 741
732 742 Visually, let's make ``N`` the new head node (* denotes head):
733 743
734 744 previous/oldest <-> head <-> next/next newest
735 745
736 746 ----<->--- A* ---<->-----
737 747 | |
738 748 E <-> D <-> N <-> C <-> B
739 749
740 750 To:
741 751
742 752 ----<->--- N* ---<->-----
743 753 | |
744 754 E <-> D <-> C <-> B <-> A
745 755
746 756 This requires the following moves:
747 757
748 758 C.next = D (node.prev.next = node.next)
749 759 D.prev = C (node.next.prev = node.prev)
750 760 E.next = N (head.prev.next = node)
751 761 N.prev = E (node.prev = head.prev)
752 762 N.next = A (node.next = head)
753 763 A.prev = N (head.prev = node)
754 764 """
755 765 head = self._head
756 766 # C.next = D
757 767 node.prev.next = node.next
758 768 # D.prev = C
759 769 node.next.prev = node.prev
760 770 # N.prev = E
761 771 node.prev = head.prev
762 772 # N.next = A
763 773 # It is tempting to do just "head" here, however if node is
764 774 # adjacent to head, this will do bad things.
765 775 node.next = head.prev.next
766 776 # E.next = N
767 777 node.next.prev = node
768 778 # A.prev = N
769 779 node.prev.next = node
770 780
771 781 self._head = node
772 782
773 783 def _addcapacity(self):
774 784 """Add a node to the circular linked list.
775 785
776 786 The new node is inserted before the head node.
777 787 """
778 788 head = self._head
779 789 node = _lrucachenode()
780 790 head.prev.next = node
781 791 node.prev = head.prev
782 792 node.next = head
783 793 head.prev = node
784 794 self._size += 1
785 795 return node
786 796
787 797 def lrucachefunc(func):
788 798 '''cache most recent results of function calls'''
789 799 cache = {}
790 800 order = collections.deque()
791 801 if func.__code__.co_argcount == 1:
792 802 def f(arg):
793 803 if arg not in cache:
794 804 if len(cache) > 20:
795 805 del cache[order.popleft()]
796 806 cache[arg] = func(arg)
797 807 else:
798 808 order.remove(arg)
799 809 order.append(arg)
800 810 return cache[arg]
801 811 else:
802 812 def f(*args):
803 813 if args not in cache:
804 814 if len(cache) > 20:
805 815 del cache[order.popleft()]
806 816 cache[args] = func(*args)
807 817 else:
808 818 order.remove(args)
809 819 order.append(args)
810 820 return cache[args]
811 821
812 822 return f
813 823
814 824 class propertycache(object):
815 825 def __init__(self, func):
816 826 self.func = func
817 827 self.name = func.__name__
818 828 def __get__(self, obj, type=None):
819 829 result = self.func(obj)
820 830 self.cachevalue(obj, result)
821 831 return result
822 832
823 833 def cachevalue(self, obj, value):
824 834 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
825 835 obj.__dict__[self.name] = value
826 836
827 837 def pipefilter(s, cmd):
828 838 '''filter string S through command CMD, returning its output'''
829 839 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
830 840 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
831 841 pout, perr = p.communicate(s)
832 842 return pout
833 843
834 844 def tempfilter(s, cmd):
835 845 '''filter string S through a pair of temporary files with CMD.
836 846 CMD is used as a template to create the real command to be run,
837 847 with the strings INFILE and OUTFILE replaced by the real names of
838 848 the temporary files generated.'''
839 849 inname, outname = None, None
840 850 try:
841 851 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
842 852 fp = os.fdopen(infd, pycompat.sysstr('wb'))
843 853 fp.write(s)
844 854 fp.close()
845 855 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
846 856 os.close(outfd)
847 857 cmd = cmd.replace('INFILE', inname)
848 858 cmd = cmd.replace('OUTFILE', outname)
849 859 code = os.system(cmd)
850 860 if pycompat.sysplatform == 'OpenVMS' and code & 1:
851 861 code = 0
852 862 if code:
853 863 raise Abort(_("command '%s' failed: %s") %
854 864 (cmd, explainexit(code)))
855 865 return readfile(outname)
856 866 finally:
857 867 try:
858 868 if inname:
859 869 os.unlink(inname)
860 870 except OSError:
861 871 pass
862 872 try:
863 873 if outname:
864 874 os.unlink(outname)
865 875 except OSError:
866 876 pass
867 877
868 878 filtertable = {
869 879 'tempfile:': tempfilter,
870 880 'pipe:': pipefilter,
871 881 }
872 882
873 883 def filter(s, cmd):
874 884 "filter a string through a command that transforms its input to its output"
875 885 for name, fn in filtertable.iteritems():
876 886 if cmd.startswith(name):
877 887 return fn(s, cmd[len(name):].lstrip())
878 888 return pipefilter(s, cmd)
879 889
880 890 def binary(s):
881 891 """return true if a string is binary data"""
882 892 return bool(s and '\0' in s)
883 893
884 894 def increasingchunks(source, min=1024, max=65536):
885 895 '''return no less than min bytes per chunk while data remains,
886 896 doubling min after each chunk until it reaches max'''
887 897 def log2(x):
888 898 if not x:
889 899 return 0
890 900 i = 0
891 901 while x:
892 902 x >>= 1
893 903 i += 1
894 904 return i - 1
895 905
896 906 buf = []
897 907 blen = 0
898 908 for chunk in source:
899 909 buf.append(chunk)
900 910 blen += len(chunk)
901 911 if blen >= min:
902 912 if min < max:
903 913 min = min << 1
904 914 nmin = 1 << log2(blen)
905 915 if nmin > min:
906 916 min = nmin
907 917 if min > max:
908 918 min = max
909 919 yield ''.join(buf)
910 920 blen = 0
911 921 buf = []
912 922 if buf:
913 923 yield ''.join(buf)
914 924
915 925 Abort = error.Abort
916 926
917 927 def always(fn):
918 928 return True
919 929
920 930 def never(fn):
921 931 return False
922 932
923 933 def nogc(func):
924 934 """disable garbage collector
925 935
926 936 Python's garbage collector triggers a GC each time a certain number of
927 937 container objects (the number being defined by gc.get_threshold()) are
928 938 allocated even when marked not to be tracked by the collector. Tracking has
929 939 no effect on when GCs are triggered, only on what objects the GC looks
930 940 into. As a workaround, disable GC while building complex (huge)
931 941 containers.
932 942
933 943 This garbage collector issue have been fixed in 2.7.
934 944 """
935 945 if sys.version_info >= (2, 7):
936 946 return func
937 947 def wrapper(*args, **kwargs):
938 948 gcenabled = gc.isenabled()
939 949 gc.disable()
940 950 try:
941 951 return func(*args, **kwargs)
942 952 finally:
943 953 if gcenabled:
944 954 gc.enable()
945 955 return wrapper
946 956
947 957 def pathto(root, n1, n2):
948 958 '''return the relative path from one place to another.
949 959 root should use os.sep to separate directories
950 960 n1 should use os.sep to separate directories
951 961 n2 should use "/" to separate directories
952 962 returns an os.sep-separated path.
953 963
954 964 If n1 is a relative path, it's assumed it's
955 965 relative to root.
956 966 n2 should always be relative to root.
957 967 '''
958 968 if not n1:
959 969 return localpath(n2)
960 970 if os.path.isabs(n1):
961 971 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
962 972 return os.path.join(root, localpath(n2))
963 973 n2 = '/'.join((pconvert(root), n2))
964 974 a, b = splitpath(n1), n2.split('/')
965 975 a.reverse()
966 976 b.reverse()
967 977 while a and b and a[-1] == b[-1]:
968 978 a.pop()
969 979 b.pop()
970 980 b.reverse()
971 981 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
972 982
973 983 def mainfrozen():
974 984 """return True if we are a frozen executable.
975 985
976 986 The code supports py2exe (most common, Windows only) and tools/freeze
977 987 (portable, not much used).
978 988 """
979 989 return (safehasattr(sys, "frozen") or # new py2exe
980 990 safehasattr(sys, "importers") or # old py2exe
981 991 imp.is_frozen(u"__main__")) # tools/freeze
982 992
983 993 # the location of data files matching the source code
984 994 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
985 995 # executable version (py2exe) doesn't support __file__
986 996 datapath = os.path.dirname(pycompat.sysexecutable)
987 997 else:
988 998 datapath = os.path.dirname(pycompat.fsencode(__file__))
989 999
990 1000 i18n.setdatapath(datapath)
991 1001
992 1002 _hgexecutable = None
993 1003
994 1004 def hgexecutable():
995 1005 """return location of the 'hg' executable.
996 1006
997 1007 Defaults to $HG or 'hg' in the search path.
998 1008 """
999 1009 if _hgexecutable is None:
1000 1010 hg = encoding.environ.get('HG')
1001 1011 mainmod = sys.modules[pycompat.sysstr('__main__')]
1002 1012 if hg:
1003 1013 _sethgexecutable(hg)
1004 1014 elif mainfrozen():
1005 1015 if getattr(sys, 'frozen', None) == 'macosx_app':
1006 1016 # Env variable set by py2app
1007 1017 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1008 1018 else:
1009 1019 _sethgexecutable(pycompat.sysexecutable)
1010 1020 elif (os.path.basename(
1011 1021 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1012 1022 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1013 1023 else:
1014 1024 exe = findexe('hg') or os.path.basename(sys.argv[0])
1015 1025 _sethgexecutable(exe)
1016 1026 return _hgexecutable
1017 1027
1018 1028 def _sethgexecutable(path):
1019 1029 """set location of the 'hg' executable"""
1020 1030 global _hgexecutable
1021 1031 _hgexecutable = path
1022 1032
1023 1033 def _isstdout(f):
1024 1034 fileno = getattr(f, 'fileno', None)
1025 1035 return fileno and fileno() == sys.__stdout__.fileno()
1026 1036
1027 1037 def shellenviron(environ=None):
1028 1038 """return environ with optional override, useful for shelling out"""
1029 1039 def py2shell(val):
1030 1040 'convert python object into string that is useful to shell'
1031 1041 if val is None or val is False:
1032 1042 return '0'
1033 1043 if val is True:
1034 1044 return '1'
1035 1045 return str(val)
1036 1046 env = dict(encoding.environ)
1037 1047 if environ:
1038 1048 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1039 1049 env['HG'] = hgexecutable()
1040 1050 return env
1041 1051
1042 1052 def system(cmd, environ=None, cwd=None, out=None):
1043 1053 '''enhanced shell command execution.
1044 1054 run with environment maybe modified, maybe in different dir.
1045 1055
1046 1056 if out is specified, it is assumed to be a file-like object that has a
1047 1057 write() method. stdout and stderr will be redirected to out.'''
1048 1058 try:
1049 1059 stdout.flush()
1050 1060 except Exception:
1051 1061 pass
1052 1062 cmd = quotecommand(cmd)
1053 1063 if pycompat.sysplatform == 'plan9' and (sys.version_info[0] == 2
1054 1064 and sys.version_info[1] < 7):
1055 1065 # subprocess kludge to work around issues in half-baked Python
1056 1066 # ports, notably bichued/python:
1057 1067 if not cwd is None:
1058 1068 os.chdir(cwd)
1059 1069 rc = os.system(cmd)
1060 1070 else:
1061 1071 env = shellenviron(environ)
1062 1072 if out is None or _isstdout(out):
1063 1073 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1064 1074 env=env, cwd=cwd)
1065 1075 else:
1066 1076 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1067 1077 env=env, cwd=cwd, stdout=subprocess.PIPE,
1068 1078 stderr=subprocess.STDOUT)
1069 1079 for line in iter(proc.stdout.readline, ''):
1070 1080 out.write(line)
1071 1081 proc.wait()
1072 1082 rc = proc.returncode
1073 1083 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1074 1084 rc = 0
1075 1085 return rc
1076 1086
1077 1087 def checksignature(func):
1078 1088 '''wrap a function with code to check for calling errors'''
1079 1089 def check(*args, **kwargs):
1080 1090 try:
1081 1091 return func(*args, **kwargs)
1082 1092 except TypeError:
1083 1093 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1084 1094 raise error.SignatureError
1085 1095 raise
1086 1096
1087 1097 return check
1088 1098
1089 1099 # a whilelist of known filesystems where hardlink works reliably
1090 1100 _hardlinkfswhitelist = set([
1091 1101 'btrfs',
1092 1102 'ext2',
1093 1103 'ext3',
1094 1104 'ext4',
1095 1105 'hfs',
1096 1106 'jfs',
1097 1107 'reiserfs',
1098 1108 'tmpfs',
1099 1109 'ufs',
1100 1110 'xfs',
1101 1111 'zfs',
1102 1112 ])
1103 1113
1104 1114 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1105 1115 '''copy a file, preserving mode and optionally other stat info like
1106 1116 atime/mtime
1107 1117
1108 1118 checkambig argument is used with filestat, and is useful only if
1109 1119 destination file is guarded by any lock (e.g. repo.lock or
1110 1120 repo.wlock).
1111 1121
1112 1122 copystat and checkambig should be exclusive.
1113 1123 '''
1114 1124 assert not (copystat and checkambig)
1115 1125 oldstat = None
1116 1126 if os.path.lexists(dest):
1117 1127 if checkambig:
1118 1128 oldstat = checkambig and filestat(dest)
1119 1129 unlink(dest)
1120 1130 if hardlink:
1121 1131 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1122 1132 # unless we are confident that dest is on a whitelisted filesystem.
1123 1133 try:
1124 1134 fstype = getfstype(os.path.dirname(dest))
1125 1135 except OSError:
1126 1136 fstype = None
1127 1137 if fstype not in _hardlinkfswhitelist:
1128 1138 hardlink = False
1129 1139 if hardlink:
1130 1140 try:
1131 1141 oslink(src, dest)
1132 1142 return
1133 1143 except (IOError, OSError):
1134 1144 pass # fall back to normal copy
1135 1145 if os.path.islink(src):
1136 1146 os.symlink(os.readlink(src), dest)
1137 1147 # copytime is ignored for symlinks, but in general copytime isn't needed
1138 1148 # for them anyway
1139 1149 else:
1140 1150 try:
1141 1151 shutil.copyfile(src, dest)
1142 1152 if copystat:
1143 1153 # copystat also copies mode
1144 1154 shutil.copystat(src, dest)
1145 1155 else:
1146 1156 shutil.copymode(src, dest)
1147 1157 if oldstat and oldstat.stat:
1148 1158 newstat = filestat(dest)
1149 1159 if newstat.isambig(oldstat):
1150 1160 # stat of copied file is ambiguous to original one
1151 1161 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1152 1162 os.utime(dest, (advanced, advanced))
1153 1163 except shutil.Error as inst:
1154 1164 raise Abort(str(inst))
1155 1165
1156 1166 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1157 1167 """Copy a directory tree using hardlinks if possible."""
1158 1168 num = 0
1159 1169
1160 1170 gettopic = lambda: hardlink and _('linking') or _('copying')
1161 1171
1162 1172 if os.path.isdir(src):
1163 1173 if hardlink is None:
1164 1174 hardlink = (os.stat(src).st_dev ==
1165 1175 os.stat(os.path.dirname(dst)).st_dev)
1166 1176 topic = gettopic()
1167 1177 os.mkdir(dst)
1168 for name, kind in osutil.listdir(src):
1178 for name, kind in listdir(src):
1169 1179 srcname = os.path.join(src, name)
1170 1180 dstname = os.path.join(dst, name)
1171 1181 def nprog(t, pos):
1172 1182 if pos is not None:
1173 1183 return progress(t, pos + num)
1174 1184 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1175 1185 num += n
1176 1186 else:
1177 1187 if hardlink is None:
1178 1188 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1179 1189 os.stat(os.path.dirname(dst)).st_dev)
1180 1190 topic = gettopic()
1181 1191
1182 1192 if hardlink:
1183 1193 try:
1184 1194 oslink(src, dst)
1185 1195 except (IOError, OSError):
1186 1196 hardlink = False
1187 1197 shutil.copy(src, dst)
1188 1198 else:
1189 1199 shutil.copy(src, dst)
1190 1200 num += 1
1191 1201 progress(topic, num)
1192 1202 progress(topic, None)
1193 1203
1194 1204 return hardlink, num
1195 1205
1196 1206 _winreservednames = '''con prn aux nul
1197 1207 com1 com2 com3 com4 com5 com6 com7 com8 com9
1198 1208 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1199 1209 _winreservedchars = ':*?"<>|'
1200 1210 def checkwinfilename(path):
1201 1211 r'''Check that the base-relative path is a valid filename on Windows.
1202 1212 Returns None if the path is ok, or a UI string describing the problem.
1203 1213
1204 1214 >>> checkwinfilename("just/a/normal/path")
1205 1215 >>> checkwinfilename("foo/bar/con.xml")
1206 1216 "filename contains 'con', which is reserved on Windows"
1207 1217 >>> checkwinfilename("foo/con.xml/bar")
1208 1218 "filename contains 'con', which is reserved on Windows"
1209 1219 >>> checkwinfilename("foo/bar/xml.con")
1210 1220 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1211 1221 "filename contains 'AUX', which is reserved on Windows"
1212 1222 >>> checkwinfilename("foo/bar/bla:.txt")
1213 1223 "filename contains ':', which is reserved on Windows"
1214 1224 >>> checkwinfilename("foo/bar/b\07la.txt")
1215 1225 "filename contains '\\x07', which is invalid on Windows"
1216 1226 >>> checkwinfilename("foo/bar/bla ")
1217 1227 "filename ends with ' ', which is not allowed on Windows"
1218 1228 >>> checkwinfilename("../bar")
1219 1229 >>> checkwinfilename("foo\\")
1220 1230 "filename ends with '\\', which is invalid on Windows"
1221 1231 >>> checkwinfilename("foo\\/bar")
1222 1232 "directory name ends with '\\', which is invalid on Windows"
1223 1233 '''
1224 1234 if path.endswith('\\'):
1225 1235 return _("filename ends with '\\', which is invalid on Windows")
1226 1236 if '\\/' in path:
1227 1237 return _("directory name ends with '\\', which is invalid on Windows")
1228 1238 for n in path.replace('\\', '/').split('/'):
1229 1239 if not n:
1230 1240 continue
1231 1241 for c in pycompat.bytestr(n):
1232 1242 if c in _winreservedchars:
1233 1243 return _("filename contains '%s', which is reserved "
1234 1244 "on Windows") % c
1235 1245 if ord(c) <= 31:
1236 1246 return _("filename contains %r, which is invalid "
1237 1247 "on Windows") % c
1238 1248 base = n.split('.')[0]
1239 1249 if base and base.lower() in _winreservednames:
1240 1250 return _("filename contains '%s', which is reserved "
1241 1251 "on Windows") % base
1242 1252 t = n[-1]
1243 1253 if t in '. ' and n not in '..':
1244 1254 return _("filename ends with '%s', which is not allowed "
1245 1255 "on Windows") % t
1246 1256
1247 1257 if pycompat.osname == 'nt':
1248 1258 checkosfilename = checkwinfilename
1249 1259 timer = time.clock
1250 1260 else:
1251 1261 checkosfilename = platform.checkosfilename
1252 1262 timer = time.time
1253 1263
1254 1264 if safehasattr(time, "perf_counter"):
1255 1265 timer = time.perf_counter
1256 1266
1257 1267 def makelock(info, pathname):
1258 1268 try:
1259 1269 return os.symlink(info, pathname)
1260 1270 except OSError as why:
1261 1271 if why.errno == errno.EEXIST:
1262 1272 raise
1263 1273 except AttributeError: # no symlink in os
1264 1274 pass
1265 1275
1266 1276 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1267 1277 os.write(ld, info)
1268 1278 os.close(ld)
1269 1279
1270 1280 def readlock(pathname):
1271 1281 try:
1272 1282 return os.readlink(pathname)
1273 1283 except OSError as why:
1274 1284 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1275 1285 raise
1276 1286 except AttributeError: # no symlink in os
1277 1287 pass
1278 1288 fp = posixfile(pathname)
1279 1289 r = fp.read()
1280 1290 fp.close()
1281 1291 return r
1282 1292
1283 1293 def fstat(fp):
1284 1294 '''stat file object that may not have fileno method.'''
1285 1295 try:
1286 1296 return os.fstat(fp.fileno())
1287 1297 except AttributeError:
1288 1298 return os.stat(fp.name)
1289 1299
1290 1300 # File system features
1291 1301
1292 1302 def fscasesensitive(path):
1293 1303 """
1294 1304 Return true if the given path is on a case-sensitive filesystem
1295 1305
1296 1306 Requires a path (like /foo/.hg) ending with a foldable final
1297 1307 directory component.
1298 1308 """
1299 1309 s1 = os.lstat(path)
1300 1310 d, b = os.path.split(path)
1301 1311 b2 = b.upper()
1302 1312 if b == b2:
1303 1313 b2 = b.lower()
1304 1314 if b == b2:
1305 1315 return True # no evidence against case sensitivity
1306 1316 p2 = os.path.join(d, b2)
1307 1317 try:
1308 1318 s2 = os.lstat(p2)
1309 1319 if s2 == s1:
1310 1320 return False
1311 1321 return True
1312 1322 except OSError:
1313 1323 return True
1314 1324
1315 1325 try:
1316 1326 import re2
1317 1327 _re2 = None
1318 1328 except ImportError:
1319 1329 _re2 = False
1320 1330
1321 1331 class _re(object):
1322 1332 def _checkre2(self):
1323 1333 global _re2
1324 1334 try:
1325 1335 # check if match works, see issue3964
1326 1336 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1327 1337 except ImportError:
1328 1338 _re2 = False
1329 1339
1330 1340 def compile(self, pat, flags=0):
1331 1341 '''Compile a regular expression, using re2 if possible
1332 1342
1333 1343 For best performance, use only re2-compatible regexp features. The
1334 1344 only flags from the re module that are re2-compatible are
1335 1345 IGNORECASE and MULTILINE.'''
1336 1346 if _re2 is None:
1337 1347 self._checkre2()
1338 1348 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1339 1349 if flags & remod.IGNORECASE:
1340 1350 pat = '(?i)' + pat
1341 1351 if flags & remod.MULTILINE:
1342 1352 pat = '(?m)' + pat
1343 1353 try:
1344 1354 return re2.compile(pat)
1345 1355 except re2.error:
1346 1356 pass
1347 1357 return remod.compile(pat, flags)
1348 1358
1349 1359 @propertycache
1350 1360 def escape(self):
1351 1361 '''Return the version of escape corresponding to self.compile.
1352 1362
1353 1363 This is imperfect because whether re2 or re is used for a particular
1354 1364 function depends on the flags, etc, but it's the best we can do.
1355 1365 '''
1356 1366 global _re2
1357 1367 if _re2 is None:
1358 1368 self._checkre2()
1359 1369 if _re2:
1360 1370 return re2.escape
1361 1371 else:
1362 1372 return remod.escape
1363 1373
1364 1374 re = _re()
1365 1375
1366 1376 _fspathcache = {}
1367 1377 def fspath(name, root):
1368 1378 '''Get name in the case stored in the filesystem
1369 1379
1370 1380 The name should be relative to root, and be normcase-ed for efficiency.
1371 1381
1372 1382 Note that this function is unnecessary, and should not be
1373 1383 called, for case-sensitive filesystems (simply because it's expensive).
1374 1384
1375 1385 The root should be normcase-ed, too.
1376 1386 '''
1377 1387 def _makefspathcacheentry(dir):
1378 1388 return dict((normcase(n), n) for n in os.listdir(dir))
1379 1389
1380 1390 seps = pycompat.ossep
1381 1391 if pycompat.osaltsep:
1382 1392 seps = seps + pycompat.osaltsep
1383 1393 # Protect backslashes. This gets silly very quickly.
1384 1394 seps.replace('\\','\\\\')
1385 1395 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1386 1396 dir = os.path.normpath(root)
1387 1397 result = []
1388 1398 for part, sep in pattern.findall(name):
1389 1399 if sep:
1390 1400 result.append(sep)
1391 1401 continue
1392 1402
1393 1403 if dir not in _fspathcache:
1394 1404 _fspathcache[dir] = _makefspathcacheentry(dir)
1395 1405 contents = _fspathcache[dir]
1396 1406
1397 1407 found = contents.get(part)
1398 1408 if not found:
1399 1409 # retry "once per directory" per "dirstate.walk" which
1400 1410 # may take place for each patches of "hg qpush", for example
1401 1411 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1402 1412 found = contents.get(part)
1403 1413
1404 1414 result.append(found or part)
1405 1415 dir = os.path.join(dir, part)
1406 1416
1407 1417 return ''.join(result)
1408 1418
1409 1419 def getfstype(dirpath):
1410 1420 '''Get the filesystem type name from a directory (best-effort)
1411 1421
1412 1422 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1413 1423 '''
1414 1424 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1415 1425
1416 1426 def checknlink(testfile):
1417 1427 '''check whether hardlink count reporting works properly'''
1418 1428
1419 1429 # testfile may be open, so we need a separate file for checking to
1420 1430 # work around issue2543 (or testfile may get lost on Samba shares)
1421 1431 f1 = testfile + ".hgtmp1"
1422 1432 if os.path.lexists(f1):
1423 1433 return False
1424 1434 try:
1425 1435 posixfile(f1, 'w').close()
1426 1436 except IOError:
1427 1437 try:
1428 1438 os.unlink(f1)
1429 1439 except OSError:
1430 1440 pass
1431 1441 return False
1432 1442
1433 1443 f2 = testfile + ".hgtmp2"
1434 1444 fd = None
1435 1445 try:
1436 1446 oslink(f1, f2)
1437 1447 # nlinks() may behave differently for files on Windows shares if
1438 1448 # the file is open.
1439 1449 fd = posixfile(f2)
1440 1450 return nlinks(f2) > 1
1441 1451 except OSError:
1442 1452 return False
1443 1453 finally:
1444 1454 if fd is not None:
1445 1455 fd.close()
1446 1456 for f in (f1, f2):
1447 1457 try:
1448 1458 os.unlink(f)
1449 1459 except OSError:
1450 1460 pass
1451 1461
1452 1462 def endswithsep(path):
1453 1463 '''Check path ends with os.sep or os.altsep.'''
1454 1464 return (path.endswith(pycompat.ossep)
1455 1465 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1456 1466
1457 1467 def splitpath(path):
1458 1468 '''Split path by os.sep.
1459 1469 Note that this function does not use os.altsep because this is
1460 1470 an alternative of simple "xxx.split(os.sep)".
1461 1471 It is recommended to use os.path.normpath() before using this
1462 1472 function if need.'''
1463 1473 return path.split(pycompat.ossep)
1464 1474
1465 1475 def gui():
1466 1476 '''Are we running in a GUI?'''
1467 1477 if pycompat.sysplatform == 'darwin':
1468 1478 if 'SSH_CONNECTION' in encoding.environ:
1469 1479 # handle SSH access to a box where the user is logged in
1470 1480 return False
1471 1481 elif getattr(osutil, 'isgui', None):
1472 1482 # check if a CoreGraphics session is available
1473 1483 return osutil.isgui()
1474 1484 else:
1475 1485 # pure build; use a safe default
1476 1486 return True
1477 1487 else:
1478 1488 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1479 1489
1480 1490 def mktempcopy(name, emptyok=False, createmode=None):
1481 1491 """Create a temporary file with the same contents from name
1482 1492
1483 1493 The permission bits are copied from the original file.
1484 1494
1485 1495 If the temporary file is going to be truncated immediately, you
1486 1496 can use emptyok=True as an optimization.
1487 1497
1488 1498 Returns the name of the temporary file.
1489 1499 """
1490 1500 d, fn = os.path.split(name)
1491 1501 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1492 1502 os.close(fd)
1493 1503 # Temporary files are created with mode 0600, which is usually not
1494 1504 # what we want. If the original file already exists, just copy
1495 1505 # its mode. Otherwise, manually obey umask.
1496 1506 copymode(name, temp, createmode)
1497 1507 if emptyok:
1498 1508 return temp
1499 1509 try:
1500 1510 try:
1501 1511 ifp = posixfile(name, "rb")
1502 1512 except IOError as inst:
1503 1513 if inst.errno == errno.ENOENT:
1504 1514 return temp
1505 1515 if not getattr(inst, 'filename', None):
1506 1516 inst.filename = name
1507 1517 raise
1508 1518 ofp = posixfile(temp, "wb")
1509 1519 for chunk in filechunkiter(ifp):
1510 1520 ofp.write(chunk)
1511 1521 ifp.close()
1512 1522 ofp.close()
1513 1523 except: # re-raises
1514 1524 try: os.unlink(temp)
1515 1525 except OSError: pass
1516 1526 raise
1517 1527 return temp
1518 1528
1519 1529 class filestat(object):
1520 1530 """help to exactly detect change of a file
1521 1531
1522 1532 'stat' attribute is result of 'os.stat()' if specified 'path'
1523 1533 exists. Otherwise, it is None. This can avoid preparative
1524 1534 'exists()' examination on client side of this class.
1525 1535 """
1526 1536 def __init__(self, path):
1527 1537 try:
1528 1538 self.stat = os.stat(path)
1529 1539 except OSError as err:
1530 1540 if err.errno != errno.ENOENT:
1531 1541 raise
1532 1542 self.stat = None
1533 1543
1534 1544 __hash__ = object.__hash__
1535 1545
1536 1546 def __eq__(self, old):
1537 1547 try:
1538 1548 # if ambiguity between stat of new and old file is
1539 1549 # avoided, comparison of size, ctime and mtime is enough
1540 1550 # to exactly detect change of a file regardless of platform
1541 1551 return (self.stat.st_size == old.stat.st_size and
1542 1552 self.stat.st_ctime == old.stat.st_ctime and
1543 1553 self.stat.st_mtime == old.stat.st_mtime)
1544 1554 except AttributeError:
1545 1555 return False
1546 1556
1547 1557 def isambig(self, old):
1548 1558 """Examine whether new (= self) stat is ambiguous against old one
1549 1559
1550 1560 "S[N]" below means stat of a file at N-th change:
1551 1561
1552 1562 - S[n-1].ctime < S[n].ctime: can detect change of a file
1553 1563 - S[n-1].ctime == S[n].ctime
1554 1564 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1555 1565 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1556 1566 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1557 1567 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1558 1568
1559 1569 Case (*2) above means that a file was changed twice or more at
1560 1570 same time in sec (= S[n-1].ctime), and comparison of timestamp
1561 1571 is ambiguous.
1562 1572
1563 1573 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1564 1574 timestamp is ambiguous".
1565 1575
1566 1576 But advancing mtime only in case (*2) doesn't work as
1567 1577 expected, because naturally advanced S[n].mtime in case (*1)
1568 1578 might be equal to manually advanced S[n-1 or earlier].mtime.
1569 1579
1570 1580 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1571 1581 treated as ambiguous regardless of mtime, to avoid overlooking
1572 1582 by confliction between such mtime.
1573 1583
1574 1584 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1575 1585 S[n].mtime", even if size of a file isn't changed.
1576 1586 """
1577 1587 try:
1578 1588 return (self.stat.st_ctime == old.stat.st_ctime)
1579 1589 except AttributeError:
1580 1590 return False
1581 1591
1582 1592 def avoidambig(self, path, old):
1583 1593 """Change file stat of specified path to avoid ambiguity
1584 1594
1585 1595 'old' should be previous filestat of 'path'.
1586 1596
1587 1597 This skips avoiding ambiguity, if a process doesn't have
1588 1598 appropriate privileges for 'path'.
1589 1599 """
1590 1600 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1591 1601 try:
1592 1602 os.utime(path, (advanced, advanced))
1593 1603 except OSError as inst:
1594 1604 if inst.errno == errno.EPERM:
1595 1605 # utime() on the file created by another user causes EPERM,
1596 1606 # if a process doesn't have appropriate privileges
1597 1607 return
1598 1608 raise
1599 1609
1600 1610 def __ne__(self, other):
1601 1611 return not self == other
1602 1612
1603 1613 class atomictempfile(object):
1604 1614 '''writable file object that atomically updates a file
1605 1615
1606 1616 All writes will go to a temporary copy of the original file. Call
1607 1617 close() when you are done writing, and atomictempfile will rename
1608 1618 the temporary copy to the original name, making the changes
1609 1619 visible. If the object is destroyed without being closed, all your
1610 1620 writes are discarded.
1611 1621
1612 1622 checkambig argument of constructor is used with filestat, and is
1613 1623 useful only if target file is guarded by any lock (e.g. repo.lock
1614 1624 or repo.wlock).
1615 1625 '''
1616 1626 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1617 1627 self.__name = name # permanent name
1618 1628 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1619 1629 createmode=createmode)
1620 1630 self._fp = posixfile(self._tempname, mode)
1621 1631 self._checkambig = checkambig
1622 1632
1623 1633 # delegated methods
1624 1634 self.read = self._fp.read
1625 1635 self.write = self._fp.write
1626 1636 self.seek = self._fp.seek
1627 1637 self.tell = self._fp.tell
1628 1638 self.fileno = self._fp.fileno
1629 1639
1630 1640 def close(self):
1631 1641 if not self._fp.closed:
1632 1642 self._fp.close()
1633 1643 filename = localpath(self.__name)
1634 1644 oldstat = self._checkambig and filestat(filename)
1635 1645 if oldstat and oldstat.stat:
1636 1646 rename(self._tempname, filename)
1637 1647 newstat = filestat(filename)
1638 1648 if newstat.isambig(oldstat):
1639 1649 # stat of changed file is ambiguous to original one
1640 1650 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1641 1651 os.utime(filename, (advanced, advanced))
1642 1652 else:
1643 1653 rename(self._tempname, filename)
1644 1654
1645 1655 def discard(self):
1646 1656 if not self._fp.closed:
1647 1657 try:
1648 1658 os.unlink(self._tempname)
1649 1659 except OSError:
1650 1660 pass
1651 1661 self._fp.close()
1652 1662
1653 1663 def __del__(self):
1654 1664 if safehasattr(self, '_fp'): # constructor actually did something
1655 1665 self.discard()
1656 1666
1657 1667 def __enter__(self):
1658 1668 return self
1659 1669
1660 1670 def __exit__(self, exctype, excvalue, traceback):
1661 1671 if exctype is not None:
1662 1672 self.discard()
1663 1673 else:
1664 1674 self.close()
1665 1675
1666 1676 def unlinkpath(f, ignoremissing=False):
1667 1677 """unlink and remove the directory if it is empty"""
1668 1678 if ignoremissing:
1669 1679 tryunlink(f)
1670 1680 else:
1671 1681 unlink(f)
1672 1682 # try removing directories that might now be empty
1673 1683 try:
1674 1684 removedirs(os.path.dirname(f))
1675 1685 except OSError:
1676 1686 pass
1677 1687
1678 1688 def tryunlink(f):
1679 1689 """Attempt to remove a file, ignoring ENOENT errors."""
1680 1690 try:
1681 1691 unlink(f)
1682 1692 except OSError as e:
1683 1693 if e.errno != errno.ENOENT:
1684 1694 raise
1685 1695
1686 1696 def makedirs(name, mode=None, notindexed=False):
1687 1697 """recursive directory creation with parent mode inheritance
1688 1698
1689 1699 Newly created directories are marked as "not to be indexed by
1690 1700 the content indexing service", if ``notindexed`` is specified
1691 1701 for "write" mode access.
1692 1702 """
1693 1703 try:
1694 1704 makedir(name, notindexed)
1695 1705 except OSError as err:
1696 1706 if err.errno == errno.EEXIST:
1697 1707 return
1698 1708 if err.errno != errno.ENOENT or not name:
1699 1709 raise
1700 1710 parent = os.path.dirname(os.path.abspath(name))
1701 1711 if parent == name:
1702 1712 raise
1703 1713 makedirs(parent, mode, notindexed)
1704 1714 try:
1705 1715 makedir(name, notindexed)
1706 1716 except OSError as err:
1707 1717 # Catch EEXIST to handle races
1708 1718 if err.errno == errno.EEXIST:
1709 1719 return
1710 1720 raise
1711 1721 if mode is not None:
1712 1722 os.chmod(name, mode)
1713 1723
1714 1724 def readfile(path):
1715 1725 with open(path, 'rb') as fp:
1716 1726 return fp.read()
1717 1727
1718 1728 def writefile(path, text):
1719 1729 with open(path, 'wb') as fp:
1720 1730 fp.write(text)
1721 1731
1722 1732 def appendfile(path, text):
1723 1733 with open(path, 'ab') as fp:
1724 1734 fp.write(text)
1725 1735
1726 1736 class chunkbuffer(object):
1727 1737 """Allow arbitrary sized chunks of data to be efficiently read from an
1728 1738 iterator over chunks of arbitrary size."""
1729 1739
1730 1740 def __init__(self, in_iter):
1731 1741 """in_iter is the iterator that's iterating over the input chunks."""
1732 1742 def splitbig(chunks):
1733 1743 for chunk in chunks:
1734 1744 if len(chunk) > 2**20:
1735 1745 pos = 0
1736 1746 while pos < len(chunk):
1737 1747 end = pos + 2 ** 18
1738 1748 yield chunk[pos:end]
1739 1749 pos = end
1740 1750 else:
1741 1751 yield chunk
1742 1752 self.iter = splitbig(in_iter)
1743 1753 self._queue = collections.deque()
1744 1754 self._chunkoffset = 0
1745 1755
1746 1756 def read(self, l=None):
1747 1757 """Read L bytes of data from the iterator of chunks of data.
1748 1758 Returns less than L bytes if the iterator runs dry.
1749 1759
1750 1760 If size parameter is omitted, read everything"""
1751 1761 if l is None:
1752 1762 return ''.join(self.iter)
1753 1763
1754 1764 left = l
1755 1765 buf = []
1756 1766 queue = self._queue
1757 1767 while left > 0:
1758 1768 # refill the queue
1759 1769 if not queue:
1760 1770 target = 2**18
1761 1771 for chunk in self.iter:
1762 1772 queue.append(chunk)
1763 1773 target -= len(chunk)
1764 1774 if target <= 0:
1765 1775 break
1766 1776 if not queue:
1767 1777 break
1768 1778
1769 1779 # The easy way to do this would be to queue.popleft(), modify the
1770 1780 # chunk (if necessary), then queue.appendleft(). However, for cases
1771 1781 # where we read partial chunk content, this incurs 2 dequeue
1772 1782 # mutations and creates a new str for the remaining chunk in the
1773 1783 # queue. Our code below avoids this overhead.
1774 1784
1775 1785 chunk = queue[0]
1776 1786 chunkl = len(chunk)
1777 1787 offset = self._chunkoffset
1778 1788
1779 1789 # Use full chunk.
1780 1790 if offset == 0 and left >= chunkl:
1781 1791 left -= chunkl
1782 1792 queue.popleft()
1783 1793 buf.append(chunk)
1784 1794 # self._chunkoffset remains at 0.
1785 1795 continue
1786 1796
1787 1797 chunkremaining = chunkl - offset
1788 1798
1789 1799 # Use all of unconsumed part of chunk.
1790 1800 if left >= chunkremaining:
1791 1801 left -= chunkremaining
1792 1802 queue.popleft()
1793 1803 # offset == 0 is enabled by block above, so this won't merely
1794 1804 # copy via ``chunk[0:]``.
1795 1805 buf.append(chunk[offset:])
1796 1806 self._chunkoffset = 0
1797 1807
1798 1808 # Partial chunk needed.
1799 1809 else:
1800 1810 buf.append(chunk[offset:offset + left])
1801 1811 self._chunkoffset += left
1802 1812 left -= chunkremaining
1803 1813
1804 1814 return ''.join(buf)
1805 1815
1806 1816 def filechunkiter(f, size=131072, limit=None):
1807 1817 """Create a generator that produces the data in the file size
1808 1818 (default 131072) bytes at a time, up to optional limit (default is
1809 1819 to read all data). Chunks may be less than size bytes if the
1810 1820 chunk is the last chunk in the file, or the file is a socket or
1811 1821 some other type of file that sometimes reads less data than is
1812 1822 requested."""
1813 1823 assert size >= 0
1814 1824 assert limit is None or limit >= 0
1815 1825 while True:
1816 1826 if limit is None:
1817 1827 nbytes = size
1818 1828 else:
1819 1829 nbytes = min(limit, size)
1820 1830 s = nbytes and f.read(nbytes)
1821 1831 if not s:
1822 1832 break
1823 1833 if limit:
1824 1834 limit -= len(s)
1825 1835 yield s
1826 1836
1827 1837 def makedate(timestamp=None):
1828 1838 '''Return a unix timestamp (or the current time) as a (unixtime,
1829 1839 offset) tuple based off the local timezone.'''
1830 1840 if timestamp is None:
1831 1841 timestamp = time.time()
1832 1842 if timestamp < 0:
1833 1843 hint = _("check your clock")
1834 1844 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1835 1845 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1836 1846 datetime.datetime.fromtimestamp(timestamp))
1837 1847 tz = delta.days * 86400 + delta.seconds
1838 1848 return timestamp, tz
1839 1849
1840 1850 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1841 1851 """represent a (unixtime, offset) tuple as a localized time.
1842 1852 unixtime is seconds since the epoch, and offset is the time zone's
1843 1853 number of seconds away from UTC.
1844 1854
1845 1855 >>> datestr((0, 0))
1846 1856 'Thu Jan 01 00:00:00 1970 +0000'
1847 1857 >>> datestr((42, 0))
1848 1858 'Thu Jan 01 00:00:42 1970 +0000'
1849 1859 >>> datestr((-42, 0))
1850 1860 'Wed Dec 31 23:59:18 1969 +0000'
1851 1861 >>> datestr((0x7fffffff, 0))
1852 1862 'Tue Jan 19 03:14:07 2038 +0000'
1853 1863 >>> datestr((-0x80000000, 0))
1854 1864 'Fri Dec 13 20:45:52 1901 +0000'
1855 1865 """
1856 1866 t, tz = date or makedate()
1857 1867 if "%1" in format or "%2" in format or "%z" in format:
1858 1868 sign = (tz > 0) and "-" or "+"
1859 1869 minutes = abs(tz) // 60
1860 1870 q, r = divmod(minutes, 60)
1861 1871 format = format.replace("%z", "%1%2")
1862 1872 format = format.replace("%1", "%c%02d" % (sign, q))
1863 1873 format = format.replace("%2", "%02d" % r)
1864 1874 d = t - tz
1865 1875 if d > 0x7fffffff:
1866 1876 d = 0x7fffffff
1867 1877 elif d < -0x80000000:
1868 1878 d = -0x80000000
1869 1879 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1870 1880 # because they use the gmtime() system call which is buggy on Windows
1871 1881 # for negative values.
1872 1882 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1873 1883 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1874 1884 return s
1875 1885
1876 1886 def shortdate(date=None):
1877 1887 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1878 1888 return datestr(date, format='%Y-%m-%d')
1879 1889
1880 1890 def parsetimezone(s):
1881 1891 """find a trailing timezone, if any, in string, and return a
1882 1892 (offset, remainder) pair"""
1883 1893
1884 1894 if s.endswith("GMT") or s.endswith("UTC"):
1885 1895 return 0, s[:-3].rstrip()
1886 1896
1887 1897 # Unix-style timezones [+-]hhmm
1888 1898 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1889 1899 sign = (s[-5] == "+") and 1 or -1
1890 1900 hours = int(s[-4:-2])
1891 1901 minutes = int(s[-2:])
1892 1902 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1893 1903
1894 1904 # ISO8601 trailing Z
1895 1905 if s.endswith("Z") and s[-2:-1].isdigit():
1896 1906 return 0, s[:-1]
1897 1907
1898 1908 # ISO8601-style [+-]hh:mm
1899 1909 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1900 1910 s[-5:-3].isdigit() and s[-2:].isdigit()):
1901 1911 sign = (s[-6] == "+") and 1 or -1
1902 1912 hours = int(s[-5:-3])
1903 1913 minutes = int(s[-2:])
1904 1914 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1905 1915
1906 1916 return None, s
1907 1917
1908 1918 def strdate(string, format, defaults=None):
1909 1919 """parse a localized time string and return a (unixtime, offset) tuple.
1910 1920 if the string cannot be parsed, ValueError is raised."""
1911 1921 if defaults is None:
1912 1922 defaults = {}
1913 1923
1914 1924 # NOTE: unixtime = localunixtime + offset
1915 1925 offset, date = parsetimezone(string)
1916 1926
1917 1927 # add missing elements from defaults
1918 1928 usenow = False # default to using biased defaults
1919 1929 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1920 1930 part = pycompat.bytestr(part)
1921 1931 found = [True for p in part if ("%"+p) in format]
1922 1932 if not found:
1923 1933 date += "@" + defaults[part][usenow]
1924 1934 format += "@%" + part[0]
1925 1935 else:
1926 1936 # We've found a specific time element, less specific time
1927 1937 # elements are relative to today
1928 1938 usenow = True
1929 1939
1930 1940 timetuple = time.strptime(date, format)
1931 1941 localunixtime = int(calendar.timegm(timetuple))
1932 1942 if offset is None:
1933 1943 # local timezone
1934 1944 unixtime = int(time.mktime(timetuple))
1935 1945 offset = unixtime - localunixtime
1936 1946 else:
1937 1947 unixtime = localunixtime + offset
1938 1948 return unixtime, offset
1939 1949
1940 1950 def parsedate(date, formats=None, bias=None):
1941 1951 """parse a localized date/time and return a (unixtime, offset) tuple.
1942 1952
1943 1953 The date may be a "unixtime offset" string or in one of the specified
1944 1954 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1945 1955
1946 1956 >>> parsedate(' today ') == parsedate(\
1947 1957 datetime.date.today().strftime('%b %d'))
1948 1958 True
1949 1959 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1950 1960 datetime.timedelta(days=1)\
1951 1961 ).strftime('%b %d'))
1952 1962 True
1953 1963 >>> now, tz = makedate()
1954 1964 >>> strnow, strtz = parsedate('now')
1955 1965 >>> (strnow - now) < 1
1956 1966 True
1957 1967 >>> tz == strtz
1958 1968 True
1959 1969 """
1960 1970 if bias is None:
1961 1971 bias = {}
1962 1972 if not date:
1963 1973 return 0, 0
1964 1974 if isinstance(date, tuple) and len(date) == 2:
1965 1975 return date
1966 1976 if not formats:
1967 1977 formats = defaultdateformats
1968 1978 date = date.strip()
1969 1979
1970 1980 if date == 'now' or date == _('now'):
1971 1981 return makedate()
1972 1982 if date == 'today' or date == _('today'):
1973 1983 date = datetime.date.today().strftime('%b %d')
1974 1984 elif date == 'yesterday' or date == _('yesterday'):
1975 1985 date = (datetime.date.today() -
1976 1986 datetime.timedelta(days=1)).strftime('%b %d')
1977 1987
1978 1988 try:
1979 1989 when, offset = map(int, date.split(' '))
1980 1990 except ValueError:
1981 1991 # fill out defaults
1982 1992 now = makedate()
1983 1993 defaults = {}
1984 1994 for part in ("d", "mb", "yY", "HI", "M", "S"):
1985 1995 # this piece is for rounding the specific end of unknowns
1986 1996 b = bias.get(part)
1987 1997 if b is None:
1988 1998 if part[0:1] in "HMS":
1989 1999 b = "00"
1990 2000 else:
1991 2001 b = "0"
1992 2002
1993 2003 # this piece is for matching the generic end to today's date
1994 2004 n = datestr(now, "%" + part[0:1])
1995 2005
1996 2006 defaults[part] = (b, n)
1997 2007
1998 2008 for format in formats:
1999 2009 try:
2000 2010 when, offset = strdate(date, format, defaults)
2001 2011 except (ValueError, OverflowError):
2002 2012 pass
2003 2013 else:
2004 2014 break
2005 2015 else:
2006 2016 raise Abort(_('invalid date: %r') % date)
2007 2017 # validate explicit (probably user-specified) date and
2008 2018 # time zone offset. values must fit in signed 32 bits for
2009 2019 # current 32-bit linux runtimes. timezones go from UTC-12
2010 2020 # to UTC+14
2011 2021 if when < -0x80000000 or when > 0x7fffffff:
2012 2022 raise Abort(_('date exceeds 32 bits: %d') % when)
2013 2023 if offset < -50400 or offset > 43200:
2014 2024 raise Abort(_('impossible time zone offset: %d') % offset)
2015 2025 return when, offset
2016 2026
2017 2027 def matchdate(date):
2018 2028 """Return a function that matches a given date match specifier
2019 2029
2020 2030 Formats include:
2021 2031
2022 2032 '{date}' match a given date to the accuracy provided
2023 2033
2024 2034 '<{date}' on or before a given date
2025 2035
2026 2036 '>{date}' on or after a given date
2027 2037
2028 2038 >>> p1 = parsedate("10:29:59")
2029 2039 >>> p2 = parsedate("10:30:00")
2030 2040 >>> p3 = parsedate("10:30:59")
2031 2041 >>> p4 = parsedate("10:31:00")
2032 2042 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2033 2043 >>> f = matchdate("10:30")
2034 2044 >>> f(p1[0])
2035 2045 False
2036 2046 >>> f(p2[0])
2037 2047 True
2038 2048 >>> f(p3[0])
2039 2049 True
2040 2050 >>> f(p4[0])
2041 2051 False
2042 2052 >>> f(p5[0])
2043 2053 False
2044 2054 """
2045 2055
2046 2056 def lower(date):
2047 2057 d = {'mb': "1", 'd': "1"}
2048 2058 return parsedate(date, extendeddateformats, d)[0]
2049 2059
2050 2060 def upper(date):
2051 2061 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2052 2062 for days in ("31", "30", "29"):
2053 2063 try:
2054 2064 d["d"] = days
2055 2065 return parsedate(date, extendeddateformats, d)[0]
2056 2066 except Abort:
2057 2067 pass
2058 2068 d["d"] = "28"
2059 2069 return parsedate(date, extendeddateformats, d)[0]
2060 2070
2061 2071 date = date.strip()
2062 2072
2063 2073 if not date:
2064 2074 raise Abort(_("dates cannot consist entirely of whitespace"))
2065 2075 elif date[0] == "<":
2066 2076 if not date[1:]:
2067 2077 raise Abort(_("invalid day spec, use '<DATE'"))
2068 2078 when = upper(date[1:])
2069 2079 return lambda x: x <= when
2070 2080 elif date[0] == ">":
2071 2081 if not date[1:]:
2072 2082 raise Abort(_("invalid day spec, use '>DATE'"))
2073 2083 when = lower(date[1:])
2074 2084 return lambda x: x >= when
2075 2085 elif date[0] == "-":
2076 2086 try:
2077 2087 days = int(date[1:])
2078 2088 except ValueError:
2079 2089 raise Abort(_("invalid day spec: %s") % date[1:])
2080 2090 if days < 0:
2081 2091 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2082 2092 % date[1:])
2083 2093 when = makedate()[0] - days * 3600 * 24
2084 2094 return lambda x: x >= when
2085 2095 elif " to " in date:
2086 2096 a, b = date.split(" to ")
2087 2097 start, stop = lower(a), upper(b)
2088 2098 return lambda x: x >= start and x <= stop
2089 2099 else:
2090 2100 start, stop = lower(date), upper(date)
2091 2101 return lambda x: x >= start and x <= stop
2092 2102
2093 2103 def stringmatcher(pattern, casesensitive=True):
2094 2104 """
2095 2105 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2096 2106 returns the matcher name, pattern, and matcher function.
2097 2107 missing or unknown prefixes are treated as literal matches.
2098 2108
2099 2109 helper for tests:
2100 2110 >>> def test(pattern, *tests):
2101 2111 ... kind, pattern, matcher = stringmatcher(pattern)
2102 2112 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2103 2113 >>> def itest(pattern, *tests):
2104 2114 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2105 2115 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2106 2116
2107 2117 exact matching (no prefix):
2108 2118 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2109 2119 ('literal', 'abcdefg', [False, False, True])
2110 2120
2111 2121 regex matching ('re:' prefix)
2112 2122 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2113 2123 ('re', 'a.+b', [False, False, True])
2114 2124
2115 2125 force exact matches ('literal:' prefix)
2116 2126 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2117 2127 ('literal', 're:foobar', [False, True])
2118 2128
2119 2129 unknown prefixes are ignored and treated as literals
2120 2130 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2121 2131 ('literal', 'foo:bar', [False, False, True])
2122 2132
2123 2133 case insensitive regex matches
2124 2134 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2125 2135 ('re', 'A.+b', [False, False, True])
2126 2136
2127 2137 case insensitive literal matches
2128 2138 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2129 2139 ('literal', 'ABCDEFG', [False, False, True])
2130 2140 """
2131 2141 if pattern.startswith('re:'):
2132 2142 pattern = pattern[3:]
2133 2143 try:
2134 2144 flags = 0
2135 2145 if not casesensitive:
2136 2146 flags = remod.I
2137 2147 regex = remod.compile(pattern, flags)
2138 2148 except remod.error as e:
2139 2149 raise error.ParseError(_('invalid regular expression: %s')
2140 2150 % e)
2141 2151 return 're', pattern, regex.search
2142 2152 elif pattern.startswith('literal:'):
2143 2153 pattern = pattern[8:]
2144 2154
2145 2155 match = pattern.__eq__
2146 2156
2147 2157 if not casesensitive:
2148 2158 ipat = encoding.lower(pattern)
2149 2159 match = lambda s: ipat == encoding.lower(s)
2150 2160 return 'literal', pattern, match
2151 2161
2152 2162 def shortuser(user):
2153 2163 """Return a short representation of a user name or email address."""
2154 2164 f = user.find('@')
2155 2165 if f >= 0:
2156 2166 user = user[:f]
2157 2167 f = user.find('<')
2158 2168 if f >= 0:
2159 2169 user = user[f + 1:]
2160 2170 f = user.find(' ')
2161 2171 if f >= 0:
2162 2172 user = user[:f]
2163 2173 f = user.find('.')
2164 2174 if f >= 0:
2165 2175 user = user[:f]
2166 2176 return user
2167 2177
2168 2178 def emailuser(user):
2169 2179 """Return the user portion of an email address."""
2170 2180 f = user.find('@')
2171 2181 if f >= 0:
2172 2182 user = user[:f]
2173 2183 f = user.find('<')
2174 2184 if f >= 0:
2175 2185 user = user[f + 1:]
2176 2186 return user
2177 2187
2178 2188 def email(author):
2179 2189 '''get email of author.'''
2180 2190 r = author.find('>')
2181 2191 if r == -1:
2182 2192 r = None
2183 2193 return author[author.find('<') + 1:r]
2184 2194
2185 2195 def ellipsis(text, maxlength=400):
2186 2196 """Trim string to at most maxlength (default: 400) columns in display."""
2187 2197 return encoding.trim(text, maxlength, ellipsis='...')
2188 2198
2189 2199 def unitcountfn(*unittable):
2190 2200 '''return a function that renders a readable count of some quantity'''
2191 2201
2192 2202 def go(count):
2193 2203 for multiplier, divisor, format in unittable:
2194 2204 if abs(count) >= divisor * multiplier:
2195 2205 return format % (count / float(divisor))
2196 2206 return unittable[-1][2] % count
2197 2207
2198 2208 return go
2199 2209
2200 2210 def processlinerange(fromline, toline):
2201 2211 """Check that linerange <fromline>:<toline> makes sense and return a
2202 2212 0-based range.
2203 2213
2204 2214 >>> processlinerange(10, 20)
2205 2215 (9, 20)
2206 2216 >>> processlinerange(2, 1)
2207 2217 Traceback (most recent call last):
2208 2218 ...
2209 2219 ParseError: line range must be positive
2210 2220 >>> processlinerange(0, 5)
2211 2221 Traceback (most recent call last):
2212 2222 ...
2213 2223 ParseError: fromline must be strictly positive
2214 2224 """
2215 2225 if toline - fromline < 0:
2216 2226 raise error.ParseError(_("line range must be positive"))
2217 2227 if fromline < 1:
2218 2228 raise error.ParseError(_("fromline must be strictly positive"))
2219 2229 return fromline - 1, toline
2220 2230
2221 2231 bytecount = unitcountfn(
2222 2232 (100, 1 << 30, _('%.0f GB')),
2223 2233 (10, 1 << 30, _('%.1f GB')),
2224 2234 (1, 1 << 30, _('%.2f GB')),
2225 2235 (100, 1 << 20, _('%.0f MB')),
2226 2236 (10, 1 << 20, _('%.1f MB')),
2227 2237 (1, 1 << 20, _('%.2f MB')),
2228 2238 (100, 1 << 10, _('%.0f KB')),
2229 2239 (10, 1 << 10, _('%.1f KB')),
2230 2240 (1, 1 << 10, _('%.2f KB')),
2231 2241 (1, 1, _('%.0f bytes')),
2232 2242 )
2233 2243
2234 2244 # Matches a single EOL which can either be a CRLF where repeated CR
2235 2245 # are removed or a LF. We do not care about old Macintosh files, so a
2236 2246 # stray CR is an error.
2237 2247 _eolre = remod.compile(br'\r*\n')
2238 2248
2239 2249 def tolf(s):
2240 2250 return _eolre.sub('\n', s)
2241 2251
2242 2252 def tocrlf(s):
2243 2253 return _eolre.sub('\r\n', s)
2244 2254
2245 2255 if pycompat.oslinesep == '\r\n':
2246 2256 tonativeeol = tocrlf
2247 2257 fromnativeeol = tolf
2248 2258 else:
2249 2259 tonativeeol = pycompat.identity
2250 2260 fromnativeeol = pycompat.identity
2251 2261
2252 2262 def escapestr(s):
2253 2263 # call underlying function of s.encode('string_escape') directly for
2254 2264 # Python 3 compatibility
2255 2265 return codecs.escape_encode(s)[0]
2256 2266
2257 2267 def unescapestr(s):
2258 2268 return codecs.escape_decode(s)[0]
2259 2269
2260 2270 def uirepr(s):
2261 2271 # Avoid double backslash in Windows path repr()
2262 2272 return repr(s).replace('\\\\', '\\')
2263 2273
2264 2274 # delay import of textwrap
2265 2275 def MBTextWrapper(**kwargs):
2266 2276 class tw(textwrap.TextWrapper):
2267 2277 """
2268 2278 Extend TextWrapper for width-awareness.
2269 2279
2270 2280 Neither number of 'bytes' in any encoding nor 'characters' is
2271 2281 appropriate to calculate terminal columns for specified string.
2272 2282
2273 2283 Original TextWrapper implementation uses built-in 'len()' directly,
2274 2284 so overriding is needed to use width information of each characters.
2275 2285
2276 2286 In addition, characters classified into 'ambiguous' width are
2277 2287 treated as wide in East Asian area, but as narrow in other.
2278 2288
2279 2289 This requires use decision to determine width of such characters.
2280 2290 """
2281 2291 def _cutdown(self, ucstr, space_left):
2282 2292 l = 0
2283 2293 colwidth = encoding.ucolwidth
2284 2294 for i in xrange(len(ucstr)):
2285 2295 l += colwidth(ucstr[i])
2286 2296 if space_left < l:
2287 2297 return (ucstr[:i], ucstr[i:])
2288 2298 return ucstr, ''
2289 2299
2290 2300 # overriding of base class
2291 2301 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2292 2302 space_left = max(width - cur_len, 1)
2293 2303
2294 2304 if self.break_long_words:
2295 2305 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2296 2306 cur_line.append(cut)
2297 2307 reversed_chunks[-1] = res
2298 2308 elif not cur_line:
2299 2309 cur_line.append(reversed_chunks.pop())
2300 2310
2301 2311 # this overriding code is imported from TextWrapper of Python 2.6
2302 2312 # to calculate columns of string by 'encoding.ucolwidth()'
2303 2313 def _wrap_chunks(self, chunks):
2304 2314 colwidth = encoding.ucolwidth
2305 2315
2306 2316 lines = []
2307 2317 if self.width <= 0:
2308 2318 raise ValueError("invalid width %r (must be > 0)" % self.width)
2309 2319
2310 2320 # Arrange in reverse order so items can be efficiently popped
2311 2321 # from a stack of chucks.
2312 2322 chunks.reverse()
2313 2323
2314 2324 while chunks:
2315 2325
2316 2326 # Start the list of chunks that will make up the current line.
2317 2327 # cur_len is just the length of all the chunks in cur_line.
2318 2328 cur_line = []
2319 2329 cur_len = 0
2320 2330
2321 2331 # Figure out which static string will prefix this line.
2322 2332 if lines:
2323 2333 indent = self.subsequent_indent
2324 2334 else:
2325 2335 indent = self.initial_indent
2326 2336
2327 2337 # Maximum width for this line.
2328 2338 width = self.width - len(indent)
2329 2339
2330 2340 # First chunk on line is whitespace -- drop it, unless this
2331 2341 # is the very beginning of the text (i.e. no lines started yet).
2332 2342 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2333 2343 del chunks[-1]
2334 2344
2335 2345 while chunks:
2336 2346 l = colwidth(chunks[-1])
2337 2347
2338 2348 # Can at least squeeze this chunk onto the current line.
2339 2349 if cur_len + l <= width:
2340 2350 cur_line.append(chunks.pop())
2341 2351 cur_len += l
2342 2352
2343 2353 # Nope, this line is full.
2344 2354 else:
2345 2355 break
2346 2356
2347 2357 # The current line is full, and the next chunk is too big to
2348 2358 # fit on *any* line (not just this one).
2349 2359 if chunks and colwidth(chunks[-1]) > width:
2350 2360 self._handle_long_word(chunks, cur_line, cur_len, width)
2351 2361
2352 2362 # If the last chunk on this line is all whitespace, drop it.
2353 2363 if (self.drop_whitespace and
2354 2364 cur_line and cur_line[-1].strip() == ''):
2355 2365 del cur_line[-1]
2356 2366
2357 2367 # Convert current line back to a string and store it in list
2358 2368 # of all lines (return value).
2359 2369 if cur_line:
2360 2370 lines.append(indent + ''.join(cur_line))
2361 2371
2362 2372 return lines
2363 2373
2364 2374 global MBTextWrapper
2365 2375 MBTextWrapper = tw
2366 2376 return tw(**kwargs)
2367 2377
2368 2378 def wrap(line, width, initindent='', hangindent=''):
2369 2379 maxindent = max(len(hangindent), len(initindent))
2370 2380 if width <= maxindent:
2371 2381 # adjust for weird terminal size
2372 2382 width = max(78, maxindent + 1)
2373 2383 line = line.decode(pycompat.sysstr(encoding.encoding),
2374 2384 pycompat.sysstr(encoding.encodingmode))
2375 2385 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2376 2386 pycompat.sysstr(encoding.encodingmode))
2377 2387 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2378 2388 pycompat.sysstr(encoding.encodingmode))
2379 2389 wrapper = MBTextWrapper(width=width,
2380 2390 initial_indent=initindent,
2381 2391 subsequent_indent=hangindent)
2382 2392 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2383 2393
2384 2394 if (pyplatform.python_implementation() == 'CPython' and
2385 2395 sys.version_info < (3, 0)):
2386 2396 # There is an issue in CPython that some IO methods do not handle EINTR
2387 2397 # correctly. The following table shows what CPython version (and functions)
2388 2398 # are affected (buggy: has the EINTR bug, okay: otherwise):
2389 2399 #
2390 2400 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2391 2401 # --------------------------------------------------
2392 2402 # fp.__iter__ | buggy | buggy | okay
2393 2403 # fp.read* | buggy | okay [1] | okay
2394 2404 #
2395 2405 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2396 2406 #
2397 2407 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2398 2408 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2399 2409 #
2400 2410 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2401 2411 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2402 2412 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2403 2413 # fp.__iter__ but not other fp.read* methods.
2404 2414 #
2405 2415 # On modern systems like Linux, the "read" syscall cannot be interrupted
2406 2416 # when reading "fast" files like on-disk files. So the EINTR issue only
2407 2417 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2408 2418 # files approximately as "fast" files and use the fast (unsafe) code path,
2409 2419 # to minimize the performance impact.
2410 2420 if sys.version_info >= (2, 7, 4):
2411 2421 # fp.readline deals with EINTR correctly, use it as a workaround.
2412 2422 def _safeiterfile(fp):
2413 2423 return iter(fp.readline, '')
2414 2424 else:
2415 2425 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2416 2426 # note: this may block longer than necessary because of bufsize.
2417 2427 def _safeiterfile(fp, bufsize=4096):
2418 2428 fd = fp.fileno()
2419 2429 line = ''
2420 2430 while True:
2421 2431 try:
2422 2432 buf = os.read(fd, bufsize)
2423 2433 except OSError as ex:
2424 2434 # os.read only raises EINTR before any data is read
2425 2435 if ex.errno == errno.EINTR:
2426 2436 continue
2427 2437 else:
2428 2438 raise
2429 2439 line += buf
2430 2440 if '\n' in buf:
2431 2441 splitted = line.splitlines(True)
2432 2442 line = ''
2433 2443 for l in splitted:
2434 2444 if l[-1] == '\n':
2435 2445 yield l
2436 2446 else:
2437 2447 line = l
2438 2448 if not buf:
2439 2449 break
2440 2450 if line:
2441 2451 yield line
2442 2452
2443 2453 def iterfile(fp):
2444 2454 fastpath = True
2445 2455 if type(fp) is file:
2446 2456 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2447 2457 if fastpath:
2448 2458 return fp
2449 2459 else:
2450 2460 return _safeiterfile(fp)
2451 2461 else:
2452 2462 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2453 2463 def iterfile(fp):
2454 2464 return fp
2455 2465
2456 2466 def iterlines(iterator):
2457 2467 for chunk in iterator:
2458 2468 for line in chunk.splitlines():
2459 2469 yield line
2460 2470
2461 2471 def expandpath(path):
2462 2472 return os.path.expanduser(os.path.expandvars(path))
2463 2473
2464 2474 def hgcmd():
2465 2475 """Return the command used to execute current hg
2466 2476
2467 2477 This is different from hgexecutable() because on Windows we want
2468 2478 to avoid things opening new shell windows like batch files, so we
2469 2479 get either the python call or current executable.
2470 2480 """
2471 2481 if mainfrozen():
2472 2482 if getattr(sys, 'frozen', None) == 'macosx_app':
2473 2483 # Env variable set by py2app
2474 2484 return [encoding.environ['EXECUTABLEPATH']]
2475 2485 else:
2476 2486 return [pycompat.sysexecutable]
2477 2487 return gethgcmd()
2478 2488
2479 2489 def rundetached(args, condfn):
2480 2490 """Execute the argument list in a detached process.
2481 2491
2482 2492 condfn is a callable which is called repeatedly and should return
2483 2493 True once the child process is known to have started successfully.
2484 2494 At this point, the child process PID is returned. If the child
2485 2495 process fails to start or finishes before condfn() evaluates to
2486 2496 True, return -1.
2487 2497 """
2488 2498 # Windows case is easier because the child process is either
2489 2499 # successfully starting and validating the condition or exiting
2490 2500 # on failure. We just poll on its PID. On Unix, if the child
2491 2501 # process fails to start, it will be left in a zombie state until
2492 2502 # the parent wait on it, which we cannot do since we expect a long
2493 2503 # running process on success. Instead we listen for SIGCHLD telling
2494 2504 # us our child process terminated.
2495 2505 terminated = set()
2496 2506 def handler(signum, frame):
2497 2507 terminated.add(os.wait())
2498 2508 prevhandler = None
2499 2509 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2500 2510 if SIGCHLD is not None:
2501 2511 prevhandler = signal.signal(SIGCHLD, handler)
2502 2512 try:
2503 2513 pid = spawndetached(args)
2504 2514 while not condfn():
2505 2515 if ((pid in terminated or not testpid(pid))
2506 2516 and not condfn()):
2507 2517 return -1
2508 2518 time.sleep(0.1)
2509 2519 return pid
2510 2520 finally:
2511 2521 if prevhandler is not None:
2512 2522 signal.signal(signal.SIGCHLD, prevhandler)
2513 2523
2514 2524 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2515 2525 """Return the result of interpolating items in the mapping into string s.
2516 2526
2517 2527 prefix is a single character string, or a two character string with
2518 2528 a backslash as the first character if the prefix needs to be escaped in
2519 2529 a regular expression.
2520 2530
2521 2531 fn is an optional function that will be applied to the replacement text
2522 2532 just before replacement.
2523 2533
2524 2534 escape_prefix is an optional flag that allows using doubled prefix for
2525 2535 its escaping.
2526 2536 """
2527 2537 fn = fn or (lambda s: s)
2528 2538 patterns = '|'.join(mapping.keys())
2529 2539 if escape_prefix:
2530 2540 patterns += '|' + prefix
2531 2541 if len(prefix) > 1:
2532 2542 prefix_char = prefix[1:]
2533 2543 else:
2534 2544 prefix_char = prefix
2535 2545 mapping[prefix_char] = prefix_char
2536 2546 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2537 2547 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2538 2548
2539 2549 def getport(port):
2540 2550 """Return the port for a given network service.
2541 2551
2542 2552 If port is an integer, it's returned as is. If it's a string, it's
2543 2553 looked up using socket.getservbyname(). If there's no matching
2544 2554 service, error.Abort is raised.
2545 2555 """
2546 2556 try:
2547 2557 return int(port)
2548 2558 except ValueError:
2549 2559 pass
2550 2560
2551 2561 try:
2552 2562 return socket.getservbyname(port)
2553 2563 except socket.error:
2554 2564 raise Abort(_("no port number associated with service '%s'") % port)
2555 2565
2556 2566 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2557 2567 '0': False, 'no': False, 'false': False, 'off': False,
2558 2568 'never': False}
2559 2569
2560 2570 def parsebool(s):
2561 2571 """Parse s into a boolean.
2562 2572
2563 2573 If s is not a valid boolean, returns None.
2564 2574 """
2565 2575 return _booleans.get(s.lower(), None)
2566 2576
2567 2577 _hextochr = dict((a + b, chr(int(a + b, 16)))
2568 2578 for a in string.hexdigits for b in string.hexdigits)
2569 2579
2570 2580 class url(object):
2571 2581 r"""Reliable URL parser.
2572 2582
2573 2583 This parses URLs and provides attributes for the following
2574 2584 components:
2575 2585
2576 2586 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2577 2587
2578 2588 Missing components are set to None. The only exception is
2579 2589 fragment, which is set to '' if present but empty.
2580 2590
2581 2591 If parsefragment is False, fragment is included in query. If
2582 2592 parsequery is False, query is included in path. If both are
2583 2593 False, both fragment and query are included in path.
2584 2594
2585 2595 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2586 2596
2587 2597 Note that for backward compatibility reasons, bundle URLs do not
2588 2598 take host names. That means 'bundle://../' has a path of '../'.
2589 2599
2590 2600 Examples:
2591 2601
2592 2602 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2593 2603 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2594 2604 >>> url('ssh://[::1]:2200//home/joe/repo')
2595 2605 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2596 2606 >>> url('file:///home/joe/repo')
2597 2607 <url scheme: 'file', path: '/home/joe/repo'>
2598 2608 >>> url('file:///c:/temp/foo/')
2599 2609 <url scheme: 'file', path: 'c:/temp/foo/'>
2600 2610 >>> url('bundle:foo')
2601 2611 <url scheme: 'bundle', path: 'foo'>
2602 2612 >>> url('bundle://../foo')
2603 2613 <url scheme: 'bundle', path: '../foo'>
2604 2614 >>> url(r'c:\foo\bar')
2605 2615 <url path: 'c:\\foo\\bar'>
2606 2616 >>> url(r'\\blah\blah\blah')
2607 2617 <url path: '\\\\blah\\blah\\blah'>
2608 2618 >>> url(r'\\blah\blah\blah#baz')
2609 2619 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2610 2620 >>> url(r'file:///C:\users\me')
2611 2621 <url scheme: 'file', path: 'C:\\users\\me'>
2612 2622
2613 2623 Authentication credentials:
2614 2624
2615 2625 >>> url('ssh://joe:xyz@x/repo')
2616 2626 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2617 2627 >>> url('ssh://joe@x/repo')
2618 2628 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2619 2629
2620 2630 Query strings and fragments:
2621 2631
2622 2632 >>> url('http://host/a?b#c')
2623 2633 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2624 2634 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2625 2635 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2626 2636
2627 2637 Empty path:
2628 2638
2629 2639 >>> url('')
2630 2640 <url path: ''>
2631 2641 >>> url('#a')
2632 2642 <url path: '', fragment: 'a'>
2633 2643 >>> url('http://host/')
2634 2644 <url scheme: 'http', host: 'host', path: ''>
2635 2645 >>> url('http://host/#a')
2636 2646 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2637 2647
2638 2648 Only scheme:
2639 2649
2640 2650 >>> url('http:')
2641 2651 <url scheme: 'http'>
2642 2652 """
2643 2653
2644 2654 _safechars = "!~*'()+"
2645 2655 _safepchars = "/!~*'()+:\\"
2646 2656 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2647 2657
2648 2658 def __init__(self, path, parsequery=True, parsefragment=True):
2649 2659 # We slowly chomp away at path until we have only the path left
2650 2660 self.scheme = self.user = self.passwd = self.host = None
2651 2661 self.port = self.path = self.query = self.fragment = None
2652 2662 self._localpath = True
2653 2663 self._hostport = ''
2654 2664 self._origpath = path
2655 2665
2656 2666 if parsefragment and '#' in path:
2657 2667 path, self.fragment = path.split('#', 1)
2658 2668
2659 2669 # special case for Windows drive letters and UNC paths
2660 2670 if hasdriveletter(path) or path.startswith('\\\\'):
2661 2671 self.path = path
2662 2672 return
2663 2673
2664 2674 # For compatibility reasons, we can't handle bundle paths as
2665 2675 # normal URLS
2666 2676 if path.startswith('bundle:'):
2667 2677 self.scheme = 'bundle'
2668 2678 path = path[7:]
2669 2679 if path.startswith('//'):
2670 2680 path = path[2:]
2671 2681 self.path = path
2672 2682 return
2673 2683
2674 2684 if self._matchscheme(path):
2675 2685 parts = path.split(':', 1)
2676 2686 if parts[0]:
2677 2687 self.scheme, path = parts
2678 2688 self._localpath = False
2679 2689
2680 2690 if not path:
2681 2691 path = None
2682 2692 if self._localpath:
2683 2693 self.path = ''
2684 2694 return
2685 2695 else:
2686 2696 if self._localpath:
2687 2697 self.path = path
2688 2698 return
2689 2699
2690 2700 if parsequery and '?' in path:
2691 2701 path, self.query = path.split('?', 1)
2692 2702 if not path:
2693 2703 path = None
2694 2704 if not self.query:
2695 2705 self.query = None
2696 2706
2697 2707 # // is required to specify a host/authority
2698 2708 if path and path.startswith('//'):
2699 2709 parts = path[2:].split('/', 1)
2700 2710 if len(parts) > 1:
2701 2711 self.host, path = parts
2702 2712 else:
2703 2713 self.host = parts[0]
2704 2714 path = None
2705 2715 if not self.host:
2706 2716 self.host = None
2707 2717 # path of file:///d is /d
2708 2718 # path of file:///d:/ is d:/, not /d:/
2709 2719 if path and not hasdriveletter(path):
2710 2720 path = '/' + path
2711 2721
2712 2722 if self.host and '@' in self.host:
2713 2723 self.user, self.host = self.host.rsplit('@', 1)
2714 2724 if ':' in self.user:
2715 2725 self.user, self.passwd = self.user.split(':', 1)
2716 2726 if not self.host:
2717 2727 self.host = None
2718 2728
2719 2729 # Don't split on colons in IPv6 addresses without ports
2720 2730 if (self.host and ':' in self.host and
2721 2731 not (self.host.startswith('[') and self.host.endswith(']'))):
2722 2732 self._hostport = self.host
2723 2733 self.host, self.port = self.host.rsplit(':', 1)
2724 2734 if not self.host:
2725 2735 self.host = None
2726 2736
2727 2737 if (self.host and self.scheme == 'file' and
2728 2738 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2729 2739 raise Abort(_('file:// URLs can only refer to localhost'))
2730 2740
2731 2741 self.path = path
2732 2742
2733 2743 # leave the query string escaped
2734 2744 for a in ('user', 'passwd', 'host', 'port',
2735 2745 'path', 'fragment'):
2736 2746 v = getattr(self, a)
2737 2747 if v is not None:
2738 2748 setattr(self, a, urlreq.unquote(v))
2739 2749
2740 2750 def __repr__(self):
2741 2751 attrs = []
2742 2752 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2743 2753 'query', 'fragment'):
2744 2754 v = getattr(self, a)
2745 2755 if v is not None:
2746 2756 attrs.append('%s: %r' % (a, v))
2747 2757 return '<url %s>' % ', '.join(attrs)
2748 2758
2749 2759 def __str__(self):
2750 2760 r"""Join the URL's components back into a URL string.
2751 2761
2752 2762 Examples:
2753 2763
2754 2764 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2755 2765 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2756 2766 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2757 2767 'http://user:pw@host:80/?foo=bar&baz=42'
2758 2768 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2759 2769 'http://user:pw@host:80/?foo=bar%3dbaz'
2760 2770 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2761 2771 'ssh://user:pw@[::1]:2200//home/joe#'
2762 2772 >>> str(url('http://localhost:80//'))
2763 2773 'http://localhost:80//'
2764 2774 >>> str(url('http://localhost:80/'))
2765 2775 'http://localhost:80/'
2766 2776 >>> str(url('http://localhost:80'))
2767 2777 'http://localhost:80/'
2768 2778 >>> str(url('bundle:foo'))
2769 2779 'bundle:foo'
2770 2780 >>> str(url('bundle://../foo'))
2771 2781 'bundle:../foo'
2772 2782 >>> str(url('path'))
2773 2783 'path'
2774 2784 >>> str(url('file:///tmp/foo/bar'))
2775 2785 'file:///tmp/foo/bar'
2776 2786 >>> str(url('file:///c:/tmp/foo/bar'))
2777 2787 'file:///c:/tmp/foo/bar'
2778 2788 >>> print url(r'bundle:foo\bar')
2779 2789 bundle:foo\bar
2780 2790 >>> print url(r'file:///D:\data\hg')
2781 2791 file:///D:\data\hg
2782 2792 """
2783 2793 return encoding.strfromlocal(self.__bytes__())
2784 2794
2785 2795 def __bytes__(self):
2786 2796 if self._localpath:
2787 2797 s = self.path
2788 2798 if self.scheme == 'bundle':
2789 2799 s = 'bundle:' + s
2790 2800 if self.fragment:
2791 2801 s += '#' + self.fragment
2792 2802 return s
2793 2803
2794 2804 s = self.scheme + ':'
2795 2805 if self.user or self.passwd or self.host:
2796 2806 s += '//'
2797 2807 elif self.scheme and (not self.path or self.path.startswith('/')
2798 2808 or hasdriveletter(self.path)):
2799 2809 s += '//'
2800 2810 if hasdriveletter(self.path):
2801 2811 s += '/'
2802 2812 if self.user:
2803 2813 s += urlreq.quote(self.user, safe=self._safechars)
2804 2814 if self.passwd:
2805 2815 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2806 2816 if self.user or self.passwd:
2807 2817 s += '@'
2808 2818 if self.host:
2809 2819 if not (self.host.startswith('[') and self.host.endswith(']')):
2810 2820 s += urlreq.quote(self.host)
2811 2821 else:
2812 2822 s += self.host
2813 2823 if self.port:
2814 2824 s += ':' + urlreq.quote(self.port)
2815 2825 if self.host:
2816 2826 s += '/'
2817 2827 if self.path:
2818 2828 # TODO: similar to the query string, we should not unescape the
2819 2829 # path when we store it, the path might contain '%2f' = '/',
2820 2830 # which we should *not* escape.
2821 2831 s += urlreq.quote(self.path, safe=self._safepchars)
2822 2832 if self.query:
2823 2833 # we store the query in escaped form.
2824 2834 s += '?' + self.query
2825 2835 if self.fragment is not None:
2826 2836 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2827 2837 return s
2828 2838
2829 2839 def authinfo(self):
2830 2840 user, passwd = self.user, self.passwd
2831 2841 try:
2832 2842 self.user, self.passwd = None, None
2833 2843 s = bytes(self)
2834 2844 finally:
2835 2845 self.user, self.passwd = user, passwd
2836 2846 if not self.user:
2837 2847 return (s, None)
2838 2848 # authinfo[1] is passed to urllib2 password manager, and its
2839 2849 # URIs must not contain credentials. The host is passed in the
2840 2850 # URIs list because Python < 2.4.3 uses only that to search for
2841 2851 # a password.
2842 2852 return (s, (None, (s, self.host),
2843 2853 self.user, self.passwd or ''))
2844 2854
2845 2855 def isabs(self):
2846 2856 if self.scheme and self.scheme != 'file':
2847 2857 return True # remote URL
2848 2858 if hasdriveletter(self.path):
2849 2859 return True # absolute for our purposes - can't be joined()
2850 2860 if self.path.startswith(r'\\'):
2851 2861 return True # Windows UNC path
2852 2862 if self.path.startswith('/'):
2853 2863 return True # POSIX-style
2854 2864 return False
2855 2865
2856 2866 def localpath(self):
2857 2867 if self.scheme == 'file' or self.scheme == 'bundle':
2858 2868 path = self.path or '/'
2859 2869 # For Windows, we need to promote hosts containing drive
2860 2870 # letters to paths with drive letters.
2861 2871 if hasdriveletter(self._hostport):
2862 2872 path = self._hostport + '/' + self.path
2863 2873 elif (self.host is not None and self.path
2864 2874 and not hasdriveletter(path)):
2865 2875 path = '/' + path
2866 2876 return path
2867 2877 return self._origpath
2868 2878
2869 2879 def islocal(self):
2870 2880 '''whether localpath will return something that posixfile can open'''
2871 2881 return (not self.scheme or self.scheme == 'file'
2872 2882 or self.scheme == 'bundle')
2873 2883
2874 2884 def hasscheme(path):
2875 2885 return bool(url(path).scheme)
2876 2886
2877 2887 def hasdriveletter(path):
2878 2888 return path and path[1:2] == ':' and path[0:1].isalpha()
2879 2889
2880 2890 def urllocalpath(path):
2881 2891 return url(path, parsequery=False, parsefragment=False).localpath()
2882 2892
2883 2893 def hidepassword(u):
2884 2894 '''hide user credential in a url string'''
2885 2895 u = url(u)
2886 2896 if u.passwd:
2887 2897 u.passwd = '***'
2888 2898 return bytes(u)
2889 2899
2890 2900 def removeauth(u):
2891 2901 '''remove all authentication information from a url string'''
2892 2902 u = url(u)
2893 2903 u.user = u.passwd = None
2894 2904 return str(u)
2895 2905
2896 2906 timecount = unitcountfn(
2897 2907 (1, 1e3, _('%.0f s')),
2898 2908 (100, 1, _('%.1f s')),
2899 2909 (10, 1, _('%.2f s')),
2900 2910 (1, 1, _('%.3f s')),
2901 2911 (100, 0.001, _('%.1f ms')),
2902 2912 (10, 0.001, _('%.2f ms')),
2903 2913 (1, 0.001, _('%.3f ms')),
2904 2914 (100, 0.000001, _('%.1f us')),
2905 2915 (10, 0.000001, _('%.2f us')),
2906 2916 (1, 0.000001, _('%.3f us')),
2907 2917 (100, 0.000000001, _('%.1f ns')),
2908 2918 (10, 0.000000001, _('%.2f ns')),
2909 2919 (1, 0.000000001, _('%.3f ns')),
2910 2920 )
2911 2921
2912 2922 _timenesting = [0]
2913 2923
2914 2924 def timed(func):
2915 2925 '''Report the execution time of a function call to stderr.
2916 2926
2917 2927 During development, use as a decorator when you need to measure
2918 2928 the cost of a function, e.g. as follows:
2919 2929
2920 2930 @util.timed
2921 2931 def foo(a, b, c):
2922 2932 pass
2923 2933 '''
2924 2934
2925 2935 def wrapper(*args, **kwargs):
2926 2936 start = timer()
2927 2937 indent = 2
2928 2938 _timenesting[0] += indent
2929 2939 try:
2930 2940 return func(*args, **kwargs)
2931 2941 finally:
2932 2942 elapsed = timer() - start
2933 2943 _timenesting[0] -= indent
2934 2944 stderr.write('%s%s: %s\n' %
2935 2945 (' ' * _timenesting[0], func.__name__,
2936 2946 timecount(elapsed)))
2937 2947 return wrapper
2938 2948
2939 2949 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2940 2950 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2941 2951
2942 2952 def sizetoint(s):
2943 2953 '''Convert a space specifier to a byte count.
2944 2954
2945 2955 >>> sizetoint('30')
2946 2956 30
2947 2957 >>> sizetoint('2.2kb')
2948 2958 2252
2949 2959 >>> sizetoint('6M')
2950 2960 6291456
2951 2961 '''
2952 2962 t = s.strip().lower()
2953 2963 try:
2954 2964 for k, u in _sizeunits:
2955 2965 if t.endswith(k):
2956 2966 return int(float(t[:-len(k)]) * u)
2957 2967 return int(t)
2958 2968 except ValueError:
2959 2969 raise error.ParseError(_("couldn't parse size: %s") % s)
2960 2970
2961 2971 class hooks(object):
2962 2972 '''A collection of hook functions that can be used to extend a
2963 2973 function's behavior. Hooks are called in lexicographic order,
2964 2974 based on the names of their sources.'''
2965 2975
2966 2976 def __init__(self):
2967 2977 self._hooks = []
2968 2978
2969 2979 def add(self, source, hook):
2970 2980 self._hooks.append((source, hook))
2971 2981
2972 2982 def __call__(self, *args):
2973 2983 self._hooks.sort(key=lambda x: x[0])
2974 2984 results = []
2975 2985 for source, hook in self._hooks:
2976 2986 results.append(hook(*args))
2977 2987 return results
2978 2988
2979 2989 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
2980 2990 '''Yields lines for a nicely formatted stacktrace.
2981 2991 Skips the 'skip' last entries, then return the last 'depth' entries.
2982 2992 Each file+linenumber is formatted according to fileline.
2983 2993 Each line is formatted according to line.
2984 2994 If line is None, it yields:
2985 2995 length of longest filepath+line number,
2986 2996 filepath+linenumber,
2987 2997 function
2988 2998
2989 2999 Not be used in production code but very convenient while developing.
2990 3000 '''
2991 3001 entries = [(fileline % (fn, ln), func)
2992 3002 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
2993 3003 ][-depth:]
2994 3004 if entries:
2995 3005 fnmax = max(len(entry[0]) for entry in entries)
2996 3006 for fnln, func in entries:
2997 3007 if line is None:
2998 3008 yield (fnmax, fnln, func)
2999 3009 else:
3000 3010 yield line % (fnmax, fnln, func)
3001 3011
3002 3012 def debugstacktrace(msg='stacktrace', skip=0,
3003 3013 f=stderr, otherf=stdout, depth=0):
3004 3014 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3005 3015 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3006 3016 By default it will flush stdout first.
3007 3017 It can be used everywhere and intentionally does not require an ui object.
3008 3018 Not be used in production code but very convenient while developing.
3009 3019 '''
3010 3020 if otherf:
3011 3021 otherf.flush()
3012 3022 f.write('%s at:\n' % msg.rstrip())
3013 3023 for line in getstackframes(skip + 1, depth=depth):
3014 3024 f.write(line)
3015 3025 f.flush()
3016 3026
3017 3027 class dirs(object):
3018 3028 '''a multiset of directory names from a dirstate or manifest'''
3019 3029
3020 3030 def __init__(self, map, skip=None):
3021 3031 self._dirs = {}
3022 3032 addpath = self.addpath
3023 3033 if safehasattr(map, 'iteritems') and skip is not None:
3024 3034 for f, s in map.iteritems():
3025 3035 if s[0] != skip:
3026 3036 addpath(f)
3027 3037 else:
3028 3038 for f in map:
3029 3039 addpath(f)
3030 3040
3031 3041 def addpath(self, path):
3032 3042 dirs = self._dirs
3033 3043 for base in finddirs(path):
3034 3044 if base in dirs:
3035 3045 dirs[base] += 1
3036 3046 return
3037 3047 dirs[base] = 1
3038 3048
3039 3049 def delpath(self, path):
3040 3050 dirs = self._dirs
3041 3051 for base in finddirs(path):
3042 3052 if dirs[base] > 1:
3043 3053 dirs[base] -= 1
3044 3054 return
3045 3055 del dirs[base]
3046 3056
3047 3057 def __iter__(self):
3048 3058 return iter(self._dirs)
3049 3059
3050 3060 def __contains__(self, d):
3051 3061 return d in self._dirs
3052 3062
3053 3063 if safehasattr(parsers, 'dirs'):
3054 3064 dirs = parsers.dirs
3055 3065
3056 3066 def finddirs(path):
3057 3067 pos = path.rfind('/')
3058 3068 while pos != -1:
3059 3069 yield path[:pos]
3060 3070 pos = path.rfind('/', 0, pos)
3061 3071
3062 3072 class ctxmanager(object):
3063 3073 '''A context manager for use in 'with' blocks to allow multiple
3064 3074 contexts to be entered at once. This is both safer and more
3065 3075 flexible than contextlib.nested.
3066 3076
3067 3077 Once Mercurial supports Python 2.7+, this will become mostly
3068 3078 unnecessary.
3069 3079 '''
3070 3080
3071 3081 def __init__(self, *args):
3072 3082 '''Accepts a list of no-argument functions that return context
3073 3083 managers. These will be invoked at __call__ time.'''
3074 3084 self._pending = args
3075 3085 self._atexit = []
3076 3086
3077 3087 def __enter__(self):
3078 3088 return self
3079 3089
3080 3090 def enter(self):
3081 3091 '''Create and enter context managers in the order in which they were
3082 3092 passed to the constructor.'''
3083 3093 values = []
3084 3094 for func in self._pending:
3085 3095 obj = func()
3086 3096 values.append(obj.__enter__())
3087 3097 self._atexit.append(obj.__exit__)
3088 3098 del self._pending
3089 3099 return values
3090 3100
3091 3101 def atexit(self, func, *args, **kwargs):
3092 3102 '''Add a function to call when this context manager exits. The
3093 3103 ordering of multiple atexit calls is unspecified, save that
3094 3104 they will happen before any __exit__ functions.'''
3095 3105 def wrapper(exc_type, exc_val, exc_tb):
3096 3106 func(*args, **kwargs)
3097 3107 self._atexit.append(wrapper)
3098 3108 return func
3099 3109
3100 3110 def __exit__(self, exc_type, exc_val, exc_tb):
3101 3111 '''Context managers are exited in the reverse order from which
3102 3112 they were created.'''
3103 3113 received = exc_type is not None
3104 3114 suppressed = False
3105 3115 pending = None
3106 3116 self._atexit.reverse()
3107 3117 for exitfunc in self._atexit:
3108 3118 try:
3109 3119 if exitfunc(exc_type, exc_val, exc_tb):
3110 3120 suppressed = True
3111 3121 exc_type = None
3112 3122 exc_val = None
3113 3123 exc_tb = None
3114 3124 except BaseException:
3115 3125 pending = sys.exc_info()
3116 3126 exc_type, exc_val, exc_tb = pending = sys.exc_info()
3117 3127 del self._atexit
3118 3128 if pending:
3119 3129 raise exc_val
3120 3130 return received and suppressed
3121 3131
3122 3132 # compression code
3123 3133
3124 3134 SERVERROLE = 'server'
3125 3135 CLIENTROLE = 'client'
3126 3136
3127 3137 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3128 3138 (u'name', u'serverpriority',
3129 3139 u'clientpriority'))
3130 3140
3131 3141 class compressormanager(object):
3132 3142 """Holds registrations of various compression engines.
3133 3143
3134 3144 This class essentially abstracts the differences between compression
3135 3145 engines to allow new compression formats to be added easily, possibly from
3136 3146 extensions.
3137 3147
3138 3148 Compressors are registered against the global instance by calling its
3139 3149 ``register()`` method.
3140 3150 """
3141 3151 def __init__(self):
3142 3152 self._engines = {}
3143 3153 # Bundle spec human name to engine name.
3144 3154 self._bundlenames = {}
3145 3155 # Internal bundle identifier to engine name.
3146 3156 self._bundletypes = {}
3147 3157 # Revlog header to engine name.
3148 3158 self._revlogheaders = {}
3149 3159 # Wire proto identifier to engine name.
3150 3160 self._wiretypes = {}
3151 3161
3152 3162 def __getitem__(self, key):
3153 3163 return self._engines[key]
3154 3164
3155 3165 def __contains__(self, key):
3156 3166 return key in self._engines
3157 3167
3158 3168 def __iter__(self):
3159 3169 return iter(self._engines.keys())
3160 3170
3161 3171 def register(self, engine):
3162 3172 """Register a compression engine with the manager.
3163 3173
3164 3174 The argument must be a ``compressionengine`` instance.
3165 3175 """
3166 3176 if not isinstance(engine, compressionengine):
3167 3177 raise ValueError(_('argument must be a compressionengine'))
3168 3178
3169 3179 name = engine.name()
3170 3180
3171 3181 if name in self._engines:
3172 3182 raise error.Abort(_('compression engine %s already registered') %
3173 3183 name)
3174 3184
3175 3185 bundleinfo = engine.bundletype()
3176 3186 if bundleinfo:
3177 3187 bundlename, bundletype = bundleinfo
3178 3188
3179 3189 if bundlename in self._bundlenames:
3180 3190 raise error.Abort(_('bundle name %s already registered') %
3181 3191 bundlename)
3182 3192 if bundletype in self._bundletypes:
3183 3193 raise error.Abort(_('bundle type %s already registered by %s') %
3184 3194 (bundletype, self._bundletypes[bundletype]))
3185 3195
3186 3196 # No external facing name declared.
3187 3197 if bundlename:
3188 3198 self._bundlenames[bundlename] = name
3189 3199
3190 3200 self._bundletypes[bundletype] = name
3191 3201
3192 3202 wiresupport = engine.wireprotosupport()
3193 3203 if wiresupport:
3194 3204 wiretype = wiresupport.name
3195 3205 if wiretype in self._wiretypes:
3196 3206 raise error.Abort(_('wire protocol compression %s already '
3197 3207 'registered by %s') %
3198 3208 (wiretype, self._wiretypes[wiretype]))
3199 3209
3200 3210 self._wiretypes[wiretype] = name
3201 3211
3202 3212 revlogheader = engine.revlogheader()
3203 3213 if revlogheader and revlogheader in self._revlogheaders:
3204 3214 raise error.Abort(_('revlog header %s already registered by %s') %
3205 3215 (revlogheader, self._revlogheaders[revlogheader]))
3206 3216
3207 3217 if revlogheader:
3208 3218 self._revlogheaders[revlogheader] = name
3209 3219
3210 3220 self._engines[name] = engine
3211 3221
3212 3222 @property
3213 3223 def supportedbundlenames(self):
3214 3224 return set(self._bundlenames.keys())
3215 3225
3216 3226 @property
3217 3227 def supportedbundletypes(self):
3218 3228 return set(self._bundletypes.keys())
3219 3229
3220 3230 def forbundlename(self, bundlename):
3221 3231 """Obtain a compression engine registered to a bundle name.
3222 3232
3223 3233 Will raise KeyError if the bundle type isn't registered.
3224 3234
3225 3235 Will abort if the engine is known but not available.
3226 3236 """
3227 3237 engine = self._engines[self._bundlenames[bundlename]]
3228 3238 if not engine.available():
3229 3239 raise error.Abort(_('compression engine %s could not be loaded') %
3230 3240 engine.name())
3231 3241 return engine
3232 3242
3233 3243 def forbundletype(self, bundletype):
3234 3244 """Obtain a compression engine registered to a bundle type.
3235 3245
3236 3246 Will raise KeyError if the bundle type isn't registered.
3237 3247
3238 3248 Will abort if the engine is known but not available.
3239 3249 """
3240 3250 engine = self._engines[self._bundletypes[bundletype]]
3241 3251 if not engine.available():
3242 3252 raise error.Abort(_('compression engine %s could not be loaded') %
3243 3253 engine.name())
3244 3254 return engine
3245 3255
3246 3256 def supportedwireengines(self, role, onlyavailable=True):
3247 3257 """Obtain compression engines that support the wire protocol.
3248 3258
3249 3259 Returns a list of engines in prioritized order, most desired first.
3250 3260
3251 3261 If ``onlyavailable`` is set, filter out engines that can't be
3252 3262 loaded.
3253 3263 """
3254 3264 assert role in (SERVERROLE, CLIENTROLE)
3255 3265
3256 3266 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3257 3267
3258 3268 engines = [self._engines[e] for e in self._wiretypes.values()]
3259 3269 if onlyavailable:
3260 3270 engines = [e for e in engines if e.available()]
3261 3271
3262 3272 def getkey(e):
3263 3273 # Sort first by priority, highest first. In case of tie, sort
3264 3274 # alphabetically. This is arbitrary, but ensures output is
3265 3275 # stable.
3266 3276 w = e.wireprotosupport()
3267 3277 return -1 * getattr(w, attr), w.name
3268 3278
3269 3279 return list(sorted(engines, key=getkey))
3270 3280
3271 3281 def forwiretype(self, wiretype):
3272 3282 engine = self._engines[self._wiretypes[wiretype]]
3273 3283 if not engine.available():
3274 3284 raise error.Abort(_('compression engine %s could not be loaded') %
3275 3285 engine.name())
3276 3286 return engine
3277 3287
3278 3288 def forrevlogheader(self, header):
3279 3289 """Obtain a compression engine registered to a revlog header.
3280 3290
3281 3291 Will raise KeyError if the revlog header value isn't registered.
3282 3292 """
3283 3293 return self._engines[self._revlogheaders[header]]
3284 3294
3285 3295 compengines = compressormanager()
3286 3296
3287 3297 class compressionengine(object):
3288 3298 """Base class for compression engines.
3289 3299
3290 3300 Compression engines must implement the interface defined by this class.
3291 3301 """
3292 3302 def name(self):
3293 3303 """Returns the name of the compression engine.
3294 3304
3295 3305 This is the key the engine is registered under.
3296 3306
3297 3307 This method must be implemented.
3298 3308 """
3299 3309 raise NotImplementedError()
3300 3310
3301 3311 def available(self):
3302 3312 """Whether the compression engine is available.
3303 3313
3304 3314 The intent of this method is to allow optional compression engines
3305 3315 that may not be available in all installations (such as engines relying
3306 3316 on C extensions that may not be present).
3307 3317 """
3308 3318 return True
3309 3319
3310 3320 def bundletype(self):
3311 3321 """Describes bundle identifiers for this engine.
3312 3322
3313 3323 If this compression engine isn't supported for bundles, returns None.
3314 3324
3315 3325 If this engine can be used for bundles, returns a 2-tuple of strings of
3316 3326 the user-facing "bundle spec" compression name and an internal
3317 3327 identifier used to denote the compression format within bundles. To
3318 3328 exclude the name from external usage, set the first element to ``None``.
3319 3329
3320 3330 If bundle compression is supported, the class must also implement
3321 3331 ``compressstream`` and `decompressorreader``.
3322 3332
3323 3333 The docstring of this method is used in the help system to tell users
3324 3334 about this engine.
3325 3335 """
3326 3336 return None
3327 3337
3328 3338 def wireprotosupport(self):
3329 3339 """Declare support for this compression format on the wire protocol.
3330 3340
3331 3341 If this compression engine isn't supported for compressing wire
3332 3342 protocol payloads, returns None.
3333 3343
3334 3344 Otherwise, returns ``compenginewireprotosupport`` with the following
3335 3345 fields:
3336 3346
3337 3347 * String format identifier
3338 3348 * Integer priority for the server
3339 3349 * Integer priority for the client
3340 3350
3341 3351 The integer priorities are used to order the advertisement of format
3342 3352 support by server and client. The highest integer is advertised
3343 3353 first. Integers with non-positive values aren't advertised.
3344 3354
3345 3355 The priority values are somewhat arbitrary and only used for default
3346 3356 ordering. The relative order can be changed via config options.
3347 3357
3348 3358 If wire protocol compression is supported, the class must also implement
3349 3359 ``compressstream`` and ``decompressorreader``.
3350 3360 """
3351 3361 return None
3352 3362
3353 3363 def revlogheader(self):
3354 3364 """Header added to revlog chunks that identifies this engine.
3355 3365
3356 3366 If this engine can be used to compress revlogs, this method should
3357 3367 return the bytes used to identify chunks compressed with this engine.
3358 3368 Else, the method should return ``None`` to indicate it does not
3359 3369 participate in revlog compression.
3360 3370 """
3361 3371 return None
3362 3372
3363 3373 def compressstream(self, it, opts=None):
3364 3374 """Compress an iterator of chunks.
3365 3375
3366 3376 The method receives an iterator (ideally a generator) of chunks of
3367 3377 bytes to be compressed. It returns an iterator (ideally a generator)
3368 3378 of bytes of chunks representing the compressed output.
3369 3379
3370 3380 Optionally accepts an argument defining how to perform compression.
3371 3381 Each engine treats this argument differently.
3372 3382 """
3373 3383 raise NotImplementedError()
3374 3384
3375 3385 def decompressorreader(self, fh):
3376 3386 """Perform decompression on a file object.
3377 3387
3378 3388 Argument is an object with a ``read(size)`` method that returns
3379 3389 compressed data. Return value is an object with a ``read(size)`` that
3380 3390 returns uncompressed data.
3381 3391 """
3382 3392 raise NotImplementedError()
3383 3393
3384 3394 def revlogcompressor(self, opts=None):
3385 3395 """Obtain an object that can be used to compress revlog entries.
3386 3396
3387 3397 The object has a ``compress(data)`` method that compresses binary
3388 3398 data. This method returns compressed binary data or ``None`` if
3389 3399 the data could not be compressed (too small, not compressible, etc).
3390 3400 The returned data should have a header uniquely identifying this
3391 3401 compression format so decompression can be routed to this engine.
3392 3402 This header should be identified by the ``revlogheader()`` return
3393 3403 value.
3394 3404
3395 3405 The object has a ``decompress(data)`` method that decompresses
3396 3406 data. The method will only be called if ``data`` begins with
3397 3407 ``revlogheader()``. The method should return the raw, uncompressed
3398 3408 data or raise a ``RevlogError``.
3399 3409
3400 3410 The object is reusable but is not thread safe.
3401 3411 """
3402 3412 raise NotImplementedError()
3403 3413
3404 3414 class _zlibengine(compressionengine):
3405 3415 def name(self):
3406 3416 return 'zlib'
3407 3417
3408 3418 def bundletype(self):
3409 3419 """zlib compression using the DEFLATE algorithm.
3410 3420
3411 3421 All Mercurial clients should support this format. The compression
3412 3422 algorithm strikes a reasonable balance between compression ratio
3413 3423 and size.
3414 3424 """
3415 3425 return 'gzip', 'GZ'
3416 3426
3417 3427 def wireprotosupport(self):
3418 3428 return compewireprotosupport('zlib', 20, 20)
3419 3429
3420 3430 def revlogheader(self):
3421 3431 return 'x'
3422 3432
3423 3433 def compressstream(self, it, opts=None):
3424 3434 opts = opts or {}
3425 3435
3426 3436 z = zlib.compressobj(opts.get('level', -1))
3427 3437 for chunk in it:
3428 3438 data = z.compress(chunk)
3429 3439 # Not all calls to compress emit data. It is cheaper to inspect
3430 3440 # here than to feed empty chunks through generator.
3431 3441 if data:
3432 3442 yield data
3433 3443
3434 3444 yield z.flush()
3435 3445
3436 3446 def decompressorreader(self, fh):
3437 3447 def gen():
3438 3448 d = zlib.decompressobj()
3439 3449 for chunk in filechunkiter(fh):
3440 3450 while chunk:
3441 3451 # Limit output size to limit memory.
3442 3452 yield d.decompress(chunk, 2 ** 18)
3443 3453 chunk = d.unconsumed_tail
3444 3454
3445 3455 return chunkbuffer(gen())
3446 3456
3447 3457 class zlibrevlogcompressor(object):
3448 3458 def compress(self, data):
3449 3459 insize = len(data)
3450 3460 # Caller handles empty input case.
3451 3461 assert insize > 0
3452 3462
3453 3463 if insize < 44:
3454 3464 return None
3455 3465
3456 3466 elif insize <= 1000000:
3457 3467 compressed = zlib.compress(data)
3458 3468 if len(compressed) < insize:
3459 3469 return compressed
3460 3470 return None
3461 3471
3462 3472 # zlib makes an internal copy of the input buffer, doubling
3463 3473 # memory usage for large inputs. So do streaming compression
3464 3474 # on large inputs.
3465 3475 else:
3466 3476 z = zlib.compressobj()
3467 3477 parts = []
3468 3478 pos = 0
3469 3479 while pos < insize:
3470 3480 pos2 = pos + 2**20
3471 3481 parts.append(z.compress(data[pos:pos2]))
3472 3482 pos = pos2
3473 3483 parts.append(z.flush())
3474 3484
3475 3485 if sum(map(len, parts)) < insize:
3476 3486 return ''.join(parts)
3477 3487 return None
3478 3488
3479 3489 def decompress(self, data):
3480 3490 try:
3481 3491 return zlib.decompress(data)
3482 3492 except zlib.error as e:
3483 3493 raise error.RevlogError(_('revlog decompress error: %s') %
3484 3494 str(e))
3485 3495
3486 3496 def revlogcompressor(self, opts=None):
3487 3497 return self.zlibrevlogcompressor()
3488 3498
3489 3499 compengines.register(_zlibengine())
3490 3500
3491 3501 class _bz2engine(compressionengine):
3492 3502 def name(self):
3493 3503 return 'bz2'
3494 3504
3495 3505 def bundletype(self):
3496 3506 """An algorithm that produces smaller bundles than ``gzip``.
3497 3507
3498 3508 All Mercurial clients should support this format.
3499 3509
3500 3510 This engine will likely produce smaller bundles than ``gzip`` but
3501 3511 will be significantly slower, both during compression and
3502 3512 decompression.
3503 3513
3504 3514 If available, the ``zstd`` engine can yield similar or better
3505 3515 compression at much higher speeds.
3506 3516 """
3507 3517 return 'bzip2', 'BZ'
3508 3518
3509 3519 # We declare a protocol name but don't advertise by default because
3510 3520 # it is slow.
3511 3521 def wireprotosupport(self):
3512 3522 return compewireprotosupport('bzip2', 0, 0)
3513 3523
3514 3524 def compressstream(self, it, opts=None):
3515 3525 opts = opts or {}
3516 3526 z = bz2.BZ2Compressor(opts.get('level', 9))
3517 3527 for chunk in it:
3518 3528 data = z.compress(chunk)
3519 3529 if data:
3520 3530 yield data
3521 3531
3522 3532 yield z.flush()
3523 3533
3524 3534 def decompressorreader(self, fh):
3525 3535 def gen():
3526 3536 d = bz2.BZ2Decompressor()
3527 3537 for chunk in filechunkiter(fh):
3528 3538 yield d.decompress(chunk)
3529 3539
3530 3540 return chunkbuffer(gen())
3531 3541
3532 3542 compengines.register(_bz2engine())
3533 3543
3534 3544 class _truncatedbz2engine(compressionengine):
3535 3545 def name(self):
3536 3546 return 'bz2truncated'
3537 3547
3538 3548 def bundletype(self):
3539 3549 return None, '_truncatedBZ'
3540 3550
3541 3551 # We don't implement compressstream because it is hackily handled elsewhere.
3542 3552
3543 3553 def decompressorreader(self, fh):
3544 3554 def gen():
3545 3555 # The input stream doesn't have the 'BZ' header. So add it back.
3546 3556 d = bz2.BZ2Decompressor()
3547 3557 d.decompress('BZ')
3548 3558 for chunk in filechunkiter(fh):
3549 3559 yield d.decompress(chunk)
3550 3560
3551 3561 return chunkbuffer(gen())
3552 3562
3553 3563 compengines.register(_truncatedbz2engine())
3554 3564
3555 3565 class _noopengine(compressionengine):
3556 3566 def name(self):
3557 3567 return 'none'
3558 3568
3559 3569 def bundletype(self):
3560 3570 """No compression is performed.
3561 3571
3562 3572 Use this compression engine to explicitly disable compression.
3563 3573 """
3564 3574 return 'none', 'UN'
3565 3575
3566 3576 # Clients always support uncompressed payloads. Servers don't because
3567 3577 # unless you are on a fast network, uncompressed payloads can easily
3568 3578 # saturate your network pipe.
3569 3579 def wireprotosupport(self):
3570 3580 return compewireprotosupport('none', 0, 10)
3571 3581
3572 3582 # We don't implement revlogheader because it is handled specially
3573 3583 # in the revlog class.
3574 3584
3575 3585 def compressstream(self, it, opts=None):
3576 3586 return it
3577 3587
3578 3588 def decompressorreader(self, fh):
3579 3589 return fh
3580 3590
3581 3591 class nooprevlogcompressor(object):
3582 3592 def compress(self, data):
3583 3593 return None
3584 3594
3585 3595 def revlogcompressor(self, opts=None):
3586 3596 return self.nooprevlogcompressor()
3587 3597
3588 3598 compengines.register(_noopengine())
3589 3599
3590 3600 class _zstdengine(compressionengine):
3591 3601 def name(self):
3592 3602 return 'zstd'
3593 3603
3594 3604 @propertycache
3595 3605 def _module(self):
3596 3606 # Not all installs have the zstd module available. So defer importing
3597 3607 # until first access.
3598 3608 try:
3599 3609 from . import zstd
3600 3610 # Force delayed import.
3601 3611 zstd.__version__
3602 3612 return zstd
3603 3613 except ImportError:
3604 3614 return None
3605 3615
3606 3616 def available(self):
3607 3617 return bool(self._module)
3608 3618
3609 3619 def bundletype(self):
3610 3620 """A modern compression algorithm that is fast and highly flexible.
3611 3621
3612 3622 Only supported by Mercurial 4.1 and newer clients.
3613 3623
3614 3624 With the default settings, zstd compression is both faster and yields
3615 3625 better compression than ``gzip``. It also frequently yields better
3616 3626 compression than ``bzip2`` while operating at much higher speeds.
3617 3627
3618 3628 If this engine is available and backwards compatibility is not a
3619 3629 concern, it is likely the best available engine.
3620 3630 """
3621 3631 return 'zstd', 'ZS'
3622 3632
3623 3633 def wireprotosupport(self):
3624 3634 return compewireprotosupport('zstd', 50, 50)
3625 3635
3626 3636 def revlogheader(self):
3627 3637 return '\x28'
3628 3638
3629 3639 def compressstream(self, it, opts=None):
3630 3640 opts = opts or {}
3631 3641 # zstd level 3 is almost always significantly faster than zlib
3632 3642 # while providing no worse compression. It strikes a good balance
3633 3643 # between speed and compression.
3634 3644 level = opts.get('level', 3)
3635 3645
3636 3646 zstd = self._module
3637 3647 z = zstd.ZstdCompressor(level=level).compressobj()
3638 3648 for chunk in it:
3639 3649 data = z.compress(chunk)
3640 3650 if data:
3641 3651 yield data
3642 3652
3643 3653 yield z.flush()
3644 3654
3645 3655 def decompressorreader(self, fh):
3646 3656 zstd = self._module
3647 3657 dctx = zstd.ZstdDecompressor()
3648 3658 return chunkbuffer(dctx.read_from(fh))
3649 3659
3650 3660 class zstdrevlogcompressor(object):
3651 3661 def __init__(self, zstd, level=3):
3652 3662 # Writing the content size adds a few bytes to the output. However,
3653 3663 # it allows decompression to be more optimal since we can
3654 3664 # pre-allocate a buffer to hold the result.
3655 3665 self._cctx = zstd.ZstdCompressor(level=level,
3656 3666 write_content_size=True)
3657 3667 self._dctx = zstd.ZstdDecompressor()
3658 3668 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3659 3669 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3660 3670
3661 3671 def compress(self, data):
3662 3672 insize = len(data)
3663 3673 # Caller handles empty input case.
3664 3674 assert insize > 0
3665 3675
3666 3676 if insize < 50:
3667 3677 return None
3668 3678
3669 3679 elif insize <= 1000000:
3670 3680 compressed = self._cctx.compress(data)
3671 3681 if len(compressed) < insize:
3672 3682 return compressed
3673 3683 return None
3674 3684 else:
3675 3685 z = self._cctx.compressobj()
3676 3686 chunks = []
3677 3687 pos = 0
3678 3688 while pos < insize:
3679 3689 pos2 = pos + self._compinsize
3680 3690 chunk = z.compress(data[pos:pos2])
3681 3691 if chunk:
3682 3692 chunks.append(chunk)
3683 3693 pos = pos2
3684 3694 chunks.append(z.flush())
3685 3695
3686 3696 if sum(map(len, chunks)) < insize:
3687 3697 return ''.join(chunks)
3688 3698 return None
3689 3699
3690 3700 def decompress(self, data):
3691 3701 insize = len(data)
3692 3702
3693 3703 try:
3694 3704 # This was measured to be faster than other streaming
3695 3705 # decompressors.
3696 3706 dobj = self._dctx.decompressobj()
3697 3707 chunks = []
3698 3708 pos = 0
3699 3709 while pos < insize:
3700 3710 pos2 = pos + self._decompinsize
3701 3711 chunk = dobj.decompress(data[pos:pos2])
3702 3712 if chunk:
3703 3713 chunks.append(chunk)
3704 3714 pos = pos2
3705 3715 # Frame should be exhausted, so no finish() API.
3706 3716
3707 3717 return ''.join(chunks)
3708 3718 except Exception as e:
3709 3719 raise error.RevlogError(_('revlog decompress error: %s') %
3710 3720 str(e))
3711 3721
3712 3722 def revlogcompressor(self, opts=None):
3713 3723 opts = opts or {}
3714 3724 return self.zstdrevlogcompressor(self._module,
3715 3725 level=opts.get('level', 3))
3716 3726
3717 3727 compengines.register(_zstdengine())
3718 3728
3719 3729 def bundlecompressiontopics():
3720 3730 """Obtains a list of available bundle compressions for use in help."""
3721 3731 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3722 3732 items = {}
3723 3733
3724 3734 # We need to format the docstring. So use a dummy object/type to hold it
3725 3735 # rather than mutating the original.
3726 3736 class docobject(object):
3727 3737 pass
3728 3738
3729 3739 for name in compengines:
3730 3740 engine = compengines[name]
3731 3741
3732 3742 if not engine.available():
3733 3743 continue
3734 3744
3735 3745 bt = engine.bundletype()
3736 3746 if not bt or not bt[0]:
3737 3747 continue
3738 3748
3739 3749 doc = pycompat.sysstr('``%s``\n %s') % (
3740 3750 bt[0], engine.bundletype.__doc__)
3741 3751
3742 3752 value = docobject()
3743 3753 value.__doc__ = doc
3744 3754
3745 3755 items[bt[0]] = value
3746 3756
3747 3757 return items
3748 3758
3749 3759 # convenient shortcut
3750 3760 dst = debugstacktrace
@@ -1,637 +1,636
1 1 # vfs.py - Mercurial 'vfs' classes
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import contextlib
10 10 import errno
11 11 import os
12 12 import shutil
13 13 import stat
14 14 import tempfile
15 15 import threading
16 16
17 17 from .i18n import _
18 18 from . import (
19 19 error,
20 osutil,
21 20 pathutil,
22 21 pycompat,
23 22 util,
24 23 )
25 24
26 25 class abstractvfs(object):
27 26 """Abstract base class; cannot be instantiated"""
28 27
29 28 def __init__(self, *args, **kwargs):
30 29 '''Prevent instantiation; don't call this from subclasses.'''
31 30 raise NotImplementedError('attempted instantiating ' + str(type(self)))
32 31
33 32 def tryread(self, path):
34 33 '''gracefully return an empty string for missing files'''
35 34 try:
36 35 return self.read(path)
37 36 except IOError as inst:
38 37 if inst.errno != errno.ENOENT:
39 38 raise
40 39 return ""
41 40
42 41 def tryreadlines(self, path, mode='rb'):
43 42 '''gracefully return an empty array for missing files'''
44 43 try:
45 44 return self.readlines(path, mode=mode)
46 45 except IOError as inst:
47 46 if inst.errno != errno.ENOENT:
48 47 raise
49 48 return []
50 49
51 50 @util.propertycache
52 51 def open(self):
53 52 '''Open ``path`` file, which is relative to vfs root.
54 53
55 54 Newly created directories are marked as "not to be indexed by
56 55 the content indexing service", if ``notindexed`` is specified
57 56 for "write" mode access.
58 57 '''
59 58 return self.__call__
60 59
61 60 def read(self, path):
62 61 with self(path, 'rb') as fp:
63 62 return fp.read()
64 63
65 64 def readlines(self, path, mode='rb'):
66 65 with self(path, mode=mode) as fp:
67 66 return fp.readlines()
68 67
69 68 def write(self, path, data, backgroundclose=False):
70 69 with self(path, 'wb', backgroundclose=backgroundclose) as fp:
71 70 return fp.write(data)
72 71
73 72 def writelines(self, path, data, mode='wb', notindexed=False):
74 73 with self(path, mode=mode, notindexed=notindexed) as fp:
75 74 return fp.writelines(data)
76 75
77 76 def append(self, path, data):
78 77 with self(path, 'ab') as fp:
79 78 return fp.write(data)
80 79
81 80 def basename(self, path):
82 81 """return base element of a path (as os.path.basename would do)
83 82
84 83 This exists to allow handling of strange encoding if needed."""
85 84 return os.path.basename(path)
86 85
87 86 def chmod(self, path, mode):
88 87 return os.chmod(self.join(path), mode)
89 88
90 89 def dirname(self, path):
91 90 """return dirname element of a path (as os.path.dirname would do)
92 91
93 92 This exists to allow handling of strange encoding if needed."""
94 93 return os.path.dirname(path)
95 94
96 95 def exists(self, path=None):
97 96 return os.path.exists(self.join(path))
98 97
99 98 def fstat(self, fp):
100 99 return util.fstat(fp)
101 100
102 101 def isdir(self, path=None):
103 102 return os.path.isdir(self.join(path))
104 103
105 104 def isfile(self, path=None):
106 105 return os.path.isfile(self.join(path))
107 106
108 107 def islink(self, path=None):
109 108 return os.path.islink(self.join(path))
110 109
111 110 def isfileorlink(self, path=None):
112 111 '''return whether path is a regular file or a symlink
113 112
114 113 Unlike isfile, this doesn't follow symlinks.'''
115 114 try:
116 115 st = self.lstat(path)
117 116 except OSError:
118 117 return False
119 118 mode = st.st_mode
120 119 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
121 120
122 121 def reljoin(self, *paths):
123 122 """join various elements of a path together (as os.path.join would do)
124 123
125 124 The vfs base is not injected so that path stay relative. This exists
126 125 to allow handling of strange encoding if needed."""
127 126 return os.path.join(*paths)
128 127
129 128 def split(self, path):
130 129 """split top-most element of a path (as os.path.split would do)
131 130
132 131 This exists to allow handling of strange encoding if needed."""
133 132 return os.path.split(path)
134 133
135 134 def lexists(self, path=None):
136 135 return os.path.lexists(self.join(path))
137 136
138 137 def lstat(self, path=None):
139 138 return os.lstat(self.join(path))
140 139
141 140 def listdir(self, path=None):
142 141 return os.listdir(self.join(path))
143 142
144 143 def makedir(self, path=None, notindexed=True):
145 144 return util.makedir(self.join(path), notindexed)
146 145
147 146 def makedirs(self, path=None, mode=None):
148 147 return util.makedirs(self.join(path), mode)
149 148
150 149 def makelock(self, info, path):
151 150 return util.makelock(info, self.join(path))
152 151
153 152 def mkdir(self, path=None):
154 153 return os.mkdir(self.join(path))
155 154
156 155 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
157 156 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
158 157 dir=self.join(dir), text=text)
159 158 dname, fname = util.split(name)
160 159 if dir:
161 160 return fd, os.path.join(dir, fname)
162 161 else:
163 162 return fd, fname
164 163
165 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 167 def readlock(self, path):
169 168 return util.readlock(self.join(path))
170 169
171 170 def rename(self, src, dst, checkambig=False):
172 171 """Rename from src to dst
173 172
174 173 checkambig argument is used with util.filestat, and is useful
175 174 only if destination file is guarded by any lock
176 175 (e.g. repo.lock or repo.wlock).
177 176 """
178 177 dstpath = self.join(dst)
179 178 oldstat = checkambig and util.filestat(dstpath)
180 179 if oldstat and oldstat.stat:
181 180 ret = util.rename(self.join(src), dstpath)
182 181 newstat = util.filestat(dstpath)
183 182 if newstat.isambig(oldstat):
184 183 # stat of renamed file is ambiguous to original one
185 184 newstat.avoidambig(dstpath, oldstat)
186 185 return ret
187 186 return util.rename(self.join(src), dstpath)
188 187
189 188 def readlink(self, path):
190 189 return os.readlink(self.join(path))
191 190
192 191 def removedirs(self, path=None):
193 192 """Remove a leaf directory and all empty intermediate ones
194 193 """
195 194 return util.removedirs(self.join(path))
196 195
197 196 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
198 197 """Remove a directory tree recursively
199 198
200 199 If ``forcibly``, this tries to remove READ-ONLY files, too.
201 200 """
202 201 if forcibly:
203 202 def onerror(function, path, excinfo):
204 203 if function is not os.remove:
205 204 raise
206 205 # read-only files cannot be unlinked under Windows
207 206 s = os.stat(path)
208 207 if (s.st_mode & stat.S_IWRITE) != 0:
209 208 raise
210 209 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
211 210 os.remove(path)
212 211 else:
213 212 onerror = None
214 213 return shutil.rmtree(self.join(path),
215 214 ignore_errors=ignore_errors, onerror=onerror)
216 215
217 216 def setflags(self, path, l, x):
218 217 return util.setflags(self.join(path), l, x)
219 218
220 219 def stat(self, path=None):
221 220 return os.stat(self.join(path))
222 221
223 222 def unlink(self, path=None):
224 223 return util.unlink(self.join(path))
225 224
226 225 def tryunlink(self, path=None):
227 226 """Attempt to remove a file, ignoring missing file errors."""
228 227 util.tryunlink(self.join(path))
229 228
230 229 def unlinkpath(self, path=None, ignoremissing=False):
231 230 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
232 231
233 232 def utime(self, path=None, t=None):
234 233 return os.utime(self.join(path), t)
235 234
236 235 def walk(self, path=None, onerror=None):
237 236 """Yield (dirpath, dirs, files) tuple for each directories under path
238 237
239 238 ``dirpath`` is relative one from the root of this vfs. This
240 239 uses ``os.sep`` as path separator, even you specify POSIX
241 240 style ``path``.
242 241
243 242 "The root of this vfs" is represented as empty ``dirpath``.
244 243 """
245 244 root = os.path.normpath(self.join(None))
246 245 # when dirpath == root, dirpath[prefixlen:] becomes empty
247 246 # because len(dirpath) < prefixlen.
248 247 prefixlen = len(pathutil.normasprefix(root))
249 248 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
250 249 yield (dirpath[prefixlen:], dirs, files)
251 250
252 251 @contextlib.contextmanager
253 252 def backgroundclosing(self, ui, expectedcount=-1):
254 253 """Allow files to be closed asynchronously.
255 254
256 255 When this context manager is active, ``backgroundclose`` can be passed
257 256 to ``__call__``/``open`` to result in the file possibly being closed
258 257 asynchronously, on a background thread.
259 258 """
260 259 # This is an arbitrary restriction and could be changed if we ever
261 260 # have a use case.
262 261 vfs = getattr(self, 'vfs', self)
263 262 if getattr(vfs, '_backgroundfilecloser', None):
264 263 raise error.Abort(
265 264 _('can only have 1 active background file closer'))
266 265
267 266 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
268 267 try:
269 268 vfs._backgroundfilecloser = bfc
270 269 yield bfc
271 270 finally:
272 271 vfs._backgroundfilecloser = None
273 272
274 273 class vfs(abstractvfs):
275 274 '''Operate files relative to a base directory
276 275
277 276 This class is used to hide the details of COW semantics and
278 277 remote file access from higher level code.
279 278 '''
280 279 def __init__(self, base, audit=True, expandpath=False, realpath=False):
281 280 if expandpath:
282 281 base = util.expandpath(base)
283 282 if realpath:
284 283 base = os.path.realpath(base)
285 284 self.base = base
286 285 self.mustaudit = audit
287 286 self.createmode = None
288 287 self._trustnlink = None
289 288
290 289 @property
291 290 def mustaudit(self):
292 291 return self._audit
293 292
294 293 @mustaudit.setter
295 294 def mustaudit(self, onoff):
296 295 self._audit = onoff
297 296 if onoff:
298 297 self.audit = pathutil.pathauditor(self.base)
299 298 else:
300 299 self.audit = util.always
301 300
302 301 @util.propertycache
303 302 def _cansymlink(self):
304 303 return util.checklink(self.base)
305 304
306 305 @util.propertycache
307 306 def _chmod(self):
308 307 return util.checkexec(self.base)
309 308
310 309 def _fixfilemode(self, name):
311 310 if self.createmode is None or not self._chmod:
312 311 return
313 312 os.chmod(name, self.createmode & 0o666)
314 313
315 314 def __call__(self, path, mode="r", text=False, atomictemp=False,
316 315 notindexed=False, backgroundclose=False, checkambig=False):
317 316 '''Open ``path`` file, which is relative to vfs root.
318 317
319 318 Newly created directories are marked as "not to be indexed by
320 319 the content indexing service", if ``notindexed`` is specified
321 320 for "write" mode access.
322 321
323 322 If ``backgroundclose`` is passed, the file may be closed asynchronously.
324 323 It can only be used if the ``self.backgroundclosing()`` context manager
325 324 is active. This should only be specified if the following criteria hold:
326 325
327 326 1. There is a potential for writing thousands of files. Unless you
328 327 are writing thousands of files, the performance benefits of
329 328 asynchronously closing files is not realized.
330 329 2. Files are opened exactly once for the ``backgroundclosing``
331 330 active duration and are therefore free of race conditions between
332 331 closing a file on a background thread and reopening it. (If the
333 332 file were opened multiple times, there could be unflushed data
334 333 because the original file handle hasn't been flushed/closed yet.)
335 334
336 335 ``checkambig`` argument is passed to atomictemplfile (valid
337 336 only for writing), and is useful only if target file is
338 337 guarded by any lock (e.g. repo.lock or repo.wlock).
339 338 '''
340 339 if self._audit:
341 340 r = util.checkosfilename(path)
342 341 if r:
343 342 raise error.Abort("%s: %r" % (r, path))
344 343 self.audit(path)
345 344 f = self.join(path)
346 345
347 346 if not text and "b" not in mode:
348 347 mode += "b" # for that other OS
349 348
350 349 nlink = -1
351 350 if mode not in ('r', 'rb'):
352 351 dirname, basename = util.split(f)
353 352 # If basename is empty, then the path is malformed because it points
354 353 # to a directory. Let the posixfile() call below raise IOError.
355 354 if basename:
356 355 if atomictemp:
357 356 util.makedirs(dirname, self.createmode, notindexed)
358 357 return util.atomictempfile(f, mode, self.createmode,
359 358 checkambig=checkambig)
360 359 try:
361 360 if 'w' in mode:
362 361 util.unlink(f)
363 362 nlink = 0
364 363 else:
365 364 # nlinks() may behave differently for files on Windows
366 365 # shares if the file is open.
367 366 with util.posixfile(f):
368 367 nlink = util.nlinks(f)
369 368 if nlink < 1:
370 369 nlink = 2 # force mktempcopy (issue1922)
371 370 except (OSError, IOError) as e:
372 371 if e.errno != errno.ENOENT:
373 372 raise
374 373 nlink = 0
375 374 util.makedirs(dirname, self.createmode, notindexed)
376 375 if nlink > 0:
377 376 if self._trustnlink is None:
378 377 self._trustnlink = nlink > 1 or util.checknlink(f)
379 378 if nlink > 1 or not self._trustnlink:
380 379 util.rename(util.mktempcopy(f), f)
381 380 fp = util.posixfile(f, mode)
382 381 if nlink == 0:
383 382 self._fixfilemode(f)
384 383
385 384 if checkambig:
386 385 if mode in ('r', 'rb'):
387 386 raise error.Abort(_('implementation error: mode %s is not'
388 387 ' valid for checkambig=True') % mode)
389 388 fp = checkambigatclosing(fp)
390 389
391 390 if backgroundclose:
392 391 if not self._backgroundfilecloser:
393 392 raise error.Abort(_('backgroundclose can only be used when a '
394 393 'backgroundclosing context manager is active')
395 394 )
396 395
397 396 fp = delayclosedfile(fp, self._backgroundfilecloser)
398 397
399 398 return fp
400 399
401 400 def symlink(self, src, dst):
402 401 self.audit(dst)
403 402 linkname = self.join(dst)
404 403 util.tryunlink(linkname)
405 404
406 405 util.makedirs(os.path.dirname(linkname), self.createmode)
407 406
408 407 if self._cansymlink:
409 408 try:
410 409 os.symlink(src, linkname)
411 410 except OSError as err:
412 411 raise OSError(err.errno, _('could not symlink to %r: %s') %
413 412 (src, err.strerror), linkname)
414 413 else:
415 414 self.write(dst, src)
416 415
417 416 def join(self, path, *insidef):
418 417 if path:
419 418 return os.path.join(self.base, path, *insidef)
420 419 else:
421 420 return self.base
422 421
423 422 opener = vfs
424 423
425 424 class auditvfs(object):
426 425 def __init__(self, vfs):
427 426 self.vfs = vfs
428 427
429 428 @property
430 429 def mustaudit(self):
431 430 return self.vfs.mustaudit
432 431
433 432 @mustaudit.setter
434 433 def mustaudit(self, onoff):
435 434 self.vfs.mustaudit = onoff
436 435
437 436 @property
438 437 def options(self):
439 438 return self.vfs.options
440 439
441 440 @options.setter
442 441 def options(self, value):
443 442 self.vfs.options = value
444 443
445 444 class filtervfs(abstractvfs, auditvfs):
446 445 '''Wrapper vfs for filtering filenames with a function.'''
447 446
448 447 def __init__(self, vfs, filter):
449 448 auditvfs.__init__(self, vfs)
450 449 self._filter = filter
451 450
452 451 def __call__(self, path, *args, **kwargs):
453 452 return self.vfs(self._filter(path), *args, **kwargs)
454 453
455 454 def join(self, path, *insidef):
456 455 if path:
457 456 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
458 457 else:
459 458 return self.vfs.join(path)
460 459
461 460 filteropener = filtervfs
462 461
463 462 class readonlyvfs(abstractvfs, auditvfs):
464 463 '''Wrapper vfs preventing any writing.'''
465 464
466 465 def __init__(self, vfs):
467 466 auditvfs.__init__(self, vfs)
468 467
469 468 def __call__(self, path, mode='r', *args, **kw):
470 469 if mode not in ('r', 'rb'):
471 470 raise error.Abort(_('this vfs is read only'))
472 471 return self.vfs(path, mode, *args, **kw)
473 472
474 473 def join(self, path, *insidef):
475 474 return self.vfs.join(path, *insidef)
476 475
477 476 class closewrapbase(object):
478 477 """Base class of wrapper, which hooks closing
479 478
480 479 Do not instantiate outside of the vfs layer.
481 480 """
482 481 def __init__(self, fh):
483 482 object.__setattr__(self, r'_origfh', fh)
484 483
485 484 def __getattr__(self, attr):
486 485 return getattr(self._origfh, attr)
487 486
488 487 def __setattr__(self, attr, value):
489 488 return setattr(self._origfh, attr, value)
490 489
491 490 def __delattr__(self, attr):
492 491 return delattr(self._origfh, attr)
493 492
494 493 def __enter__(self):
495 494 return self._origfh.__enter__()
496 495
497 496 def __exit__(self, exc_type, exc_value, exc_tb):
498 497 raise NotImplementedError('attempted instantiating ' + str(type(self)))
499 498
500 499 def close(self):
501 500 raise NotImplementedError('attempted instantiating ' + str(type(self)))
502 501
503 502 class delayclosedfile(closewrapbase):
504 503 """Proxy for a file object whose close is delayed.
505 504
506 505 Do not instantiate outside of the vfs layer.
507 506 """
508 507 def __init__(self, fh, closer):
509 508 super(delayclosedfile, self).__init__(fh)
510 509 object.__setattr__(self, r'_closer', closer)
511 510
512 511 def __exit__(self, exc_type, exc_value, exc_tb):
513 512 self._closer.close(self._origfh)
514 513
515 514 def close(self):
516 515 self._closer.close(self._origfh)
517 516
518 517 class backgroundfilecloser(object):
519 518 """Coordinates background closing of file handles on multiple threads."""
520 519 def __init__(self, ui, expectedcount=-1):
521 520 self._running = False
522 521 self._entered = False
523 522 self._threads = []
524 523 self._threadexception = None
525 524
526 525 # Only Windows/NTFS has slow file closing. So only enable by default
527 526 # on that platform. But allow to be enabled elsewhere for testing.
528 527 defaultenabled = pycompat.osname == 'nt'
529 528 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
530 529
531 530 if not enabled:
532 531 return
533 532
534 533 # There is overhead to starting and stopping the background threads.
535 534 # Don't do background processing unless the file count is large enough
536 535 # to justify it.
537 536 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount',
538 537 2048)
539 538 # FUTURE dynamically start background threads after minfilecount closes.
540 539 # (We don't currently have any callers that don't know their file count)
541 540 if expectedcount > 0 and expectedcount < minfilecount:
542 541 return
543 542
544 543 # Windows defaults to a limit of 512 open files. A buffer of 128
545 544 # should give us enough headway.
546 545 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384)
547 546 threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4)
548 547
549 548 ui.debug('starting %d threads for background file closing\n' %
550 549 threadcount)
551 550
552 551 self._queue = util.queue(maxsize=maxqueue)
553 552 self._running = True
554 553
555 554 for i in range(threadcount):
556 555 t = threading.Thread(target=self._worker, name='backgroundcloser')
557 556 self._threads.append(t)
558 557 t.start()
559 558
560 559 def __enter__(self):
561 560 self._entered = True
562 561 return self
563 562
564 563 def __exit__(self, exc_type, exc_value, exc_tb):
565 564 self._running = False
566 565
567 566 # Wait for threads to finish closing so open files don't linger for
568 567 # longer than lifetime of context manager.
569 568 for t in self._threads:
570 569 t.join()
571 570
572 571 def _worker(self):
573 572 """Main routine for worker thread."""
574 573 while True:
575 574 try:
576 575 fh = self._queue.get(block=True, timeout=0.100)
577 576 # Need to catch or the thread will terminate and
578 577 # we could orphan file descriptors.
579 578 try:
580 579 fh.close()
581 580 except Exception as e:
582 581 # Stash so can re-raise from main thread later.
583 582 self._threadexception = e
584 583 except util.empty:
585 584 if not self._running:
586 585 break
587 586
588 587 def close(self, fh):
589 588 """Schedule a file for closing."""
590 589 if not self._entered:
591 590 raise error.Abort(_('can only call close() when context manager '
592 591 'active'))
593 592
594 593 # If a background thread encountered an exception, raise now so we fail
595 594 # fast. Otherwise we may potentially go on for minutes until the error
596 595 # is acted on.
597 596 if self._threadexception:
598 597 e = self._threadexception
599 598 self._threadexception = None
600 599 raise e
601 600
602 601 # If we're not actively running, close synchronously.
603 602 if not self._running:
604 603 fh.close()
605 604 return
606 605
607 606 self._queue.put(fh, block=True, timeout=None)
608 607
609 608 class checkambigatclosing(closewrapbase):
610 609 """Proxy for a file object, to avoid ambiguity of file stat
611 610
612 611 See also util.filestat for detail about "ambiguity of file stat".
613 612
614 613 This proxy is useful only if the target file is guarded by any
615 614 lock (e.g. repo.lock or repo.wlock)
616 615
617 616 Do not instantiate outside of the vfs layer.
618 617 """
619 618 def __init__(self, fh):
620 619 super(checkambigatclosing, self).__init__(fh)
621 620 object.__setattr__(self, r'_oldstat', util.filestat(fh.name))
622 621
623 622 def _checkambig(self):
624 623 oldstat = self._oldstat
625 624 if oldstat.stat:
626 625 newstat = util.filestat(self._origfh.name)
627 626 if newstat.isambig(oldstat):
628 627 # stat of changed file is ambiguous to original one
629 628 newstat.avoidambig(self._origfh.name, oldstat)
630 629
631 630 def __exit__(self, exc_type, exc_value, exc_tb):
632 631 self._origfh.__exit__(exc_type, exc_value, exc_tb)
633 632 self._checkambig()
634 633
635 634 def close(self):
636 635 self._origfh.close()
637 636 self._checkambig()
@@ -1,472 +1,475
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import msvcrt
12 12 import os
13 13 import re
14 14 import stat
15 15 import sys
16 16
17 17 from .i18n import _
18 18 from . import (
19 19 encoding,
20 20 osutil,
21 21 pycompat,
22 22 win32,
23 23 )
24 24
25 25 try:
26 26 import _winreg as winreg
27 27 winreg.CloseKey
28 28 except ImportError:
29 29 import winreg
30 30
31 31 executablepath = win32.executablepath
32 32 getuser = win32.getuser
33 33 hidewindow = win32.hidewindow
34 34 makedir = win32.makedir
35 35 nlinks = win32.nlinks
36 36 oslink = win32.oslink
37 37 samedevice = win32.samedevice
38 38 samefile = win32.samefile
39 39 setsignalhandler = win32.setsignalhandler
40 40 spawndetached = win32.spawndetached
41 41 split = os.path.split
42 42 testpid = win32.testpid
43 43 unlink = win32.unlink
44 44
45 45 umask = 0o022
46 46
47 47 class mixedfilemodewrapper(object):
48 48 """Wraps a file handle when it is opened in read/write mode.
49 49
50 50 fopen() and fdopen() on Windows have a specific-to-Windows requirement
51 51 that files opened with mode r+, w+, or a+ make a call to a file positioning
52 52 function when switching between reads and writes. Without this extra call,
53 53 Python will raise a not very intuitive "IOError: [Errno 0] Error."
54 54
55 55 This class wraps posixfile instances when the file is opened in read/write
56 56 mode and automatically adds checks or inserts appropriate file positioning
57 57 calls when necessary.
58 58 """
59 59 OPNONE = 0
60 60 OPREAD = 1
61 61 OPWRITE = 2
62 62
63 63 def __init__(self, fp):
64 64 object.__setattr__(self, r'_fp', fp)
65 65 object.__setattr__(self, r'_lastop', 0)
66 66
67 67 def __enter__(self):
68 68 return self._fp.__enter__()
69 69
70 70 def __exit__(self, exc_type, exc_val, exc_tb):
71 71 self._fp.__exit__(exc_type, exc_val, exc_tb)
72 72
73 73 def __getattr__(self, name):
74 74 return getattr(self._fp, name)
75 75
76 76 def __setattr__(self, name, value):
77 77 return self._fp.__setattr__(name, value)
78 78
79 79 def _noopseek(self):
80 80 self._fp.seek(0, os.SEEK_CUR)
81 81
82 82 def seek(self, *args, **kwargs):
83 83 object.__setattr__(self, r'_lastop', self.OPNONE)
84 84 return self._fp.seek(*args, **kwargs)
85 85
86 86 def write(self, d):
87 87 if self._lastop == self.OPREAD:
88 88 self._noopseek()
89 89
90 90 object.__setattr__(self, r'_lastop', self.OPWRITE)
91 91 return self._fp.write(d)
92 92
93 93 def writelines(self, *args, **kwargs):
94 94 if self._lastop == self.OPREAD:
95 95 self._noopeseek()
96 96
97 97 object.__setattr__(self, r'_lastop', self.OPWRITE)
98 98 return self._fp.writelines(*args, **kwargs)
99 99
100 100 def read(self, *args, **kwargs):
101 101 if self._lastop == self.OPWRITE:
102 102 self._noopseek()
103 103
104 104 object.__setattr__(self, r'_lastop', self.OPREAD)
105 105 return self._fp.read(*args, **kwargs)
106 106
107 107 def readline(self, *args, **kwargs):
108 108 if self._lastop == self.OPWRITE:
109 109 self._noopseek()
110 110
111 111 object.__setattr__(self, r'_lastop', self.OPREAD)
112 112 return self._fp.readline(*args, **kwargs)
113 113
114 114 def readlines(self, *args, **kwargs):
115 115 if self._lastop == self.OPWRITE:
116 116 self._noopseek()
117 117
118 118 object.__setattr__(self, r'_lastop', self.OPREAD)
119 119 return self._fp.readlines(*args, **kwargs)
120 120
121 121 def posixfile(name, mode='r', buffering=-1):
122 122 '''Open a file with even more POSIX-like semantics'''
123 123 try:
124 124 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
125 125
126 126 # The position when opening in append mode is implementation defined, so
127 127 # make it consistent with other platforms, which position at EOF.
128 128 if 'a' in mode:
129 129 fp.seek(0, os.SEEK_END)
130 130
131 131 if '+' in mode:
132 132 return mixedfilemodewrapper(fp)
133 133
134 134 return fp
135 135 except WindowsError as err:
136 136 # convert to a friendlier exception
137 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 142 class winstdout(object):
140 143 '''stdout on windows misbehaves if sent through a pipe'''
141 144
142 145 def __init__(self, fp):
143 146 self.fp = fp
144 147
145 148 def __getattr__(self, key):
146 149 return getattr(self.fp, key)
147 150
148 151 def close(self):
149 152 try:
150 153 self.fp.close()
151 154 except IOError:
152 155 pass
153 156
154 157 def write(self, s):
155 158 try:
156 159 # This is workaround for "Not enough space" error on
157 160 # writing large size of data to console.
158 161 limit = 16000
159 162 l = len(s)
160 163 start = 0
161 164 self.softspace = 0
162 165 while start < l:
163 166 end = start + limit
164 167 self.fp.write(s[start:end])
165 168 start = end
166 169 except IOError as inst:
167 170 if inst.errno != 0:
168 171 raise
169 172 self.close()
170 173 raise IOError(errno.EPIPE, 'Broken pipe')
171 174
172 175 def flush(self):
173 176 try:
174 177 return self.fp.flush()
175 178 except IOError as inst:
176 179 if inst.errno != errno.EINVAL:
177 180 raise
178 181 self.close()
179 182 raise IOError(errno.EPIPE, 'Broken pipe')
180 183
181 184 def _is_win_9x():
182 185 '''return true if run on windows 95, 98 or me.'''
183 186 try:
184 187 return sys.getwindowsversion()[3] == 1
185 188 except AttributeError:
186 189 return 'command' in encoding.environ.get('comspec', '')
187 190
188 191 def openhardlinks():
189 192 return not _is_win_9x()
190 193
191 194 def parsepatchoutput(output_line):
192 195 """parses the output produced by patch and returns the filename"""
193 196 pf = output_line[14:]
194 197 if pf[0] == '`':
195 198 pf = pf[1:-1] # Remove the quotes
196 199 return pf
197 200
198 201 def sshargs(sshcmd, host, user, port):
199 202 '''Build argument list for ssh or Plink'''
200 203 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
201 204 args = user and ("%s@%s" % (user, host)) or host
202 205 return port and ("%s %s %s" % (args, pflag, port)) or args
203 206
204 207 def setflags(f, l, x):
205 208 pass
206 209
207 210 def copymode(src, dst, mode=None):
208 211 pass
209 212
210 213 def checkexec(path):
211 214 return False
212 215
213 216 def checklink(path):
214 217 return False
215 218
216 219 def setbinary(fd):
217 220 # When run without console, pipes may expose invalid
218 221 # fileno(), usually set to -1.
219 222 fno = getattr(fd, 'fileno', None)
220 223 if fno is not None and fno() >= 0:
221 224 msvcrt.setmode(fno(), os.O_BINARY)
222 225
223 226 def pconvert(path):
224 227 return path.replace(pycompat.ossep, '/')
225 228
226 229 def localpath(path):
227 230 return path.replace('/', '\\')
228 231
229 232 def normpath(path):
230 233 return pconvert(os.path.normpath(path))
231 234
232 235 def normcase(path):
233 236 return encoding.upper(path) # NTFS compares via upper()
234 237
235 238 # see posix.py for definitions
236 239 normcasespec = encoding.normcasespecs.upper
237 240 normcasefallback = encoding.upperfallback
238 241
239 242 def samestat(s1, s2):
240 243 return False
241 244
242 245 # A sequence of backslashes is special iff it precedes a double quote:
243 246 # - if there's an even number of backslashes, the double quote is not
244 247 # quoted (i.e. it ends the quoted region)
245 248 # - if there's an odd number of backslashes, the double quote is quoted
246 249 # - in both cases, every pair of backslashes is unquoted into a single
247 250 # backslash
248 251 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
249 252 # So, to quote a string, we must surround it in double quotes, double
250 253 # the number of backslashes that precede double quotes and add another
251 254 # backslash before every double quote (being careful with the double
252 255 # quote we've appended to the end)
253 256 _quotere = None
254 257 _needsshellquote = None
255 258 def shellquote(s):
256 259 r"""
257 260 >>> shellquote(r'C:\Users\xyz')
258 261 '"C:\\Users\\xyz"'
259 262 >>> shellquote(r'C:\Users\xyz/mixed')
260 263 '"C:\\Users\\xyz/mixed"'
261 264 >>> # Would be safe not to quote too, since it is all double backslashes
262 265 >>> shellquote(r'C:\\Users\\xyz')
263 266 '"C:\\\\Users\\\\xyz"'
264 267 >>> # But this must be quoted
265 268 >>> shellquote(r'C:\\Users\\xyz/abc')
266 269 '"C:\\\\Users\\\\xyz/abc"'
267 270 """
268 271 global _quotere
269 272 if _quotere is None:
270 273 _quotere = re.compile(r'(\\*)("|\\$)')
271 274 global _needsshellquote
272 275 if _needsshellquote is None:
273 276 # ":" is also treated as "safe character", because it is used as a part
274 277 # of path name on Windows. "\" is also part of a path name, but isn't
275 278 # safe because shlex.split() (kind of) treats it as an escape char and
276 279 # drops it. It will leave the next character, even if it is another
277 280 # "\".
278 281 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
279 282 if s and not _needsshellquote(s) and not _quotere.search(s):
280 283 # "s" shouldn't have to be quoted
281 284 return s
282 285 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
283 286
284 287 def quotecommand(cmd):
285 288 """Build a command string suitable for os.popen* calls."""
286 289 if sys.version_info < (2, 7, 1):
287 290 # Python versions since 2.7.1 do this extra quoting themselves
288 291 return '"' + cmd + '"'
289 292 return cmd
290 293
291 294 def popen(command, mode='r'):
292 295 # Work around "popen spawned process may not write to stdout
293 296 # under windows"
294 297 # http://bugs.python.org/issue1366
295 298 command += " 2> %s" % os.devnull
296 299 return os.popen(quotecommand(command), mode)
297 300
298 301 def explainexit(code):
299 302 return _("exited with status %d") % code, code
300 303
301 304 # if you change this stub into a real check, please try to implement the
302 305 # username and groupname functions above, too.
303 306 def isowner(st):
304 307 return True
305 308
306 309 def findexe(command):
307 310 '''Find executable for command searching like cmd.exe does.
308 311 If command is a basename then PATH is searched for command.
309 312 PATH isn't searched if command is an absolute or relative path.
310 313 An extension from PATHEXT is found and added if not present.
311 314 If command isn't found None is returned.'''
312 315 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
313 316 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
314 317 if os.path.splitext(command)[1].lower() in pathexts:
315 318 pathexts = ['']
316 319
317 320 def findexisting(pathcommand):
318 321 'Will append extension (if needed) and return existing file'
319 322 for ext in pathexts:
320 323 executable = pathcommand + ext
321 324 if os.path.exists(executable):
322 325 return executable
323 326 return None
324 327
325 328 if pycompat.ossep in command:
326 329 return findexisting(command)
327 330
328 331 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
329 332 executable = findexisting(os.path.join(path, command))
330 333 if executable is not None:
331 334 return executable
332 335 return findexisting(os.path.expanduser(os.path.expandvars(command)))
333 336
334 337 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
335 338
336 339 def statfiles(files):
337 340 '''Stat each file in files. Yield each stat, or None if a file
338 341 does not exist or has a type we don't care about.
339 342
340 343 Cluster and cache stat per directory to minimize number of OS stat calls.'''
341 344 dircache = {} # dirname -> filename -> status | None if file does not exist
342 345 getkind = stat.S_IFMT
343 346 for nf in files:
344 347 nf = normcase(nf)
345 348 dir, base = os.path.split(nf)
346 349 if not dir:
347 350 dir = '.'
348 351 cache = dircache.get(dir, None)
349 352 if cache is None:
350 353 try:
351 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 356 if getkind(s.st_mode) in _wantedkinds])
354 357 except OSError as err:
355 358 # Python >= 2.5 returns ENOENT and adds winerror field
356 359 # EINVAL is raised if dir is not a directory.
357 360 if err.errno not in (errno.ENOENT, errno.EINVAL,
358 361 errno.ENOTDIR):
359 362 raise
360 363 dmap = {}
361 364 cache = dircache.setdefault(dir, dmap)
362 365 yield cache.get(base, None)
363 366
364 367 def username(uid=None):
365 368 """Return the name of the user with the given uid.
366 369
367 370 If uid is None, return the name of the current user."""
368 371 return None
369 372
370 373 def groupname(gid=None):
371 374 """Return the name of the group with the given gid.
372 375
373 376 If gid is None, return the name of the current group."""
374 377 return None
375 378
376 379 def removedirs(name):
377 380 """special version of os.removedirs that does not remove symlinked
378 381 directories or junction points if they actually contain files"""
379 if osutil.listdir(name):
382 if listdir(name):
380 383 return
381 384 os.rmdir(name)
382 385 head, tail = os.path.split(name)
383 386 if not tail:
384 387 head, tail = os.path.split(head)
385 388 while head and tail:
386 389 try:
387 if osutil.listdir(head):
390 if listdir(head):
388 391 return
389 392 os.rmdir(head)
390 393 except (ValueError, OSError):
391 394 break
392 395 head, tail = os.path.split(head)
393 396
394 397 def rename(src, dst):
395 398 '''atomically rename file src to dst, replacing dst if it exists'''
396 399 try:
397 400 os.rename(src, dst)
398 401 except OSError as e:
399 402 if e.errno != errno.EEXIST:
400 403 raise
401 404 unlink(dst)
402 405 os.rename(src, dst)
403 406
404 407 def gethgcmd():
405 408 return [sys.executable] + sys.argv[:1]
406 409
407 410 def groupmembers(name):
408 411 # Don't support groups on Windows for now
409 412 raise KeyError
410 413
411 414 def isexec(f):
412 415 return False
413 416
414 417 class cachestat(object):
415 418 def __init__(self, path):
416 419 pass
417 420
418 421 def cacheable(self):
419 422 return False
420 423
421 424 def lookupreg(key, valname=None, scope=None):
422 425 ''' Look up a key/value name in the Windows registry.
423 426
424 427 valname: value name. If unspecified, the default value for the key
425 428 is used.
426 429 scope: optionally specify scope for registry lookup, this can be
427 430 a sequence of scopes to look up in order. Default (CURRENT_USER,
428 431 LOCAL_MACHINE).
429 432 '''
430 433 if scope is None:
431 434 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
432 435 elif not isinstance(scope, (list, tuple)):
433 436 scope = (scope,)
434 437 for s in scope:
435 438 try:
436 439 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
437 440 # never let a Unicode string escape into the wild
438 441 return encoding.unitolocal(val)
439 442 except EnvironmentError:
440 443 pass
441 444
442 445 expandglobs = True
443 446
444 447 def statislink(st):
445 448 '''check whether a stat result is a symlink'''
446 449 return False
447 450
448 451 def statisexec(st):
449 452 '''check whether a stat result is an executable file'''
450 453 return False
451 454
452 455 def poll(fds):
453 456 # see posix.py for description
454 457 raise NotImplementedError()
455 458
456 459 def readpipe(pipe):
457 460 """Read all available data from a pipe."""
458 461 chunks = []
459 462 while True:
460 463 size = win32.peekpipe(pipe)
461 464 if not size:
462 465 break
463 466
464 467 s = pipe.read(size)
465 468 if not s:
466 469 break
467 470 chunks.append(s)
468 471
469 472 return ''.join(chunks)
470 473
471 474 def bindunixsocket(sock, path):
472 475 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now