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