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