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