##// END OF EJS Templates
py3: use bytes stdout in hghave.py...
Matt Harbison -
r41039:87c98ffb default
parent child Browse files
Show More
@@ -1,812 +1,823 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import errno
3 import errno
4 import os
4 import os
5 import re
5 import re
6 import socket
6 import socket
7 import stat
7 import stat
8 import subprocess
8 import subprocess
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 checks = {
14 checks = {
15 "true": (lambda: True, "yak shaving"),
15 "true": (lambda: True, "yak shaving"),
16 "false": (lambda: False, "nail clipper"),
16 "false": (lambda: False, "nail clipper"),
17 }
17 }
18
18
19 try:
20 import msvcrt
21 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
22 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
23 except ImportError:
24 pass
25
26 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
27 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
28
19 if sys.version_info[0] >= 3:
29 if sys.version_info[0] >= 3:
20 def _bytespath(p):
30 def _bytespath(p):
21 if p is None:
31 if p is None:
22 return p
32 return p
23 return p.encode('utf-8')
33 return p.encode('utf-8')
24
34
25 def _strpath(p):
35 def _strpath(p):
26 if p is None:
36 if p is None:
27 return p
37 return p
28 return p.decode('utf-8')
38 return p.decode('utf-8')
29 else:
39 else:
30 def _bytespath(p):
40 def _bytespath(p):
31 return p
41 return p
32
42
33 _strpath = _bytespath
43 _strpath = _bytespath
34
44
35 def check(name, desc):
45 def check(name, desc):
36 """Registers a check function for a feature."""
46 """Registers a check function for a feature."""
37 def decorator(func):
47 def decorator(func):
38 checks[name] = (func, desc)
48 checks[name] = (func, desc)
39 return func
49 return func
40 return decorator
50 return decorator
41
51
42 def checkvers(name, desc, vers):
52 def checkvers(name, desc, vers):
43 """Registers a check function for each of a series of versions.
53 """Registers a check function for each of a series of versions.
44
54
45 vers can be a list or an iterator"""
55 vers can be a list or an iterator"""
46 def decorator(func):
56 def decorator(func):
47 def funcv(v):
57 def funcv(v):
48 def f():
58 def f():
49 return func(v)
59 return func(v)
50 return f
60 return f
51 for v in vers:
61 for v in vers:
52 v = str(v)
62 v = str(v)
53 f = funcv(v)
63 f = funcv(v)
54 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
64 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
55 return func
65 return func
56 return decorator
66 return decorator
57
67
58 def checkfeatures(features):
68 def checkfeatures(features):
59 result = {
69 result = {
60 'error': [],
70 'error': [],
61 'missing': [],
71 'missing': [],
62 'skipped': [],
72 'skipped': [],
63 }
73 }
64
74
65 for feature in features:
75 for feature in features:
66 negate = feature.startswith('no-')
76 negate = feature.startswith('no-')
67 if negate:
77 if negate:
68 feature = feature[3:]
78 feature = feature[3:]
69
79
70 if feature not in checks:
80 if feature not in checks:
71 result['missing'].append(feature)
81 result['missing'].append(feature)
72 continue
82 continue
73
83
74 check, desc = checks[feature]
84 check, desc = checks[feature]
75 try:
85 try:
76 available = check()
86 available = check()
77 except Exception:
87 except Exception:
78 result['error'].append('hghave check failed: %s' % feature)
88 result['error'].append('hghave check failed: %s' % feature)
79 continue
89 continue
80
90
81 if not negate and not available:
91 if not negate and not available:
82 result['skipped'].append('missing feature: %s' % desc)
92 result['skipped'].append('missing feature: %s' % desc)
83 elif negate and available:
93 elif negate and available:
84 result['skipped'].append('system supports %s' % desc)
94 result['skipped'].append('system supports %s' % desc)
85
95
86 return result
96 return result
87
97
88 def require(features):
98 def require(features):
89 """Require that features are available, exiting if not."""
99 """Require that features are available, exiting if not."""
90 result = checkfeatures(features)
100 result = checkfeatures(features)
91
101
92 for missing in result['missing']:
102 for missing in result['missing']:
93 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
103 stderr.write(('skipped: unknown feature: %s\n'
104 % missing).encode('utf-8'))
94 for msg in result['skipped']:
105 for msg in result['skipped']:
95 sys.stderr.write('skipped: %s\n' % msg)
106 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
96 for msg in result['error']:
107 for msg in result['error']:
97 sys.stderr.write('%s\n' % msg)
108 stderr.write(('%s\n' % msg).encode('utf-8'))
98
109
99 if result['missing']:
110 if result['missing']:
100 sys.exit(2)
111 sys.exit(2)
101
112
102 if result['skipped'] or result['error']:
113 if result['skipped'] or result['error']:
103 sys.exit(1)
114 sys.exit(1)
104
115
105 def matchoutput(cmd, regexp, ignorestatus=False):
116 def matchoutput(cmd, regexp, ignorestatus=False):
106 """Return the match object if cmd executes successfully and its output
117 """Return the match object if cmd executes successfully and its output
107 is matched by the supplied regular expression.
118 is matched by the supplied regular expression.
108 """
119 """
109 r = re.compile(regexp)
120 r = re.compile(regexp)
110 try:
121 try:
111 p = subprocess.Popen(
122 p = subprocess.Popen(
112 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
123 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
113 except OSError as e:
124 except OSError as e:
114 if e.errno != errno.ENOENT:
125 if e.errno != errno.ENOENT:
115 raise
126 raise
116 ret = -1
127 ret = -1
117 s = p.communicate()[0]
128 s = p.communicate()[0]
118 ret = p.returncode
129 ret = p.returncode
119 return (ignorestatus or not ret) and r.search(s)
130 return (ignorestatus or not ret) and r.search(s)
120
131
121 @check("baz", "GNU Arch baz client")
132 @check("baz", "GNU Arch baz client")
122 def has_baz():
133 def has_baz():
123 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
134 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
124
135
125 @check("bzr", "Canonical's Bazaar client")
136 @check("bzr", "Canonical's Bazaar client")
126 def has_bzr():
137 def has_bzr():
127 try:
138 try:
128 import bzrlib
139 import bzrlib
129 import bzrlib.bzrdir
140 import bzrlib.bzrdir
130 import bzrlib.errors
141 import bzrlib.errors
131 import bzrlib.revision
142 import bzrlib.revision
132 import bzrlib.revisionspec
143 import bzrlib.revisionspec
133 bzrlib.revisionspec.RevisionSpec
144 bzrlib.revisionspec.RevisionSpec
134 return bzrlib.__doc__ is not None
145 return bzrlib.__doc__ is not None
135 except (AttributeError, ImportError):
146 except (AttributeError, ImportError):
136 return False
147 return False
137
148
138 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
149 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
139 def has_bzr_range(v):
150 def has_bzr_range(v):
140 major, minor = v.split('.')[0:2]
151 major, minor = v.split('.')[0:2]
141 try:
152 try:
142 import bzrlib
153 import bzrlib
143 return (bzrlib.__doc__ is not None
154 return (bzrlib.__doc__ is not None
144 and bzrlib.version_info[:2] >= (int(major), int(minor)))
155 and bzrlib.version_info[:2] >= (int(major), int(minor)))
145 except ImportError:
156 except ImportError:
146 return False
157 return False
147
158
148 @check("chg", "running with chg")
159 @check("chg", "running with chg")
149 def has_chg():
160 def has_chg():
150 return 'CHGHG' in os.environ
161 return 'CHGHG' in os.environ
151
162
152 @check("cvs", "cvs client/server")
163 @check("cvs", "cvs client/server")
153 def has_cvs():
164 def has_cvs():
154 re = br'Concurrent Versions System.*?server'
165 re = br'Concurrent Versions System.*?server'
155 return matchoutput('cvs --version 2>&1', re) and not has_msys()
166 return matchoutput('cvs --version 2>&1', re) and not has_msys()
156
167
157 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
168 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
158 def has_cvs112():
169 def has_cvs112():
159 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
170 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
160 return matchoutput('cvs --version 2>&1', re) and not has_msys()
171 return matchoutput('cvs --version 2>&1', re) and not has_msys()
161
172
162 @check("cvsnt", "cvsnt client/server")
173 @check("cvsnt", "cvsnt client/server")
163 def has_cvsnt():
174 def has_cvsnt():
164 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
175 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
165 return matchoutput('cvsnt --version 2>&1', re)
176 return matchoutput('cvsnt --version 2>&1', re)
166
177
167 @check("darcs", "darcs client")
178 @check("darcs", "darcs client")
168 def has_darcs():
179 def has_darcs():
169 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
180 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
170
181
171 @check("mtn", "monotone client (>= 1.0)")
182 @check("mtn", "monotone client (>= 1.0)")
172 def has_mtn():
183 def has_mtn():
173 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
184 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
174 'mtn --version', br'monotone 0\.', True)
185 'mtn --version', br'monotone 0\.', True)
175
186
176 @check("eol-in-paths", "end-of-lines in paths")
187 @check("eol-in-paths", "end-of-lines in paths")
177 def has_eol_in_paths():
188 def has_eol_in_paths():
178 try:
189 try:
179 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
190 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
180 os.close(fd)
191 os.close(fd)
181 os.remove(path)
192 os.remove(path)
182 return True
193 return True
183 except (IOError, OSError):
194 except (IOError, OSError):
184 return False
195 return False
185
196
186 @check("execbit", "executable bit")
197 @check("execbit", "executable bit")
187 def has_executablebit():
198 def has_executablebit():
188 try:
199 try:
189 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
200 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
190 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
201 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
191 try:
202 try:
192 os.close(fh)
203 os.close(fh)
193 m = os.stat(fn).st_mode & 0o777
204 m = os.stat(fn).st_mode & 0o777
194 new_file_has_exec = m & EXECFLAGS
205 new_file_has_exec = m & EXECFLAGS
195 os.chmod(fn, m ^ EXECFLAGS)
206 os.chmod(fn, m ^ EXECFLAGS)
196 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
207 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
197 finally:
208 finally:
198 os.unlink(fn)
209 os.unlink(fn)
199 except (IOError, OSError):
210 except (IOError, OSError):
200 # we don't care, the user probably won't be able to commit anyway
211 # we don't care, the user probably won't be able to commit anyway
201 return False
212 return False
202 return not (new_file_has_exec or exec_flags_cannot_flip)
213 return not (new_file_has_exec or exec_flags_cannot_flip)
203
214
204 @check("icasefs", "case insensitive file system")
215 @check("icasefs", "case insensitive file system")
205 def has_icasefs():
216 def has_icasefs():
206 # Stolen from mercurial.util
217 # Stolen from mercurial.util
207 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
218 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
208 os.close(fd)
219 os.close(fd)
209 try:
220 try:
210 s1 = os.stat(path)
221 s1 = os.stat(path)
211 d, b = os.path.split(path)
222 d, b = os.path.split(path)
212 p2 = os.path.join(d, b.upper())
223 p2 = os.path.join(d, b.upper())
213 if path == p2:
224 if path == p2:
214 p2 = os.path.join(d, b.lower())
225 p2 = os.path.join(d, b.lower())
215 try:
226 try:
216 s2 = os.stat(p2)
227 s2 = os.stat(p2)
217 return s2 == s1
228 return s2 == s1
218 except OSError:
229 except OSError:
219 return False
230 return False
220 finally:
231 finally:
221 os.remove(path)
232 os.remove(path)
222
233
223 @check("fifo", "named pipes")
234 @check("fifo", "named pipes")
224 def has_fifo():
235 def has_fifo():
225 if getattr(os, "mkfifo", None) is None:
236 if getattr(os, "mkfifo", None) is None:
226 return False
237 return False
227 name = tempfile.mktemp(dir='.', prefix=tempprefix)
238 name = tempfile.mktemp(dir='.', prefix=tempprefix)
228 try:
239 try:
229 os.mkfifo(name)
240 os.mkfifo(name)
230 os.unlink(name)
241 os.unlink(name)
231 return True
242 return True
232 except OSError:
243 except OSError:
233 return False
244 return False
234
245
235 @check("killdaemons", 'killdaemons.py support')
246 @check("killdaemons", 'killdaemons.py support')
236 def has_killdaemons():
247 def has_killdaemons():
237 return True
248 return True
238
249
239 @check("cacheable", "cacheable filesystem")
250 @check("cacheable", "cacheable filesystem")
240 def has_cacheable_fs():
251 def has_cacheable_fs():
241 from mercurial import util
252 from mercurial import util
242
253
243 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
254 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
244 os.close(fd)
255 os.close(fd)
245 try:
256 try:
246 return util.cachestat(path).cacheable()
257 return util.cachestat(path).cacheable()
247 finally:
258 finally:
248 os.remove(path)
259 os.remove(path)
249
260
250 @check("lsprof", "python lsprof module")
261 @check("lsprof", "python lsprof module")
251 def has_lsprof():
262 def has_lsprof():
252 try:
263 try:
253 import _lsprof
264 import _lsprof
254 _lsprof.Profiler # silence unused import warning
265 _lsprof.Profiler # silence unused import warning
255 return True
266 return True
256 except ImportError:
267 except ImportError:
257 return False
268 return False
258
269
259 def gethgversion():
270 def gethgversion():
260 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
271 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
261 if not m:
272 if not m:
262 return (0, 0)
273 return (0, 0)
263 return (int(m.group(1)), int(m.group(2)))
274 return (int(m.group(1)), int(m.group(2)))
264
275
265 @checkvers("hg", "Mercurial >= %s",
276 @checkvers("hg", "Mercurial >= %s",
266 list([(1.0 * x) / 10 for x in range(9, 99)]))
277 list([(1.0 * x) / 10 for x in range(9, 99)]))
267 def has_hg_range(v):
278 def has_hg_range(v):
268 major, minor = v.split('.')[0:2]
279 major, minor = v.split('.')[0:2]
269 return gethgversion() >= (int(major), int(minor))
280 return gethgversion() >= (int(major), int(minor))
270
281
271 @check("hg08", "Mercurial >= 0.8")
282 @check("hg08", "Mercurial >= 0.8")
272 def has_hg08():
283 def has_hg08():
273 if checks["hg09"][0]():
284 if checks["hg09"][0]():
274 return True
285 return True
275 return matchoutput('hg help annotate 2>&1', '--date')
286 return matchoutput('hg help annotate 2>&1', '--date')
276
287
277 @check("hg07", "Mercurial >= 0.7")
288 @check("hg07", "Mercurial >= 0.7")
278 def has_hg07():
289 def has_hg07():
279 if checks["hg08"][0]():
290 if checks["hg08"][0]():
280 return True
291 return True
281 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
292 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
282
293
283 @check("hg06", "Mercurial >= 0.6")
294 @check("hg06", "Mercurial >= 0.6")
284 def has_hg06():
295 def has_hg06():
285 if checks["hg07"][0]():
296 if checks["hg07"][0]():
286 return True
297 return True
287 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
298 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
288
299
289 @check("gettext", "GNU Gettext (msgfmt)")
300 @check("gettext", "GNU Gettext (msgfmt)")
290 def has_gettext():
301 def has_gettext():
291 return matchoutput('msgfmt --version', br'GNU gettext-tools')
302 return matchoutput('msgfmt --version', br'GNU gettext-tools')
292
303
293 @check("git", "git command line client")
304 @check("git", "git command line client")
294 def has_git():
305 def has_git():
295 return matchoutput('git --version 2>&1', br'^git version')
306 return matchoutput('git --version 2>&1', br'^git version')
296
307
297 def getgitversion():
308 def getgitversion():
298 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
309 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
299 if not m:
310 if not m:
300 return (0, 0)
311 return (0, 0)
301 return (int(m.group(1)), int(m.group(2)))
312 return (int(m.group(1)), int(m.group(2)))
302
313
303 # https://github.com/git-lfs/lfs-test-server
314 # https://github.com/git-lfs/lfs-test-server
304 @check("lfs-test-server", "git-lfs test server")
315 @check("lfs-test-server", "git-lfs test server")
305 def has_lfsserver():
316 def has_lfsserver():
306 exe = 'lfs-test-server'
317 exe = 'lfs-test-server'
307 if has_windows():
318 if has_windows():
308 exe = 'lfs-test-server.exe'
319 exe = 'lfs-test-server.exe'
309 return any(
320 return any(
310 os.access(os.path.join(path, exe), os.X_OK)
321 os.access(os.path.join(path, exe), os.X_OK)
311 for path in os.environ["PATH"].split(os.pathsep)
322 for path in os.environ["PATH"].split(os.pathsep)
312 )
323 )
313
324
314 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
325 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
315 def has_git_range(v):
326 def has_git_range(v):
316 major, minor = v.split('.')[0:2]
327 major, minor = v.split('.')[0:2]
317 return getgitversion() >= (int(major), int(minor))
328 return getgitversion() >= (int(major), int(minor))
318
329
319 @check("docutils", "Docutils text processing library")
330 @check("docutils", "Docutils text processing library")
320 def has_docutils():
331 def has_docutils():
321 try:
332 try:
322 import docutils.core
333 import docutils.core
323 docutils.core.publish_cmdline # silence unused import
334 docutils.core.publish_cmdline # silence unused import
324 return True
335 return True
325 except ImportError:
336 except ImportError:
326 return False
337 return False
327
338
328 def getsvnversion():
339 def getsvnversion():
329 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
340 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
330 if not m:
341 if not m:
331 return (0, 0)
342 return (0, 0)
332 return (int(m.group(1)), int(m.group(2)))
343 return (int(m.group(1)), int(m.group(2)))
333
344
334 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
345 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
335 def has_svn_range(v):
346 def has_svn_range(v):
336 major, minor = v.split('.')[0:2]
347 major, minor = v.split('.')[0:2]
337 return getsvnversion() >= (int(major), int(minor))
348 return getsvnversion() >= (int(major), int(minor))
338
349
339 @check("svn", "subversion client and admin tools")
350 @check("svn", "subversion client and admin tools")
340 def has_svn():
351 def has_svn():
341 return matchoutput('svn --version 2>&1', br'^svn, version') and \
352 return matchoutput('svn --version 2>&1', br'^svn, version') and \
342 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
353 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
343
354
344 @check("svn-bindings", "subversion python bindings")
355 @check("svn-bindings", "subversion python bindings")
345 def has_svn_bindings():
356 def has_svn_bindings():
346 try:
357 try:
347 import svn.core
358 import svn.core
348 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
359 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
349 if version < (1, 4):
360 if version < (1, 4):
350 return False
361 return False
351 return True
362 return True
352 except ImportError:
363 except ImportError:
353 return False
364 return False
354
365
355 @check("p4", "Perforce server and client")
366 @check("p4", "Perforce server and client")
356 def has_p4():
367 def has_p4():
357 return (matchoutput('p4 -V', br'Rev\. P4/') and
368 return (matchoutput('p4 -V', br'Rev\. P4/') and
358 matchoutput('p4d -V', br'Rev\. P4D/'))
369 matchoutput('p4d -V', br'Rev\. P4D/'))
359
370
360 @check("symlink", "symbolic links")
371 @check("symlink", "symbolic links")
361 def has_symlink():
372 def has_symlink():
362 if getattr(os, "symlink", None) is None:
373 if getattr(os, "symlink", None) is None:
363 return False
374 return False
364 name = tempfile.mktemp(dir='.', prefix=tempprefix)
375 name = tempfile.mktemp(dir='.', prefix=tempprefix)
365 try:
376 try:
366 os.symlink(".", name)
377 os.symlink(".", name)
367 os.unlink(name)
378 os.unlink(name)
368 return True
379 return True
369 except (OSError, AttributeError):
380 except (OSError, AttributeError):
370 return False
381 return False
371
382
372 @check("hardlink", "hardlinks")
383 @check("hardlink", "hardlinks")
373 def has_hardlink():
384 def has_hardlink():
374 from mercurial import util
385 from mercurial import util
375 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
386 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
376 os.close(fh)
387 os.close(fh)
377 name = tempfile.mktemp(dir='.', prefix=tempprefix)
388 name = tempfile.mktemp(dir='.', prefix=tempprefix)
378 try:
389 try:
379 util.oslink(_bytespath(fn), _bytespath(name))
390 util.oslink(_bytespath(fn), _bytespath(name))
380 os.unlink(name)
391 os.unlink(name)
381 return True
392 return True
382 except OSError:
393 except OSError:
383 return False
394 return False
384 finally:
395 finally:
385 os.unlink(fn)
396 os.unlink(fn)
386
397
387 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
398 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
388 def has_hardlink_whitelisted():
399 def has_hardlink_whitelisted():
389 from mercurial import util
400 from mercurial import util
390 try:
401 try:
391 fstype = util.getfstype(b'.')
402 fstype = util.getfstype(b'.')
392 except OSError:
403 except OSError:
393 return False
404 return False
394 return fstype in util._hardlinkfswhitelist
405 return fstype in util._hardlinkfswhitelist
395
406
396 @check("rmcwd", "can remove current working directory")
407 @check("rmcwd", "can remove current working directory")
397 def has_rmcwd():
408 def has_rmcwd():
398 ocwd = os.getcwd()
409 ocwd = os.getcwd()
399 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
410 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
400 try:
411 try:
401 os.chdir(temp)
412 os.chdir(temp)
402 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
413 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
403 # On Solaris and Windows, the cwd can't be removed by any names.
414 # On Solaris and Windows, the cwd can't be removed by any names.
404 os.rmdir(os.getcwd())
415 os.rmdir(os.getcwd())
405 return True
416 return True
406 except OSError:
417 except OSError:
407 return False
418 return False
408 finally:
419 finally:
409 os.chdir(ocwd)
420 os.chdir(ocwd)
410 # clean up temp dir on platforms where cwd can't be removed
421 # clean up temp dir on platforms where cwd can't be removed
411 try:
422 try:
412 os.rmdir(temp)
423 os.rmdir(temp)
413 except OSError:
424 except OSError:
414 pass
425 pass
415
426
416 @check("tla", "GNU Arch tla client")
427 @check("tla", "GNU Arch tla client")
417 def has_tla():
428 def has_tla():
418 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
429 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
419
430
420 @check("gpg", "gpg client")
431 @check("gpg", "gpg client")
421 def has_gpg():
432 def has_gpg():
422 return matchoutput('gpg --version 2>&1', br'GnuPG')
433 return matchoutput('gpg --version 2>&1', br'GnuPG')
423
434
424 @check("gpg2", "gpg client v2")
435 @check("gpg2", "gpg client v2")
425 def has_gpg2():
436 def has_gpg2():
426 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
437 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
427
438
428 @check("gpg21", "gpg client v2.1+")
439 @check("gpg21", "gpg client v2.1+")
429 def has_gpg21():
440 def has_gpg21():
430 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
441 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
431
442
432 @check("unix-permissions", "unix-style permissions")
443 @check("unix-permissions", "unix-style permissions")
433 def has_unix_permissions():
444 def has_unix_permissions():
434 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
445 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
435 try:
446 try:
436 fname = os.path.join(d, 'foo')
447 fname = os.path.join(d, 'foo')
437 for umask in (0o77, 0o07, 0o22):
448 for umask in (0o77, 0o07, 0o22):
438 os.umask(umask)
449 os.umask(umask)
439 f = open(fname, 'w')
450 f = open(fname, 'w')
440 f.close()
451 f.close()
441 mode = os.stat(fname).st_mode
452 mode = os.stat(fname).st_mode
442 os.unlink(fname)
453 os.unlink(fname)
443 if mode & 0o777 != ~umask & 0o666:
454 if mode & 0o777 != ~umask & 0o666:
444 return False
455 return False
445 return True
456 return True
446 finally:
457 finally:
447 os.rmdir(d)
458 os.rmdir(d)
448
459
449 @check("unix-socket", "AF_UNIX socket family")
460 @check("unix-socket", "AF_UNIX socket family")
450 def has_unix_socket():
461 def has_unix_socket():
451 return getattr(socket, 'AF_UNIX', None) is not None
462 return getattr(socket, 'AF_UNIX', None) is not None
452
463
453 @check("root", "root permissions")
464 @check("root", "root permissions")
454 def has_root():
465 def has_root():
455 return getattr(os, 'geteuid', None) and os.geteuid() == 0
466 return getattr(os, 'geteuid', None) and os.geteuid() == 0
456
467
457 @check("pyflakes", "Pyflakes python linter")
468 @check("pyflakes", "Pyflakes python linter")
458 def has_pyflakes():
469 def has_pyflakes():
459 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
470 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
460 br"<stdin>:1: 're' imported but unused",
471 br"<stdin>:1: 're' imported but unused",
461 True)
472 True)
462
473
463 @check("pylint", "Pylint python linter")
474 @check("pylint", "Pylint python linter")
464 def has_pylint():
475 def has_pylint():
465 return matchoutput("pylint --help",
476 return matchoutput("pylint --help",
466 br"Usage: pylint",
477 br"Usage: pylint",
467 True)
478 True)
468
479
469 @check("clang-format", "clang-format C code formatter")
480 @check("clang-format", "clang-format C code formatter")
470 def has_clang_format():
481 def has_clang_format():
471 m = matchoutput('clang-format --version', br'clang-format version (\d)')
482 m = matchoutput('clang-format --version', br'clang-format version (\d)')
472 # style changed somewhere between 4.x and 6.x
483 # style changed somewhere between 4.x and 6.x
473 return m and int(m.group(1)) >= 6
484 return m and int(m.group(1)) >= 6
474
485
475 @check("jshint", "JSHint static code analysis tool")
486 @check("jshint", "JSHint static code analysis tool")
476 def has_jshint():
487 def has_jshint():
477 return matchoutput("jshint --version 2>&1", br"jshint v")
488 return matchoutput("jshint --version 2>&1", br"jshint v")
478
489
479 @check("pygments", "Pygments source highlighting library")
490 @check("pygments", "Pygments source highlighting library")
480 def has_pygments():
491 def has_pygments():
481 try:
492 try:
482 import pygments
493 import pygments
483 pygments.highlight # silence unused import warning
494 pygments.highlight # silence unused import warning
484 return True
495 return True
485 except ImportError:
496 except ImportError:
486 return False
497 return False
487
498
488 @check("outer-repo", "outer repo")
499 @check("outer-repo", "outer repo")
489 def has_outer_repo():
500 def has_outer_repo():
490 # failing for other reasons than 'no repo' imply that there is a repo
501 # failing for other reasons than 'no repo' imply that there is a repo
491 return not matchoutput('hg root 2>&1',
502 return not matchoutput('hg root 2>&1',
492 br'abort: no repository found', True)
503 br'abort: no repository found', True)
493
504
494 @check("ssl", "ssl module available")
505 @check("ssl", "ssl module available")
495 def has_ssl():
506 def has_ssl():
496 try:
507 try:
497 import ssl
508 import ssl
498 ssl.CERT_NONE
509 ssl.CERT_NONE
499 return True
510 return True
500 except ImportError:
511 except ImportError:
501 return False
512 return False
502
513
503 @check("sslcontext", "python >= 2.7.9 ssl")
514 @check("sslcontext", "python >= 2.7.9 ssl")
504 def has_sslcontext():
515 def has_sslcontext():
505 try:
516 try:
506 import ssl
517 import ssl
507 ssl.SSLContext
518 ssl.SSLContext
508 return True
519 return True
509 except (ImportError, AttributeError):
520 except (ImportError, AttributeError):
510 return False
521 return False
511
522
512 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
523 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
513 def has_defaultcacerts():
524 def has_defaultcacerts():
514 from mercurial import sslutil, ui as uimod
525 from mercurial import sslutil, ui as uimod
515 ui = uimod.ui.load()
526 ui = uimod.ui.load()
516 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
527 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
517
528
518 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
529 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
519 def has_defaultcacertsloaded():
530 def has_defaultcacertsloaded():
520 import ssl
531 import ssl
521 from mercurial import sslutil, ui as uimod
532 from mercurial import sslutil, ui as uimod
522
533
523 if not has_defaultcacerts():
534 if not has_defaultcacerts():
524 return False
535 return False
525 if not has_sslcontext():
536 if not has_sslcontext():
526 return False
537 return False
527
538
528 ui = uimod.ui.load()
539 ui = uimod.ui.load()
529 cafile = sslutil._defaultcacerts(ui)
540 cafile = sslutil._defaultcacerts(ui)
530 ctx = ssl.create_default_context()
541 ctx = ssl.create_default_context()
531 if cafile:
542 if cafile:
532 ctx.load_verify_locations(cafile=cafile)
543 ctx.load_verify_locations(cafile=cafile)
533 else:
544 else:
534 ctx.load_default_certs()
545 ctx.load_default_certs()
535
546
536 return len(ctx.get_ca_certs()) > 0
547 return len(ctx.get_ca_certs()) > 0
537
548
538 @check("tls1.2", "TLS 1.2 protocol support")
549 @check("tls1.2", "TLS 1.2 protocol support")
539 def has_tls1_2():
550 def has_tls1_2():
540 from mercurial import sslutil
551 from mercurial import sslutil
541 return 'tls1.2' in sslutil.supportedprotocols
552 return 'tls1.2' in sslutil.supportedprotocols
542
553
543 @check("windows", "Windows")
554 @check("windows", "Windows")
544 def has_windows():
555 def has_windows():
545 return os.name == 'nt'
556 return os.name == 'nt'
546
557
547 @check("system-sh", "system() uses sh")
558 @check("system-sh", "system() uses sh")
548 def has_system_sh():
559 def has_system_sh():
549 return os.name != 'nt'
560 return os.name != 'nt'
550
561
551 @check("serve", "platform and python can manage 'hg serve -d'")
562 @check("serve", "platform and python can manage 'hg serve -d'")
552 def has_serve():
563 def has_serve():
553 return True
564 return True
554
565
555 @check("test-repo", "running tests from repository")
566 @check("test-repo", "running tests from repository")
556 def has_test_repo():
567 def has_test_repo():
557 t = os.environ["TESTDIR"]
568 t = os.environ["TESTDIR"]
558 return os.path.isdir(os.path.join(t, "..", ".hg"))
569 return os.path.isdir(os.path.join(t, "..", ".hg"))
559
570
560 @check("tic", "terminfo compiler and curses module")
571 @check("tic", "terminfo compiler and curses module")
561 def has_tic():
572 def has_tic():
562 try:
573 try:
563 import curses
574 import curses
564 curses.COLOR_BLUE
575 curses.COLOR_BLUE
565 return matchoutput('test -x "`which tic`"', br'')
576 return matchoutput('test -x "`which tic`"', br'')
566 except ImportError:
577 except ImportError:
567 return False
578 return False
568
579
569 @check("msys", "Windows with MSYS")
580 @check("msys", "Windows with MSYS")
570 def has_msys():
581 def has_msys():
571 return os.getenv('MSYSTEM')
582 return os.getenv('MSYSTEM')
572
583
573 @check("aix", "AIX")
584 @check("aix", "AIX")
574 def has_aix():
585 def has_aix():
575 return sys.platform.startswith("aix")
586 return sys.platform.startswith("aix")
576
587
577 @check("osx", "OS X")
588 @check("osx", "OS X")
578 def has_osx():
589 def has_osx():
579 return sys.platform == 'darwin'
590 return sys.platform == 'darwin'
580
591
581 @check("osxpackaging", "OS X packaging tools")
592 @check("osxpackaging", "OS X packaging tools")
582 def has_osxpackaging():
593 def has_osxpackaging():
583 try:
594 try:
584 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
595 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
585 and matchoutput(
596 and matchoutput(
586 'productbuild', br'Usage: productbuild ',
597 'productbuild', br'Usage: productbuild ',
587 ignorestatus=1)
598 ignorestatus=1)
588 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
599 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
589 and matchoutput(
600 and matchoutput(
590 'xar --help', br'Usage: xar', ignorestatus=1))
601 'xar --help', br'Usage: xar', ignorestatus=1))
591 except ImportError:
602 except ImportError:
592 return False
603 return False
593
604
594 @check('linuxormacos', 'Linux or MacOS')
605 @check('linuxormacos', 'Linux or MacOS')
595 def has_linuxormacos():
606 def has_linuxormacos():
596 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
607 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
597 return sys.platform.startswith(('linux', 'darwin'))
608 return sys.platform.startswith(('linux', 'darwin'))
598
609
599 @check("docker", "docker support")
610 @check("docker", "docker support")
600 def has_docker():
611 def has_docker():
601 pat = br'A self-sufficient runtime for'
612 pat = br'A self-sufficient runtime for'
602 if matchoutput('docker --help', pat):
613 if matchoutput('docker --help', pat):
603 if 'linux' not in sys.platform:
614 if 'linux' not in sys.platform:
604 # TODO: in theory we should be able to test docker-based
615 # TODO: in theory we should be able to test docker-based
605 # package creation on non-linux using boot2docker, but in
616 # package creation on non-linux using boot2docker, but in
606 # practice that requires extra coordination to make sure
617 # practice that requires extra coordination to make sure
607 # $TESTTEMP is going to be visible at the same path to the
618 # $TESTTEMP is going to be visible at the same path to the
608 # boot2docker VM. If we figure out how to verify that, we
619 # boot2docker VM. If we figure out how to verify that, we
609 # can use the following instead of just saying False:
620 # can use the following instead of just saying False:
610 # return 'DOCKER_HOST' in os.environ
621 # return 'DOCKER_HOST' in os.environ
611 return False
622 return False
612
623
613 return True
624 return True
614 return False
625 return False
615
626
616 @check("debhelper", "debian packaging tools")
627 @check("debhelper", "debian packaging tools")
617 def has_debhelper():
628 def has_debhelper():
618 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
629 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
619 # quote), so just accept anything in that spot.
630 # quote), so just accept anything in that spot.
620 dpkg = matchoutput('dpkg --version',
631 dpkg = matchoutput('dpkg --version',
621 br"Debian .dpkg' package management program")
632 br"Debian .dpkg' package management program")
622 dh = matchoutput('dh --help',
633 dh = matchoutput('dh --help',
623 br'dh is a part of debhelper.', ignorestatus=True)
634 br'dh is a part of debhelper.', ignorestatus=True)
624 dh_py2 = matchoutput('dh_python2 --help',
635 dh_py2 = matchoutput('dh_python2 --help',
625 br'other supported Python versions')
636 br'other supported Python versions')
626 # debuild comes from the 'devscripts' package, though you might want
637 # debuild comes from the 'devscripts' package, though you might want
627 # the 'build-debs' package instead, which has a dependency on devscripts.
638 # the 'build-debs' package instead, which has a dependency on devscripts.
628 debuild = matchoutput('debuild --help',
639 debuild = matchoutput('debuild --help',
629 br'to run debian/rules with given parameter')
640 br'to run debian/rules with given parameter')
630 return dpkg and dh and dh_py2 and debuild
641 return dpkg and dh and dh_py2 and debuild
631
642
632 @check("debdeps",
643 @check("debdeps",
633 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
644 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
634 def has_debdeps():
645 def has_debdeps():
635 # just check exit status (ignoring output)
646 # just check exit status (ignoring output)
636 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
647 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
637 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
648 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
638
649
639 @check("demandimport", "demandimport enabled")
650 @check("demandimport", "demandimport enabled")
640 def has_demandimport():
651 def has_demandimport():
641 # chg disables demandimport intentionally for performance wins.
652 # chg disables demandimport intentionally for performance wins.
642 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
653 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
643
654
644 @check("py3", "running with Python 3.x")
655 @check("py3", "running with Python 3.x")
645 def has_py3():
656 def has_py3():
646 return 3 == sys.version_info[0]
657 return 3 == sys.version_info[0]
647
658
648 @check("py3exe", "a Python 3.x interpreter is available")
659 @check("py3exe", "a Python 3.x interpreter is available")
649 def has_python3exe():
660 def has_python3exe():
650 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
661 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
651
662
652 @check("pure", "running with pure Python code")
663 @check("pure", "running with pure Python code")
653 def has_pure():
664 def has_pure():
654 return any([
665 return any([
655 os.environ.get("HGMODULEPOLICY") == "py",
666 os.environ.get("HGMODULEPOLICY") == "py",
656 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
667 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
657 ])
668 ])
658
669
659 @check("slow", "allow slow tests (use --allow-slow-tests)")
670 @check("slow", "allow slow tests (use --allow-slow-tests)")
660 def has_slow():
671 def has_slow():
661 return os.environ.get('HGTEST_SLOW') == 'slow'
672 return os.environ.get('HGTEST_SLOW') == 'slow'
662
673
663 @check("hypothesis", "Hypothesis automated test generation")
674 @check("hypothesis", "Hypothesis automated test generation")
664 def has_hypothesis():
675 def has_hypothesis():
665 try:
676 try:
666 import hypothesis
677 import hypothesis
667 hypothesis.given
678 hypothesis.given
668 return True
679 return True
669 except ImportError:
680 except ImportError:
670 return False
681 return False
671
682
672 @check("unziplinks", "unzip(1) understands and extracts symlinks")
683 @check("unziplinks", "unzip(1) understands and extracts symlinks")
673 def unzip_understands_symlinks():
684 def unzip_understands_symlinks():
674 return matchoutput('unzip --help', br'Info-ZIP')
685 return matchoutput('unzip --help', br'Info-ZIP')
675
686
676 @check("zstd", "zstd Python module available")
687 @check("zstd", "zstd Python module available")
677 def has_zstd():
688 def has_zstd():
678 try:
689 try:
679 import mercurial.zstd
690 import mercurial.zstd
680 mercurial.zstd.__version__
691 mercurial.zstd.__version__
681 return True
692 return True
682 except ImportError:
693 except ImportError:
683 return False
694 return False
684
695
685 @check("devfull", "/dev/full special file")
696 @check("devfull", "/dev/full special file")
686 def has_dev_full():
697 def has_dev_full():
687 return os.path.exists('/dev/full')
698 return os.path.exists('/dev/full')
688
699
689 @check("virtualenv", "Python virtualenv support")
700 @check("virtualenv", "Python virtualenv support")
690 def has_virtualenv():
701 def has_virtualenv():
691 try:
702 try:
692 import virtualenv
703 import virtualenv
693 virtualenv.ACTIVATE_SH
704 virtualenv.ACTIVATE_SH
694 return True
705 return True
695 except ImportError:
706 except ImportError:
696 return False
707 return False
697
708
698 @check("fsmonitor", "running tests with fsmonitor")
709 @check("fsmonitor", "running tests with fsmonitor")
699 def has_fsmonitor():
710 def has_fsmonitor():
700 return 'HGFSMONITOR_TESTS' in os.environ
711 return 'HGFSMONITOR_TESTS' in os.environ
701
712
702 @check("fuzzywuzzy", "Fuzzy string matching library")
713 @check("fuzzywuzzy", "Fuzzy string matching library")
703 def has_fuzzywuzzy():
714 def has_fuzzywuzzy():
704 try:
715 try:
705 import fuzzywuzzy
716 import fuzzywuzzy
706 fuzzywuzzy.__version__
717 fuzzywuzzy.__version__
707 return True
718 return True
708 except ImportError:
719 except ImportError:
709 return False
720 return False
710
721
711 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
722 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
712 def has_clang_libfuzzer():
723 def has_clang_libfuzzer():
713 mat = matchoutput('clang --version', b'clang version (\d)')
724 mat = matchoutput('clang --version', b'clang version (\d)')
714 if mat:
725 if mat:
715 # libfuzzer is new in clang 6
726 # libfuzzer is new in clang 6
716 return int(mat.group(1)) > 5
727 return int(mat.group(1)) > 5
717 return False
728 return False
718
729
719 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
730 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
720 def has_clang60():
731 def has_clang60():
721 return matchoutput('clang-6.0 --version', b'clang version 6\.')
732 return matchoutput('clang-6.0 --version', b'clang version 6\.')
722
733
723 @check("xdiff", "xdiff algorithm")
734 @check("xdiff", "xdiff algorithm")
724 def has_xdiff():
735 def has_xdiff():
725 try:
736 try:
726 from mercurial import policy
737 from mercurial import policy
727 bdiff = policy.importmod('bdiff')
738 bdiff = policy.importmod('bdiff')
728 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
739 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
729 except (ImportError, AttributeError):
740 except (ImportError, AttributeError):
730 return False
741 return False
731
742
732 @check('extraextensions', 'whether tests are running with extra extensions')
743 @check('extraextensions', 'whether tests are running with extra extensions')
733 def has_extraextensions():
744 def has_extraextensions():
734 return 'HGTESTEXTRAEXTENSIONS' in os.environ
745 return 'HGTESTEXTRAEXTENSIONS' in os.environ
735
746
736 def getrepofeatures():
747 def getrepofeatures():
737 """Obtain set of repository features in use.
748 """Obtain set of repository features in use.
738
749
739 HGREPOFEATURES can be used to define or remove features. It contains
750 HGREPOFEATURES can be used to define or remove features. It contains
740 a space-delimited list of feature strings. Strings beginning with ``-``
751 a space-delimited list of feature strings. Strings beginning with ``-``
741 mean to remove.
752 mean to remove.
742 """
753 """
743 # Default list provided by core.
754 # Default list provided by core.
744 features = {
755 features = {
745 'bundlerepo',
756 'bundlerepo',
746 'revlogstore',
757 'revlogstore',
747 'fncache',
758 'fncache',
748 }
759 }
749
760
750 # Features that imply other features.
761 # Features that imply other features.
751 implies = {
762 implies = {
752 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
763 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
753 }
764 }
754
765
755 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
766 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
756 if not override:
767 if not override:
757 continue
768 continue
758
769
759 if override.startswith('-'):
770 if override.startswith('-'):
760 if override[1:] in features:
771 if override[1:] in features:
761 features.remove(override[1:])
772 features.remove(override[1:])
762 else:
773 else:
763 features.add(override)
774 features.add(override)
764
775
765 for imply in implies.get(override, []):
776 for imply in implies.get(override, []):
766 if imply.startswith('-'):
777 if imply.startswith('-'):
767 if imply[1:] in features:
778 if imply[1:] in features:
768 features.remove(imply[1:])
779 features.remove(imply[1:])
769 else:
780 else:
770 features.add(imply)
781 features.add(imply)
771
782
772 return features
783 return features
773
784
774 @check('reporevlogstore', 'repository using the default revlog store')
785 @check('reporevlogstore', 'repository using the default revlog store')
775 def has_reporevlogstore():
786 def has_reporevlogstore():
776 return 'revlogstore' in getrepofeatures()
787 return 'revlogstore' in getrepofeatures()
777
788
778 @check('reposimplestore', 'repository using simple storage extension')
789 @check('reposimplestore', 'repository using simple storage extension')
779 def has_reposimplestore():
790 def has_reposimplestore():
780 return 'simplestore' in getrepofeatures()
791 return 'simplestore' in getrepofeatures()
781
792
782 @check('repobundlerepo', 'whether we can open bundle files as repos')
793 @check('repobundlerepo', 'whether we can open bundle files as repos')
783 def has_repobundlerepo():
794 def has_repobundlerepo():
784 return 'bundlerepo' in getrepofeatures()
795 return 'bundlerepo' in getrepofeatures()
785
796
786 @check('repofncache', 'repository has an fncache')
797 @check('repofncache', 'repository has an fncache')
787 def has_repofncache():
798 def has_repofncache():
788 return 'fncache' in getrepofeatures()
799 return 'fncache' in getrepofeatures()
789
800
790 @check('sqlite', 'sqlite3 module is available')
801 @check('sqlite', 'sqlite3 module is available')
791 def has_sqlite():
802 def has_sqlite():
792 try:
803 try:
793 import sqlite3
804 import sqlite3
794 version = sqlite3.sqlite_version_info
805 version = sqlite3.sqlite_version_info
795 except ImportError:
806 except ImportError:
796 return False
807 return False
797
808
798 if version < (3, 8, 3):
809 if version < (3, 8, 3):
799 # WITH clause not supported
810 # WITH clause not supported
800 return False
811 return False
801
812
802 return matchoutput('sqlite3 -version', b'^3\.\d+')
813 return matchoutput('sqlite3 -version', b'^3\.\d+')
803
814
804 @check('vcr', 'vcr http mocking library')
815 @check('vcr', 'vcr http mocking library')
805 def has_vcr():
816 def has_vcr():
806 try:
817 try:
807 import vcr
818 import vcr
808 vcr.VCR
819 vcr.VCR
809 return True
820 return True
810 except (ImportError, AttributeError):
821 except (ImportError, AttributeError):
811 pass
822 pass
812 return False
823 return False
General Comments 0
You need to be logged in to leave comments. Login now