##// END OF EJS Templates
hghave: split apart testing for the curses module and `tic` executable...
Matt Harbison -
r47043:e5e6282f default
parent child Browse files
Show More
@@ -1,1090 +1,1096 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import distutils.version
3 import distutils.version
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 "known-bad-output": (lambda: True, "use for currently known bad output"),
17 "known-bad-output": (lambda: True, "use for currently known bad output"),
18 "missing-correct-output": (lambda: False, "use for missing good output"),
18 "missing-correct-output": (lambda: False, "use for missing good output"),
19 }
19 }
20
20
21 try:
21 try:
22 import msvcrt
22 import msvcrt
23
23
24 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
24 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
25 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
25 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
26 except ImportError:
26 except ImportError:
27 pass
27 pass
28
28
29 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
29 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
30 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
30 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
31
31
32 if sys.version_info[0] >= 3:
32 if sys.version_info[0] >= 3:
33
33
34 def _sys2bytes(p):
34 def _sys2bytes(p):
35 if p is None:
35 if p is None:
36 return p
36 return p
37 return p.encode('utf-8')
37 return p.encode('utf-8')
38
38
39 def _bytes2sys(p):
39 def _bytes2sys(p):
40 if p is None:
40 if p is None:
41 return p
41 return p
42 return p.decode('utf-8')
42 return p.decode('utf-8')
43
43
44
44
45 else:
45 else:
46
46
47 def _sys2bytes(p):
47 def _sys2bytes(p):
48 return p
48 return p
49
49
50 _bytes2sys = _sys2bytes
50 _bytes2sys = _sys2bytes
51
51
52
52
53 def check(name, desc):
53 def check(name, desc):
54 """Registers a check function for a feature."""
54 """Registers a check function for a feature."""
55
55
56 def decorator(func):
56 def decorator(func):
57 checks[name] = (func, desc)
57 checks[name] = (func, desc)
58 return func
58 return func
59
59
60 return decorator
60 return decorator
61
61
62
62
63 def checkvers(name, desc, vers):
63 def checkvers(name, desc, vers):
64 """Registers a check function for each of a series of versions.
64 """Registers a check function for each of a series of versions.
65
65
66 vers can be a list or an iterator.
66 vers can be a list or an iterator.
67
67
68 Produces a series of feature checks that have the form <name><vers> without
68 Produces a series of feature checks that have the form <name><vers> without
69 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
69 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
70 'py38', not 'py3.8' or 'py-38')."""
70 'py38', not 'py3.8' or 'py-38')."""
71
71
72 def decorator(func):
72 def decorator(func):
73 def funcv(v):
73 def funcv(v):
74 def f():
74 def f():
75 return func(v)
75 return func(v)
76
76
77 return f
77 return f
78
78
79 for v in vers:
79 for v in vers:
80 v = str(v)
80 v = str(v)
81 f = funcv(v)
81 f = funcv(v)
82 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
82 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
83 return func
83 return func
84
84
85 return decorator
85 return decorator
86
86
87
87
88 def checkfeatures(features):
88 def checkfeatures(features):
89 result = {
89 result = {
90 'error': [],
90 'error': [],
91 'missing': [],
91 'missing': [],
92 'skipped': [],
92 'skipped': [],
93 }
93 }
94
94
95 for feature in features:
95 for feature in features:
96 negate = feature.startswith('no-')
96 negate = feature.startswith('no-')
97 if negate:
97 if negate:
98 feature = feature[3:]
98 feature = feature[3:]
99
99
100 if feature not in checks:
100 if feature not in checks:
101 result['missing'].append(feature)
101 result['missing'].append(feature)
102 continue
102 continue
103
103
104 check, desc = checks[feature]
104 check, desc = checks[feature]
105 try:
105 try:
106 available = check()
106 available = check()
107 except Exception:
107 except Exception:
108 result['error'].append('hghave check failed: %s' % feature)
108 result['error'].append('hghave check failed: %s' % feature)
109 continue
109 continue
110
110
111 if not negate and not available:
111 if not negate and not available:
112 result['skipped'].append('missing feature: %s' % desc)
112 result['skipped'].append('missing feature: %s' % desc)
113 elif negate and available:
113 elif negate and available:
114 result['skipped'].append('system supports %s' % desc)
114 result['skipped'].append('system supports %s' % desc)
115
115
116 return result
116 return result
117
117
118
118
119 def require(features):
119 def require(features):
120 """Require that features are available, exiting if not."""
120 """Require that features are available, exiting if not."""
121 result = checkfeatures(features)
121 result = checkfeatures(features)
122
122
123 for missing in result['missing']:
123 for missing in result['missing']:
124 stderr.write(
124 stderr.write(
125 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
125 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
126 )
126 )
127 for msg in result['skipped']:
127 for msg in result['skipped']:
128 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
128 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
129 for msg in result['error']:
129 for msg in result['error']:
130 stderr.write(('%s\n' % msg).encode('utf-8'))
130 stderr.write(('%s\n' % msg).encode('utf-8'))
131
131
132 if result['missing']:
132 if result['missing']:
133 sys.exit(2)
133 sys.exit(2)
134
134
135 if result['skipped'] or result['error']:
135 if result['skipped'] or result['error']:
136 sys.exit(1)
136 sys.exit(1)
137
137
138
138
139 def matchoutput(cmd, regexp, ignorestatus=False):
139 def matchoutput(cmd, regexp, ignorestatus=False):
140 """Return the match object if cmd executes successfully and its output
140 """Return the match object if cmd executes successfully and its output
141 is matched by the supplied regular expression.
141 is matched by the supplied regular expression.
142 """
142 """
143 r = re.compile(regexp)
143 r = re.compile(regexp)
144 p = subprocess.Popen(
144 p = subprocess.Popen(
145 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
145 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
146 )
146 )
147 s = p.communicate()[0]
147 s = p.communicate()[0]
148 ret = p.returncode
148 ret = p.returncode
149 return (ignorestatus or not ret) and r.search(s)
149 return (ignorestatus or not ret) and r.search(s)
150
150
151
151
152 @check("baz", "GNU Arch baz client")
152 @check("baz", "GNU Arch baz client")
153 def has_baz():
153 def has_baz():
154 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
154 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
155
155
156
156
157 @check("bzr", "Canonical's Bazaar client")
157 @check("bzr", "Canonical's Bazaar client")
158 def has_bzr():
158 def has_bzr():
159 try:
159 try:
160 import bzrlib
160 import bzrlib
161 import bzrlib.bzrdir
161 import bzrlib.bzrdir
162 import bzrlib.errors
162 import bzrlib.errors
163 import bzrlib.revision
163 import bzrlib.revision
164 import bzrlib.revisionspec
164 import bzrlib.revisionspec
165
165
166 bzrlib.revisionspec.RevisionSpec
166 bzrlib.revisionspec.RevisionSpec
167 return bzrlib.__doc__ is not None
167 return bzrlib.__doc__ is not None
168 except (AttributeError, ImportError):
168 except (AttributeError, ImportError):
169 return False
169 return False
170
170
171
171
172 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
172 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
173 def has_bzr_range(v):
173 def has_bzr_range(v):
174 major, minor = v.split('rc')[0].split('.')[0:2]
174 major, minor = v.split('rc')[0].split('.')[0:2]
175 try:
175 try:
176 import bzrlib
176 import bzrlib
177
177
178 return bzrlib.__doc__ is not None and bzrlib.version_info[:2] >= (
178 return bzrlib.__doc__ is not None and bzrlib.version_info[:2] >= (
179 int(major),
179 int(major),
180 int(minor),
180 int(minor),
181 )
181 )
182 except ImportError:
182 except ImportError:
183 return False
183 return False
184
184
185
185
186 @check("chg", "running with chg")
186 @check("chg", "running with chg")
187 def has_chg():
187 def has_chg():
188 return 'CHGHG' in os.environ
188 return 'CHGHG' in os.environ
189
189
190
190
191 @check("cvs", "cvs client/server")
191 @check("cvs", "cvs client/server")
192 def has_cvs():
192 def has_cvs():
193 re = br'Concurrent Versions System.*?server'
193 re = br'Concurrent Versions System.*?server'
194 return matchoutput('cvs --version 2>&1', re) and not has_msys()
194 return matchoutput('cvs --version 2>&1', re) and not has_msys()
195
195
196
196
197 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
197 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
198 def has_cvs112():
198 def has_cvs112():
199 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
199 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
200 return matchoutput('cvs --version 2>&1', re) and not has_msys()
200 return matchoutput('cvs --version 2>&1', re) and not has_msys()
201
201
202
202
203 @check("cvsnt", "cvsnt client/server")
203 @check("cvsnt", "cvsnt client/server")
204 def has_cvsnt():
204 def has_cvsnt():
205 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
205 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
206 return matchoutput('cvsnt --version 2>&1', re)
206 return matchoutput('cvsnt --version 2>&1', re)
207
207
208
208
209 @check("darcs", "darcs client")
209 @check("darcs", "darcs client")
210 def has_darcs():
210 def has_darcs():
211 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
211 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
212
212
213
213
214 @check("mtn", "monotone client (>= 1.0)")
214 @check("mtn", "monotone client (>= 1.0)")
215 def has_mtn():
215 def has_mtn():
216 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
216 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
217 'mtn --version', br'monotone 0\.', True
217 'mtn --version', br'monotone 0\.', True
218 )
218 )
219
219
220
220
221 @check("eol-in-paths", "end-of-lines in paths")
221 @check("eol-in-paths", "end-of-lines in paths")
222 def has_eol_in_paths():
222 def has_eol_in_paths():
223 try:
223 try:
224 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
224 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
225 os.close(fd)
225 os.close(fd)
226 os.remove(path)
226 os.remove(path)
227 return True
227 return True
228 except (IOError, OSError):
228 except (IOError, OSError):
229 return False
229 return False
230
230
231
231
232 @check("execbit", "executable bit")
232 @check("execbit", "executable bit")
233 def has_executablebit():
233 def has_executablebit():
234 try:
234 try:
235 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
235 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
236 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
236 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
237 try:
237 try:
238 os.close(fh)
238 os.close(fh)
239 m = os.stat(fn).st_mode & 0o777
239 m = os.stat(fn).st_mode & 0o777
240 new_file_has_exec = m & EXECFLAGS
240 new_file_has_exec = m & EXECFLAGS
241 os.chmod(fn, m ^ EXECFLAGS)
241 os.chmod(fn, m ^ EXECFLAGS)
242 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
242 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
243 finally:
243 finally:
244 os.unlink(fn)
244 os.unlink(fn)
245 except (IOError, OSError):
245 except (IOError, OSError):
246 # we don't care, the user probably won't be able to commit anyway
246 # we don't care, the user probably won't be able to commit anyway
247 return False
247 return False
248 return not (new_file_has_exec or exec_flags_cannot_flip)
248 return not (new_file_has_exec or exec_flags_cannot_flip)
249
249
250
250
251 @check("icasefs", "case insensitive file system")
251 @check("icasefs", "case insensitive file system")
252 def has_icasefs():
252 def has_icasefs():
253 # Stolen from mercurial.util
253 # Stolen from mercurial.util
254 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
254 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
255 os.close(fd)
255 os.close(fd)
256 try:
256 try:
257 s1 = os.stat(path)
257 s1 = os.stat(path)
258 d, b = os.path.split(path)
258 d, b = os.path.split(path)
259 p2 = os.path.join(d, b.upper())
259 p2 = os.path.join(d, b.upper())
260 if path == p2:
260 if path == p2:
261 p2 = os.path.join(d, b.lower())
261 p2 = os.path.join(d, b.lower())
262 try:
262 try:
263 s2 = os.stat(p2)
263 s2 = os.stat(p2)
264 return s2 == s1
264 return s2 == s1
265 except OSError:
265 except OSError:
266 return False
266 return False
267 finally:
267 finally:
268 os.remove(path)
268 os.remove(path)
269
269
270
270
271 @check("fifo", "named pipes")
271 @check("fifo", "named pipes")
272 def has_fifo():
272 def has_fifo():
273 if getattr(os, "mkfifo", None) is None:
273 if getattr(os, "mkfifo", None) is None:
274 return False
274 return False
275 name = tempfile.mktemp(dir='.', prefix=tempprefix)
275 name = tempfile.mktemp(dir='.', prefix=tempprefix)
276 try:
276 try:
277 os.mkfifo(name)
277 os.mkfifo(name)
278 os.unlink(name)
278 os.unlink(name)
279 return True
279 return True
280 except OSError:
280 except OSError:
281 return False
281 return False
282
282
283
283
284 @check("killdaemons", 'killdaemons.py support')
284 @check("killdaemons", 'killdaemons.py support')
285 def has_killdaemons():
285 def has_killdaemons():
286 return True
286 return True
287
287
288
288
289 @check("cacheable", "cacheable filesystem")
289 @check("cacheable", "cacheable filesystem")
290 def has_cacheable_fs():
290 def has_cacheable_fs():
291 from mercurial import util
291 from mercurial import util
292
292
293 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
293 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
294 os.close(fd)
294 os.close(fd)
295 try:
295 try:
296 return util.cachestat(path).cacheable()
296 return util.cachestat(path).cacheable()
297 finally:
297 finally:
298 os.remove(path)
298 os.remove(path)
299
299
300
300
301 @check("lsprof", "python lsprof module")
301 @check("lsprof", "python lsprof module")
302 def has_lsprof():
302 def has_lsprof():
303 try:
303 try:
304 import _lsprof
304 import _lsprof
305
305
306 _lsprof.Profiler # silence unused import warning
306 _lsprof.Profiler # silence unused import warning
307 return True
307 return True
308 except ImportError:
308 except ImportError:
309 return False
309 return False
310
310
311
311
312 def _gethgversion():
312 def _gethgversion():
313 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
313 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
314 if not m:
314 if not m:
315 return (0, 0)
315 return (0, 0)
316 return (int(m.group(1)), int(m.group(2)))
316 return (int(m.group(1)), int(m.group(2)))
317
317
318
318
319 _hgversion = None
319 _hgversion = None
320
320
321
321
322 def gethgversion():
322 def gethgversion():
323 global _hgversion
323 global _hgversion
324 if _hgversion is None:
324 if _hgversion is None:
325 _hgversion = _gethgversion()
325 _hgversion = _gethgversion()
326 return _hgversion
326 return _hgversion
327
327
328
328
329 @checkvers(
329 @checkvers(
330 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
330 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
331 )
331 )
332 def has_hg_range(v):
332 def has_hg_range(v):
333 major, minor = v.split('.')[0:2]
333 major, minor = v.split('.')[0:2]
334 return gethgversion() >= (int(major), int(minor))
334 return gethgversion() >= (int(major), int(minor))
335
335
336
336
337 @check("rust", "Using the Rust extensions")
337 @check("rust", "Using the Rust extensions")
338 def has_rust():
338 def has_rust():
339 """Check is the mercurial currently running is using some rust code"""
339 """Check is the mercurial currently running is using some rust code"""
340 cmd = 'hg debuginstall --quiet 2>&1'
340 cmd = 'hg debuginstall --quiet 2>&1'
341 match = br'checking module policy \(([^)]+)\)'
341 match = br'checking module policy \(([^)]+)\)'
342 policy = matchoutput(cmd, match)
342 policy = matchoutput(cmd, match)
343 if not policy:
343 if not policy:
344 return False
344 return False
345 return b'rust' in policy.group(1)
345 return b'rust' in policy.group(1)
346
346
347
347
348 @check("hg08", "Mercurial >= 0.8")
348 @check("hg08", "Mercurial >= 0.8")
349 def has_hg08():
349 def has_hg08():
350 if checks["hg09"][0]():
350 if checks["hg09"][0]():
351 return True
351 return True
352 return matchoutput('hg help annotate 2>&1', '--date')
352 return matchoutput('hg help annotate 2>&1', '--date')
353
353
354
354
355 @check("hg07", "Mercurial >= 0.7")
355 @check("hg07", "Mercurial >= 0.7")
356 def has_hg07():
356 def has_hg07():
357 if checks["hg08"][0]():
357 if checks["hg08"][0]():
358 return True
358 return True
359 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
359 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
360
360
361
361
362 @check("hg06", "Mercurial >= 0.6")
362 @check("hg06", "Mercurial >= 0.6")
363 def has_hg06():
363 def has_hg06():
364 if checks["hg07"][0]():
364 if checks["hg07"][0]():
365 return True
365 return True
366 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
366 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
367
367
368
368
369 @check("gettext", "GNU Gettext (msgfmt)")
369 @check("gettext", "GNU Gettext (msgfmt)")
370 def has_gettext():
370 def has_gettext():
371 return matchoutput('msgfmt --version', br'GNU gettext-tools')
371 return matchoutput('msgfmt --version', br'GNU gettext-tools')
372
372
373
373
374 @check("git", "git command line client")
374 @check("git", "git command line client")
375 def has_git():
375 def has_git():
376 return matchoutput('git --version 2>&1', br'^git version')
376 return matchoutput('git --version 2>&1', br'^git version')
377
377
378
378
379 def getgitversion():
379 def getgitversion():
380 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
380 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
381 if not m:
381 if not m:
382 return (0, 0)
382 return (0, 0)
383 return (int(m.group(1)), int(m.group(2)))
383 return (int(m.group(1)), int(m.group(2)))
384
384
385
385
386 @check("pygit2", "pygit2 Python library")
386 @check("pygit2", "pygit2 Python library")
387 def has_git():
387 def has_git():
388 try:
388 try:
389 import pygit2
389 import pygit2
390
390
391 pygit2.Oid # silence unused import
391 pygit2.Oid # silence unused import
392 return True
392 return True
393 except ImportError:
393 except ImportError:
394 return False
394 return False
395
395
396
396
397 # https://github.com/git-lfs/lfs-test-server
397 # https://github.com/git-lfs/lfs-test-server
398 @check("lfs-test-server", "git-lfs test server")
398 @check("lfs-test-server", "git-lfs test server")
399 def has_lfsserver():
399 def has_lfsserver():
400 exe = 'lfs-test-server'
400 exe = 'lfs-test-server'
401 if has_windows():
401 if has_windows():
402 exe = 'lfs-test-server.exe'
402 exe = 'lfs-test-server.exe'
403 return any(
403 return any(
404 os.access(os.path.join(path, exe), os.X_OK)
404 os.access(os.path.join(path, exe), os.X_OK)
405 for path in os.environ["PATH"].split(os.pathsep)
405 for path in os.environ["PATH"].split(os.pathsep)
406 )
406 )
407
407
408
408
409 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
409 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
410 def has_git_range(v):
410 def has_git_range(v):
411 major, minor = v.split('.')[0:2]
411 major, minor = v.split('.')[0:2]
412 return getgitversion() >= (int(major), int(minor))
412 return getgitversion() >= (int(major), int(minor))
413
413
414
414
415 @check("docutils", "Docutils text processing library")
415 @check("docutils", "Docutils text processing library")
416 def has_docutils():
416 def has_docutils():
417 try:
417 try:
418 import docutils.core
418 import docutils.core
419
419
420 docutils.core.publish_cmdline # silence unused import
420 docutils.core.publish_cmdline # silence unused import
421 return True
421 return True
422 except ImportError:
422 except ImportError:
423 return False
423 return False
424
424
425
425
426 def getsvnversion():
426 def getsvnversion():
427 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
427 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
428 if not m:
428 if not m:
429 return (0, 0)
429 return (0, 0)
430 return (int(m.group(1)), int(m.group(2)))
430 return (int(m.group(1)), int(m.group(2)))
431
431
432
432
433 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
433 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
434 def has_svn_range(v):
434 def has_svn_range(v):
435 major, minor = v.split('.')[0:2]
435 major, minor = v.split('.')[0:2]
436 return getsvnversion() >= (int(major), int(minor))
436 return getsvnversion() >= (int(major), int(minor))
437
437
438
438
439 @check("svn", "subversion client and admin tools")
439 @check("svn", "subversion client and admin tools")
440 def has_svn():
440 def has_svn():
441 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
441 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
442 'svnadmin --version 2>&1', br'^svnadmin, version'
442 'svnadmin --version 2>&1', br'^svnadmin, version'
443 )
443 )
444
444
445
445
446 @check("svn-bindings", "subversion python bindings")
446 @check("svn-bindings", "subversion python bindings")
447 def has_svn_bindings():
447 def has_svn_bindings():
448 try:
448 try:
449 import svn.core
449 import svn.core
450
450
451 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
451 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
452 if version < (1, 4):
452 if version < (1, 4):
453 return False
453 return False
454 return True
454 return True
455 except ImportError:
455 except ImportError:
456 return False
456 return False
457
457
458
458
459 @check("p4", "Perforce server and client")
459 @check("p4", "Perforce server and client")
460 def has_p4():
460 def has_p4():
461 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
461 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
462 'p4d -V', br'Rev\. P4D/'
462 'p4d -V', br'Rev\. P4D/'
463 )
463 )
464
464
465
465
466 @check("symlink", "symbolic links")
466 @check("symlink", "symbolic links")
467 def has_symlink():
467 def has_symlink():
468 # mercurial.windows.checklink() is a hard 'no' at the moment
468 # mercurial.windows.checklink() is a hard 'no' at the moment
469 if os.name == 'nt' or getattr(os, "symlink", None) is None:
469 if os.name == 'nt' or getattr(os, "symlink", None) is None:
470 return False
470 return False
471 name = tempfile.mktemp(dir='.', prefix=tempprefix)
471 name = tempfile.mktemp(dir='.', prefix=tempprefix)
472 try:
472 try:
473 os.symlink(".", name)
473 os.symlink(".", name)
474 os.unlink(name)
474 os.unlink(name)
475 return True
475 return True
476 except (OSError, AttributeError):
476 except (OSError, AttributeError):
477 return False
477 return False
478
478
479
479
480 @check("hardlink", "hardlinks")
480 @check("hardlink", "hardlinks")
481 def has_hardlink():
481 def has_hardlink():
482 from mercurial import util
482 from mercurial import util
483
483
484 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
484 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
485 os.close(fh)
485 os.close(fh)
486 name = tempfile.mktemp(dir='.', prefix=tempprefix)
486 name = tempfile.mktemp(dir='.', prefix=tempprefix)
487 try:
487 try:
488 util.oslink(_sys2bytes(fn), _sys2bytes(name))
488 util.oslink(_sys2bytes(fn), _sys2bytes(name))
489 os.unlink(name)
489 os.unlink(name)
490 return True
490 return True
491 except OSError:
491 except OSError:
492 return False
492 return False
493 finally:
493 finally:
494 os.unlink(fn)
494 os.unlink(fn)
495
495
496
496
497 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
497 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
498 def has_hardlink_whitelisted():
498 def has_hardlink_whitelisted():
499 from mercurial import util
499 from mercurial import util
500
500
501 try:
501 try:
502 fstype = util.getfstype(b'.')
502 fstype = util.getfstype(b'.')
503 except OSError:
503 except OSError:
504 return False
504 return False
505 return fstype in util._hardlinkfswhitelist
505 return fstype in util._hardlinkfswhitelist
506
506
507
507
508 @check("rmcwd", "can remove current working directory")
508 @check("rmcwd", "can remove current working directory")
509 def has_rmcwd():
509 def has_rmcwd():
510 ocwd = os.getcwd()
510 ocwd = os.getcwd()
511 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
511 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
512 try:
512 try:
513 os.chdir(temp)
513 os.chdir(temp)
514 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
514 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
515 # On Solaris and Windows, the cwd can't be removed by any names.
515 # On Solaris and Windows, the cwd can't be removed by any names.
516 os.rmdir(os.getcwd())
516 os.rmdir(os.getcwd())
517 return True
517 return True
518 except OSError:
518 except OSError:
519 return False
519 return False
520 finally:
520 finally:
521 os.chdir(ocwd)
521 os.chdir(ocwd)
522 # clean up temp dir on platforms where cwd can't be removed
522 # clean up temp dir on platforms where cwd can't be removed
523 try:
523 try:
524 os.rmdir(temp)
524 os.rmdir(temp)
525 except OSError:
525 except OSError:
526 pass
526 pass
527
527
528
528
529 @check("tla", "GNU Arch tla client")
529 @check("tla", "GNU Arch tla client")
530 def has_tla():
530 def has_tla():
531 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
531 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
532
532
533
533
534 @check("gpg", "gpg client")
534 @check("gpg", "gpg client")
535 def has_gpg():
535 def has_gpg():
536 return matchoutput('gpg --version 2>&1', br'GnuPG')
536 return matchoutput('gpg --version 2>&1', br'GnuPG')
537
537
538
538
539 @check("gpg2", "gpg client v2")
539 @check("gpg2", "gpg client v2")
540 def has_gpg2():
540 def has_gpg2():
541 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
541 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
542
542
543
543
544 @check("gpg21", "gpg client v2.1+")
544 @check("gpg21", "gpg client v2.1+")
545 def has_gpg21():
545 def has_gpg21():
546 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
546 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
547
547
548
548
549 @check("unix-permissions", "unix-style permissions")
549 @check("unix-permissions", "unix-style permissions")
550 def has_unix_permissions():
550 def has_unix_permissions():
551 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
551 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
552 try:
552 try:
553 fname = os.path.join(d, 'foo')
553 fname = os.path.join(d, 'foo')
554 for umask in (0o77, 0o07, 0o22):
554 for umask in (0o77, 0o07, 0o22):
555 os.umask(umask)
555 os.umask(umask)
556 f = open(fname, 'w')
556 f = open(fname, 'w')
557 f.close()
557 f.close()
558 mode = os.stat(fname).st_mode
558 mode = os.stat(fname).st_mode
559 os.unlink(fname)
559 os.unlink(fname)
560 if mode & 0o777 != ~umask & 0o666:
560 if mode & 0o777 != ~umask & 0o666:
561 return False
561 return False
562 return True
562 return True
563 finally:
563 finally:
564 os.rmdir(d)
564 os.rmdir(d)
565
565
566
566
567 @check("unix-socket", "AF_UNIX socket family")
567 @check("unix-socket", "AF_UNIX socket family")
568 def has_unix_socket():
568 def has_unix_socket():
569 return getattr(socket, 'AF_UNIX', None) is not None
569 return getattr(socket, 'AF_UNIX', None) is not None
570
570
571
571
572 @check("root", "root permissions")
572 @check("root", "root permissions")
573 def has_root():
573 def has_root():
574 return getattr(os, 'geteuid', None) and os.geteuid() == 0
574 return getattr(os, 'geteuid', None) and os.geteuid() == 0
575
575
576
576
577 @check("pyflakes", "Pyflakes python linter")
577 @check("pyflakes", "Pyflakes python linter")
578 def has_pyflakes():
578 def has_pyflakes():
579 try:
579 try:
580 import pyflakes
580 import pyflakes
581
581
582 pyflakes.__version__
582 pyflakes.__version__
583 except ImportError:
583 except ImportError:
584 return False
584 return False
585 else:
585 else:
586 return True
586 return True
587
587
588
588
589 @check("pylint", "Pylint python linter")
589 @check("pylint", "Pylint python linter")
590 def has_pylint():
590 def has_pylint():
591 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
591 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
592
592
593
593
594 @check("clang-format", "clang-format C code formatter")
594 @check("clang-format", "clang-format C code formatter")
595 def has_clang_format():
595 def has_clang_format():
596 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
596 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
597 # style changed somewhere between 10.x and 11.x
597 # style changed somewhere between 10.x and 11.x
598 return m and int(m.group(1)) >= 11
598 return m and int(m.group(1)) >= 11
599
599
600
600
601 @check("jshint", "JSHint static code analysis tool")
601 @check("jshint", "JSHint static code analysis tool")
602 def has_jshint():
602 def has_jshint():
603 return matchoutput("jshint --version 2>&1", br"jshint v")
603 return matchoutput("jshint --version 2>&1", br"jshint v")
604
604
605
605
606 @check("pygments", "Pygments source highlighting library")
606 @check("pygments", "Pygments source highlighting library")
607 def has_pygments():
607 def has_pygments():
608 try:
608 try:
609 import pygments
609 import pygments
610
610
611 pygments.highlight # silence unused import warning
611 pygments.highlight # silence unused import warning
612 return True
612 return True
613 except ImportError:
613 except ImportError:
614 return False
614 return False
615
615
616
616
617 @check("pygments25", "Pygments version >= 2.5")
617 @check("pygments25", "Pygments version >= 2.5")
618 def pygments25():
618 def pygments25():
619 try:
619 try:
620 import pygments
620 import pygments
621
621
622 v = pygments.__version__
622 v = pygments.__version__
623 except ImportError:
623 except ImportError:
624 return False
624 return False
625
625
626 parts = v.split(".")
626 parts = v.split(".")
627 major = int(parts[0])
627 major = int(parts[0])
628 minor = int(parts[1])
628 minor = int(parts[1])
629
629
630 return (major, minor) >= (2, 5)
630 return (major, minor) >= (2, 5)
631
631
632
632
633 @check("outer-repo", "outer repo")
633 @check("outer-repo", "outer repo")
634 def has_outer_repo():
634 def has_outer_repo():
635 # failing for other reasons than 'no repo' imply that there is a repo
635 # failing for other reasons than 'no repo' imply that there is a repo
636 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
636 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
637
637
638
638
639 @check("ssl", "ssl module available")
639 @check("ssl", "ssl module available")
640 def has_ssl():
640 def has_ssl():
641 try:
641 try:
642 import ssl
642 import ssl
643
643
644 ssl.CERT_NONE
644 ssl.CERT_NONE
645 return True
645 return True
646 except ImportError:
646 except ImportError:
647 return False
647 return False
648
648
649
649
650 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
650 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
651 def has_defaultcacertsloaded():
651 def has_defaultcacertsloaded():
652 import ssl
652 import ssl
653 from mercurial import sslutil, ui as uimod
653 from mercurial import sslutil, ui as uimod
654
654
655 ui = uimod.ui.load()
655 ui = uimod.ui.load()
656 cafile = sslutil._defaultcacerts(ui)
656 cafile = sslutil._defaultcacerts(ui)
657 ctx = ssl.create_default_context()
657 ctx = ssl.create_default_context()
658 if cafile:
658 if cafile:
659 ctx.load_verify_locations(cafile=cafile)
659 ctx.load_verify_locations(cafile=cafile)
660 else:
660 else:
661 ctx.load_default_certs()
661 ctx.load_default_certs()
662
662
663 return len(ctx.get_ca_certs()) > 0
663 return len(ctx.get_ca_certs()) > 0
664
664
665
665
666 @check("tls1.2", "TLS 1.2 protocol support")
666 @check("tls1.2", "TLS 1.2 protocol support")
667 def has_tls1_2():
667 def has_tls1_2():
668 from mercurial import sslutil
668 from mercurial import sslutil
669
669
670 return b'tls1.2' in sslutil.supportedprotocols
670 return b'tls1.2' in sslutil.supportedprotocols
671
671
672
672
673 @check("windows", "Windows")
673 @check("windows", "Windows")
674 def has_windows():
674 def has_windows():
675 return os.name == 'nt'
675 return os.name == 'nt'
676
676
677
677
678 @check("system-sh", "system() uses sh")
678 @check("system-sh", "system() uses sh")
679 def has_system_sh():
679 def has_system_sh():
680 return os.name != 'nt'
680 return os.name != 'nt'
681
681
682
682
683 @check("serve", "platform and python can manage 'hg serve -d'")
683 @check("serve", "platform and python can manage 'hg serve -d'")
684 def has_serve():
684 def has_serve():
685 return True
685 return True
686
686
687
687
688 @check("setprocname", "whether osutil.setprocname is available or not")
688 @check("setprocname", "whether osutil.setprocname is available or not")
689 def has_setprocname():
689 def has_setprocname():
690 try:
690 try:
691 from mercurial.utils import procutil
691 from mercurial.utils import procutil
692
692
693 procutil.setprocname
693 procutil.setprocname
694 return True
694 return True
695 except AttributeError:
695 except AttributeError:
696 return False
696 return False
697
697
698
698
699 @check("test-repo", "running tests from repository")
699 @check("test-repo", "running tests from repository")
700 def has_test_repo():
700 def has_test_repo():
701 t = os.environ["TESTDIR"]
701 t = os.environ["TESTDIR"]
702 return os.path.isdir(os.path.join(t, "..", ".hg"))
702 return os.path.isdir(os.path.join(t, "..", ".hg"))
703
703
704
704
705 @check("tic", "terminfo compiler and curses module")
705 @check("curses", "terminfo compiler and curses module")
706 def has_tic():
706 def has_curses():
707 try:
707 try:
708 import curses
708 import curses
709
709
710 curses.COLOR_BLUE
710 curses.COLOR_BLUE
711
711
712 # Windows doesn't have a `tic` executable, but the windows_curses
712 # Windows doesn't have a `tic` executable, but the windows_curses
713 # package is sufficient to run the tests without it.
713 # package is sufficient to run the tests without it.
714 if os.name == 'nt':
714 if os.name == 'nt':
715 return True
715 return True
716
716
717 return matchoutput('test -x "`which tic`"', br'')
717 return has_tic()
718
718 except (ImportError, AttributeError):
719 except (ImportError, AttributeError):
719 return False
720 return False
720
721
721
722
723 @check("tic", "terminfo compiler")
724 def has_tic():
725 return matchoutput('test -x "`which tic`"', br'')
726
727
722 @check("xz", "xz compression utility")
728 @check("xz", "xz compression utility")
723 def has_xz():
729 def has_xz():
724 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
730 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
725 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
731 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
726 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
732 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
727
733
728
734
729 @check("msys", "Windows with MSYS")
735 @check("msys", "Windows with MSYS")
730 def has_msys():
736 def has_msys():
731 return os.getenv('MSYSTEM')
737 return os.getenv('MSYSTEM')
732
738
733
739
734 @check("aix", "AIX")
740 @check("aix", "AIX")
735 def has_aix():
741 def has_aix():
736 return sys.platform.startswith("aix")
742 return sys.platform.startswith("aix")
737
743
738
744
739 @check("osx", "OS X")
745 @check("osx", "OS X")
740 def has_osx():
746 def has_osx():
741 return sys.platform == 'darwin'
747 return sys.platform == 'darwin'
742
748
743
749
744 @check("osxpackaging", "OS X packaging tools")
750 @check("osxpackaging", "OS X packaging tools")
745 def has_osxpackaging():
751 def has_osxpackaging():
746 try:
752 try:
747 return (
753 return (
748 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
754 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
749 and matchoutput(
755 and matchoutput(
750 'productbuild', br'Usage: productbuild ', ignorestatus=1
756 'productbuild', br'Usage: productbuild ', ignorestatus=1
751 )
757 )
752 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
758 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
753 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
759 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
754 )
760 )
755 except ImportError:
761 except ImportError:
756 return False
762 return False
757
763
758
764
759 @check('linuxormacos', 'Linux or MacOS')
765 @check('linuxormacos', 'Linux or MacOS')
760 def has_linuxormacos():
766 def has_linuxormacos():
761 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
767 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
762 return sys.platform.startswith(('linux', 'darwin'))
768 return sys.platform.startswith(('linux', 'darwin'))
763
769
764
770
765 @check("docker", "docker support")
771 @check("docker", "docker support")
766 def has_docker():
772 def has_docker():
767 pat = br'A self-sufficient runtime for'
773 pat = br'A self-sufficient runtime for'
768 if matchoutput('docker --help', pat):
774 if matchoutput('docker --help', pat):
769 if 'linux' not in sys.platform:
775 if 'linux' not in sys.platform:
770 # TODO: in theory we should be able to test docker-based
776 # TODO: in theory we should be able to test docker-based
771 # package creation on non-linux using boot2docker, but in
777 # package creation on non-linux using boot2docker, but in
772 # practice that requires extra coordination to make sure
778 # practice that requires extra coordination to make sure
773 # $TESTTEMP is going to be visible at the same path to the
779 # $TESTTEMP is going to be visible at the same path to the
774 # boot2docker VM. If we figure out how to verify that, we
780 # boot2docker VM. If we figure out how to verify that, we
775 # can use the following instead of just saying False:
781 # can use the following instead of just saying False:
776 # return 'DOCKER_HOST' in os.environ
782 # return 'DOCKER_HOST' in os.environ
777 return False
783 return False
778
784
779 return True
785 return True
780 return False
786 return False
781
787
782
788
783 @check("debhelper", "debian packaging tools")
789 @check("debhelper", "debian packaging tools")
784 def has_debhelper():
790 def has_debhelper():
785 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
791 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
786 # quote), so just accept anything in that spot.
792 # quote), so just accept anything in that spot.
787 dpkg = matchoutput(
793 dpkg = matchoutput(
788 'dpkg --version', br"Debian .dpkg' package management program"
794 'dpkg --version', br"Debian .dpkg' package management program"
789 )
795 )
790 dh = matchoutput(
796 dh = matchoutput(
791 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
797 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
792 )
798 )
793 dh_py2 = matchoutput(
799 dh_py2 = matchoutput(
794 'dh_python2 --help', br'other supported Python versions'
800 'dh_python2 --help', br'other supported Python versions'
795 )
801 )
796 # debuild comes from the 'devscripts' package, though you might want
802 # debuild comes from the 'devscripts' package, though you might want
797 # the 'build-debs' package instead, which has a dependency on devscripts.
803 # the 'build-debs' package instead, which has a dependency on devscripts.
798 debuild = matchoutput(
804 debuild = matchoutput(
799 'debuild --help', br'to run debian/rules with given parameter'
805 'debuild --help', br'to run debian/rules with given parameter'
800 )
806 )
801 return dpkg and dh and dh_py2 and debuild
807 return dpkg and dh and dh_py2 and debuild
802
808
803
809
804 @check(
810 @check(
805 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
811 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
806 )
812 )
807 def has_debdeps():
813 def has_debdeps():
808 # just check exit status (ignoring output)
814 # just check exit status (ignoring output)
809 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
815 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
810 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
816 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
811
817
812
818
813 @check("demandimport", "demandimport enabled")
819 @check("demandimport", "demandimport enabled")
814 def has_demandimport():
820 def has_demandimport():
815 # chg disables demandimport intentionally for performance wins.
821 # chg disables demandimport intentionally for performance wins.
816 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
822 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
817
823
818
824
819 # Add "py27", "py35", ... as possible feature checks. Note that there's no
825 # Add "py27", "py35", ... as possible feature checks. Note that there's no
820 # punctuation here.
826 # punctuation here.
821 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
827 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
822 def has_python_range(v):
828 def has_python_range(v):
823 major, minor = v.split('.')[0:2]
829 major, minor = v.split('.')[0:2]
824 py_major, py_minor = sys.version_info.major, sys.version_info.minor
830 py_major, py_minor = sys.version_info.major, sys.version_info.minor
825
831
826 return (py_major, py_minor) >= (int(major), int(minor))
832 return (py_major, py_minor) >= (int(major), int(minor))
827
833
828
834
829 @check("py3", "running with Python 3.x")
835 @check("py3", "running with Python 3.x")
830 def has_py3():
836 def has_py3():
831 return 3 == sys.version_info[0]
837 return 3 == sys.version_info[0]
832
838
833
839
834 @check("py3exe", "a Python 3.x interpreter is available")
840 @check("py3exe", "a Python 3.x interpreter is available")
835 def has_python3exe():
841 def has_python3exe():
836 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
842 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
837
843
838
844
839 @check("pure", "running with pure Python code")
845 @check("pure", "running with pure Python code")
840 def has_pure():
846 def has_pure():
841 return any(
847 return any(
842 [
848 [
843 os.environ.get("HGMODULEPOLICY") == "py",
849 os.environ.get("HGMODULEPOLICY") == "py",
844 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
850 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
845 ]
851 ]
846 )
852 )
847
853
848
854
849 @check("slow", "allow slow tests (use --allow-slow-tests)")
855 @check("slow", "allow slow tests (use --allow-slow-tests)")
850 def has_slow():
856 def has_slow():
851 return os.environ.get('HGTEST_SLOW') == 'slow'
857 return os.environ.get('HGTEST_SLOW') == 'slow'
852
858
853
859
854 @check("hypothesis", "Hypothesis automated test generation")
860 @check("hypothesis", "Hypothesis automated test generation")
855 def has_hypothesis():
861 def has_hypothesis():
856 try:
862 try:
857 import hypothesis
863 import hypothesis
858
864
859 hypothesis.given
865 hypothesis.given
860 return True
866 return True
861 except ImportError:
867 except ImportError:
862 return False
868 return False
863
869
864
870
865 @check("unziplinks", "unzip(1) understands and extracts symlinks")
871 @check("unziplinks", "unzip(1) understands and extracts symlinks")
866 def unzip_understands_symlinks():
872 def unzip_understands_symlinks():
867 return matchoutput('unzip --help', br'Info-ZIP')
873 return matchoutput('unzip --help', br'Info-ZIP')
868
874
869
875
870 @check("zstd", "zstd Python module available")
876 @check("zstd", "zstd Python module available")
871 def has_zstd():
877 def has_zstd():
872 try:
878 try:
873 import mercurial.zstd
879 import mercurial.zstd
874
880
875 mercurial.zstd.__version__
881 mercurial.zstd.__version__
876 return True
882 return True
877 except ImportError:
883 except ImportError:
878 return False
884 return False
879
885
880
886
881 @check("devfull", "/dev/full special file")
887 @check("devfull", "/dev/full special file")
882 def has_dev_full():
888 def has_dev_full():
883 return os.path.exists('/dev/full')
889 return os.path.exists('/dev/full')
884
890
885
891
886 @check("ensurepip", "ensurepip module")
892 @check("ensurepip", "ensurepip module")
887 def has_ensurepip():
893 def has_ensurepip():
888 try:
894 try:
889 import ensurepip
895 import ensurepip
890
896
891 ensurepip.bootstrap
897 ensurepip.bootstrap
892 return True
898 return True
893 except ImportError:
899 except ImportError:
894 return False
900 return False
895
901
896
902
897 @check("virtualenv", "virtualenv support")
903 @check("virtualenv", "virtualenv support")
898 def has_virtualenv():
904 def has_virtualenv():
899 try:
905 try:
900 import virtualenv
906 import virtualenv
901
907
902 # --no-site-package became the default in 1.7 (Nov 2011), and the
908 # --no-site-package became the default in 1.7 (Nov 2011), and the
903 # argument was removed in 20.0 (Feb 2020). Rather than make the
909 # argument was removed in 20.0 (Feb 2020). Rather than make the
904 # script complicated, just ignore ancient versions.
910 # script complicated, just ignore ancient versions.
905 return int(virtualenv.__version__.split('.')[0]) > 1
911 return int(virtualenv.__version__.split('.')[0]) > 1
906 except (AttributeError, ImportError, IndexError):
912 except (AttributeError, ImportError, IndexError):
907 return False
913 return False
908
914
909
915
910 @check("fsmonitor", "running tests with fsmonitor")
916 @check("fsmonitor", "running tests with fsmonitor")
911 def has_fsmonitor():
917 def has_fsmonitor():
912 return 'HGFSMONITOR_TESTS' in os.environ
918 return 'HGFSMONITOR_TESTS' in os.environ
913
919
914
920
915 @check("fuzzywuzzy", "Fuzzy string matching library")
921 @check("fuzzywuzzy", "Fuzzy string matching library")
916 def has_fuzzywuzzy():
922 def has_fuzzywuzzy():
917 try:
923 try:
918 import fuzzywuzzy
924 import fuzzywuzzy
919
925
920 fuzzywuzzy.__version__
926 fuzzywuzzy.__version__
921 return True
927 return True
922 except ImportError:
928 except ImportError:
923 return False
929 return False
924
930
925
931
926 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
932 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
927 def has_clang_libfuzzer():
933 def has_clang_libfuzzer():
928 mat = matchoutput('clang --version', br'clang version (\d)')
934 mat = matchoutput('clang --version', br'clang version (\d)')
929 if mat:
935 if mat:
930 # libfuzzer is new in clang 6
936 # libfuzzer is new in clang 6
931 return int(mat.group(1)) > 5
937 return int(mat.group(1)) > 5
932 return False
938 return False
933
939
934
940
935 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
941 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
936 def has_clang60():
942 def has_clang60():
937 return matchoutput('clang-6.0 --version', br'clang version 6\.')
943 return matchoutput('clang-6.0 --version', br'clang version 6\.')
938
944
939
945
940 @check("xdiff", "xdiff algorithm")
946 @check("xdiff", "xdiff algorithm")
941 def has_xdiff():
947 def has_xdiff():
942 try:
948 try:
943 from mercurial import policy
949 from mercurial import policy
944
950
945 bdiff = policy.importmod('bdiff')
951 bdiff = policy.importmod('bdiff')
946 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
952 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
947 except (ImportError, AttributeError):
953 except (ImportError, AttributeError):
948 return False
954 return False
949
955
950
956
951 @check('extraextensions', 'whether tests are running with extra extensions')
957 @check('extraextensions', 'whether tests are running with extra extensions')
952 def has_extraextensions():
958 def has_extraextensions():
953 return 'HGTESTEXTRAEXTENSIONS' in os.environ
959 return 'HGTESTEXTRAEXTENSIONS' in os.environ
954
960
955
961
956 def getrepofeatures():
962 def getrepofeatures():
957 """Obtain set of repository features in use.
963 """Obtain set of repository features in use.
958
964
959 HGREPOFEATURES can be used to define or remove features. It contains
965 HGREPOFEATURES can be used to define or remove features. It contains
960 a space-delimited list of feature strings. Strings beginning with ``-``
966 a space-delimited list of feature strings. Strings beginning with ``-``
961 mean to remove.
967 mean to remove.
962 """
968 """
963 # Default list provided by core.
969 # Default list provided by core.
964 features = {
970 features = {
965 'bundlerepo',
971 'bundlerepo',
966 'revlogstore',
972 'revlogstore',
967 'fncache',
973 'fncache',
968 }
974 }
969
975
970 # Features that imply other features.
976 # Features that imply other features.
971 implies = {
977 implies = {
972 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
978 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
973 }
979 }
974
980
975 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
981 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
976 if not override:
982 if not override:
977 continue
983 continue
978
984
979 if override.startswith('-'):
985 if override.startswith('-'):
980 if override[1:] in features:
986 if override[1:] in features:
981 features.remove(override[1:])
987 features.remove(override[1:])
982 else:
988 else:
983 features.add(override)
989 features.add(override)
984
990
985 for imply in implies.get(override, []):
991 for imply in implies.get(override, []):
986 if imply.startswith('-'):
992 if imply.startswith('-'):
987 if imply[1:] in features:
993 if imply[1:] in features:
988 features.remove(imply[1:])
994 features.remove(imply[1:])
989 else:
995 else:
990 features.add(imply)
996 features.add(imply)
991
997
992 return features
998 return features
993
999
994
1000
995 @check('reporevlogstore', 'repository using the default revlog store')
1001 @check('reporevlogstore', 'repository using the default revlog store')
996 def has_reporevlogstore():
1002 def has_reporevlogstore():
997 return 'revlogstore' in getrepofeatures()
1003 return 'revlogstore' in getrepofeatures()
998
1004
999
1005
1000 @check('reposimplestore', 'repository using simple storage extension')
1006 @check('reposimplestore', 'repository using simple storage extension')
1001 def has_reposimplestore():
1007 def has_reposimplestore():
1002 return 'simplestore' in getrepofeatures()
1008 return 'simplestore' in getrepofeatures()
1003
1009
1004
1010
1005 @check('repobundlerepo', 'whether we can open bundle files as repos')
1011 @check('repobundlerepo', 'whether we can open bundle files as repos')
1006 def has_repobundlerepo():
1012 def has_repobundlerepo():
1007 return 'bundlerepo' in getrepofeatures()
1013 return 'bundlerepo' in getrepofeatures()
1008
1014
1009
1015
1010 @check('repofncache', 'repository has an fncache')
1016 @check('repofncache', 'repository has an fncache')
1011 def has_repofncache():
1017 def has_repofncache():
1012 return 'fncache' in getrepofeatures()
1018 return 'fncache' in getrepofeatures()
1013
1019
1014
1020
1015 @check('sqlite', 'sqlite3 module and matching cli is available')
1021 @check('sqlite', 'sqlite3 module and matching cli is available')
1016 def has_sqlite():
1022 def has_sqlite():
1017 try:
1023 try:
1018 import sqlite3
1024 import sqlite3
1019
1025
1020 version = sqlite3.sqlite_version_info
1026 version = sqlite3.sqlite_version_info
1021 except ImportError:
1027 except ImportError:
1022 return False
1028 return False
1023
1029
1024 if version < (3, 8, 3):
1030 if version < (3, 8, 3):
1025 # WITH clause not supported
1031 # WITH clause not supported
1026 return False
1032 return False
1027
1033
1028 return matchoutput('sqlite3 -version', br'^3\.\d+')
1034 return matchoutput('sqlite3 -version', br'^3\.\d+')
1029
1035
1030
1036
1031 @check('vcr', 'vcr http mocking library')
1037 @check('vcr', 'vcr http mocking library')
1032 def has_vcr():
1038 def has_vcr():
1033 try:
1039 try:
1034 import vcr
1040 import vcr
1035
1041
1036 vcr.VCR
1042 vcr.VCR
1037 return True
1043 return True
1038 except (ImportError, AttributeError):
1044 except (ImportError, AttributeError):
1039 pass
1045 pass
1040 return False
1046 return False
1041
1047
1042
1048
1043 @check('emacs', 'GNU Emacs')
1049 @check('emacs', 'GNU Emacs')
1044 def has_emacs():
1050 def has_emacs():
1045 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1051 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1046 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1052 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1047 # 24 release)
1053 # 24 release)
1048 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1054 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1049
1055
1050
1056
1051 @check('black', 'the black formatter for python')
1057 @check('black', 'the black formatter for python')
1052 def has_black():
1058 def has_black():
1053 blackcmd = 'black --version'
1059 blackcmd = 'black --version'
1054 version_regex = b'black, version ([0-9a-b.]+)'
1060 version_regex = b'black, version ([0-9a-b.]+)'
1055 version = matchoutput(blackcmd, version_regex)
1061 version = matchoutput(blackcmd, version_regex)
1056 sv = distutils.version.StrictVersion
1062 sv = distutils.version.StrictVersion
1057 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1063 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1058
1064
1059
1065
1060 @check('pytype', 'the pytype type checker')
1066 @check('pytype', 'the pytype type checker')
1061 def has_pytype():
1067 def has_pytype():
1062 pytypecmd = 'pytype --version'
1068 pytypecmd = 'pytype --version'
1063 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1069 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1064 sv = distutils.version.StrictVersion
1070 sv = distutils.version.StrictVersion
1065 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1071 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1066
1072
1067
1073
1068 @check("rustfmt", "rustfmt tool at version nightly-2020-10-04")
1074 @check("rustfmt", "rustfmt tool at version nightly-2020-10-04")
1069 def has_rustfmt():
1075 def has_rustfmt():
1070 # We use Nightly's rustfmt due to current unstable config options.
1076 # We use Nightly's rustfmt due to current unstable config options.
1071 return matchoutput(
1077 return matchoutput(
1072 '`rustup which --toolchain nightly-2020-10-04 rustfmt` --version',
1078 '`rustup which --toolchain nightly-2020-10-04 rustfmt` --version',
1073 b'rustfmt',
1079 b'rustfmt',
1074 )
1080 )
1075
1081
1076
1082
1077 @check("cargo", "cargo tool")
1083 @check("cargo", "cargo tool")
1078 def has_cargo():
1084 def has_cargo():
1079 return matchoutput('`rustup which cargo` --version', b'cargo')
1085 return matchoutput('`rustup which cargo` --version', b'cargo')
1080
1086
1081
1087
1082 @check("lzma", "python lzma module")
1088 @check("lzma", "python lzma module")
1083 def has_lzma():
1089 def has_lzma():
1084 try:
1090 try:
1085 import _lzma
1091 import _lzma
1086
1092
1087 _lzma.FORMAT_XZ
1093 _lzma.FORMAT_XZ
1088 return True
1094 return True
1089 except ImportError:
1095 except ImportError:
1090 return False
1096 return False
@@ -1,489 +1,489 b''
1 #require tic
1 #require curses
2
2
3 Set up a repo
3 Set up a repo
4
4
5 $ cp $HGRCPATH $HGRCPATH.pretest
5 $ cp $HGRCPATH $HGRCPATH.pretest
6 $ cat <<EOF >> $HGRCPATH
6 $ cat <<EOF >> $HGRCPATH
7 > [ui]
7 > [ui]
8 > interactive = true
8 > interactive = true
9 > interface = curses
9 > interface = curses
10 > [experimental]
10 > [experimental]
11 > crecordtest = testModeCommands
11 > crecordtest = testModeCommands
12 > EOF
12 > EOF
13
13
14 Record with noeol at eof (issue5268)
14 Record with noeol at eof (issue5268)
15 $ hg init noeol
15 $ hg init noeol
16 $ cd noeol
16 $ cd noeol
17 $ printf '0' > a
17 $ printf '0' > a
18 $ printf '0\n' > b
18 $ printf '0\n' > b
19 $ hg ci -Aqm initial
19 $ hg ci -Aqm initial
20 $ printf '1\n0' > a
20 $ printf '1\n0' > a
21 $ printf '1\n0\n' > b
21 $ printf '1\n0\n' > b
22 $ cat <<EOF >testModeCommands
22 $ cat <<EOF >testModeCommands
23 > c
23 > c
24 > EOF
24 > EOF
25 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "add hunks" -d "0 0"
25 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "add hunks" -d "0 0"
26 $ cd ..
26 $ cd ..
27
27
28 Normal repo
28 Normal repo
29 $ hg init a
29 $ hg init a
30 $ cd a
30 $ cd a
31
31
32 Committing some changes but stopping on the way
32 Committing some changes but stopping on the way
33
33
34 $ echo "a" > a
34 $ echo "a" > a
35 $ hg add a
35 $ hg add a
36 $ cat <<EOF >testModeCommands
36 $ cat <<EOF >testModeCommands
37 > x
37 > x
38 > c
38 > c
39 > EOF
39 > EOF
40 $ hg commit -i -m "a" -d "0 0"
40 $ hg commit -i -m "a" -d "0 0"
41 no changes to record
41 no changes to record
42 [1]
42 [1]
43 $ hg tip
43 $ hg tip
44 changeset: -1:000000000000
44 changeset: -1:000000000000
45 tag: tip
45 tag: tip
46 user:
46 user:
47 date: Thu Jan 01 00:00:00 1970 +0000
47 date: Thu Jan 01 00:00:00 1970 +0000
48
48
49
49
50 Committing some changes
50 Committing some changes
51
51
52 $ cat <<EOF >testModeCommands
52 $ cat <<EOF >testModeCommands
53 > c
53 > c
54 > EOF
54 > EOF
55 $ hg commit -i -m "a" -d "0 0"
55 $ hg commit -i -m "a" -d "0 0"
56 $ hg tip
56 $ hg tip
57 changeset: 0:cb9a9f314b8b
57 changeset: 0:cb9a9f314b8b
58 tag: tip
58 tag: tip
59 user: test
59 user: test
60 date: Thu Jan 01 00:00:00 1970 +0000
60 date: Thu Jan 01 00:00:00 1970 +0000
61 summary: a
61 summary: a
62
62
63 Check that commit -i works with no changes
63 Check that commit -i works with no changes
64 $ hg commit -i
64 $ hg commit -i
65 no changes to record
65 no changes to record
66 [1]
66 [1]
67
67
68 Committing only one file
68 Committing only one file
69
69
70 $ echo "a" >> a
70 $ echo "a" >> a
71 >>> open('b', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
71 >>> open('b', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
72 $ hg add b
72 $ hg add b
73 $ cat <<EOF >testModeCommands
73 $ cat <<EOF >testModeCommands
74 > x
74 > x
75 > KEY_DOWN
75 > KEY_DOWN
76 > c
76 > c
77 > EOF
77 > EOF
78 $ hg commit -i -m "one file" -d "0 0"
78 $ hg commit -i -m "one file" -d "0 0"
79 $ hg tip
79 $ hg tip
80 changeset: 1:fb2705a663ea
80 changeset: 1:fb2705a663ea
81 tag: tip
81 tag: tip
82 user: test
82 user: test
83 date: Thu Jan 01 00:00:00 1970 +0000
83 date: Thu Jan 01 00:00:00 1970 +0000
84 summary: one file
84 summary: one file
85
85
86 $ hg cat -r tip a
86 $ hg cat -r tip a
87 a
87 a
88 $ cat a
88 $ cat a
89 a
89 a
90 a
90 a
91
91
92 Committing only one hunk while aborting edition of hunk
92 Committing only one hunk while aborting edition of hunk
93
93
94 - Untoggle all the hunks, go down to the second file
94 - Untoggle all the hunks, go down to the second file
95 - unfold it
95 - unfold it
96 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
96 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
97 - toggle the second hunk
97 - toggle the second hunk
98 - toggle all lines twice (to check that it does nothing)
98 - toggle all lines twice (to check that it does nothing)
99 - edit the hunk and quit the editor immediately with non-zero status
99 - edit the hunk and quit the editor immediately with non-zero status
100 - commit
100 - commit
101
101
102 $ printf "printf 'editor ran\n'; exit 1" > editor.sh
102 $ printf "printf 'editor ran\n'; exit 1" > editor.sh
103 $ echo "x" > c
103 $ echo "x" > c
104 $ cat b >> c
104 $ cat b >> c
105 $ echo "y" >> c
105 $ echo "y" >> c
106 $ mv c b
106 $ mv c b
107 $ cat <<EOF >testModeCommands
107 $ cat <<EOF >testModeCommands
108 > A
108 > A
109 > KEY_DOWN
109 > KEY_DOWN
110 > f
110 > f
111 > KEY_DOWN
111 > KEY_DOWN
112 > KEY_DOWN
112 > KEY_DOWN
113 > KEY_DOWN
113 > KEY_DOWN
114 > KEY_DOWN
114 > KEY_DOWN
115 > x
115 > x
116 > a
116 > a
117 > a
117 > a
118 > e
118 > e
119 > c
119 > c
120 > EOF
120 > EOF
121 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "one hunk" -d "0 0"
121 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "one hunk" -d "0 0"
122 editor ran
122 editor ran
123 $ rm editor.sh
123 $ rm editor.sh
124 $ hg tip
124 $ hg tip
125 changeset: 2:7d10dfe755a8
125 changeset: 2:7d10dfe755a8
126 tag: tip
126 tag: tip
127 user: test
127 user: test
128 date: Thu Jan 01 00:00:00 1970 +0000
128 date: Thu Jan 01 00:00:00 1970 +0000
129 summary: one hunk
129 summary: one hunk
130
130
131 $ hg cat -r tip b
131 $ hg cat -r tip b
132 1
132 1
133 2
133 2
134 3
134 3
135 4
135 4
136 5
136 5
137 6
137 6
138 7
138 7
139 8
139 8
140 9
140 9
141 10
141 10
142 y
142 y
143 $ cat b
143 $ cat b
144 x
144 x
145 1
145 1
146 2
146 2
147 3
147 3
148 4
148 4
149 5
149 5
150 6
150 6
151 7
151 7
152 8
152 8
153 9
153 9
154 10
154 10
155 y
155 y
156 $ hg commit -m "other hunks"
156 $ hg commit -m "other hunks"
157 $ hg tip
157 $ hg tip
158 changeset: 3:a6735021574d
158 changeset: 3:a6735021574d
159 tag: tip
159 tag: tip
160 user: test
160 user: test
161 date: Thu Jan 01 00:00:00 1970 +0000
161 date: Thu Jan 01 00:00:00 1970 +0000
162 summary: other hunks
162 summary: other hunks
163
163
164 $ hg cat -r tip b
164 $ hg cat -r tip b
165 x
165 x
166 1
166 1
167 2
167 2
168 3
168 3
169 4
169 4
170 5
170 5
171 6
171 6
172 7
172 7
173 8
173 8
174 9
174 9
175 10
175 10
176 y
176 y
177
177
178 Newly added files can be selected with the curses interface
178 Newly added files can be selected with the curses interface
179
179
180 $ hg update -C .
180 $ hg update -C .
181 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
181 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
182 $ echo "hello" > x
182 $ echo "hello" > x
183 $ hg add x
183 $ hg add x
184 $ cat <<EOF >testModeCommands
184 $ cat <<EOF >testModeCommands
185 > x
185 > x
186 > x
186 > x
187 > c
187 > c
188 > EOF
188 > EOF
189 $ hg st
189 $ hg st
190 A x
190 A x
191 ? testModeCommands
191 ? testModeCommands
192 $ hg commit -i -m "newly added file" -d "0 0"
192 $ hg commit -i -m "newly added file" -d "0 0"
193 $ hg st
193 $ hg st
194 ? testModeCommands
194 ? testModeCommands
195
195
196 Test toggling all selections works
196 Test toggling all selections works
197
197
198 - Change one line
198 - Change one line
199 - Add an extra line at the end
199 - Add an extra line at the end
200 - Unselect all
200 - Unselect all
201 - Select the extra line at the end
201 - Select the extra line at the end
202 - Toggle all selections (so the extra line at the is unselected and the modified line is selected)
202 - Toggle all selections (so the extra line at the is unselected and the modified line is selected)
203 - Commit
203 - Commit
204
204
205 $ echo "hello world" > x
205 $ echo "hello world" > x
206 $ echo "goodbye world" >> x
206 $ echo "goodbye world" >> x
207 $ hg diff
207 $ hg diff
208 diff -r 2b0e9be4d336 x
208 diff -r 2b0e9be4d336 x
209 --- a/x Thu Jan 01 00:00:00 1970 +0000
209 --- a/x Thu Jan 01 00:00:00 1970 +0000
210 +++ b/x Thu Jan 01 00:00:00 1970 +0000
210 +++ b/x Thu Jan 01 00:00:00 1970 +0000
211 @@ -1,1 +1,2 @@
211 @@ -1,1 +1,2 @@
212 -hello
212 -hello
213 +hello world
213 +hello world
214 +goodbye world
214 +goodbye world
215 $ cat <<EOF >testModeCommands
215 $ cat <<EOF >testModeCommands
216 > f
216 > f
217 > j
217 > j
218 > x
218 > x
219 > j
219 > j
220 > j
220 > j
221 > j
221 > j
222 > x
222 > x
223 > a
223 > a
224 > c
224 > c
225 > EOF
225 > EOF
226 $ hg commit -i --amend -m "newly added file" -d "0 0" x
226 $ hg commit -i --amend -m "newly added file" -d "0 0" x
227 saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg
227 saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg
228 $ hg rev x --no-backup
228 $ hg rev x --no-backup
229 $ hg diff -c .
229 $ hg diff -c .
230 diff -r a6735021574d -r c1d239d165ae x
230 diff -r a6735021574d -r c1d239d165ae x
231 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
231 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
232 +++ b/x Thu Jan 01 00:00:00 1970 +0000
232 +++ b/x Thu Jan 01 00:00:00 1970 +0000
233 @@ -0,0 +1,1 @@
233 @@ -0,0 +1,1 @@
234 +hello world
234 +hello world
235
235
236 Make file empty
236 Make file empty
237 $ printf "" > x
237 $ printf "" > x
238 $ cat <<EOF >testModeCommands
238 $ cat <<EOF >testModeCommands
239 > c
239 > c
240 > EOF
240 > EOF
241 $ hg ci -i -m emptify -d "0 0"
241 $ hg ci -i -m emptify -d "0 0"
242 $ hg update -C '.^' -q
242 $ hg update -C '.^' -q
243
243
244 Editing a hunk puts you back on that hunk when done editing (issue5041)
244 Editing a hunk puts you back on that hunk when done editing (issue5041)
245 To do that, we change two lines in a file, pretend to edit the second line,
245 To do that, we change two lines in a file, pretend to edit the second line,
246 exit, toggle the line selected at the end of the edit and commit.
246 exit, toggle the line selected at the end of the edit and commit.
247 The first line should be recorded if we were put on the second line at the end
247 The first line should be recorded if we were put on the second line at the end
248 of the edit.
248 of the edit.
249
249
250 $ hg update -C .
250 $ hg update -C .
251 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 $ echo "foo" > x
252 $ echo "foo" > x
253 $ echo "hello world" >> x
253 $ echo "hello world" >> x
254 $ echo "bar" >> x
254 $ echo "bar" >> x
255 $ cat <<EOF >testModeCommands
255 $ cat <<EOF >testModeCommands
256 > f
256 > f
257 > KEY_DOWN
257 > KEY_DOWN
258 > KEY_DOWN
258 > KEY_DOWN
259 > KEY_DOWN
259 > KEY_DOWN
260 > KEY_DOWN
260 > KEY_DOWN
261 > e
261 > e
262 > x
262 > x
263 > c
263 > c
264 > EOF
264 > EOF
265 $ printf "printf 'editor ran\n'; exit 0" > editor.sh
265 $ printf "printf 'editor ran\n'; exit 0" > editor.sh
266 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "edit hunk" -d "0 0" -q
266 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "edit hunk" -d "0 0" -q
267 editor ran
267 editor ran
268 $ hg cat -r . x
268 $ hg cat -r . x
269 foo
269 foo
270 hello world
270 hello world
271
271
272 Testing the review option. The entire final filtered patch should show
272 Testing the review option. The entire final filtered patch should show
273 up in the editor and be editable. We will unselect the second file and
273 up in the editor and be editable. We will unselect the second file and
274 the first hunk of the third file. During review, we will decide that
274 the first hunk of the third file. During review, we will decide that
275 "lower" sounds better than "bottom", and the final commit should
275 "lower" sounds better than "bottom", and the final commit should
276 reflect this edition.
276 reflect this edition.
277
277
278 $ hg update -C .
278 $ hg update -C .
279 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
279 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
280 $ echo "top" > c
280 $ echo "top" > c
281 $ cat x >> c
281 $ cat x >> c
282 $ echo "bottom" >> c
282 $ echo "bottom" >> c
283 $ mv c x
283 $ mv c x
284 $ echo "third a" >> a
284 $ echo "third a" >> a
285 $ echo "we will unselect this" >> b
285 $ echo "we will unselect this" >> b
286
286
287 $ cat > editor.sh <<EOF
287 $ cat > editor.sh <<EOF
288 > cat "\$1"
288 > cat "\$1"
289 > cat "\$1" | sed s/bottom/lower/ > tmp
289 > cat "\$1" | sed s/bottom/lower/ > tmp
290 > mv tmp "\$1"
290 > mv tmp "\$1"
291 > EOF
291 > EOF
292 $ cat > testModeCommands <<EOF
292 $ cat > testModeCommands <<EOF
293 > KEY_DOWN
293 > KEY_DOWN
294 > x
294 > x
295 > KEY_DOWN
295 > KEY_DOWN
296 > f
296 > f
297 > KEY_DOWN
297 > KEY_DOWN
298 > x
298 > x
299 > R
299 > R
300 > EOF
300 > EOF
301
301
302 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "review hunks" -d "0 0"
302 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "review hunks" -d "0 0"
303 # To remove '-' lines, make them ' ' lines (context).
303 # To remove '-' lines, make them ' ' lines (context).
304 # To remove '+' lines, delete them.
304 # To remove '+' lines, delete them.
305 # Lines starting with # will be removed from the patch.
305 # Lines starting with # will be removed from the patch.
306 #
306 #
307 # If the patch applies cleanly, the edited patch will immediately
307 # If the patch applies cleanly, the edited patch will immediately
308 # be finalised. If it does not apply cleanly, rejects files will be
308 # be finalised. If it does not apply cleanly, rejects files will be
309 # generated. You can use those when you try again.
309 # generated. You can use those when you try again.
310 diff --git a/a b/a
310 diff --git a/a b/a
311 --- a/a
311 --- a/a
312 +++ b/a
312 +++ b/a
313 @@ -1,2 +1,3 @@
313 @@ -1,2 +1,3 @@
314 a
314 a
315 a
315 a
316 +third a
316 +third a
317 diff --git a/x b/x
317 diff --git a/x b/x
318 --- a/x
318 --- a/x
319 +++ b/x
319 +++ b/x
320 @@ -1,2 +1,3 @@
320 @@ -1,2 +1,3 @@
321 foo
321 foo
322 hello world
322 hello world
323 +bottom
323 +bottom
324
324
325 $ hg cat -r . a
325 $ hg cat -r . a
326 a
326 a
327 a
327 a
328 third a
328 third a
329
329
330 $ hg cat -r . b
330 $ hg cat -r . b
331 x
331 x
332 1
332 1
333 2
333 2
334 3
334 3
335 4
335 4
336 5
336 5
337 6
337 6
338 7
338 7
339 8
339 8
340 9
340 9
341 10
341 10
342 y
342 y
343
343
344 $ hg cat -r . x
344 $ hg cat -r . x
345 foo
345 foo
346 hello world
346 hello world
347 lower
347 lower
348
348
349 Test range select: unselect 3, 5, and 6, reselect 5, then go back up to 2 and
349 Test range select: unselect 3, 5, and 6, reselect 5, then go back up to 2 and
350 press 'X', unselecting (because 2 is currently selected) 5 (because it's the
350 press 'X', unselecting (because 2 is currently selected) 5 (because it's the
351 start of the range) and 4, leaving 3 unselected.
351 start of the range) and 4, leaving 3 unselected.
352
352
353 $ hg init $TESTTMP/range_select
353 $ hg init $TESTTMP/range_select
354 $ cd $TESTTMP/range_select
354 $ cd $TESTTMP/range_select
355 >>> open('range_select', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
355 >>> open('range_select', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
356 $ hg add range_select
356 $ hg add range_select
357 $ cat <<EOF >testModeCommands
357 $ cat <<EOF >testModeCommands
358 > KEY_RIGHT
358 > KEY_RIGHT
359 > KEY_RIGHT
359 > KEY_RIGHT
360 > KEY_DOWN
360 > KEY_DOWN
361 > KEY_DOWN
361 > KEY_DOWN
362 > KEY_ENTER
362 > KEY_ENTER
363 > KEY_DOWN
363 > KEY_DOWN
364 > KEY_ENTER
364 > KEY_ENTER
365 > x
365 > x
366 > KEY_UP
366 > KEY_UP
367 > x
367 > x
368 > KEY_UP
368 > KEY_UP
369 > KEY_UP
369 > KEY_UP
370 > KEY_UP
370 > KEY_UP
371 > X
371 > X
372 > c
372 > c
373 > EOF
373 > EOF
374 $ hg commit -i -m "range_select" -d "0 0"
374 $ hg commit -i -m "range_select" -d "0 0"
375 $ hg cat -r tip range_select
375 $ hg cat -r tip range_select
376 1
376 1
377 7
377 7
378 8
378 8
379 9
379 9
380 10
380 10
381 $ cat range_select
381 $ cat range_select
382 1
382 1
383 2
383 2
384 3
384 3
385 4
385 4
386 5
386 5
387 6
387 6
388 7
388 7
389 8
389 8
390 9
390 9
391 10
391 10
392
392
393 Check ui.interface logic for the chunkselector
393 Check ui.interface logic for the chunkselector
394
394
395 The default interface is text
395 The default interface is text
396 $ cp $HGRCPATH.pretest $HGRCPATH
396 $ cp $HGRCPATH.pretest $HGRCPATH
397 $ chunkselectorinterface() {
397 $ chunkselectorinterface() {
398 > "$PYTHON" <<EOF
398 > "$PYTHON" <<EOF
399 > from mercurial import hg, pycompat, ui;\
399 > from mercurial import hg, pycompat, ui;\
400 > repo = hg.repository(ui.ui.load(), b".");\
400 > repo = hg.repository(ui.ui.load(), b".");\
401 > print(pycompat.sysstr(repo.ui.interface(b"chunkselector")))
401 > print(pycompat.sysstr(repo.ui.interface(b"chunkselector")))
402 > EOF
402 > EOF
403 > }
403 > }
404 $ chunkselectorinterface
404 $ chunkselectorinterface
405 text
405 text
406
406
407 If only the default is set, we'll use that for the feature, too
407 If only the default is set, we'll use that for the feature, too
408 $ cp $HGRCPATH.pretest $HGRCPATH
408 $ cp $HGRCPATH.pretest $HGRCPATH
409 $ cat <<EOF >> $HGRCPATH
409 $ cat <<EOF >> $HGRCPATH
410 > [ui]
410 > [ui]
411 > interface = curses
411 > interface = curses
412 > EOF
412 > EOF
413 $ chunkselectorinterface
413 $ chunkselectorinterface
414 curses
414 curses
415
415
416 If TERM=dumb, we use text, even if the config says curses
416 If TERM=dumb, we use text, even if the config says curses
417 $ chunkselectorinterface
417 $ chunkselectorinterface
418 curses
418 curses
419 $ TERM=dumb chunkselectorinterface
419 $ TERM=dumb chunkselectorinterface
420 text
420 text
421 (Something is keeping TERM=dumb in the environment unless I do this, it's not
421 (Something is keeping TERM=dumb in the environment unless I do this, it's not
422 scoped to just that previous command like in many shells)
422 scoped to just that previous command like in many shells)
423 $ TERM=xterm chunkselectorinterface
423 $ TERM=xterm chunkselectorinterface
424 curses
424 curses
425
425
426 It is possible to override the default interface with a feature specific
426 It is possible to override the default interface with a feature specific
427 interface
427 interface
428 $ cp $HGRCPATH.pretest $HGRCPATH
428 $ cp $HGRCPATH.pretest $HGRCPATH
429 $ cat <<EOF >> $HGRCPATH
429 $ cat <<EOF >> $HGRCPATH
430 > [ui]
430 > [ui]
431 > interface = text
431 > interface = text
432 > interface.chunkselector = curses
432 > interface.chunkselector = curses
433 > EOF
433 > EOF
434
434
435 $ chunkselectorinterface
435 $ chunkselectorinterface
436 curses
436 curses
437
437
438 $ cp $HGRCPATH.pretest $HGRCPATH
438 $ cp $HGRCPATH.pretest $HGRCPATH
439 $ cat <<EOF >> $HGRCPATH
439 $ cat <<EOF >> $HGRCPATH
440 > [ui]
440 > [ui]
441 > interface = curses
441 > interface = curses
442 > interface.chunkselector = text
442 > interface.chunkselector = text
443 > EOF
443 > EOF
444
444
445 $ chunkselectorinterface
445 $ chunkselectorinterface
446 text
446 text
447
447
448 If a bad interface name is given, we use the default value (with a nice
448 If a bad interface name is given, we use the default value (with a nice
449 error message to suggest that the configuration needs to be fixed)
449 error message to suggest that the configuration needs to be fixed)
450
450
451 $ cp $HGRCPATH.pretest $HGRCPATH
451 $ cp $HGRCPATH.pretest $HGRCPATH
452 $ cat <<EOF >> $HGRCPATH
452 $ cat <<EOF >> $HGRCPATH
453 > [ui]
453 > [ui]
454 > interface = blah
454 > interface = blah
455 > EOF
455 > EOF
456 $ chunkselectorinterface
456 $ chunkselectorinterface
457 invalid value for ui.interface: blah (using text)
457 invalid value for ui.interface: blah (using text)
458 text
458 text
459
459
460 $ cp $HGRCPATH.pretest $HGRCPATH
460 $ cp $HGRCPATH.pretest $HGRCPATH
461 $ cat <<EOF >> $HGRCPATH
461 $ cat <<EOF >> $HGRCPATH
462 > [ui]
462 > [ui]
463 > interface = curses
463 > interface = curses
464 > interface.chunkselector = blah
464 > interface.chunkselector = blah
465 > EOF
465 > EOF
466 $ chunkselectorinterface
466 $ chunkselectorinterface
467 invalid value for ui.interface.chunkselector: blah (using curses)
467 invalid value for ui.interface.chunkselector: blah (using curses)
468 curses
468 curses
469
469
470 $ cp $HGRCPATH.pretest $HGRCPATH
470 $ cp $HGRCPATH.pretest $HGRCPATH
471 $ cat <<EOF >> $HGRCPATH
471 $ cat <<EOF >> $HGRCPATH
472 > [ui]
472 > [ui]
473 > interface = blah
473 > interface = blah
474 > interface.chunkselector = curses
474 > interface.chunkselector = curses
475 > EOF
475 > EOF
476 $ chunkselectorinterface
476 $ chunkselectorinterface
477 invalid value for ui.interface: blah
477 invalid value for ui.interface: blah
478 curses
478 curses
479
479
480 $ cp $HGRCPATH.pretest $HGRCPATH
480 $ cp $HGRCPATH.pretest $HGRCPATH
481 $ cat <<EOF >> $HGRCPATH
481 $ cat <<EOF >> $HGRCPATH
482 > [ui]
482 > [ui]
483 > interface = blah
483 > interface = blah
484 > interface.chunkselector = blah
484 > interface.chunkselector = blah
485 > EOF
485 > EOF
486 $ chunkselectorinterface
486 $ chunkselectorinterface
487 invalid value for ui.interface: blah
487 invalid value for ui.interface: blah
488 invalid value for ui.interface.chunkselector: blah (using text)
488 invalid value for ui.interface.chunkselector: blah (using text)
489 text
489 text
@@ -1,55 +1,55 b''
1 #require tic
1 #require curses
2
2
3 Revert interactive tests with the Curses interface
3 Revert interactive tests with the Curses interface
4
4
5 $ cat <<EOF >> $HGRCPATH
5 $ cat <<EOF >> $HGRCPATH
6 > [ui]
6 > [ui]
7 > interactive = true
7 > interactive = true
8 > interface = curses
8 > interface = curses
9 > [experimental]
9 > [experimental]
10 > crecordtest = testModeCommands
10 > crecordtest = testModeCommands
11 > EOF
11 > EOF
12
12
13 TODO: Make a curses version of the other tests from test-revert-interactive.t.
13 TODO: Make a curses version of the other tests from test-revert-interactive.t.
14
14
15 When a line without EOL is selected during "revert -i"
15 When a line without EOL is selected during "revert -i"
16
16
17 $ hg init $TESTTMP/revert-i-curses-eol
17 $ hg init $TESTTMP/revert-i-curses-eol
18 $ cd $TESTTMP/revert-i-curses-eol
18 $ cd $TESTTMP/revert-i-curses-eol
19 $ echo 0 > a
19 $ echo 0 > a
20 $ hg ci -qAm 0
20 $ hg ci -qAm 0
21 $ printf 1 >> a
21 $ printf 1 >> a
22 $ hg ci -qAm 1
22 $ hg ci -qAm 1
23 $ cat a
23 $ cat a
24 0
24 0
25 1 (no-eol)
25 1 (no-eol)
26
26
27 $ cat <<EOF >testModeCommands
27 $ cat <<EOF >testModeCommands
28 > c
28 > c
29 > EOF
29 > EOF
30
30
31 $ hg revert -ir'.^'
31 $ hg revert -ir'.^'
32 reverting a
32 reverting a
33 $ cat a
33 $ cat a
34 0
34 0
35
35
36 When a selected line is reverted to have no EOL
36 When a selected line is reverted to have no EOL
37
37
38 $ hg init $TESTTMP/revert-i-curses-eol2
38 $ hg init $TESTTMP/revert-i-curses-eol2
39 $ cd $TESTTMP/revert-i-curses-eol2
39 $ cd $TESTTMP/revert-i-curses-eol2
40 $ printf 0 > a
40 $ printf 0 > a
41 $ hg ci -qAm 0
41 $ hg ci -qAm 0
42 $ echo 0 > a
42 $ echo 0 > a
43 $ hg ci -qAm 1
43 $ hg ci -qAm 1
44 $ cat a
44 $ cat a
45 0
45 0
46
46
47 $ cat <<EOF >testModeCommands
47 $ cat <<EOF >testModeCommands
48 > c
48 > c
49 > EOF
49 > EOF
50
50
51 $ hg revert -ir'.^'
51 $ hg revert -ir'.^'
52 reverting a
52 reverting a
53 $ cat a
53 $ cat a
54 0 (no-eol)
54 0 (no-eol)
55
55
General Comments 0
You need to be logged in to leave comments. Login now