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